揭祕webpack插件工作流程和原理

前言

通過插件我們可以擴展webpack,在合適的時機通過Webpack提供的 API 改變輸出結果,使webpack可以執行更廣泛的任務,擁有更強的構建能力。 本文將嘗試探索 webpack 插件的工作流程,進而去揭祕它的工作原理。同時需要你對webpack底層和構建流程的一些東西有一定的瞭解。

想要了解 webpack 的插件的機制,需要弄明白以下幾個知識點:

  1. 一個簡單的插件的構成

  2. webpack構建流程

  3. Tapable是如何把各個插件串聯到一起的

  4. compiler以及compilation對象的使用以及它們對應的事件鉤子。

插件基本結構

plugins是可以用自身原型方法apply來實例化的對象。apply只在安裝插件被Webpack compiler執行一次。apply方法傳入一個webpck compiler的引用,來訪問編譯器回調。

一個簡單的插件結構:

class HelloPlugin{
  // 在構造函數中獲取用戶給該插件傳入的配置
  constructor(options){
  }
  // Webpack 會調用 HelloPlugin 實例的 apply 方法給插件實例傳入 compiler 對象
  apply(compiler) {
    // 在emit階段插入鉤子函數,用於特定時機處理額外的邏輯;
    compiler.hooks.emit.tap('HelloPlugin', (compilation) => {
      // 在功能流程完成後可以調用 webpack 提供的回調函數;
    });
    // 如果事件是異步的,會帶兩個參數,第二個參數爲回調函數,在插件處理完任務時需要調用回調函數通知webpack,纔會進入下一個處理流程。
    compiler.plugin('emit',function(compilation, callback) {
      // 支持處理邏輯
      // 處理完畢後執行 callback 以通知 Webpack 
      // 如果不執行 callback,運行流程將會一直卡在這不往下執行 
      callback();
    });
  }
}

module.exports = HelloPlugin;

安裝插件時, 只需要將它的一個實例放到Webpack config plugins 數組裏面:

const HelloPlugin = require('./hello-plugin.js');
var webpackConfig = {
  plugins: [
    new HelloPlugin({options: true})
  ]
};

先來分析一下webpack Plugin的工作原理

  1. 讀取配置的過程中會先執行 new HelloPlugin(options) 初始化一個 HelloPlugin 獲得其實例。

  2. 初始化 compiler 對象後調用 HelloPlugin.apply(compiler) 給插件實例傳入 compiler 對象。

  3. 插件實例在獲取到 compiler 對象後,就可以通過compiler.plugin(事件名稱, 回調函數) 監聽到 Webpack 廣播出來的事件。 並且可以通過 compiler 對象去操作 Webpack

webapck 構建流程

在編寫插件之前,還需要了解一下Webpack的構建流程,以便在合適的時機插入合適的插件邏輯。

Webpack的基本構建流程如下:

  1. 校驗配置文件 :讀取命令行傳入或者webpack.config.js文件,初始化本次構建的配置參數

  2. 生成Compiler對象:執行配置文件中的插件實例化語句new MyWebpackPlugin(),爲webpack事件流掛上自定義hooks

  3. 進入entryOption階段:webpack開始讀取配置的Entries,遞歸遍歷所有的入口文件

  4. run/watch:如果運行在watch模式則執行watch方法,否則執行run方法

  5. compilation:創建Compilation對象回調compilation相關鉤子,依次進入每一個入口文件(entry),使用loader對文件進行編譯。通過compilation我可以可以讀取到moduleresource(資源路徑)、loaders(使用的loader)等信息。再將編譯好的文件內容使用acorn解析生成AST靜態語法樹。然後遞歸、重複的執行這個過程, 所有模塊和和依賴分析完成後,執行 compilationseal 方法對每個 chunk 進行整理、優化、封裝__webpack_require__來模擬模塊化操作.

  6. emit:所有文件的編譯及轉化都已經完成,包含了最終輸出的資源,我們可以在傳入事件回調的compilation.assets上拿到所需數據,其中包括即將輸出的資源、代碼塊Chunk等等信息。

// 修改或添加資源
compilation.assets['new-file.js'] = {
  source() {
    return 'var a=1';
  },
  size() {
    return this.source().length;
  }
};
  1. afterEmit:文件已經寫入磁盤完成

  2. done:完成編譯

奉上一張滴滴雲博客的WebPack 編譯流程圖,不喜歡看文字講解的可以看流程圖理解記憶

