前言
- 第一篇tapable,第二篇ast,第三篇loader
webpack構建流程
- 從配置文件和命令行獲取參數
- 創建Complier對象
- 執行Compiler的run方法創建Compliation
- 尋找入口文件,調用所有配置的loader對所有模塊進行編譯。
- 經loader編譯完後得到每個模塊最終內容及依賴關係。
- 根據入口和模塊依賴關係,組裝包含多模塊的chunk,把每個chunk轉換成單獨文件加入輸出列表。這步是修改輸出內容的最後機會。
寫個loader插件
- 首先是webpack配置,一般可以通過配置resolveLoader來定位自己loader的位置:
resolveLoader: {
modules: [path.resolve(__dirname, 'loaders'), 'node_modules']
},
function myloader(source) {
return source + '//1111'
}
module.exports = myloader
function myloader2(soucrce) {
return soucrce + '//222'
}
module.exports = myloader2
- 在入口文件裏寫個
console.log('yehuozhili')
- rules對其進行配置:
rules: [{
test: /\.js$/,
use: [{
loader: 'myloader'
}, {
loader: 'myloader2'
}],
exclude: /node_modules/,
eval("console.log('yehuozhili')//222//1111\n\n//# sourceURL=webpack:///./src/index.js?");
- 可以看見是從下到上走的。**所以第一個loader返回的內容,必須是js。**中間loader返回啥不用管。
- 除了配置webpack目錄,想使用自定義Loader還可以直接在loader上配置絕對路徑或者使用npm link連接自定義loader。
loaderAPI
緩存功能
- 通過調用this.cacheable來使得loader是否有緩存,默認true。
function myloader(source) {
this.cacheable(true)
return source + '//1111'
}
- 簡單說就是webpack多次編譯時文件已經走過這個loader就不會走了,只有修改過的文件纔會走。
異步功能
- 有些時候,寫的loader需要異步操作才能取得返回值,比如讀一個文件,這時就得用異步api。
let fs = require('fs')
const path = require('path')
function myloader2(source) {
let cb = this.async()
fs.readFile(path.resolve(__dirname, 'yehuo.txt'), 'utf8', (err, content) => {
cb(err, source + content)
})
return
}
module.exports = myloader2
拿到二進制文件
- webpack默認是把代碼轉成utf-8格式讓你在loader裏拿到。可以設置raw使得拿到二進制文件,這個對處理圖片等文件很有用。
function myloader2(source) {
console.log(source)
return source
}
myloader2.raw = true
module.exports = myloader2
拿到配置項
- 很多時候,loader會有配置選項,可以使用loader-utils拿到配置選項:
let loaderUtils = require('loader-utils')
function myloader2(source) {
let options = loaderUtils.getOptions(this)
console.log(options)
return source
}
module.exports = myloader2
{
loader: 'myloader2',
options: {
msg: '一個msg配置'
}
}
實現babel-loader
- 實現babel-loader並沒有想的那麼複雜,複雜功能babel都做好了,只要調就行。
const babel = require('@babel/core')
function myloader(source) {
let options = {
presets: [["@babel/preset-env"]],
sourceMap: true,
filename: this.resourcePath.split('\\').pop()
}
console.log(options)
let { code, map, ast } = babel.transform(source, options)
return this.callback(null, code, map, ast)
}
module.exports = myloader
- callback和上面的async其實一個玩意,除了轉換後的代碼,還可以傳map和ast,因爲webpack本來就要生成ast,傳了後就用生成的。map是用來放sourcemap調試用的。options裏的filename就是編譯前的文件名,會根據映射找編譯前的代碼。這個options是babel的配置項。this.resourcePath是動態獲取路徑。
- 這樣就完成了轉換。可以寫一段帶箭頭函數的,只用我們手寫的loader,看編譯後結果是不是變成了function了。
實現file-loader
- 首先看正版file-loader的功能。自動生成個hash名圖片然後拷貝過去,這樣在顯示的時候就會通過src找到這個路徑。
let img = new Image()
let src = require('./logo.jpg')
img.src = src.default
document.body.appendChild(img)
let loaderUtils = require('loader-utils')
function myloader2(source) {
let filename = loaderUtils.interpolateName(this, "[hash].[ext]", { content: source })
console.log(filename)
this.emitFile(filename, source)
return `
exports.__esModule=true;
exports[Symbol.toStringTag]='Module';
exports.default="${filename}"`
}
myloader2.raw = true
module.exports = myloader2
- 爲什麼返回這個,可以看一下前面寫的一篇webpack打包規律。
- 實測ok。
實現url-loader
- 看一下正版url-loader會有個配置,把一定大小以內的圖片轉成base64。
use: [
{
loader: 'url-loader',
options: {
limit: 100 * 1024
}
}
]
let loaderUtils = require('loader-utils')
function myloader3(source) {
let { limit } = loaderUtils.getOptions(this)
limit = parseInt(limit)
if (!limit || source.length < limit) {
let base64 = `data:image/png;base64,${source.toString('base64')}`
return `
exports.__esModule=true;
exports[Symbol.toStringTag]='Module';
exports.default="${base64}"`
} else {
let fileLoader = require('./myloader2')
return fileLoader.call(this, source)
}
}
myloader3.raw = true
module.exports = myloader3
- 大於設置的值傳給前面寫的file-loader執行,小於的值把圖片轉成base64返回。實測ok。