webpack 可谓是让人欣喜又让人忧,功能强大但需要一定的学习成本。在探寻 webpack 插件机制前,首先需要了解一件有意思的事情,webpack 插件机制是整个 webpack 工具的骨架,而 webpack 本身也是利用这套插件机制构建出来的。因此在深入认识 webpack 插件机制后,再来进行项目的相关优化,想必会大有裨益。

webpack 插件

先来瞅瞅 webpack 插件在项目中的运用

const MyPlugin = require('myplugin')
const webpack = require('webpack')webpack({...,plugins: [new MyPlugin()]...,
})
复制代码

那么符合什么样的条件能作为 webpack 插件呢?一般来说,webpack 插件有以下特点:

  1. 独立的 JS 模块,暴露相应的函数

  2. 函数原型上的 apply 方法会注入 compiler 对象

  3. compiler 对象上挂载了相应的 webpack 事件钩子

  4. 事件钩子的回调函数里能拿到编译后的 compilation 对象,如果是异步钩子还能拿到相应的 callback

下面结合代码来看看:

function MyPlugin(options) {}
// 2.函数原型上的 apply 方法会注入 compiler 对象
MyPlugin.prototype.apply = function(compiler) {// 3.compiler 对象上挂载了相应的 webpack 事件钩子 4.事件钩子的回调函数里能拿到编译后的 compilation 对象compiler.plugin('emit', (compilation, callback) => {...})
}
// 1.独立的 JS 模块,暴露相应的函数
module.exports = MyPlugin
复制代码

这样子,webpack 插件的基本轮廓就勾勒出来了,此时疑问点有几点,

  1. 疑问 1:函数的原型上为什么要定义 apply 方法?阅读源码后发现源码中是通过 plugin.apply() 调用插件的。
const webpack = (options, callback) => {...for (const plugin of options.plugins) {plugin.apply(compiler);}...
}
复制代码
  1. 疑问 2:compiler 对象是什么呢?

  2. 疑问 3:compiler 对象上的事件钩子是怎样的?

  3. 疑问 4:事件钩子的回调函数里能拿到的 compilation 对象又是什么呢?

这些疑问也是本文的线索,让我们一个个探索。

compiler 对象

compiler 即 webpack 的编辑器对象,在调用 webpack 时,会自动初始化 compiler 对象,源码如下:

// webpack/lib/webpack.js
const Compiler = require("./Compiler")const webpack = (options, callback) => {...options = new WebpackOptionsDefaulter().process(options) // 初始化 webpack 各配置参数let compiler = new Compiler(options.context)             // 初始化 compiler 对象,这里 options.context 为 process.cwd()compiler.options = options                               // 往 compiler 添加初始化参数new NodeEnvironmentPlugin().apply(compiler)              // 往 compiler 添加 Node 环境相关方法for (const plugin of options.plugins) {plugin.apply(compiler);}...
}
复制代码

终上,compiler 对象中包含了所有 webpack 可配置的内容,开发插件时,我们可以从 compiler 对象中拿到所有和 webpack 主环境相关的内容。

compilation 对象

compilation 对象代表了一次单一的版本构建和生成资源。当运行 webpack 时,每当检测到一个文件变化,一次新的编译将被创建,从而生成一组新的编译资源。一个编译对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态信息。

结合源码来理解下上面这段话,首先 webpack 在每次执行时会调用 compiler.run() (源码位置),接着追踪 onCompiled 函数传入的 compilation 参数,可以发现 compilation 来自构造函数 Compilation。

// webpack/lib/Compiler.js
const Compilation = require("./Compilation");newCompilation(params) {const compilation = new Compilation(this);...return compilation;
}
复制代码

不得不提的 tapable 库

再介绍完 compiler 对象和 compilation 对象后,不得不提的是 tapable 这个库,这个库暴露了所有和事件相关的 pub/sub 的方法。而且函数 Compiler 以及函数 Compilation 都继承自 Tapable。