WebPack 編譯流程圖原圖出自:https://blog.didiyun.com/index.php/2019/03/01/webpack/

看完之後,如果還是看不懂或者對縷不清webpack構建流程的話,建議通讀一下全文,再回來看這段話,相信一定會對webpack構建流程有很更加深刻的理解。

理解事件流機制 Tapable

webpack本質上是一種事件流的機制,它的工作流程就是將各個插件串聯起來,而實現這一切的核心就是Tapable。

WebpackTapable 事件流機制保證了插件的有序性,將各個插件串聯起來, Webpack 在運行過程中會廣播事件,插件只需要監聽它所關心的事件,就能加入到這條webapck機制中,去改變webapck的運作,使得整個系統擴展性良好。

Tapable也是一個小型的 library,是Webpack的一個核心工具。類似於node中的events庫,核心原理就是一個訂閱發佈模式。作用是提供類似的插件接口。

webpack中最核心的負責編譯的Compiler和負責創建bundles的Compilation都是Tapable的實例,可以直接在 CompilerCompilation 對象上廣播和監聽事件,方法如下:

/**
* 廣播事件
* event-name 爲事件名稱,注意不要和現有的事件重名
*/
compiler.apply('event-name',params);
compilation.apply('event-name',params);
/**
* 監聽事件
*/
compiler.plugin('event-name',function(params){});
compilation.plugin('event-name', function(params){});

Tapable類暴露了taptapAsynctapPromise方法,可以根據鉤子的同步/異步方式來選擇一個函數注入邏輯。

tap 同步鉤子

compiler.hooks.compile.tap('MyPlugin', params => {
  console.log('以同步方式觸及 compile 鉤子。')
})

tapAsync 異步鉤子,通過callback回調告訴Webpack異步執行完畢tapPromise 異步鉤子,返回一個Promise告訴Webpack異步執行完畢

compiler.hooks.run.tapAsync('MyPlugin', (compiler, callback) => {
  console.log('以異步方式觸及 run 鉤子。')
  callback()
})

compiler.hooks.run.tapPromise('MyPlugin', compiler => {
  return new Promise(resolve => setTimeout(resolve, 1000)).then(() => {
    console.log('以具有延遲的異步方式觸及 run 鉤子')
  })
})

Tabable用法

const {
 SyncHook,
 SyncBailHook,
 SyncWaterfallHook,
 SyncLoopHook,
 AsyncParallelHook,
 AsyncParallelBailHook,
 AsyncSeriesHook,
 AsyncSeriesBailHook,
 AsyncSeriesWaterfallHook
 } = require("tapable");
tapable

簡單實現一個 SyncHook

class Hook{
    constructor(args){
        this.taps = []
        this.interceptors = [] // 這個放在後面用
        this._args = args 
    }
    tap(name,fn){
        this.taps.push({name,fn})
    }
}
class SyncHook extends Hook{
    call(name,fn){
        try {
            this.taps.forEach(tap => tap.fn(name))
            fn(null,name)
        } catch (error) {
            fn(error)
        }

    }
}

tapable是如何將webapck/webpack插件關聯的?

Compiler.js

const { AsyncSeriesHook ,SyncHook } = require("tapable");
//創建類
class Compiler {
    constructor() {
        this.hooks = {
           run: new AsyncSeriesHook(["compiler"]), //異步鉤子
           compile: new SyncHook(["params"]),//同步鉤子
        };
    },
    run(){
      //執行異步鉤子
      this.hooks.run.callAsync(this, err => {
         this.compile(onCompiled);
      });
    },
    compile(){
      //執行同步鉤子 並傳參
      this.hooks.compile.call(params);
    }
}
module.exports = Compiler

MyPlugin.js

const Compiler = require('./Compiler')

class MyPlugin{
    apply(compiler){//接受 compiler參數
        compiler.hooks.run.tap("MyPlugin", () => console.log('開始編譯...'));
        compiler.hooks.compile.tapAsync('MyPlugin', (name, age) => {
          setTimeout(() => {
            console.log('編譯中...')
          }, 1000)
        });
    }
}

//這裏類似於webpack.config.js的plugins配置
//向 plugins 屬性傳入 new 實例

const myPlugin = new MyPlugin();

const options = {
    plugins: [myPlugin]
}
let compiler = new Compiler(options)
compiler.run()

想要深入瞭解tapable的文章可以看看這篇文章:

webpack4核心模塊tapable源碼解析: https://www.cnblogs.com/tugenhua0707/p/11317557.html

理解Compiler(負責編譯)

