loader不難,loader不難,loader不難。默唸三遍,而後開始。javascript
loader
這個東西配置 webpack
的時候常常用到,剛開始會讓人以爲有一種神祕感。css
看了文檔以後才發現,loader 只是一個導出爲函數的 JavaScript 模塊
。對,不要怕它,它只是個函數模塊。java
webpack
內部用了loader-runner 這個開源庫,在webpack
項目下lib\NormalModule.js
中的doBuild
方法中調用。loader-runner
具體的內部邏輯還不是很清晰,這裏先不說了。webpack
咱們常常用 loader
來對模塊的源代碼進行轉換。例如ES6+
轉ES5
,sass
轉css
等等。git
loader
的常見用法以下:github
rules: [{
test: 'xxx',
use: ['a-loader', 'b-loader', 'c-loader']
}]
rules: [{
test: 'xxx',
use: 'xx-loader'
}]
rules: [{
test: 'xxx',
user: [
{ loader: 'a-loader' },
{
loader: 'b-loader',
options: {}
}
]
}]
複製代碼
loader
的運行順序是從右到左,後面的先執行,是反着來的。web
下一個執行的loader
會接收上一個執行的loader
後的返回值做爲參數繼續處理。相似gulp中的pipe管道流
。npm
到這裏應該能理解爲何css
相關的loader
,都是style-loader
放在最前面吧。gulp
每一個 loader
都支持加一個 Pitch
函數,我這裏把它理解成前置函數。api
loader
自己是按相反順序執行,但在(從右到左
)執行 loader
以前,會先從左到右
調用 loader 上的 pitch 方法。
能夠參考官網給出的例子:
若是是這樣的配置的loader
rules: [{
test: 'xxx',
use: ['a-loader', 'b-loader', 'c-loader']
}]
複製代碼
內部的執行邏輯是這樣的:
|- a-loader `pitch`
|- b-loader `pitch`
|- c-loader `pitch`
|- requested module is picked up as a dependency
|- c-loader normal execution
|- b-loader normal execution
|- a-loader normal execution
複製代碼
注意:若是某個loader 的 pitch 方法有返回值,那麼webpack會忽略 當前loader 和 剩下的loader,直接執行以前已經調用過 pitch 方法的loader
。
關於 pitch
的詳細解析,能夠參考webpack中文文檔;
咱們已經知道了 loader
只是一個 javascript函數模塊而已
,並且有固定的參數格式(官網有介紹):
module.exports = function( content, // 資源文件的內容,多是 String 或 Buffer map, // sourceMap,能夠不寫 meta // 能夠是任何東西(例如元數據),能夠不寫 ){
// do something
}
複製代碼
接下來咱們就能夠本身寫個簡單的loader
玩一下,
這裏我寫一個my-img-loader
,用於將圖片轉爲base64
編碼。
先安裝mime
工具包,用來獲取資源類型
npm i -D mime
複製代碼
而後在項目根目錄下新建一個 loaders
文件夾,而後新建一個 my-img-loader.js
,編寫代碼:
// 用來獲取資源的類型
const mime = require('mime');
module.exports = function (content, map, meta) {
// 將資源內容轉換爲 Buffer
if (typeof content === 'string') {
content = Buffer.from(content);
}
// this.resourcePath 表示當前資源的絕對路徑
let mimetype = mime.getType(this.resourcePath);
// 生成 base64 碼
let base64 = `data:${mimetype || ''};base64,${content.toString('base64')}`;
// 若是是處理順序排在最後一個的 loader,那麼它的返回值將最終交給 webpack 的 require
// 也就是說這裏要返回一串 CommonJs 規範的 JS 代碼
// 由於我這裏只用到一個loader,因此須要這樣設置,否則的話,css裏面會出現 background: url([object Ojbect])
// 若是不是最後一個 loader,就不須要 `module.exports` 了
return `module.exports = ${JSON.stringify(base64)}`;
}
// 設置用 Buffer 格式 來傳遞處理結果,也就是二進制數據
// 我不清楚這裏爲何要用 Buffer 格式來傳遞,不設置的話,顯示不了base64圖片
// 有知道的讀者請告訴下我
module.exports.raw = true;
複製代碼
而後設置 webpack.config.js
的rules
,
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
exclude: [],
use: ['./loaders/my-img-loader']
// use: {
// loader: 'url-loader',
// options: {
// limit: 10000,
// name: 'img/[name].[hash:7].[ext]'
// }
// }
}
複製代碼
爲了方便起見,讀者能夠下載我以前提交的spa-webpack-demo來更改。
亦或者,直接把代碼複製到已有的項目嘗試。
而後 npm run dev
看效果。
代碼結構以下:
若是想要給 loader 添加 options 參數,能夠用 loader-utils工具庫來處理。
module.exports = function(content, map, meta) {
// 作一些同步的操做
let result = someSyncOperation(content);
// 將資源返回
return result;
};
複製代碼
異步loader須要先調用this.async()
,執行這個方法後,loader-runner
內部會將loader
識別爲異步的,並返回一個callback
。
module.exports = function(content, map, meta) {
// 執行 this.async(),告訴 webpack 這是個異步的 loader
let callback = this.async();
// 作一些異步的操做
someAsyncOperation(content, function(err, result) {
if (err) return callback(err);
// 告訴 webpack,loader執行完畢,並把 result 傳入,供下一個loader 使用
callback(null, result, map, meta);
});
};
複製代碼
但願本文能對讀者有幫助。
若是有錯誤的地方,還請指出。
謝謝閱讀。