前言
- 第一篇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。