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