開發插件首先要知道compilercompilation 對象是做什麼的

Compiler 對象包含了當前運行Webpack的配置,包括entry、output、loaders等配置,這個對象在啓動Webpack時被實例化,而且是全局唯一的。Plugin可以通過該對象獲取到Webpack的配置信息進行處理。

如果看完這段話,你還是沒理解compiler是做啥的,不要怕,接着看。 運行npm run build,把compiler的全部信息輸出到控制檯上console.log(Compiler)

compiler
// 爲了能更直觀的讓大家看清楚compiler的結構,裏面的大量代碼使用省略號(...)代替。
Compiler {
  _pluginCompat: SyncBailHook {
    ...
  },
  hooks: {
    shouldEmit: SyncBailHook {
     ...
    },
    done: AsyncSeriesHook {
     ...
    },
    additionalPass: AsyncSeriesHook {
     ...
    },
    beforeRun: AsyncSeriesHook {
     ...
    },
    run: AsyncSeriesHook {
     ...
    },
    emit: AsyncSeriesHook {
     ...
    },
    assetEmitted: AsyncSeriesHook {
     ...
    },
    afterEmit: AsyncSeriesHook {
     ...
    },
    thisCompilation: SyncHook {
     ...
    },
    compilation: SyncHook {
     ...
    },
    normalModuleFactory: SyncHook {
     ...
    },
    contextModuleFactory: SyncHook {
     ...
    },
    beforeCompile: AsyncSeriesHook {
      ...
    },
    compile: SyncHook {
     ...
    },
    make: AsyncParallelHook {
     ...
    },
    afterCompile: AsyncSeriesHook {
     ...
    },
    watchRun: AsyncSeriesHook {
     ...
    },
    failed: SyncHook {
     ...
    },
    invalid: SyncHook {
     ...
    },
    watchClose: SyncHook {
     ...
    },
    infrastructureLog: SyncBailHook {
     ...
    },
    environment: SyncHook {
     ...
    },
    afterEnvironment: SyncHook {
     ...
    },
    afterPlugins: SyncHook {
     ...
    },
    afterResolvers: SyncHook {
     ...
    },
    entryOption: SyncBailHook {
     ...
    },
    infrastructurelog: SyncBailHook {
     ...
    }
  },
  ...
  outputPath: '',//輸出目錄
  outputFileSystem: NodeOutputFileSystem {
   ...
  },
  inputFileSystem: CachedInputFileSystem {
    ...
  },
  ...
  options: {
    //Compiler對象包含了webpack的所有配置信息,entry、module、output、resolve等信息
    entry: [
      'babel-polyfill',
      '/Users/frank/Desktop/fe/fe-blog/webpack-plugin/src/index.js'
    ],
    devServer: { port: 3000 },
    output: {
      ...
    },
    module: {
      ...
    },
    plugins: [ MyWebpackPlugin {} ],
    mode: 'production',
    context: '/Users/frank/Desktop/fe/fe-blog/webpack-plugin',
    devtool: false,
    ...
    performance: {
      maxAssetSize: 250000,
      maxEntrypointSize: 250000,
      hints: 'warning'
    },
    optimization: {
      ... 
    },
    resolve: {
      ...
    },
    resolveLoader: {
      ...
    },
    infrastructureLogging: { level: 'info', debug: false }
  },
  context: '/Users/frank/Desktop/fe/fe-blog/webpack-plugin',//上下文,文件目錄
  requestShortener: RequestShortener {
    ...
  },
  ...
  watchFileSystem: NodeWatchFileSystem {
    //監聽文件變化列表信息
     ...
  }
}

Compiler源碼精簡版代碼解析

源碼地址(948行):https://github.com/webpack/webpack/blob/master/lib/Compiler.js

