官網上的定義:css
loader 是一個轉換器,用於對源代碼進行轉換。html
例如 babel-loader
能夠將 ES6 代碼轉換爲 ES5 代碼;sass-loader
將 sass
代碼轉換爲 css
代碼。webpack
通常 loader 的配置代碼以下:git
module: {
rules: [
{
test: /\.js$/,
use: [
// loader 的執行順序從下到上
{
loader: path.resolve('./src/loader2.js'),
},
{
loader: path.resolve('./src/loader1.js'),
},
]
}
]
},
複製代碼
rules 數組包含了一個個匹配規則和具體的 loader 文件。github
上述代碼中的 test: /\.js$/
就是匹配規則,表示對 js 文件使用下面的兩個 loader。web
而 loader 的處理順序是自下向上的,即先用 loader1 處理源碼,而後將處理後的代碼再傳給 loader2。npm
loader2 處理後的代碼就是最終的打包代碼。api
loader 實際上是一個函數,它的參數是匹配文件的源碼,返回結果是處理後的源碼。下面是一個最簡單的 loader,它什麼都沒作:數組
module.exports = function (source) {
return source
}
複製代碼
這麼簡單的 loader 沒有挑戰性,咱們能夠寫一個稍微複雜一點的 loader,它的做用是將 var
關鍵詞替換爲 const
:sass
module.exports = function (source) {
return source.replace(/var/g, 'const')
}
複製代碼
寫完以後,咱們來測試一下,測試文件爲:
function test() {
var a = 1;
var b = 2;
var c = 3;
console.log(a, b, c);
}
test()
複製代碼
wepback.config.js
配置文件爲:
const path = require('path')
module.exports = {
mode: 'development',
entry: {
main: './src/index.js'
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader: path.resolve('./src/loader1.js'),
},
]
}
]
},
}
複製代碼
運行 npm run build
,獲得打包文件 bundle.js
,咱們來看一看打包後的代碼:
eval("function test() {\r\n const a = 1;\r\n const b = 2;\r\n const c = 3;\r\n console.log(a, b, c);\r\n}\r\n\r\ntest()\n\n//# sourceURL=webpack:///./src/index.js?");
複製代碼
能夠看到,代碼中的 var
已經變成了 const
。
剛纔實現的 loader 是一個同步 loader,在處理完源碼後用 return
返回。
下面咱們來實現一個異步 loader:
module.exports = function (source) {
const callback = this.async()
// 因爲有 3 秒延遲,因此打包時須要 3+ 秒的時間
setTimeout(() => {
callback(null, `${source.replace(/;/g, '')}`)
}, 3000)
}
複製代碼
異步 loader 須要調用 webpack 的 async()
生成一個 callback,它的第一個參數是 error
,這裏可設爲 null
,第二個參數就是處理後的源碼。當你異步處理完源碼後,調用 callback
便可。
下面來試一下異步 loader 到底有沒生效,這裏設置了一個 3 秒延遲。咱們來對比一下打包時間:
上圖是調用同步 loader 的打包時間,爲 141 ms;下圖是調用異步 loader 的打包時間,爲 3105 ms,說明異步 loader 生效了。
若是想看完整 demo 源碼,請點擊個人 github。
webpack 在整個編譯週期中會觸發不少不一樣的事件,plugin 能夠監聽這些事件,而且能夠調用 webpack 的 API 對輸出資源進行處理。
這是它和 loader 的不一樣之處,loader 通常只能對源文件代碼進行轉換,而 plugin 能夠作得更多。plugin 在整個編譯週期中均可以被調用,只要監聽事件。
對於 webpack 編譯,有兩個重要的對象須要瞭解一下:
Compiler 和 Compilation
在插件開發中最重要的兩個資源就是 compiler 和 compilation 對象。理解它們的角色是擴展 webpack 引擎重要的第一步。
compiler 對象表明了完整的 webpack 環境配置。這個對象在啓動 webpack 時被一次性創建,並配置好全部可操做的設置,包括 options,loader 和 plugin。當在 webpack 環境中應用一個插件時,插件將收到此 compiler 對象的引用。能夠使用它來訪問 webpack 的主環境。
compilation 對象表明了一次資源版本構建。當運行 webpack 開發環境中間件時,每當檢測到一個文件變化,就會建立一個新的 compilation,從而生成一組新的編譯資源。一個 compilation 對象表現了當前的模塊資源、編譯生成資源、變化的文件、以及被跟蹤依賴的狀態信息。compilation 對象也提供了不少關鍵時機的回調,以供插件作自定義處理時選擇使用。
這兩個組件是任何 webpack 插件不可或缺的部分(特別是 compilation),所以,開發者在閱讀源碼,並熟悉它們以後,會感到獲益匪淺。
咱們看一下官網的定義,webpack 插件由如下部分組成:
簡單的說,一個具備 apply 方法的函數就是一個插件,而且它要監聽 webpack 的某個事件。下面來看一個簡單的示例:
function Plugin(options) { }
Plugin.prototype.apply = function (compiler) {
// 全部文件資源都被 loader 處理後觸發這個事件
compiler.plugin('emit', function (compilation, callback) {
// 功能完成後調用 webpack 提供的回調
console.log('Hello World')
callback()
})
}
module.exports = Plugin
複製代碼
寫完插件後要怎麼調用呢?
先在 webpack 配置文件中引入插件,而後在 plugins 選項中配置:
const Plugin = require('./src/plugin')
module.exports = {
...
plugins: [
new Plugin()
]
}
複製代碼
這就是一個簡單的插件了。
下面咱們再來寫一個複雜點的插件,它的做用是將通過 loader 處理後的打包文件 bundle.js
引入到 index.html
中:
function Plugin(options) { }
Plugin.prototype.apply = function (compiler) {
// 全部文件資源通過不一樣的 loader 處理後觸發這個事件
compiler.plugin('emit', function (compilation, callback) {
// 獲取打包後的 js 文件名
const filename = compiler.options.output.filename
// 生成一個 index.html 並引入打包後的 js 文件
const html = `<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="${filename}"></script> </head> <body> </body> </html>`
// 全部處理後的資源都放在 compilation.assets 中
// 添加一個 index.html 文件
compilation.assets['index.html'] = {
source: function () {
return html
},
size: function () {
return html.length
}
}
// 功能完成後調用 webpack 提供的回調
callback()
})
}
module.exports = Plugin
複製代碼
OK,執行一下,看看效果。
完美,和預測的結果如出一轍。
完整 demo 源碼,請看個人 github。
想了解更多的事件,請看官網介紹 compiler 鉤子。