如何編寫一個webpack loader
loader是什麼
webpack官方定義
A loader is just a JavaScript module that exports a function
-
從語法角度看,loader就是一個普通的Node.js模組,只是必須以函式格式匯出來供使用。如果有必要可以使用一切Node.js功能模組。
-
從功能角度看,一個loader是在應用中作用於指定格式的資原始檔並將其按照一定格式轉換輸出。例如:sass-loader將scss檔案轉換為標準css檔案輸出。
1 2 3 4 5 6 | // base loader var fs = require("fs"); module.exports = function(source) { return source; }; |
loader 的種類
preloaders
就是在呼叫loader之前需要呼叫的loader, 他不做任何程式碼的轉換,只是進行檢查loaders
就是我們本文章重點講述的用來處理檔案,做程式碼轉換的loader,例如將 scss檔案轉換為css檔案。postloaders
就是在呼叫loader之後需要呼叫的loader,比如進行程式碼覆蓋率測試
loader如何使用
-
loader 是支援鏈式執行的,如處理 sass 檔案的 loader,可以由 sass-loader、css-loader、style-loader 組成,由 compiler 編譯器對其由右向左執行,第一個 loader 將會拿到需處理的原內容,上一個 loader 處理後的結果回傳給下一個接著處理,最後的 Loader 將處理後的結果以 String 或 Buffer 的形式返回給 compiler。
-
每個 loader 只做該做的事,純粹的事,而不希望一籮筐的功能都整合到一個 loader 中。
-
loader函式還可以選擇使用 JSON 格式來傳遞資料。
loader 呼叫語法
- 命令列呼叫
1 |
webpack --module-bind jade --module-bind 'css=style!css' |
- require呼叫
在需要作用的資原始檔名前面加上loader
名稱和 !
感嘆號。
1 |
require('style-loader!css-loader?modules!./styles.css'); |
- webpack配置檔案
webpack 2.x語法中需要寫全loader全名,不再允許省略
-loader
語法格式一:
webpack 1.x語法
1 2 3 4 5 6 7 8 |
{ module: { loaders: [{ test: /\.scss$/, loader: 'style!css!sass' }] } }; |
webpack 2.x語法
1 2 3 4 5 6 7 8 |
{ module: { loaders: [{ test: /\.scss$/, loader: 'style-loader!css-loader!sass-loader' }] } }; |
語法格式二:
webpack 1.x
1 2 3 4 5 6 7 8 |
{ module: { loaders: [{ test: /\.scss$/, loader: ['style','css','sass'] }] } }; |
語法格式三:
webpack 2.x 語法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
{ module: { rules: [{ test: /\.scss$/, use: [ 'style-loader', 'css-loader', { loader: "sass-loader", options: { /* ... */ } } ] }] } }; |
loader 執行順序
當一個檔案從檔案系統中被讀取進入到loader執行序列中,其執行次序如下:
- webpack配置檔案中
preloaders
配置項(webpack2.x移除)
- webpack配置檔案中
loaders
配置項 -
request請求中指定的loaders,例如:
1
require('raw!./file.js')
-
webpack配置檔案中
postloaders
配置項(webpack2.x移除)
在某些情況下,我們需要在js模組請求中覆蓋webpack配置檔案中的loaders順序來實現一些特定需求。具體規則如下:
- 模組請求引數最前面新增感嘆號
!
來禁用配置檔案中的preLoaders
1 |
require("!raw!./script.coffee") |
- 模組請求引數最前面新增雙感嘆號
!!
來禁用配置檔案中所有的loaders
1 |
require("!!raw!./script.coffee") |
- 模組請求引數最前面新增
-!
來禁用配置檔案中preLoaders
和loaders
,除了postLoaders
1 |
require("-!raw!./script.coffee") |
官方的建議
- 對於結果為js檔案的情況,建議在
loaders
中處理 - 對於預先編譯的非js(如coffee等)轉換js檔案,建議在
preloader
中處理,如果不是全域性應用也可以在loaders
中處理 - 對於相同的語言,建議
preloader
和postloader
過程中處理
如何編寫一個loader
loader 輸出處理結果
- 同步模式(sync mode)
直接返回loader的處理結果。
1 2 3 |
module.exports = function(content) { return someSyncOperation(content); }; |
- 非同步模式(async mode)
呼叫 this.async()
來獲取this.callback()
方法,然後在非同步呼叫的回撥函式中通過callback
返回null
以及處理結果
。
1 2 3 4 5 6 7 8 |
module.exports = function(content) { var callback = this.async(); if(!callback) return someSyncOperation(content); someAsyncOperation(content, function(err, result) { if(err) return callback(err); callback(null, result); }); }; |
Raw loader
預設情況下,不同loader之間是以UTF-8格式的文字字串來傳遞資料。我們可以通過設定module.exports.raw = true;
來選擇使用Buffer
資料格式來傳遞資料,而編譯器會負責相鄰loader之間的格式轉換。
1 2 3 4 5 6 7 |
module.exports = function(content) { assert(content instanceof Buffer); return someSyncOperation(content); // return value can be a `Buffer` too // This is also allowed if loader is not "raw" }; module.exports.raw = true; |
Pitching Loader
多個loader的鏈式呼叫預設都是遵循從右到左的順序。但在某些情況下,loaders並不關心前一個loader的處理結果或者資原始檔資料,它僅僅關心元資料metadata
即原始輸入內容。
而pitch
方法則支援從左到右的執行順序,當pitch方法執行完畢,loaders呼叫流程反轉並跳過剩餘未執行的loaders,繼續呼叫當前loader更左側的loaders。並且通過 data
屬性來在當前loader的pitch
方法呼叫和 normal
方法呼叫之間來傳遞資料。
1 2 3 4 5 6 7 8 9 10 |
module.exports = function(content) { return someSyncOperation(content, this.data.value); }; module.exports.pitch = function(remainingRequest, precedingRequest, data) { if(someCondition()) { // fast exit return "module.exports = require(" + JSON.stringify("-!" + remainingRequest) + ");"; } data.value = 42; }; |
如何做好loader開發
善用loader中的this
loader函式作用域內的 this
變數掛載了一些很有用的方法屬性來實現諸如非同步呼叫、query引數獲取等功能。
- query則能獲取到 Loader 上附有的引數。 如
require("./somg-loader?ls")
; 通過 query 就可以得到 “ls” 了。 emitFile
能夠讓開發者更方便的輸出一個 file 檔案,這是 webpack 特有的方法,使用的方法也很直接
1 |
emitFile(name: string, content: Buffer|String, sourceMap: {...}) |
啟用結果快取 cacheable
1 2 3 4 |
module.exports = function(source) { this.cacheable(); return source; }; |
呼叫exec
方法執行一些程式碼片段。
1 |
exec(code: string, filename: string) |
loader-utils
loader開發中經常用到的一個工具模組,其中提供了一些很有用的解析方法。點選檢視文件
code split
require.ensure使得我們可在所有的dependencies項載入完畢後,再執行回撥
require.ensure僅僅是載入元件,並不會執行,若要執行,需要藉助傳進去的require引數
1 2 3 4 |
require.ensure(["module-a", "module-b"], function(require) { var a = require("module-a"); // ... }); |
參考資料
- 如何開發一個 Webpack Loader ( 一 )
- Loader API
- loaders
- using-loaders
- loader concepts
- webpack: require.ensure與require AMD的區別
原文https://fengmiaosen.github.io/2017/01/07/write-webpack-loader/