const { SyncHook, SyncBailHook, AsyncSeriesHook } = require("tapable");
class Compiler {
  constructor() {
    // 1. 定義生命週期鉤子
    this.hooks = Object.freeze({
      // ...只列舉幾個常用的常見鉤子,更多hook就不列舉了,有興趣看源碼
      done: new AsyncSeriesHook(["stats"]),//一次編譯完成後執行,回調參數:stats
      beforeRun: new AsyncSeriesHook(["compiler"]),
      run: new AsyncSeriesHook(["compiler"]),//在編譯器開始讀取記錄前執行
      emit: new AsyncSeriesHook(["compilation"]),//在生成文件到output目錄之前執行,回調參數: compilation
      afterEmit: new AsyncSeriesHook(["compilation"]),//在生成文件到output目錄之後執行
      compilation: new SyncHook(["compilation", "params"]),//在一次compilation創建後執行插件
      beforeCompile: new AsyncSeriesHook(["params"]),
      compile: new SyncHook(["params"]),//在一個新的compilation創建之前執行
      make:new AsyncParallelHook(["compilation"]),//完成一次編譯之前執行
      afterCompile: new AsyncSeriesHook(["compilation"]),
      watchRun: new AsyncSeriesHook(["compiler"]),
      failed: new SyncHook(["error"]),
      watchClose: new SyncHook([]),
      afterPlugins: new SyncHook(["compiler"]),
      entryOption: new SyncBailHook(["context", "entry"])
    });
    // ...省略代碼
  }
  newCompilation() {
    // 創建Compilation對象回調compilation相關鉤子
    const compilation = new Compilation(this);
    //...一系列操作
    this.hooks.compilation.call(compilation, params); //compilation對象創建完成 
    return compilation
  }
  watch() {
    //如果運行在watch模式則執行watch方法,否則執行run方法
    if (this.running) {
      return handler(new ConcurrentCompilationError());
    }
    this.running = true;
    this.watchMode = true;
    return new Watching(this, watchOptions, handler);
  }
  run(callback) {
    if (this.running) {
      return callback(new ConcurrentCompilationError());
    }
    this.running = true;
    process.nextTick(() => {
      this.emitAssets(compilation, err => {
        if (err) {
          // 在編譯和輸出的流程中遇到異常時,會觸發 failed 事件 
          this.hooks.failed.call(err)
        };
        if (compilation.hooks.needAdditionalPass.call()) {
          // ...
          // done:完成編譯
          this.hooks.done.callAsync(stats, err => {
            // 創建compilation對象之前   
            this.compile(onCompiled);
          });
        }
        this.emitRecords(err => {
          this.hooks.done.callAsync(stats, err => {

          });
        });
      });
    });

    this.hooks.beforeRun.callAsync(this, err => {
      this.hooks.run.callAsync(this, err => {
        this.readRecords(err => {
          this.compile(onCompiled);
        });
      });
    });

  }
  compile(callback) {
    const params = this.newCompilationParams();
    this.hooks.beforeCompile.callAsync(params, err => {
      this.hooks.compile.call(params);
      const compilation = this.newCompilation(params);
      //觸發make事件並調用addEntry,找到入口js,進行下一步
      this.hooks.make.callAsync(compilation, err => {
        process.nextTick(() => {
          compilation.finish(err => {
            // 封裝構建結果(seal),逐次對每個module和chunk進行整理,每個chunk對應一個入口文件
            compilation.seal(err => {
              this.hooks.afterCompile.callAsync(compilation, err => {
                // 異步的事件需要在插件處理完任務時調用回調函數通知 Webpack 進入下一個流程,
                // 不然運行流程將會一直卡在這不往下執行
                return callback(null, compilation);
              });
            });
          });
        });
      });
    });
  }
  emitAssets(compilation, callback) {
    const emitFiles = (err) => {
      //...省略一系列代碼
      // afterEmit:文件已經寫入磁盤完成
      this.hooks.afterEmit.callAsync(compilation, err => {
        if (err) return callback(err);
        return callback();
      });
    }

    // emit 事件發生時,可以讀取到最終輸出的資源、代碼塊、模塊及其依賴,並進行修改(這是最後一次修改最終文件的機會)
    this.hooks.emit.callAsync(compilation, err => {
      if (err) return callback(err);
      outputPath = compilation.getPath(this.outputPath, {});
      mkdirp(this.outputFileSystem, outputPath, emitFiles);
    });
  }
  // ...省略代碼
}

apply方法中插入鉤子的一般形式如下:

// compiler提供了compiler.hooks,可以根據這些不同的時刻去讓插件做不同的事情。
compiler.hooks.階段.tap函數('插件名稱', (階段回調參數) => {

});
compiler.run(callback)

理解Compilation

Compilation對象代表了一次資源版本構建。當運行 webpack 開發環境中間件時,每當檢測到一個文件變化,就會創建一個新的 compilation,從而生成一組新的編譯資源。一個 Compilation 對象表現了當前的模塊資源、編譯生成資源、變化的文件、以及被跟蹤依賴的狀態信息,簡單來講就是把本次打包編譯的內容存到內存裏。Compilation 對象也提供了插件需要自定義功能的回調,以供插件做自定義處理時選擇使用拓展。

