原文地址
編寫 loader 和 plugins
github
一、loader
1.loader 介紹
loader 是什麼
loader 其實是一個函數,對匹配到的內容進行轉換,將轉換後的結果返回。
loader 作用
在webpack中loader就像是一位翻譯官。webpack只認識JavaScript這們語言,對於其他的資源通過loader後可以轉化做預處理
- loader 的執行是有順序的,
支持鏈式的調用。loader的執行順序是從下到上,從右到左。比如處理樣式類的文件,use:['style-loader', 'css-loader']。css-loader處理後的文件返回給style-loader。 - 一個 Loader 的職責是單一的,只需要完成一種轉換。
- Webpack 會默認緩存所有 Loader 的處理結果,對沒有修改的 loader 不會重新加載,關閉webpack 的默認緩存結果需要添加
this.cacheable(false);
常見的loader
- 樣式類的 loader:
css-loader, style-loader, less-loader, postcss-loader(添加-webkit)等 - 文件類的 loader:
url-loader, file-loader, raw-loader等。 - 編譯類的 loader:
babel-loader, ts-loader等 - 校驗測試類 loader:
eslint-loader, jslint-loader等
4. loader 的三種使用方式
-
-
在
webpack.config.js中配置module.exports = { module:{ rules:[{ test:/\.css$/, use:['css-loader'], // use:{loader:'css-loader',options:{}} } ] } }
-
-
-
通過命令行的參數方式
webpack --module-bind 'css=css-loader'
-
-
-
通過內聯使用
import txt from 'css-loader!./file.css';
-
2. 編寫一個 loader
思路:前面我們説過1.loader 是一個函數;2.對匹配到的內容進行轉換;3.再將轉換內容返回,按照這個思路我們可以編寫一個最簡單的loader
// 在 ./loader/replaceLoader.js 創建一個替換字符串的 loader
module.exports = function(source) {
return source.replace('a', 'b')
}
// 在webpack.config.js 使用 自己寫的loader
module.exports = {
module:{
rules:[{
test:"/\.js$/",
use:[{
loader: path.resolve(__dirname, './loader/replaceLoader.js')
options:{
name: '林一一'
}
}
]
}]
}
}
// 或者使用 replaceLoader
module.exports={
resolveLoader:['node_modules', './loader']
module:{
rules:[{
test:"/\.js$/",
use:['resolveLoader']
}]
}
}
上面就是一個最簡單的編寫 loader 的案例
-
loader 還可以接收
options傳入的參數,詳情查看 loader API,也可以使用官方提供的loader-util接收參數const loaderUtil = require('loader-utils') module.exports = function(source) { console.log(this.query.name) // 林一一 const options = loaderUtil.getOptions(this) return source.replace('a', 'b') } -
異步:loader 是一個函數自然有同步和異步的區分。使用異步的loader需要添加
this.async()申明異步操作const loaderUtils = require('loader-utils') module.exports = function(source) { const options = loaderUtils.getOptions(this) const callback = this.async() setTimeout(()=>{ console.log(options.name) let res = source.replace('a', options.name) callback(null, res, sourceMaps, ast) }, 4000) }上面的代碼會在4秒後打包成功,如果沒有
this.async()異步操作就會失敗,callback()回調函數將結果放回。 - 默認情況下 webpack 給 loader 傳遞的字符串編碼是
utf-8,如果需要處理二進制的文件需要添加exports.raw = true。 - 上面提到過 webpack 會默認將
loader的加載結果緩存如果需要關閉webpack的緩存結果需要添加this.cacheable(false);。 Npm link專門用於開發和調試本地 Npm 模塊,在沒有發佈到 npm 上面也可以在調式本地的loader。具體需要在package.json中配置本地loader,在根目錄下執行npm link loader-name就可以在node_modules中使用本地的loader了。同時也可以採用上面的resolveLoader實現導入loader的方式
總結編寫 loader 的思路
- loader 是一個導出函數,有返回值,可以藉助第三方模塊和Node api 實現。
- loader 可以使用
loader-utils接收到options中傳遞過來的參數- loader 的異步編寫需要顯示的申明
const callback = this.async()表明異步。- loader 如果需要處理二進制文件也需要聲明
exports.raw = true- loader 的允許結果會被webpack緩存,如果需要關閉
webpack的緩存結果需要聲明this.cacheable(false)- 編寫後的本地
loader可以藉助Npm link或resolveLoader導入。
二、webpack 的構建流程
再講 plugins 之前需要先清楚 webpack 的構建流程是怎樣的
初始化參數。從配置文件和shell語句中合併的參數開始編譯。將上一步得到的參數初始化成complier對象,加載所有的導入插件,執行對象的 run 方法開始執行編譯;確定入口。從配置的entry入口找出所有的入口文件。編譯模塊。根據入口文件的依賴,調用所有配置的loader進行轉換。完成模塊編譯並輸出。根據入口文件之間的依賴關係,形成一個個代碼塊chunk。輸出完成。將形成的代碼塊chunk輸出到文件系統。
上面初始化形成的complier對象 會被注入到插件的apply(complier)內。complier對象對象包含了 Webpack 環境所有的的配置信息比如options, loaders, plugins等等屬性,可以簡單的認為complier是 webpack 的實例,通過compler.plugin()可以監聽到webpack廣播出來的事件。
三、plugin
1 plugin 介紹
plugin 是什麼
plugin 是一個插件,這個插件也就是一個類,基於事件流框架Tapable實現。在 webpack 的構建流程中在初始化參數後,就會加載所有的plugin插件,創建插件的實例。
plugin 作用
plugin通過鈎子可以涉及到webpack的整一個事件流程。也就是説plugin可以通過監聽這些生命週期的鈎子在合適的時機使用webpack提供的API 做一些事情。
常見的 plugin
html-webpack-plugin會在打包後自動生成一個html文件,並且會將打包後的 js 文件引入到html文件內。optimize-css-assets-webpack-plugin對CSS 代碼進行壓縮。mini-css-extract-plugin。將寫入style標籤內的 css 抽離成一個 用link導入 生成的 CSS 文件webpack-parallel-uglify-plugin。開啓多進程執行代碼壓縮,提高打包的速度。clean-webpack-plugin。每次打包前都將舊生成的文件刪除。serviceworker-webpack-plugin。為網頁應用增加離線緩存功能。
plugin 的使用方式
在plugins中使用
const ServiceworkerWebpackPlugin = require('serviceworker-webpack-plugin')
module.exports = {
plugins:[
new ServiceworkerWebpackPlugin(),
]
}
2 編寫一個 plugin
思路:plugins是一個類,webpack為 plugin 提供了很多內置的 api,需要在原型上定義apply(compliers)函數。同時指定要掛載的webpack鈎子。
class MyPlugin {
constructor(params){
console.log(params)
}
// webpack 初始化參數後會調用這個引用函數,闖入初始化的 complier對象。
apply(complier){
// 綁定鈎子事件
// complier.hooks.emit.tapAsync()
compiler.plugin('emit', compilation => {
console.log('MyPlugin')
))
}
}
module.export = MyPlugin
compilation對象包含當前的模塊資源、編譯生成資源、和能監聽變化的文件。每一個文件發生變化後,都會生成一個compilation對象,通過compilation也能讀取到compiler對象。
調式plugin可以使用 node 的調式工具在package.json中添加"debug":"node --inspect --inspect brk node_modules/webpack/bin/webpack.js"
總結編寫 plugin 的思路
- 編寫一個 class 類 。
- 在類中定義一個 apply 方法。
- 在應用方法
apply()中指定掛載的webpack事件鈎子complier.hooks.。- 處理 webpack 內部實例的特定數據。
- 功能完成後調用 webpack 提供的回調。
四、面試題
1. loader 和 plugin 的區別
- loader 是一個函數,用來匹配處理某一個特定的模塊,將接收到的內容進行轉換後返回。在
webpack中操作文件,充當文件轉換器的角色。在modules.rules中配置。- plugin 是一個插件,不直接操作文件,基於事件流框架
Tapable實現,plugin通過鈎子可以涉及到webpack的整一個事件流程。也就是説plugin可以通過監聽這些生命週期的鈎子在合適的時機使用webpack提供的API 做一些事情。在plugins中配置插件
2. loader 的編寫思路
參上
3. plugin 的編寫思路
參上
4. complier 和 compilation 區別
- complier 對象暴露了 webpack 整一個生命週期相關的鈎子,是
webpack初始化的參數的產物,包含options, entry, plugins等屬性可以簡單的理解為webpack的一個實例。compilation對象是complier的實例,是每一次webpack構建過程中的生命週期對象。每一個文件發生變化後都能生成一個complition對象。
總結:兩個對象都有自己的生命週期鈎子,compilation 對象負責的是粒度更小的生命週期鈎子。compiler對象是webpack整一個整個生命週期鈎子的對象。
參考
webpack之loader和plugin簡介
webpack 構建流程
webpack loader和plugin編寫
深入Webpack-編寫Loader