1. 前言
最近在学习 Webpack
相关的原理,以前只知道 Webpack 的配置方法,但并不知道其内部流程,经过一轮的学习,感觉获益良多,为了巩固学习的内容,我决定尝试自己动手写一个插件。
这个插件实现的功能比较简单:
默认清除 js
代码中的 console.log
的打印输出;
可通过传入配置,实现移除 console
的其它方法,如 console.warn
、console.error
等;
2. Webpack 的构建流程以及 plugin 的原理
2.1 Webpack 构建流程
Webpack
的主要构建流程,可以分为三个阶段:
初始化阶段:启动构建,读取与合并配置参数,加载 Plugin
,实例化 Compiler
。
编译阶段:从 Entry
发出,针对每个 Module
串行调用对应的 Loader
去翻译文件内容,再找到该 Module
依赖的 Module
,递归地进行编译处理。
生成阶段:对编译后的 Module
组合成 Chunk
,把 Chunk
转换成文件,输出到文件系统。
如果 Webpack
打包生产环境文件时,只会执行一次构建,以上阶段会按顺序执行一遍。但是在开启监听模式时,如开发环境,Webpack 会持续的进行构建。
2.2 plugin 原理
Webpack
插件通常是一个带有 apply
函数的类,其中 constructor
可以接收传入的配置项。插件被安装时,apply
函数会被调用一次,并接收 Compiler
对象,然后我们可以在 Compiler
对象上监听不同的事件钩子,从而进行插件功能的开发。
// 定义一个插件 class MyPlugin { // 构造函数,接收插件的配置项 options constructor(options) { // 获取配置项,初始化插件 } // 插件安装时会调用 apply,并传入 compiler apply(compiler) { // 获取 comolier 独享,可以监听事件钩子 // 功能开发 ... } }
2.3 compiler 和 compilation 对象
在开发 Plugin
过程中最常用的两个对象就是 Compiler
和 Compilation
:
Compiler
对象在 Webpack
启动时被实例化,该对象包含了 Webpack
环境所有的配置信息,包括 options
、loaders
、plugins
等。在整个 Webpack
构建过程中,Compiler
对象是全局唯一的, 它提供了很多事件钩子回调供插件使用。
Compilation
对象包含了当前的模块资源、编译生成资源、变化的文件等。Compilation
对象在 Webpack
构建过程中并不是唯一的,如果在开发模式下 Webpack
开启了文件检测功能,每当文件变化时,Webpack
会重新构建,此时会生成一个新的 Compilation
对象。Compilation
对象也提供了很多事件回调供插件做扩展。
3. 插件开发
3.1 项目目录
该插件实现的功能比较简单,文件目录也不复杂。首先新建一个空文件夹 remove-console-Webpack-plugin
,并在该文件夹目录下运行 npm init
,根据提示来填写 package.json
相关信息。然后再新建一个 src
文件夹,插件主要代码就放在 src/index.js
里面。如果你需要把项目放到 github
上,最好也添加一下 .gitignore
、README.md
等文件。
// remove-console-Webpack-plugin ├─src │ └─index.js ├─.gitignore ├─package.json └─README.md
3.2 插件代码
插件代码逻辑也并不复杂,主要有几点:
在构造函数中接收配置参数,并对参数进行合并,得到需要清除的 console
函数, 存放在 removed
数组中;
在 apply
函数中监听 compiler.hook.compilation
钩子,该钩子触发后,拿到 compilation
后进一步监听它的钩子,这里 Webpack4
和 Webpack5
的钩子不一样,需要做兼容;
定义 assetsHandler
方法来处理 js
文件,利用正则表达式清除 removed
中包括的 console
函数;
class RemoveConsoleWebpackPlugin { // 构造函数接受配置参数 constructor(options) { let include = options && options.include; let removed = ['log']; // 默认清除的方法 if (include) { if (!Array.isArray(include)) { console.error('options.include must be an Array.'); } else if (include.includes('*')) { // 传入 * 表示清除所有 console 的方法 removed = Object.keys(console).filter(fn => { return typeof console[fn] === 'function'; }) } else { removed = include; // 根据传入配置覆盖 } } this.removed = removed; } // Webpack 会调用插件实例的 apply 方法,并传入compiler 对象 apply(compiler) { // js 资源代码处理函数 let assetsHandler = (assets, compilation) => { let removedStr = this.removed.reduce((a, b) => (a + '|' + b)); let reDict = { 1: [RegExp(`\\.console\\.(${removedStr})\\(\\)`, 'g'), ''], 2: [RegExp(`\\.console\\.(${removedStr})\\(`, 'g'), ';('], 3: [RegExp(`console\\.(${removedStr})\\(\\)`, 'g'), ''], 4: [RegExp(`console\\.(${removedStr})\\(`, 'g'), '('] } Object.entries(assets).forEach(([filename, source]) => { // 匹配js文件 if (/\.js$/.test(filename)) { // 处理前文件内容 let outputContent = source.source(); Object.keys(reDict).forEach(i => { let [re, s] = reDict[i]; outputContent = outputContent.replace(re, s); }) compilation.assets[filename] = { // 返回文件内容 source: () => { return outputContent }, // 返回文件大小 size: () => { return Buffer.byteLength(outputContent, 'utf8') } } } }) } /** * 通过 compiler.hooks.compilation.tap 监听事件 * 在回调方法中获取到 compilation 对象 */ compiler.hooks.compilation.tap('RemoveConsoleWebpackPlugin', compilation => { // Webpack 5 if (compilation.hooks.processAssets) { compilation.hooks.processAssets.tap( { name: 'RemoveConsoleWebpackPlugin' }, assets => assetsHandler(assets, compilation) ); } else if (compilation.hooks.optimizeAssets) { // Webpack 4 compilation.hooks.optimizeAssets.tap( 'RemoveConsoleWebpackPlugin', assets => assetsHandler(assets, compilation) ); } }) } } // export Plugin module.exports = RemoveConsoleWebpackPlugin;
4. 发布到npm
希望别人能使用到你的插件,就需要把插件发布到 npm
上,发布的主要流程:
首先在 npm
官网上注册账号,然后打开命令行工具,在任意目录下输入 npm login
并按提示登录;
登录后可用 npm whoami
查看是否登录成功;
发布前检查一下根目录下的 package.json
文件信息是否填写正确,主要字段:
name:决定用户下载你的插件时用的名称,不可与 npm
上已有的第三方包重名,否则无法发布;
main:插件主文件入口,Webpack
引入插件时,就从该目录导入;
version:每次更新发布时,需要与上一版本的版本号不一样,否则上传不成功;
repository:如果你的插件代码放在 github
、gitee
等网站,可以填一下;
private:不能设置为 true
,否则无法发布;
一切准备就绪后,切换到插件所在的目录下,运行 npm publish
即可上传插件;
上传成功后,到 npm
官网上搜索,看看是否能搜到插件;
评论(0)