在Webpack学习-工做原理(上)一文中咱们就已经介绍了Plugin
的基本概念,同时知道了webpack其实很像一条生产线,要通过一系列处理流程后才能将源文件转换成咱们理想的输出结果。而webpack构建过程当中,会在特定的时机广播对应的事件,插件能够监听这些事件的发生,Plugin
在webpack构建流程中就是这样的一个角色。同时咱们也介绍了不少整个构建流程会广播的事件,那么这篇文章咱们一块儿详细地学习一下如何编写Plugin
。javascript
其实Plugin
本质上就是一个class,一个最基础的Plugin
代码以下:css
class BasePlugin { // 构造函数,接收options配置 constructor(options) { ... } apply(compiler) { // 在此处去监听webpack广播的全部事件 compiler.plugin('compilation', function(compilation) { ... }); } } moudle.exports = BasePlugin;
咱们能够再看看,webpack会怎么配置Plugin
,html
module.exports = { plugins: [ new BasePlugin(options) ] }
咱们回忆一下,Webpack学习-工做原理(上)文章中们介绍过webpack的构建详细流程,初始化的时候会去new Plugin()
,那么即是会去实例化webpack配置plugins全部的插件,那么第一步插件实例化就有了,而插件中的apply方法会在开始编译时依次被调用,而且传入Compiler对象(后面会深刻介绍),而后调用Compiler.run()开始编译。java
- Compiler对象包含了Webpack环境全部的配置信息,包含options,loaders,plugins这些信息,这个对象在webpack启动时被实例化,全局惟一,能够简单理解成就是webpack实例
- Compilation表明着一次新的编译,包含当前的模块资源、编译生成的资源,变化的文件,以前咱们了解到compilation事件中compilation对象也会提供不少事件给插件作扩展,同时不少事件的的回调中都会将compilation传入,以便使用
- Webpack的事件机制应用了观察者模式,Compiler和Compilation同时继承Taptable,因此能够直接在Compiler和Compilation对象广播和监听事件,广播事件
[Compiler | Compilation].apply('event-name', params)
,监听事件[Compiler | Compilation].plugin('event-name', function(params){...})
,event-name不能和现有的事件重名
- 只要能拿到Compiler或是Compilation对象,就能广播新的事件,供其余插件使用
- Compiler或是Compilation对象为同一个引用,一旦修改就会影响后面的插件
- 若是事件是异步的,会带两个参数,第二个参数为回调函数,在插件处理完任务时须要调用回调函数通知webpack,才会进入下一个处理流程。如:
compiler.plugin('emit',function(compilation, callback) { // 支持处理逻辑 // 处理完毕后执行 callback 以通知 Webpack // 若是不执行 callback,运行流程将会一直卡在这不往下执行 callback(); });
class Plugin { apply(compiler) { compiler.plugin('emit', function (compilation, callback) { // compilation.chunks 存放全部代码块,是一个数组 compilation.chunks.forEach(function (chunk) { // chunk 表明一个代码块 // 代码块由多个模块组成,经过 chunk.forEachModule 能读取组成代码块的每一个模块 chunk.forEachModule(function (module) { // module 表明一个模块 // module.fileDependencies 存放当前模块的全部依赖的文件路径,是一个数组 module.fileDependencies.forEach(function (filepath) { }); }); // Webpack 会根据 Chunk 去生成输出的文件资源,每一个 Chunk 都对应一个及其以上的输出文件 // 例如在 Chunk 中包含了 CSS 模块而且使用了 ExtractTextPlugin 时, // 该 Chunk 就会生成 .js 和 .css 两个文件 chunk.files.forEach(function (filename) { // compilation.assets 存放当前全部即将输出的资源 // 调用一个输出资源的 source() 方法能获取到输出资源的内容 let source = compilation.assets[filename].source(); }); }); // 这是一个异步事件,要记得调用 callback 通知 Webpack 本次事件监听处理结束。 // 若是忘记了调用 callback,Webpack 将一直卡在这里而不会日后执行。 callback(); }) } }
// 当依赖的文件发生变化时会触发 watch-run 事件 compiler.plugin('watch-run', (watching, callback) => { // 获取发生变化的文件列表 const changedFiles = watching.compiler.watchFileSystem.watcher.mtimes; // changedFiles 格式为键值对,键为发生变化的文件路径。 if (changedFiles[filePath] !== undefined) { // filePath 对应的文件发生了变化 } callback(); });
compiler.plugin('after-compile', (compilation, callback) => { // 把 HTML 文件添加到文件依赖列表,好让 Webpack 去监听 HTML 模块文件,在 HTML 模版文件发生变化时从新启动一次编译 compilation.fileDependencies.push(filePath); callback(); });
// 设置 compilation.assets 的代码以下: compiler.plugin('emit', (compilation, callback) => { // 设置名称为 fileName 的输出资源 compilation.assets[fileName] = { // 返回文件内容 source: () => { // fileContent 既能够是表明文本文件的字符串,也能够是表明二进制文件的 Buffer return fileContent; }, // 返回文件大小 size: () => { return Buffer.byteLength(fileContent, 'utf8'); } }; callback(); }); // 读取 compilation.assets 的代码以下: compiler.plugin('emit', (compilation, callback) => { // 读取名称为 fileName 的输出资源 const asset = compilation.assets[fileName]; // 获取输出资源的内容 asset.source(); // 获取输出资源的文件大小 asset.size(); callback(); });
// 判断当前配置使用使用了 ExtractTextPlugin, // compiler 参数即为 Webpack 在 apply(compiler) 中传入的参数 function hasExtractTextPlugin(compiler) { // 当前配置全部使用的插件列表 const plugins = compiler.options.plugins; // 去 plugins 中寻找有没有 ExtractTextPlugin 的实例 return plugins.find(plugin=>plugin.__proto__.constructor === ExtractTextPlugin) != null; }
extract-text-webpack-plugin
插件进行断点调试的截图,能够来看看这两个分别打印出来的东西,
通常状况下,咱们是不须要去写Plugin
,可是有时候咱们有些业务需求是没有插件能够知足的,那么咱们便得须要本身去写Plugin
,那了解Plugin
的一些相关知识点就是有必要的,咱们不必定要每一个钩子或是API都至关熟,可是咱们须要思路,了解如何编写Plugin
,也是有必要的,Plugin
中最重要的compiler和compilation,一个Plugin
插件也就是围绕着这个去扩展,对应详细内容能够去webpack官网了解,compiler连接,compilation连接。webpack