一个为了让console.log写起来更偷懒的webpack-plugin
栏目: JavaScript · 发布时间: 5年前
内容简介:作为一个敲码5分钟,调试两小时的bug大叔,每天和console.log打的交道自然不少,人到中年,越来越懒,于是想把console.log('bug: ', bug)变成log.bug来让我的懒癌病发得更加彻底。于是硬着头皮看了下webpack插件的写法,在此记录一下webpack系统的学习笔记。去吧!皮卡丘!!!compiler 对象代表了完整的 webpack 环境配置。这个对象在启动 webpack 时被一次性建立,并配置好所有可操作的设置,包括 options,loader 和 plugin。可以
作为一个敲码5分钟,调试两小时的bug大叔,每天和console.log打的交道自然不少,人到中年,越来越懒,于是想把console.log('bug: ', bug)变成log.bug来让我的懒癌病发得更加彻底。于是硬着头皮看了下webpack插件的写法,在此记录一下webpack系统的学习笔记。
跟着webpack源码摸石过河
去吧!皮卡丘!!!
- webpack初始化
// webpack.config.js const webpack = require('webpack'); const WebpackPlugin = require('webpack-plugin'); const options = { entry: 'index.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'index.bundle.js' }, module: { rules: [] }, plugins: [ new WebpackPlugin() ] // ... }; webpack(options); 复制代码
// webpack.js // 引入Compiler类 const Compiler = require("./Compiler"); // webpack入口函数 const webpack = (options, callback) => { // 创建一个Compiler实例 compiler = new Compiler(options.context); // 实例保存webpack的配置对象 compiler.options = options // 依次调用webpack插件的apply方法,并传入compiler引用 for (const plugin of options.plugins) { plugin.apply(compiler); } //执行实例的run方法 compiler.run(callback) //返回实例 return compiler } module.exports = webpack 复制代码
- 最开始,无论我们在控制台输入webpack指令还是使用Node.js的API,都是调用了webpack函数( 源码 ),并传入了webpack的配置选项,创建了一个
compiler
实例。 -
compiler
是什么?—— 明显发现compiler保存了完整的webpack的配置参数options。所以官方说:
compiler 对象代表了完整的 webpack 环境配置。这个对象在启动 webpack 时被一次性建立,并配置好所有可操作的设置,包括 options,loader 和 plugin。可以使用 compiler 来访问 webpack 的主环境。
- 所有
webpack-plugin
也在这里通过提供一个叫apply
的方法给webpack调用,以完成初始化的工作,并且接收到刚创建的compiler
的引用。
- compiler、compilation与tapable
// Compiler.js const { Tapable, SyncHook, SyncBailHook, AsyncParallelHook, AsyncSeriesHook } = require("tapable"); const Compilation = require("./Compilation"); class Compiler extends Tapable { hooks = [ hook1: new SyncHook(), hook2: new AsyncSeriesHook(), hook3: new AsyncParallelHook() ] run () { // ... // 触发钩子回调执行 this.hooks[hook1].call() this.hooks[hook2].callAsync(() => { this.hooks[hook4].call() }) // 进入编译流程 this.compile() // ... this.hooks[hook3].promise(() => {}) } compile () { // 创建一个Compilation实例 const compilation = new Compilation(this) } } 复制代码
-
研究下Compiler.js这个文件,它引入了一个
tapable
类( 源码 )的库,这个库提供了一些Hook
类,内部实现了类似的事件订阅/发布系统
。 -
哪里用到
Hook
类?—— 在Compiler
类( Compiler.js源码 ) 里拥有很多有意思的hook,这些hook代表了整个编译过程的各个关键事件节点,它们都是继承于Hook
类 ,所以都支持监听/订阅
,只要我们的插件提前做好事件订阅,那么编译流程进行到该事件点时,就会执行我们提供的事件回调
,做我们想做的事情了。 -
如何进行订阅呢?——在上文中每个webpack-plugin中都有一个apply方法。其实注册的代码就藏在里面,通过以下类似的代码实现。任何的webpack插件都可以订阅hook1,因此hook1维护了一个taps数组,保存着所有的callback。
compiler.hooks.hook1.tap(name, callback) // 注册/订阅
compiler.hooks.hook1.call() // 触发/发布
-
准备工作做好了之后,当
run()
方法开始被调用时,编译就正式开始了。在该方法里,执行call/callAsync/promise
这些事件时(他们由webpack内部包括一些官方使用的webpack插件进行触发的管理,无需开发者操心),相应的hook就会把自己的taps里的函数均执行一遍。大概的逻辑如下所示。 -
其中,hooks的执行是按照编译的流程顺序来的,hooks之间有彼此依赖的关系。
-
compilation
实例也在这里创建了,它代表了一次资源版本构建。每当检测到文件变化,就会创建一个新的 compilation,从而生成一组新的编译资源。一个 compilation 对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态信息。而且compilation也和compiler类似,拥有很多hooks( Compilation.js源码 ),因此同样提供了很多事件节点给予我们订阅使用。
- webpack插件体系的使用套路
class MyPlugin { apply(compiler) { // 设置回调来访问 compilation 对象: compiler.hooks.compilation.tap('myPlugin', (compilation) => { // 现在,设置回调来访问 compilation 中的任务点: compilation.hooks.optimize.tap('myPlugin', () => { console.log('Hello compilation!'); }); }); } } module.exports = MyPlugin; 复制代码
- 订阅
compiler
和complation
的事件节点都在webpack-plugin中的apply
方法里书写,具体的演示如上。当然你想拿到编译过程中的什么资源,首先得要找出能提供该资源引用的对应的compiler事件节点进行订阅(上帝先创造了亚当,再有夏娃)。每个compiler时间节点能提供什么参数,在hook的实例化时已经做了说明(如下),更多可查看 源码
this.hooks = { // 拿到compilation和params compilation: new SyncHook(["compilation", "params"]), // 拿到stats done: new AsyncSeriesHook(["stats"]), // 拿到compiler beforeRun: new AsyncSeriesHook(["compiler"]) } 复制代码
梳理webpack流程
经过以上的初步探索,写webpack插件需要了解的几个知识点应该有了大概的掌握:
- 插件提供
apply
方法供webpack
调用进行初始化 - 使用
tap
注册方式钩入compiler
和compilation
的编译流程 - 使用
webpack
提供的api
进行资源的个性化处理。
写插件的套路已经知道了,现在还剩如何找出合适的钩子,修改资源这件事。在webpack系统里, 钩子即流程,是编译构建工作的生命周期
。当然,想要了解所有 tapable
实例对象的钩子的具体作用,需要探索webpack所有的内部插件如何使用这些钩子,做了什么工作来进行总结,想想就复杂,所以只能抽取重要流程做思路概括,借用淘宝的一张经典图示。![webpack_flow.jpg](file:///Users/Ando/Documents/webpack-plugin/webpack_flow.jpg) 整个编译流程大概分成三个阶段,现在重新整理一下:
-
准备阶段
,webpack的初始化
- webpack依次调用开发者自定义的插件的
apply
方法,让插件们做好事件注册。 -
WebpackOptionsApply
接收组合了命令行
和webpack.config.js
的配置参数,负责webpack内部基础流程使用的插件和compiler上下文环境的初始化工作。(*Plugin
均为webpack内部使用的插件)// webpack.js // 开发者自定义的插件初始化 if (options.plugins && Array.isArray(options.plugins)) { for (const plugin of options.plugins) { if (typeof plugin === "function") { plugin.call(compiler, compiler) } else { plugin.apply(compiler) } } } // ... // webpack内部使用的插件初始化 compiler.options = new WebpackOptionsApply().process(options, compiler) // WebpackOptionsApply.js class WebpackOptionsApply extends OptionsApply { process (options, compiler) { // ... new WebAssemblyModulesPlugin({ mangleImports: options.optimization.mangleWasmImports }).apply(compiler); new EntryOptionPlugin().apply(compiler); compiler.hooks.entryOption.call(options.context, options.entry); new CompatibilityPlugin().apply(compiler); new HarmonyModulesPlugin(options.module).apply(compiler); new AMDPlugin(options.module, options.amd || {}).apply(compiler); // ... } } 复制代码
- 执行
run / watch
(一次打包/监听打包模式)触发编译
阶段
-
编译阶段
,生成module
,chunk
资源。run
→compile
编译 → 创建compilation
对象。compilation
的创建是一次编译的起步,即将对所有模块加载(load)
、封存(seal)
、优化(optimiz)
、分块(chunk)
、哈希(hash)
和重新创建(restore)
。module.exports = class Compiler extends Tapable { run () { // 声明编译结束回调 function onCompiled () {} // 触发run钩子 this.hooks.run.callAsync(this, err => { this.compile(onCompiled) }) } compile(callback) { // ... // 编译开始前,触发beforeCompile钩子 this.hooks.beforeCompile.callAsync(params, err => { // 编译开始,触发compile钩子 this.hooks.compile.call(params); // 创建compilation实例 const compilation = this.newCompilation(params); // 触发make钩子 this.hooks.make.callAsync(compilation, err => { // 模块解析完毕,执行compilation的finish方法 compilation.finish(); // 资源封存,执行seal方法 compilation.seal(err => { // 编译结束,执行afterCompile钩子 this.hooks.afterCompile.callAsync(compilation, err => { // ... }); }); }); }); } } 复制代码
-
加载模块
:make
钩子触发 →DllEntryPlugin
内部插件调用compilation.addEntry
→compilation
维护了一些资源生成工厂方法compilation.dependencyFactories
,负责把入口文件及其(循环)依赖转换成module
(module
的解析过程会应用匹配的loader
)。每个module
的解析过程提供buildModule / succeedModule
等钩子, 所有module
解析完成后触发finishModules
钩子。 -
封存
:seal
方法包含了优化/分块/哈希
, 编译停止接收新模块,开始生成chunks。此阶段依赖了一些webpack内部插件对module进行优化,为本次构建生成的chunk加入hash等。createChunkAssets()会根据chunk类型使用不同的模板进行渲染。此阶段执行完毕后就代表一次编译完成,触发afterCompile
钩子-
优化
:BannerPlugin
compilation.hooks.optimizeChunkAssets.tapAsync('MyPlugin', (chunks, callback) => { chunks.forEach(chunk => { chunk.files.forEach(file => { compilation.assets[file] = new ConcatSource( '\/**Sweet Banner**\/', '\n', compilation.assets[file] ); }); }); callback(); }); 复制代码
-
分块
:用来分割chunk的SplitChunksPlugin
插件监听了optimizeChunksAdvanced
钩子 -
哈希
:createHash
-
- 文件生成阶段
- 编译完成后,触发
emit
,遍历compilation.assets
生成所有文件。
写一个增强console.log调试体验的webpack插件 simple-log-webpack-plugin
一张效果图先上为敬。(对照图片)只需写 log.a
,通过自己的webpack插件自动补全字段标识 a字段:
,加入 文件路径
,轻松支持 打印颜色
效果,相同文件的日志信息 可折叠
,给你一个简洁方便的调试环境。
const ColorHash = require('color-hash') const colorHash = new ColorHash() const Dependency = require('webpack/lib/Dependency'); class LogDependency extends Dependency { constructor(module) { super(); this.module = module; } } LogDependency.Template = class { apply(dep, source) { const before = `;console.group('${source._source._name}');` const after = `;console.groupEnd();` const _size = source.size() source.insert(0, before) source.replace(_size, _size, after) } }; module.exports = class LogPlugin { constructor (opts) { this.options = { expression: /\blog\.(\w+)\b/ig, ...opts } this.plugin = { name: 'LogPlugin' } } doLog (module) { if (!module._source) return let _val = module._source.source(), _name = module.resource; const filedColor = colorHash.hex(module._buildHash) // 判断是否需要加入 if (this.options.expression.test(_val)) { module.addDependency(new LogDependency(module)); } _val = _val.replace( this.options.expression, `console.log('%c$1字段:%c%o, %c%s', 'color: ${filedColor}', 'color: red', $1, 'color: pink', '${_name}')` ) return _val } apply (compiler) { compiler.hooks.compilation.tap(this.plugin, (compilation) => { // 注册自定义依赖模板 compilation.dependencyTemplates.set( LogDependency, new LogDependency.Template() ); // modlue解析完毕钩子 compilation.hooks.succeedModule.tap(this.plugin, module => { // 修改模块的代码 module._source._value = this.doLog(module) }) }) } } 复制代码
以上所述就是小编给大家介绍的《一个为了让console.log写起来更偷懒的webpack-plugin》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 做前端也有“捷径”,科学偷懒大法了解一下!
- 程序员日常工作中如何正确的偷懒?
- 这个Python库可以偷懒,和import说再见!
- 如何通过 JavaCSV 类库来优雅地(偷懒)读写 CSV 文件?
- 机器人在线“偷懒”怎么办?阿里研究出了这两套算法
- “偷懒”却意外探得AI视觉新象——访GAAC参赛者王铮、蒙胜宇
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。