在前两天同学的面试中,有一位同学被问到 如何开发 webpack 的 loader 和 plugin?有没有实际 loader 或者 plugin 的开发经验。果然,面试只会越来越卷啊。
webpack
是大家所熟知的打包工具,里面包含了 5 个核心概念:
- 入口:entry
- 出口:output
- 加载器:loader
- 插件:plugin
- 模式:mode
图片
入口、出口、模式 的概念其实都比较好理解。但是一旦涉及到 loader
和 plugin
,特别是实现 loader
和 plugin
很多小伙伴就比较懵了。
所以,今天咱们就拿出 10 分钟的时间,一起来看那看那 loader
和 plugin
是如何实现的!
module、chunk、bundle
如果要讲解 plugin
和 loader
那么会涉及到三个术语:module、chunk、bundle
。
所以咱们就先说明下 module、chunk、bundle
然后再来看下 plugin
和 loader
。
在打包工具中,有三个 “术语”:module、chunk、bundle
:
module:模块
:通常一个模块代表了一个文件。一般指的是js
文件,当然也可以是css 文件
或者 图像文件。chunk:块
:块通常是在构建过程中由打包工具(如:Webpack
)根据配置生成的,它们由一组相关的模块放在一起打包组成。bundle:打包文件
:打包文件是指构建工具在打包过程中生成的最终输出文件,可以在浏览器中加载并运行。
总的来说:模块是组成项目的一个个文件,块是由一组相关模块组成的单元,而打包文件是构建工具最终生成的包含模块和资源的输出文件。
实现 loader 和 plguin
那么明确好了这三个基本的概念之后,接下来咱们来看下 loader
和 plugin
:
loader
一般被称为 加载器, webpack
默认只能处理 .js
的文件。如果项目中遇到其他类型的文件,那么就需要通过 loader
进行处理。
plugin
一般被叫做 插件,它可以为 构建工具(不只是 webpack
,还包含 vite
或者 rollup
) 提供一些附加的功能。比如说,咱们上一小节用到了 HtmlWebpackPlugin
它就是一个典型的插件,它可以在 webpack
构建的过程中生成一个新的 HTML
文件。并且自动生成新的 bundle
文件。
明确好了它们的基本概念之后,接下来咱们来看下它们 运行时机 的区别,咱们来看下面这张图:
图片
从图中咱们可以看到:
loader
是在打包之前执行的,执行的时机比较固定。其实也很好理解嘛。loader
它实质就是一个转换器,将A
文件进行编译形成B
文件,操作的是文件,比如:将A.scss
转变为B.css
,单纯的文件转换过程。- 而
plugin
是在整个编译的周期中都会起作用。webpack
在整个运行的生命周期中,会广播出很多的事件,plugin
就可以监听这些事件,然后在某一个时机下,改变输出结果就可以了。
那么明确好了它们的一个运行机制之后,接下来咱们来实现一个简单的 loader
和 plugin
:
loader 的简单实现
需求:
实现一个
loader
处理txt
文件,把hello world
转化为 配置对象下content
属性的值
- 在
webpack-project/vue.config.js
中利用chainWebpack
添加新的loader
:
// 添加一个处理 txt 文件的loader
config.module
// 创建一个新的规则,命名为 'custom-loader'
.rule('txt-loader')
// 适用于哪些文件
.test(/\.txt$/)
// 指定要使用的 loader 的名称
.use('txt-loader')
// loader 的路径
.loader('./src/loaders/textLoader')
// 配置对象
.options({
content: '你好,世界'
})
.end()
- 然后创建
test.txt
文件:
hello world
- 实现
textLoader
:
const loaderUtils = require('loader-utils')
// 接收options配置
module.exports = function (source) {
// 获取配置文件
const options = loaderUtils.getOptions(this)
// 把 hello world 替换成 content 属性配置
source = source.replace(/hello world/, options.content)
// 最后需要返回一个可执行的代码,所以需要 module.exports = '内容'
return `module.exports = '${source}'`
}
- 最后在
main.js
中导入该文件,并打印:
const text = require('./test.txt')
console.log(text)
plugin 的简单实现
看完 loader
之后,接下来咱们来看一个 plugin
的构建。
需求:
在
webpack
打印完成之后,在终端输出指定内容
- 创建
webpack-project/src/plugins/logPlugin.js
文件:
class LogPlugin {
// 通过构造函数,获取到传入的内容 content
constructor(options) {
this.content = options.content
}
// Webpack 会调用 logPlugin 实例的 apply 方法给插件实例传入 compiler 对象。compiler 表示编译器的实例,它代表了完整的 webpack 环境配置
apply(compiler) {
// 指定一个挂载到 webpack 自身的事件钩子。done 会在 webpack 构建完成后回调
compiler.hooks.done.tap('logPlugin', (compilation) => {
// compilation: 当前打包构建流程的上下文
console.log(this.content)
})
}
}
module.exports = LogPlugin
- 在
vue.config.js
中,添加plugin
:
// 添加一个新的 plugin
// 添加一个新的插件
config
.plugin('LogPlugin')
.use(LogPlugin, [{ content: 'hello sunday' }])
.end()
此时,运行项目可在终端打印指定内容。
webpack
在官网中提供了 如何构建 loader 和 如何构建 plugin 的文档,大家如果想要深入了解它们的构建方式的话,那么可以查询下对应的文档内容。