最近,專案用了 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 中。
1 2 3 4 5 6 7 8 |
{ 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,那麼基本的寫法可以是
1 2 3 4 |
// base loader module.exports = function(source) { return source; }; |
如果你所寫的 Loader 需要依賴其他模組的話,那麼同樣以 module 的寫法,將依賴放在檔案的頂部宣告,讓人清晰看到
1 2 3 4 5 |
// 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 。
1 2 3 4 5 6 |
// 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(); 就是這麼簡單
1 2 3 4 5 |
// 讓 Loader 快取 module.exports = function(source) { this.cacheable(); return source; }; |
很多 Loader 都是可以快取的,但也有例外。可以快取的 Loader 需要具備可預見性,不變性等等。
❷ 非同步
非同步並不陌生,當一個 Loader 無依賴,可非同步的時候我想都應該讓它不再阻塞地去非同步。在一個非同步的模組中,回傳時需要呼叫 Loader API 提供的回撥方法 this.async(),使用起來也很簡單
1 2 3 4 5 6 7 8 9 |
// 讓 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 之間傳遞。
1 2 3 4 5 6 |
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 中了
1 2 3 4 |
module.exports = function(content) { }; module.exports.raw = true; |
❹ 善用 Loader 中的 this
Loader API 將提供給每一個 Loader 的 this 中,API 可以讓我們的呼叫方式更加地方便,更加靈活。
data pitch loader 中可以通過 data 讓 pitch 和 normal module 進行資料共享。
query 則能獲取到 Loader 上附有的引數。 如 require(“./somg-loader?ls”); 通過 query 就可以得到 “ls” 了。
emitFile emitFile 能夠讓開發者更方便的輸出一個 file 檔案,這是 webpack 特有的方法,使用的方法也很直接
1 |
emitFile(name: string, content: Buffer|String, sourceMap: {...}) |
在 file-loader 中有呼叫到 this.emitFile(url, content); 這個方法,具體可以檢視其原始碼瞭解。
更多的 API 就不在此 一 一 說明了,建議檢視官網文件瞭解。最後推薦一個工具模組 loader-utils,大多數的 Loader 都會用上它來解析或者使用它提供的一些 util 方法,很方便。
話不多說
針對 Loader 的基礎介紹大致就到這了,不多,希望這篇文章能夠對 Webpack Loader 有一個大致的瞭解。 更多進階的方案及實戰經驗容我再整理整理,遲些輸出。