如何将Webpack 4自定义插件迁移适配至Webpack 5
Webpack 4 自定义插件迁移 Webpack 5 适配方案
Webpack 5 完全移除了旧版Tapable的.plugin()钩子注册API,所有钩子统一挂载在对应实例的hooks属性下,通过tap/tapAsync/tapPromise注册,旧写法失效是API迭代导致的。
适配后完整代码
const ConstDependency = require('webpack/lib/dependencies/ConstDependency'); const NullFactory = require('webpack/lib/NullFactory'); class StaticAssetPlugin { constructor(localization, options, failOnMissing) { this.options = options || {}; this.localization = localization; this.functionName = this.options.functionName || '__'; this.failOnMissing = !!this.options.failOnMissing; this.hideMessage = this.options.hideMessage || false; } apply(compiler) { const { localization } = this; const name = this.functionName; const PLUGIN_NAME = 'StaticAssetPlugin'; compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation, { normalModuleFactory }) => { // 注册依赖工厂与模板,逻辑与原版本一致 compilation.dependencyFactories.set(ConstDependency, new NullFactory()); compilation.dependencyTemplates.set(ConstDependency, new ConstDependency.Template()); // 绑定JS解析器钩子 normalModuleFactory.hooks.parser .for('javascript/auto') .tap(PLUGIN_NAME, (parser) => { // 保留普通函数写法,确保Tapable上下文绑定正确,不要改成箭头函数 parser.hooks.call.for(name).tap(PLUGIN_NAME, function staticAssetPlugin(expr) { let param; switch (expr.arguments.length) { case 1: param = this.evaluateExpression(expr.arguments[0]); if (!param.isString()) return; param = param.string; break; default: return; } const result = localization(param); const dep = new ConstDependency(JSON.stringify(result), expr.range); dep.loc = expr.loc; this.state.current.addDependency(dep); return true; }); }); }); } } module.exports = StaticAssetPlugin;
核心改动说明
- 所有旧格式
实例.plugin(钩子名, 回调)统一替换为实例.hooks.钩子名.tap(插件唯一标识, 回调),同步逻辑用tap注册,异步逻辑根据场景选择tapAsync(回调式)或tapPromise(Promise式)。 - 原代码中两次重复监听
compilation钩子的逻辑合并为一次,normalModuleFactory可以直接从compilation钩子的第二个参数解构获取,不需要额外通过回调参数的data属性读取。 normalModuleFactory的parser钩子需要通过.for('javascript/auto')指定要挂载的解析器类型,覆盖绝大多数CommonJS/ESM JS模块场景,如果需要处理JSX、特殊JS资源可以追加对应模块类型的绑定。- 原parser上通过字符串
call ${函数名}匹配的函数调用钩子,改为通过parser.hooks.call.for(函数名)精准匹配。 - 原代码中parser回调使用普通函数的写法必须保留,新版Tapable依然会将parser实例绑定到回调的
this上,改成箭头函数会导致this.evaluateExpression、this.state.current等调用找不到上下文报错。 - 移除了原代码中定义后未使用的冗余变量
defaultValue,不影响原有逻辑。
迁移参考规则
插件迁移不需要记忆特殊映射表,所有钩子的名称和触发逻辑在Webpack 5中基本完全保留,只是注册方式变化:
- 编译器相关钩子统一挂载在
compiler.hooks下- 编译实例相关钩子统一挂载在
compilation.hooks下- 模块工厂相关钩子统一挂载在对应工厂实例的
hooks下- 解析器相关钩子统一挂载在
parser.hooks下,需要匹配特定场景(比如指定函数调用、指定表达式类型)时,通过钩子的for()方法传入匹配值即可
注册钩子时传入的插件名称保持唯一即可,主要用于Webpack报错、调试日志中定位插件来源。
内容的提问来源于stack exchange,提问作者Mohammad Dohadwala
相关产品推荐
相关产品推荐

