如何開發一個 Webpack Loader ( 一 )
最近,項目用了 React,配套使用了 Webpack,畢竟熱替換(react-hot-loader)吸引力確實高,開發模式下使用 webpack 構建其實也夠用,并且相對 gulp-webpack 來說,模塊的編譯等待時間大大縮小,這是生命啊! 發布時,借助 gulp 來進行其他方面的處理,如合圖,打包等。或許把這些邊幅修一修、支持下,Webpack 估計就要逆天了吧?
仰望天空,還是腳踏實地,Webpack 雖非新鮮之物,但也沒有多成熟。對應的 Plugin 及 Loader 的量并不多,還是有很多輪子沒造,很多坑沒踩呢。從源碼中似乎看到了一些可能在接下來會暴漏出來的新接口,想想還有點小激動,期待下 Webpack2 吧。
碎碎念完畢,以下講講如何開發一個基礎的 Webpack Loader 及一些心得。
1 開發 Webpack Loader 前須知
Loader 是支持鏈式執行的,如處理 sass 文件的 loader,可以由 sass-loader、css-loader、style-loader 組成,由 compiler 對其由右向左執行,第一個 Loader 將會拿到需處理的原內容,上一個 Loader 處理后的結果回傳給下一個接著處理,最后的 Loader 將處理后的結果以 String 或 Buffer 的形式返回給 compiler。
這種鏈式的處理方式倒是和 gulp 有點兒類似,固然也是希望每個 loader 只做該做的事,純粹的事 ,而不希望一籮筐的功能都集成到一個 Loader 中。
{ module: { loaders: [{ test: /\.scss$/, loader: 'style!css!sass' }] } };
另一方面,雖然鏈式之間可以依賴其前一個Loader所返回的結果來執行自己的內容。但這并不支持兩個 Loader 之間進行數據交流的做法,一個標準的 Loader 應該是要求著 強獨立性、以及輸入什么,就輸出什么的可預見性。
2 Webpack Loader 基礎
官網說了,A loader is a node module exporting a function.
既然是 node module,那么基本的寫法可以是
// base loader module.exports = function(source) { return source; };
如果你所寫的 Loader 需要依賴其他模塊的話,那么同樣以 module 的寫法,將依賴放在文件的頂部聲明,讓人清晰看到
// Module dependencies. var fs = require("fs"); module.exports = function(source) { return source; };
上面使用返回 return 返回,是因為是同步類的 Loader 且返回的內容唯一,如果你希望將處理后的結果(不止一個)返回給下一個 Loader,那么就需要調用 Webpack 所提供的 API。 一般來說,構建系統都會提供一些特有的 API 供開發者使用。Webpack 也如此,提供了一套 Loader API,可以通過在 node module 中使用 this 來調用,如 this.callback(err, value…),這個 API 支持返回多個內容的結果給下一個 Loader 。
// return multiple result module.exports = function(source, other) { // do whatever you want // ... this.callback(null, source, other); };
以上的內容,稍總結下
- 從右到左,鏈式執行
- 上一個 Loader 的處理結果給下一個接著處理
- node module 寫法
- module 依賴
- return && this.callback()
而實際上,掌握上面所介紹的內容及思想,就可以開始寫一個簡單的 Loader 了,不是嗎? 由上所說的,在你的 Loader 中,你可以拿到需要處理的文件內容,并且知道了處理后的結果應該怎么去返回,在中間部分,你可以以正常使用 node 的姿態對內容進行怎樣的處理,Do Whatever You Want,Loader 沒有其他特殊要求。
3 如何開發更好用的 Webapck Loader
上半部分的介紹雖然確實能搭建起一個普通的 Loader 了,但這樣就夠了嗎?
? 緩存
從提高執行效率上,如何處理利用 緩存 是極其重要的。 Mac OS 會讓內存充分使用、盡量占滿來提高交互效率。回到 Webpack,Hot-Replace 以及 React Hot Loader 也充分地利用緩存來提高編譯效率。 Webpack Loader 同樣可以利用緩存來提高效率,并且只需在一個可緩存的 Loader 上加一句 this.cacheable(); 就是這么簡單
// 讓 Loader 緩存 module.exports = function(source) { this.cacheable(); return source; };
很多 Loader 都是可以緩存的,但也有例外。可以緩存的 Loader 需要具備可預見性,不變性等等。
? 異步
異步并不陌生,當一個 Loader 無依賴,可異步的時候我想都應該讓它不再阻塞地去異步。在一個異步的模塊中,回傳時需要調用 Loader API 提供的回調方法 this.async(),使用起來也很簡單
// 讓 Loader 緩存 module.exports = function(source) { var callback = this.async(); // 做異步的事 doSomeAsyncOperation(content, function(err, result) { if(err) return callback(err); callback(null, result); }); };
? 認識更多的 Loader
pitching Loader
前面所述的 Loader 從右到左鏈式執行。這種說法實際說的是 Loader 中 module.exports 出來的執行方法順序。在一些場景下,Loader 并不依賴上一個 Loader 的結果,而只關心原輸入內容。這時候,從左到右執行并沒有什么問題。在 Loader 的 module 中,可使用 module.exports.pitch = function(); pitch 方法在 Loader 中便是從左到右執行的,并且可以通過 data 這個變量來進行 pitch 和 normal 之間傳遞。
module.exports.pitch = function(remaining, preceding, data) { if(somothingFlag()) { return "module.exports = require(" + JSON.stringify("-!" + remaining) + ");"; } data.value = 1; };
具體的實踐可以查看 style-loader,里面就有使用到 pitch。
raw loader
默認的情況,原文件是以 UTF-8 String 的形式傳入給 Loader,而在上面有提到的,module 可使用 buffer 的形式進行處理,針對這種情況,只需要設置 module.exports.raw = true; 這樣內容將會以 raw Buffer 的形式傳入到 loader 中了
module.exports = function(content) { }; module.exports.raw = true;
? 善用 Loader 中的 this
Loader API 將提供給每一個 Loader 的 this 中,API 可以讓我們的調用方式更加地方便,更加靈活。
datapitch loader 中可以通過 data 讓 pitch 和 normal module 進行數據共享。
query則能獲取到 Loader 上附有的參數。 如 require("./somg-loader?ls"); 通過 query 就可以得到 "ls" 了。
emitFileemitFile 能夠讓開發者更方便的輸出一個 file 文件,這是 webpack 特有的方法,使用的方法也很直接
emitFile(name: string, content: Buffer|String, sourceMap: {...})
在 file-loader 中有調用到 this.emitFile(url, content); 這個方法,具體可以查看其源碼了解。
更多的 API 就不在此 一 一 說明了,建議查看官網文檔了解。最后推薦一個工具模塊 loader-utils,大多數的 Loader 都會用上它來解析或者使用它提供的一些 util 方法,很方便。
話不多說
針對 Loader 的基礎介紹大致就到這了,不多,希望這篇文章能夠對 Webpack Loader 有一個大致的了解。 更多進階的方案及實戰經驗容我再整理整理,遲些輸出。