一、區別 前面兩節我們有提到Loader與Plugin對應的概念,先來回顧下 loader 是文件載入器,能夠載入資源文件,並對這些文件進行一些處理,諸如編譯、壓縮等,最終一起打包到指定的文件中 plugin 賦予了 webpack 各種靈活的功能,例如打包優化、資源管理、環境變數註入等,目的是解決 ...
一、區別
前面兩節我們有提到Loader
與Plugin
對應的概念,先來回顧下
- loader 是文件載入器,能夠載入資源文件,並對這些文件進行一些處理,諸如編譯、壓縮等,最終一起打包到指定的文件中
- plugin 賦予了 webpack 各種靈活的功能,例如打包優化、資源管理、環境變數註入等,目的是解決 loader 無法實現的其他事
從整個運行時機上來看,如下圖所示:
可以看到,兩者在運行時機上的區別:
- loader 運行在打包文件之前
- plugins 在整個編譯周期都起作用
在Webpack
運行的生命周期中會廣播出許多事件,Plugin
可以監聽這些事件,在合適的時機通過Webpack
提供的 API
改變輸出結果
對於loader
,實質是一個轉換器,將A文件進行編譯形成B文件,操作的是文件,比如將A.scss
或A.less
轉變為B.css
,單純的文件轉換過程
二、編寫loader
在編寫 loader
前,我們首先需要瞭解 loader
的本質
其本質為函數,函數中的 this
作為上下文會被 webpack
填充,因此我們不能將 loader
設為一個箭頭函數
函數接受一個參數,為 webpack
傳遞給 loader
的文件源內容
函數中 this
是由 webpack
提供的對象,能夠獲取當前 loader
所需要的各種信息
函數中有非同步操作或同步操作,非同步操作通過 this.callback
返回,返回值要求為 string
或者 Buffer
代碼如下所示:
// 導出一個函數,source為webpack傳遞給loader的文件源內容 module.exports = function(source) { const content = doSomeThing2JsString(source); // 如果 loader 配置了 options 對象,那麼this.query將指向 options const options = this.query; // 可以用作解析其他模塊路徑的上下文 console.log('this.context'); /* * this.callback 參數: * error:Error | null,當 loader 出錯時向外拋出一個 error * content:String | Buffer,經過 loader 編譯後需要導出的內容 * sourceMap:為方便調試生成的編譯後內容的 source map * ast:本次編譯生成的 AST 靜態語法樹,之後執行的 loader 可以直接使用這個 AST,進而省去重覆生成 AST 的過程 */ this.callback(null, content); // 非同步 return content; // 同步 }
一般在編寫loader
的過程中,保持功能單一,避免做多種功能
如less
文件轉換成 css
文件也不是一步到位,而是 less-loader
、css-loader
、style-loader
幾個 loader
的鏈式調用才能完成轉換
三、編寫plugin
由於webpack
基於發佈訂閱模式,在運行的生命周期中會廣播出許多事件,插件通過監聽這些事件,就可以在特定的階段執行自己的插件任務
在之前也瞭解過,webpack
編譯會創建兩個核心對象:
- compiler:包含了 webpack 環境的所有的配置信息,包括 options,loader 和 plugin,和 webpack 整個生命周期相關的鉤子
- compilation:作為 plugin 內置事件回調函數的參數,包含了當前的模塊資源、編譯生成資源、變化的文件以及被跟蹤依賴的狀態信息。當檢測到一個文件變化,一次新的 Compilation 將被創建
如果自己要實現plugin
,也需要遵循一定的規範:
- 插件必須是一個函數或者是一個包含
apply
方法的對象,這樣才能訪問compiler
實例 - 傳給每個插件的
compiler
和compilation
對象都是同一個引用,因此不建議修改 - 非同步的事件需要在插件處理完任務時調用回調函數通知
Webpack
進入下一個流程,不然會卡住
實現plugin
的模板如下:
class MyPlugin { // Webpack 會調用 MyPlugin 實例的 apply 方法給插件實例傳入 compiler 對象 apply (compiler) { // 找到合適的事件鉤子,實現自己的插件功能 compiler.hooks.emit.tap('MyPlugin', compilation => { // compilation: 當前打包構建流程的上下文 console.log(compilation); // do something... }) } }
在 emit
事件發生時,代表源文件的轉換和組裝已經完成,可以讀取到最終將輸出的資源、代碼塊、模塊及其依賴,並且可以修改輸出資源的內容
參考文獻
- https://webpack.docschina.org/api/loaders/
- https://webpack.docschina.org/api/compiler-hooks/
- https://segmentfault.com/a/1190000039877943
- https://vue3js.cn/interview
如果對您有所幫助,歡迎您點個關註,我會定時更新技術文檔,大家一起討論學習,一起進步。