目录
1. Plugin 用作和工作原理
1.1 Plugin 的作用
1.2 Plugin 的工作原理
2. Webpack 底层逻辑和钩子介绍
2.1 Webpack 内部执行流程
2.2 Webpack 内部钩子
2.2.1 钩子是什么
2.2.2 Tapable —— 为 Webpack 提供 Plugin 钩子 数据类型接口 定义
2.2.3 Compiler Hooks(继承了 Tapable )
2.2.4 Compilation Hooks(继承了 Tapable )
2.2.5 JavascriptParser Hooks(继承了 Tapable )
3. 手写 Webpack Plugin
3.1 Plugin 基本结构
3.2 在 html 项目中,使用自定义插件
3.3 在 Vue 项目中,使用自定义插件
3.4 开发 Webpack 文件清单插件
4. 常用的 Webpack Plugin
5. 参考文章
关于 Plugin 的作用,Webpack 官方是这样介绍的:
Plugins expose the full potential of the webpack engine to third-party developers. Using staged build callbacks, developers can introduce their own behaviors into the webpack build process.
我们可以通过插件,扩展 Webpack,加入自定义的构建行为,使 Webpack 可以执行更广泛的任务,拥有更强的构建能力。
简单来说,就是扩展 Webpack 功能
关于 Plugin 的工作原理,「深入浅出 Webpack」是这样介绍的:
webpack 就像一条生产线,要经过一系列处理流程后才能将源文件转换成输出结果。 这条生产线上的每个处理流程的职责都是单一的,多个流程之间有存在依赖关系,只有完成当前处理后才能交给下一个流程去处理。
插件就像是一个插入到生产线中的一个功能,在特定的时机对生产线上的资源做处理。webpack 通过 Tapable 来组织这条复杂的生产线。 webpack 在运行过程中会广播事件,插件只需要监听它所关心的事件,就能加入到这条生产线中,去改变生产线的运作。
webpack 的事件流机制保证了插件的有序性,使得整个系统扩展性很好。
——「深入浅出 Webpack」
简单来说:
站在代码逻辑的角度:
开发 Plugin,需要用到一些 Webpack 底层的逻辑
一次完整的 Webpack 打包,大致是这样的过程:
钩子的本质就是:事件
为了方便 开发者 直接介入和控制编译过程,Webpack 把编译过程中,触发的各类关键事件,封装成事件接口暴露了出来,这些接口被很形象地称做:hooks(钩子)
开发插件,离不开这些钩子
Tapable 暴露了三个方法给 Plugin,用于注入 不同类型的 自定义构建行为:
举个栗子~~~~~
Webpack 里的几个非常重要的对象,Compiler, Compilation 和 JavascriptParser 都继承了 Tapable 类,它们身上挂着丰富的钩子
Tapable 是 Webpack 的核心功能库
在 Tapable 源码中可以看到,Webpack 中目前有十种 hooks
// https://github.com/webpack/tapable/blob/master/lib/index.js
exports.SyncHook = require("./SyncHook");
exports.SyncBailHook = require("./SyncBailHook");
exports.SyncWaterfallHook = require("./SyncWaterfallHook");
exports.SyncLoopHook = require("./SyncLoopHook");
exports.AsyncParallelHook = require("./AsyncParallelHook");
exports.AsyncParallelBailHook = require("./AsyncParallelBailHook");
exports.AsyncSeriesHook = require("./AsyncSeriesHook");
exports.AsyncSeriesBailHook = require("./AsyncSeriesBailHook");
exports.AsyncSeriesLoopHook = require("./AsyncSeriesLoopHook");
exports.AsyncSeriesWaterfallHook = require("./AsyncSeriesWaterfallHook");
Compiler 上暴露的一些常用的钩子:
钩子 | 类型 | 什么时候调用 |
---|---|---|
run | AsyncSeriesHook | 在编译器 开始读取记录前 执行 |
compile | SyncHook | 在一个新的 compilation 创建之前执行 |
compilation | SyncHook | 在一次 compilation 创建后执行插件 |
make | AsyncParallelHook | 完成一次编译前执行 |
emit | AsyncSeriesHook | 在生成文件到 output 目录之前执行,回调参数:compilation |
afterEmit | AsyncSeriesHook | 在生成文件到 output 目录之后执行 |
assetEmitted | AsyncSeriesHook | 生成文件的时候执行,提供访问产出文件信息的入口,回调参数:file,info |
done | AsyncSeriesHook | 一次编译完成后执行,回调参数:stats |
Compilation 上暴露的一些常用的钩子:
钩子 | 类型 | 什么时候调用 |
---|---|---|
buildModule | SyncHook | 在模块开始编译之前触发,可以用于修改模块 |
succeedModule | SyncHook | 当一个模块被成功编译,会执行这个钩子 |
finishModules | AsyncSeriesHook | 当所有模块都编译成功后被调用 |
seal | SyncHook | 当一次 compilation 停止接收新模块时触发 |
optimizeDependencies | SyncBailHook | 在依赖优化的开始执行 |
optimize | SyncHook | 在优化阶段的开始执行 |
optimizeModules | SyncBailHook | 在模块优化阶段开始时执行,插件可以在这个钩子里执行对模块的优化,回调参数:modules |
optimizeChunks | SyncBailHook | 在代码块优化阶段开始时执行,插件可以在这个钩子里执行对代码块的优化,回调参数:chunks |
optimizeChunkAssets | AsyncSeriesHook | 优化任何代码块资源,这些资源存放在 compilation.assets 上。一个 chunk 有一个 files 属性,它指向由一个 chunk 创建的所有文件。任何额外的 chunk 资源都存放在 compilation.additionalChunkAssets 上。回调参数:chunks |
optimizeAssets | AsyncSeriesHook | 优化所有存放在 compilation.assets 的所有资源。回调参数:assets |
JavascriptParser 上暴露的一些常用的钩子:
钩子 | 类型 | 什么时候调用 |
---|---|---|
evaluate | SyncBailHook | 在计算表达式的时候调用 |
statement | SyncBailHook | 为代码片段中每个 已解析的语句 调用的通用钩子 |
import | SyncBailHook | 为代码片段中每个 import 语句调用,回调参数:statement,source |
export | SyncBailHook | 为代码片段中每个 export 语句调用,回调参数:statement |
call | SyncBailHook | 解析一个 call 方法的时候调用,回调参数:expression |
program | SyncBailHook | 解析一个 表达式 的时候调用,回调参数:expression |
① 本质上是 Node 模块,该导出了一个 Javascript 方法 或 JavaScript 类
② 它的原型上,需要定义一个叫做 apply 的方法
③ 通过 compiler 获取 Webpack 内部的钩子,获取 Webpack 打包过程中的各个阶段;钩子分为同步和异步的钩子,异步钩子在功能完成后,必须执行对应的回调
④ 通过 compilation,操作 Webpack 内部实例特定数据
⑤ 功能完成后,调用 Webpack 提供的回调
新建项目,通过 npm init -y 创建 package.json,并在 script 中配置打包命令 "build": "webpack"
安装 webpack、webpack-cli
新建 MyPlugin.js 文件,声明一个 class,并导出(这就是自定义手写的 Webpack 插件)
通过 compiler 获取 webpack 内部的钩子 done,并在钩子中注入一条 控制台打印 的语句
根据上文 2.2.3 钩子介绍,done 会在一次编译完成后执行
所以这个插件会在每次打包结束时,向控制台输出 “♪(^∇^*) Lyrelion 打包已完成!”
// 自定义 MyPlugin 插件,该插件在打包完成后,在控制台输出 "打包已完成"
// 一个命名的 Javascript 方法 或 JavaScript 类
class MyPlugin {
// 原型上需要定义 apply 的方法
apply(compiler) {
// 通过 compiler 获取 webpack 内部的钩子,获取 Webpack 打包过程中的各个阶段
compiler.hooks.done.tap("My Plugin", (compilation, cb) => {
// 通过 compilation,操作 Webpack 内部实例特定数据
console.log("♪(^∇^*) Lyrelion 打包已完成!");
// 分为同步和异步的钩子,异步钩子在功能完成后,必须执行对应的回调
// cb();
});
}
}
module.exports = MyPlugin;
新建 webpack.config.js,引入自定义插件
const path = require('path');
const MyPlugin = require('./plugins/MyPlugin');
module.exports = {
mode: 'development',
entry: {
index: './src/index.js',
a: './src/a.js'
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
},
plugins: [
new MyPlugin(),
]
}
执行打包,控制台输入 yarn build,打包完成后,控制台输出效果:
在 vue.config.js 引入该插件
const MyPlugin = require('./MyPlugin.js')
在 configureWebpack 的 plugins 列表中,注册该插件
module.exports = {
configureWebpack: {
plugins: [new MyPlugin()]
}
};
执行项目打包命令,会在控制台输出 “♪(^∇^*) Lyrelion 打包已完成!”
需求:
基本思路:
编写插件:
/**
* 自定义 FileListPlugin 插件
* 每次 Webpack 打包后,自动产生一个打包文件清单
* 清单上要记录 文件名、文件数量 等信息
*/
class FileListPlugin {
constructor(options) {
// 获取插件配置项
this.filename = options && options.filename ? options.filename : 'FILELIST.md';
}
apply(compiler) {
// 注册 compiler 上的 emit 钩子
compiler.hooks.emit.tapAsync('FileListPlugin', (compilation, cb) => {
// 通过 compilation.assets 获取文件数量
let len = Object.keys(compilation.assets).length;
// 添加统计信息
let content = `# ${len} file${len > 1 ? 's' : ''} emitted by webpack\n\n`;
// 通过 compilation.assets 获取文件名列表
for (let filename in compilation.assets) {
content += `- ${filename}\n`;
}
// 往 compilation.assets 中添加清单文件
compilation.assets[this.filename] = {
// 写入新文件的内容
source: function () {
return content;
},
// 新文件大小(给 webapck 输出展示用)
size: function () {
return content.length;
}
}
// 执行回调,让 webpack 继续执行
cb();
})
}
}
module.exports = FileListPlugin;
配置 webpack.config.js:
const path = require('path');
const MyPlugin = require('./plugins/MyPlugin');
const FileListPlugin = require('./plugins/FileListPlugin');
module.exports = {
mode: 'development',
entry: {
index: './src/index.js',
a: './src/a.js'
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
},
plugins: [
new MyPlugin(),
new FileListPlugin({
filename: '_filelist.md'
})
]
}
最终效果:
插件名称 | 作用 |
---|---|
html-webpack-plugin | 生成 html 文件,引入公共的 js 和 css 资源 |
webpack-bundle-analyzer | 对打包后的文件进行分析,生成资源分析图 |
terser-webpack-plugin | 代码压缩,移除 console.log 打印等 |
HappyPack Plugin | 开启多线程打包,提升打包速度 |
Dllplugin | 动态链接库,将项目中依赖的三方模块抽离出来,单独打包 |
DllReferencePlugin | 配合 Dllplugin,通过 manifest.json 映射到相关的依赖上去 |
clean-webpack-plugin | 清理上一次项目生成的文件 |
vue-skeleton-webpack-plugin | vue 项目实现骨架屏 |
揭秘webpack plugin | ChampYin's BlogPlugin(插件) 是 webpack 生态的的一个关键部分。它为社区提供了一种强大的方法来扩展 webpack 和开发 webpack 的编译过程。本文将尝试探索 webpack plugin,揭秘它的工作原理,以及如何开发一个 plugin。https://champyin.com/2020/01/12/%E6%8F%AD%E7%A7%98webpack-plugin/