其实从
loader
开始,个人已经没有多少想法了,自己基本上用不到loader
和plugin
的开发, 目前的情况也不需要开发或者是调试这些玩意,而且课程里面干货嗯,所以差点弃坑,但是反正就剩几章了,咬咬牙吧,这是写给自己的,后面把一个技术栈弄得七七八八了再来写专栏。
插件的健壮性其实和自己写的项目类似,要排查问题,需要记录日志,分析性能瓶颈等,这些webpack
都替你考虑过了, 所以会有一些工具来帮助你做这些事。
日志处理
在之前的loader
中写过,可以通过this.getLogger()
来使用日志功能,它是内置了infrastructureLogging
,还不了解可以翻翻之前写的或者去webpack
官网搜索一下这个。
在loader
中使用是通过注入的上下文this
来获取log
,在plugin
中是通过apply
中传入的compiler
或者compilation
来使用的,如下:
class MyPlugin { apply(compiler) { // 通过compiler.getInfrastructureLogger获取一个logger const logger = compiler.getInfrastructureLogger('MyPlugin'); logger.info('MyPlugin is starting'); compiler.hooks.emit.tapAsync('MyPlugin', (compilation, callback) => { // 通过compilation.getLogger获取一个logger const logger = compilation.getLogger('MyPlugin'); logger.info('Hello from MyPlugin'); callback(); }) } } module.exports = MyPlugin;
log
的日志分级就不用讲了吧,接下来就是通过log
上报异常,通过log
上报的异常不会中断webpack
的构建:
class MyPlugin { apply(compiler) { compiler.hooks.emit.tapAsync('MyPlugin', (compilation, callback) => { // 通过compilation.getLogger获取一个logger const logger = compilation.getLogger('MyPlugin'); // log的分级 logger.info('Hello from MyPlugin'); logger.log('Log from MyPlugin'); logger.debug('Debug from MyPlugin'); logger.warn('Warning from MyPlugin'); logger.error('Error from MyPlugin'); // 可以通过 logger.warn() logger.error() 等方法输出异常日志,这不会影响webpack的编译结果 logger.warn('Warning'); logger.error('Error'); // 还可以通过compilation.errors和compilation.warnings添加错误和警告 compilation.errors.push(new Error("Emit Error From FooPlugin")); compilation.warnings.push("Emit Warning From FooPlugin"); callback(); }) } } module.exports = MyPlugin;
这里可以通过logger
的warn
和error
方法输出异常日志,也可以通过compilation
的warnings
和errors
添加错误警告,他们都不会中断webpack
的构建。
他们的区别在于,使用log
仅仅是出入错误信息到控制台,而使用compilation.warnings
和compilation.errors
还会将错误信息汇总到 stats 统计对象:
处理错误信息
上面讲到通过log
来记录编译过程中的一些信息,包括错误信息,但是这个错误信息并不会影响构建,其实除了通过log
来处理异常还有其他的方式来处理异常。
- 直接抛出异常
class MyPlugin { apply(compiler) { compiler.hooks.emit.tapAsync('MyPlugin', (compilation, callback) => { if (Math.random() > 0.5) { // 直接抛出异常会中断webpack的构建 throw new Error('MyPlugin error'); } callback(); }) } } module.exports = MyPlugin;
- 向下透传错误信息
class MyPlugin { apply(compiler) { compiler.hooks.emit.tapAsync('MyPlugin', (compilation, callback) => { let error = undefined; if (Math.random() > 0.5) { error = new Error('MyPlugin error'); } // callback的第一个参数是错误信息,错误信息会被webpack捕获,如果有错误信息,webpack会停止打包 callback(error); }) } } module.exports = MyPlugin;
上报统计信息
有时候处理一些文件会特别耗时,用户可能需要对此进行一些优化,这个时候就需要插件的开发者提供统计信息了,上报统计信息有两种方式:
- 使用 ProgressPlugin 插件的
reportProgress
接口上报执行进度;
- 简单用法:
npx webpack --progress
这种方式最终输出的进度和真实的构建进度差别很大(虽然进度条都是假的),因为有些插件可能没有提交任何构建进度相关的信息。
- 使用
webpack
提供的ProgressPlugin
插件
- 首先在配置文件中配置一下
const MyPlugin = require('./src/plugin/index') const {ProgressPlugin} = require('webpack') module.exports = { // 省略其他配置 plugins: [ new MyPlugin(), new ProgressPlugin({ activeModules: false, entries: true }) ] }
- 然后在插件中编写上报进度的代码
const {ProgressPlugin} = require('webpack'); const wait = (misec) => new Promise((r) => setTimeout(r, misec)); class MyPlugin { apply(compiler) { compiler.hooks.emit.tapAsync('MyPlugin', async (compilation, callback) => { // 获取 Reporter ,这个有可能为 null,我这里没有写兜底的空函数 const reportProgress = ProgressPlugin.getReporter(compiler); for (let i = 0; i < 100; i++) { await wait(100); reportProgress(i / 100, `Our plugin is working ${i}%`); } callback() }) } } module.exports = MyPlugin;
- 使用 stats 接口汇总插件运行的统计数据。
- 编写代码
class MyPlugin { apply(compiler) { compiler.hooks.compilation.tap('MyPlugin', (compilation) => { const statsMap = new Map(); // buildModule 钩子将在开始处理模块时触发 compilation.hooks.buildModule.tap('MyPlugin', (module) => { const ident = module.identifier(); const startTime = Date.now(); // 模拟5秒钟的构建时间 while (new Date().getTime() - startTime < 5000); const endTime = Date.now(); // 记录处理耗时 statsMap.set(ident, endTime - startTime); }); compilation.hooks.statsFactory.tap('MyPlugin', (factory) => { factory.hooks.result .for("module") .tap('MyPlugin', (module, context) => { const { identifier } = module; const duration = statsMap.get(identifier); // 添加统计信息 module.fooDuration = duration || 0; }); }); }) } } module.exports = MyPlugin;
- 输出结果:
npx webpack --profile --json=compilation-stats.json
这个命令会在根目录创建一个
compilation-stats.json
的文件,可以找到module.fooDuration
就是构建时间,也是上面自己记录的信息,不出意外应该是5000
。
总结
插件其实就是一个工程项目,提高项目的健壮性是很有必要的一件事,上面的对于插件的开发没有任何帮助,但是对一个软件系统质量提升有很大的帮助,这有益于问题的排查,软件的运行状态,性能等有一个可靠的输出结果来查看,让软件更加可靠。
课程中还讲到了参数校验、测试环境搭建、编写测试用例,但是这些在之前我写loader
的时候都有讲过,这里我也就不做记录了。