簡單來說,Compilation的職責就是構建模塊和Chunk,並利用插件優化構建過程。

Compiler 用法相同,鉤子類型不同,也可以在某些鉤子上訪問 tapAsynctapPromise。

控制檯輸出console.log(compilation)

通過 Compilation 也能讀取到 Compiler 對象。

源碼2000多行,看不動了- -,有興趣的可以自己看看。 https://github.com/webpack/webpack/blob/master/lib/Compilation.js

介紹幾個常用的Compilation Hooks

buildModule(SyncHook):在模塊開始編譯之前觸發,可以用於修改模

succeedModule(SyncHook):在模塊開始編譯之前觸發,可以用於修改模塊

finishModules(AsyncSeriesHook):當所有模塊都編譯成功後被調用

seal(SyncHook):當一次compilation停止接收新模塊時觸發

optimizeDependencies(SyncBailHook):在依賴優化的開始執行

optimize(SyncHook):在優化階段的開始執行

optimizeModules(SyncBailHook):在模塊優化階段開始時執行,插件可以在這個鉤子裏執行對模塊的優化,回調參數:modules

optimizeChunks(SyncBailHook):在代碼塊優化階段開始時執行,插件可以在這個鉤子裏執行對代碼塊的優化,回調參數:chunks

optimizeChunkAssets(AsyncSeriesHook):優化任何代碼塊資源,這些資源存放在compilation.assets 上。一個 chunk 有一個 files 屬性,它指向由一個chunk創建的所有文件。任何額外的 chunk 資源都存放在 compilation.additionalChunkAssets 上。回調參數:chunks

optimizeAssets(AsyncSeriesHook):優化所有存放在 compilation.assets 的所有資源。回調參數:assets                                                                                                                                          |

Compiler 和 Compilation 的區別

Compiler 代表了整個 Webpack 從啓動到關閉的生命週期,而 Compilation 只是代表了一次新的編譯,只要文件有改動,compilation就會被重新創建。

常用 API

插件可以用來修改輸出文件、增加輸出文件、甚至可以提升 Webpack 性能、等等,總之插件通過調用Webpack 提供的 API 能完成很多事情。 由於 Webpack提供的 API 非常多,有很多 API 很少用的上,又加上篇幅有限,下面來介紹一些常用的 API。

讀取輸出資源、代碼塊、模塊及其依賴

有些插件可能需要讀取 Webpack 的處理結果,例如輸出資源、代碼塊、模塊及其依賴,以便做下一步處理。 在 emit 事件發生時,代表源文件的轉換和組裝已經完成,在這裏可以讀取到最終將輸出的資源、代碼塊、模塊及其依賴,並且可以修改輸出資源的內容。 插件代碼如下:

class Plugin {
  apply(compiler) {
    compiler.plugin('emit', function (compilation, callback) {
      // compilation.chunks 存放所有代碼塊,是一個數組
      compilation.chunks.forEach(function (chunk) {
        // chunk 代表一個代碼塊
        // 代碼塊由多個模塊組成,通過 chunk.forEachModule 能讀取組成代碼塊的每個模塊
        chunk.forEachModule(function (module) {
          // module 代表一個模塊
          // module.fileDependencies 存放當前模塊的所有依賴的文件路徑,是一個數組
          module.fileDependencies.forEach(function (filepath) {
          });
        });

        // Webpack 會根據 Chunk 去生成輸出的文件資源,每個 Chunk 都對應一個及其以上的輸出文件
        // 例如在 Chunk 中包含了 CSS 模塊並且使用了 ExtractTextPlugin 時,
        // 該 Chunk 就會生成 .js 和 .css 兩個文件
        chunk.files.forEach(function (filename) {
          // compilation.assets 存放當前所有即將輸出的資源
          // 調用一個輸出資源的 source() 方法能獲取到輸出資源的內容
          let source = compilation.assets[filename].source();
        });
      });

      // 這是一個異步事件,要記得調用 callback 通知 Webpack 本次事件監聽處理結束。
      // 如果忘記了調用 callback,Webpack 將一直卡在這裏而不會往後執行。
      callback();
    })
  }
}

監聽文件變化

Webpack 會從配置的入口模塊出發,依次找出所有的依賴模塊,當入口模塊或者其依賴的模塊發生變化時, 就會觸發一次新的 Compilation

在開發插件時經常需要知道是哪個文件發生變化導致了新的 Compilation,爲此可以使用如下代碼:

