Hello,大家好,我是 Sunday。
在前两天同学的面试中,有一位同学被问到 如何开发 webpack 的 loader 和 plugin?有没有实际 loader 或者 plugin 的开发经验。果然,面试只会越来越卷啊。
webpack 是大家所熟知的打包工具,里面包含了 5 个核心概念:
入口:entry
出口:output
加载器:loader
插件:plugin
模式:mode
图片
入口、出口、模式 的概念其实都比较好理解。但是一旦涉及到 loader 和 plugin,特别是实现 loader 和 plugin 很多小伙伴就比较懵了。
所以,今天咱们就拿出 10 分钟的时间,一起来看那看那 loader 和 plugin 是如何实现的!
如果要讲解 plugin 和 loader 那么会涉及到三个术语:module、chunk、bundle。
所以咱们就先说明下 module、chunk、bundle 然后再来看下 plugin 和 loader。
在打包工具中,有三个 “术语”:module、chunk、bundle:
module:模块:通常一个模块代表了一个文件。一般指的是 js 文件,当然也可以是 css 文件 或者 图像文件。
chunk:块:块通常是在构建过程中由打包工具(如: Webpack)根据配置生成的,它们由一组相关的模块放在一起打包组成。
bundle:打包文件:打包文件是指构建工具在打包过程中生成的最终输出文件,可以在浏览器中加载并运行。
总的来说:模块是组成项目的一个个文件,块是由一组相关模块组成的单元,而打包文件是构建工具最终生成的包含模块和资源的输出文件。
那么明确好了这三个基本的概念之后,接下来咱们来看下 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 处理 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)
看完 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 的文档,大家如果想要深入了解它们的构建方式的话,那么可以查询下对应的文档内容。