事件钩子

事件钩子其实就是类似 MVVM 框架的生命周期函数,在特定阶段能做特殊的逻辑处理。了解一些常见的事件钩子是写 webpack 插件的前置条件,下面列举些常见的事件钩子以及作用:

钩子 作用 参数 类型
after-plugins 设置完一组初始化插件之后 compiler sync
after-resolvers 设置完 resolvers 之后 compiler sync
run 在读取记录之前 compiler async
compile 在创建新 compilation 之前 compilationParams sync
compilation compilation 创建完成 compilation sync
emit 在生成资源并输出到目录之前 compilation async
after-emit 在生成资源并输出到目录之后 compilation async
done 完成编译 stats sync

完整地请参阅官方文档手册,同时浏览相关源码 也能比较清晰地看到各个事件钩子的定义。

插件流程浅析

拿 emit 钩子为例,下面分析下插件调用源码:

compiler.plugin('emit', (compilation, callback) => {// 在生成资源并输出到目录之前完成某些逻辑
})
复制代码

此处调用的 plugin 函数源自上文提到的 tapable 库,其最终调用栈指向了 hook.tapAsync(),其作用类似于 EventEmitter 的 on,源码如下:

// Tapable.js
options => {...if(hook !== undefined) {const tapOpt = {name: options.fn.name || "unnamed compat plugin",stage: options.stage || 0};if(options.async)hook.tapAsync(tapOpt, options.fn); // 将插件中异步钩子的回调函数注入elsehook.tap(tapOpt, options.fn);return true;}
};
复制代码

有注入必有触发的地方,源码中通过 callAsync 方法触发之前注入的异步事件,callAsync 类似 EventEmitter 的 emit,相关源码如下:

this.hooks.emit.callAsync(compilation, err => {if (err) return callback(err);outputPath = compilation.getPath(this.outputPath);this.outputFileSystem.mkdirp(outputPath, emitFiles);
});
复制代码

一些深入细节这里就不展开了,说下关于阅读比较大型项目的源码的两点体会,

  • 要抓住一条主线索去读,忽视细节。否则会浪费很多时间而且会有挫败感;

  • 结合调试工具来分析,很多点不用调试工具的话很容易顾此失彼;

动手实现个 webpack 插件

结合上述知识点的分析,不难写出自己的 webpack 插件,关键在于想法。为了统计项目中 webpack 各包的有效使用情况,在 fork webpack-visualizer 的基础上对代码升级了一番,项目地址。效果如下:

插件核心代码正是基于上文提到的 emit 钩子,以及 compiler 和 compilation 对象。代码如下:

class AnalyzeWebpackPlugin {constructor(opts = { filename: 'analyze.html' }) {this.opts = opts}apply(compiler) {const self = thiscompiler.plugin("emit", function (compilation, callback) {let stats = compilation.getStats().toJson({ chunkModules: true }) // 获取各个模块的状态let stringifiedStats = JSON.stringify(stats)// 服务端渲染let html = `<!doctype html><meta charset="UTF-8"><title>AnalyzeWebpackPlugin</title><style>${cssString}</style><div id="App"></div><script>window.stats = ${stringifiedStats};</script><script>${jsString}</script>`compilation.assets[`${self.opts.filename}`] = { // 生成文件路径source: () => html,size: () => html.length}callback()})}
}
复制代码

参考资料

看清楚真正的 Webpack 插件

webpack 官网

探寻 webpack 插件机制相关推荐

  1. webpack插件机制

    webpack插件机制是整个webpack工具的核心,那么webpack插件有什么特点呢? 1.独立的JS模块,暴露相应的函数 2.函数原型上的apply方法会注入compiler对象(之所以要定义a ...

  2. mixin机制 vue_读?VuePress(四)插件机制

    前言 从 9 月份开始,vuepress 源码进行了重新设计和拆分.先是开了个 next 分支,后来又合并到 master 分支,为即将发布的 1.x 版本做准备. 最主要的变化是:大部分的全局功能都 ...

  3. delphi 的插件机制与自动更新

    delphi 的插件机制与自动更新 : 1.https://download.csdn.net/download/cxp_2008/2226978   参考 2.https://download.cs ...

  4. 【面试】815- 面试官常问的 webpack 插件

    Plugin ❝ 何为插件(Plugin)?专注处理 webpack 在编译过程中的某个特定的任务的功能模块,可以称为插件.plugin 是一个扩展器,它丰富了 webpack 本身,针对是 load ...

  5. 霖呆呆的六个自定义Webpack插件详解-自定义plugin篇(3)

    霖呆呆的webpack之路-自定义plugin篇 你盼世界,我盼望你无bug.Hello 大家好!我是霖呆呆! 有很多小伙伴在打算学写一个webpack插件的时候,就被官网上那一长条一长条的API给吓 ...

  6. Webpack插件是如何编写的——prerender-spa-plugin源码解析

    概述 本文主要的内容是通过之前使用的prerender-spa-plugin插件的源码阅读,来看下我们应该如何编写一个webpack的插件,同时了解下预渲染插件到底是如何实现的. 这个内容其实已经在使 ...

  7. 探寻背后的机制化繁为简:网站程序升级不过是文件同步

    苹果落到地上而不是天上,这是重力的作用: 树叶从树枝上飘落的样子谁也无法预测,这是混沌过程: 热恋中的恋人总是难分难舍,这是荷尔蒙等激素作用于下丘脑的结果: ... ... 诸如这些"复杂& ...

  8. 小程序工程化实践(上篇)-- 手把手教你撸一个小程序 webpack 插件,一个例子带你熟悉 webpack 工作流程...

    本文基于 webpack 4 和 babel 7,Mac OS,VS Code 小程序开发现状: 小程序开发者工具不好用,官方对 npm 的支持有限,缺少对 webpack, babel 等前端常用工 ...

  9. 初探maven插件机制

    初探maven插件机制 第一部分 Plexus 本质上说,Maven是一个plugin的框架,所以需要有一个管理者来管理这些plugin.Maven选择了Plexus作为plugin的管理者.作为初探 ...

最新文章

  1. 深度神经网络中的Batch Normalization介绍及实现
  2. idea软件,如何不每次弹出“欢迎界面!”
  3. Linux安装zookeeper并验证
  4. android最佳活动启动方法,026-启动活动的最佳写法
  5. Effective Java之列表由于数组(二十五)
  6. (JAVA)Arrays数组工具类
  7. 【人生】比未知更可怕的是预知---献给那些毕业季的童鞋
  8. 「Leetcode」242. 有效的字母异位词:可以拿数组当哈希表来用,但哈希值不要太大!
  9. Couldn't find leader offsets for Set news_topic
  10. sql 2005 用户 sa 登录失败
  11. 全球供应链报告显示,2020年中国采购业一枝独秀
  12. Centos7.6搭建FTP服务器
  13. Unknown column 'salary' in 'field list'
  14. 靶机渗透练习97-hacksudo:ProximaCentauri
  15. 数据链路层的主要功能与服务
  16. 彻底搞懂ResNet50
  17. java 里面耦合和解耦
  18. SpringBootSecurity与Shiro
  19. 浅谈共轭梯度法的原理
  20. Java大作业-商品管理系统

热门文章

  1. boost::hana::maximum.by用法的测试程序
  2. boost::hana::reverse_fold用法的测试程序
  3. boost::graph模块实现一个只读隐式加权图的简单示例的测试程序
  4. Boost:compute::copy的复制数据测试程序
  5. Boost:基于Boost的HTTP客户端的程序
  6. DCMTK:测试CT Table Dynamics FG类
  7. VTK:图表之VisualizeDirectedGraph
  8. OpenCV基本线性变换轨迹栏的实例(附完整代码)
  9. STL的deque容器
  10. QT的QMutex类的使用