// 當依賴的文件發生變化時會觸發 watch-run 事件
compiler.hooks.watchRun.tap('MyPlugin', (watching, callback) => {
  // 獲取發生變化的文件列表
  const changedFiles = watching.compiler.watchFileSystem.watcher.mtimes;
  // changedFiles 格式爲鍵值對,鍵爲發生變化的文件路徑。
  if (changedFiles[filePath] !== undefined) {
    // filePath 對應的文件發生了變化
  }
  callback();
});

默認情況下 Webpack 只會監視入口和其依賴的模塊是否發生變化,在有些情況下項目可能需要引入新的文件,例如引入一個 HTML 文件。 由於 JavaScript 文件不會去導入 HTML 文件,Webpack 就不會監聽 HTML 文件的變化,編輯 HTML 文件時就不會重新觸發新的 Compilation。 爲了監聽 HTML 文件的變化,我們需要把 HTML 文件加入到依賴列表中,爲此可以使用如下代碼:

compiler.hooks.afterCompile.tap('MyPlugin', (compilation, callback) => {
  // 把 HTML 文件添加到文件依賴列表,好讓 Webpack 去監聽 HTML 模塊文件,在 HTML 模版文件發生變化時重新啓動一次編譯
  compilation.fileDependencies.push(filePath);
  callback();
});

3、修改輸出資源

有些場景下插件需要修改、增加、刪除輸出的資源,要做到這點需要監聽 emit 事件,因爲發生 emit 事件時所有模塊的轉換和代碼塊對應的文件已經生成好, 需要輸出的資源即將輸出,因此emit事件是修改 Webpack 輸出資源的最後時機。

所有需要輸出的資源會存放在 compilation.assets 中,compilation.assets 是一個鍵值對,鍵爲需要輸出的文件名稱,值爲文件對應的內容。

設置 compilation.assets 的代碼如下:

// 設置名稱爲 fileName 的輸出資源
  compilation.assets[fileName] = {
    // 返回文件內容
    source: () => {
      // fileContent 既可以是代表文本文件的字符串,也可以是代表二進制文件的 Buffer
      return fileContent;
      },
    // 返回文件大小
      size: () => {
      return Buffer.byteLength(fileContent, 'utf8');
    }
  };
  callback();

判斷webpack使用了哪些插件

// 判斷當前配置使用使用了 ExtractTextPlugin,
// compiler 參數即爲 Webpack 在 apply(compiler) 中傳入的參數
function hasExtractTextPlugin(compiler) {
  // 當前配置所有使用的插件列表
  const plugins = compiler.options.plugins;
  // 去 plugins 中尋找有沒有 ExtractTextPlugin 的實例
  return plugins.find(plugin=>plugin.__proto__.constructor === ExtractTextPlugin) != null;
}

以上4種方法來源於文章: [Webpack學習-Plugin] :http://wushaobin.top/2019/03/15/webpackPlugin/

管理 Warnings 和 Errors

做一個實驗,如果你在 apply函數內插入 throw new Error("Message"),會發生什麼,終端會打印出 Unhandled rejection Error: Message。然後 webpack 中斷執行。 爲了不影響 webpack 的執行,要在編譯期間向用戶發出警告或錯誤消息,則應使用 compilation.warnings 和 compilation.errors。

compilation.warnings.push("warning");
compilation.errors.push("error");

文章中的案例demo代碼展示

https://github.com/6fedcom/fe-blog/tree/master/webpack/plugin

webpack打包過程或者插件代碼裏該如何調試?

  1. 在當前webpack項目工程文件夾下面,執行命令行:

node --inspect-brk ./node_modules/webpack/bin/webpack.js --inline --progress

其中參數--inspect-brk就是以調試模式啓動node:

終端會輸出:

Debugger listening on ws://127.0.0.1:9229/1018c03f-7473-4d60-b62c-949a6404c81d
For help, see: https://nodejs.org/en/docs/inspector
  1. 谷歌瀏覽器輸入 chrome://inspect/#devices

點擊inspect
  1. 然後點一下Chrome調試器裏的“繼續執行”,斷點就提留在我們設置在插件裏的debugger斷點了。

debugger

推薦閱讀:

螞蟻金服是怎麼優化移動端體驗的?

提高github下載速度的方法「100%有效」可達到2MB/s

不可不知的 7 個 JDK 命令

一文搞懂瀏覽器緩存機制

用好這 7 個 VS Code 插件,前端編程效率蹭蹭漲

點個在看,小編感恩大家❤️

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章