升级至Webpack 4后,自定义Webpack插件中window为何未定义?
嘿,我懂你觉得把 Webpack 3 的 compiler.plugin("after-emit") 改成 Webpack 4 的 compiler.hooks.afterEmit.tap 就完事了,但实际迁移过程中还有不少容易忽略的细节——毕竟 Webpack 4 对编译流程、compilation 对象的结构都做了不少调整,尤其是你这种要在 Node 里加载生成的 bundle 做预渲染的场景,下面是几个关键的修复点:
1. 钩子的异步处理逻辑
Webpack 3 里的 after-emit 钩子支持传入 callback 参数处理异步操作,但 Webpack 4 的钩子体系明确区分了同步和异步调用:
- 如果你的插件里有异步操作(比如读写文件、异步渲染),不能再用普通的
tap,得换成tapAsync或者tapPromise:// 异步回调风格 compiler.hooks.afterEmit.tapAsync('prerender-plugin', (compilation, callback) => { // 你的异步预渲染逻辑 prerender().then(() => { callback(); // 必须调用callback告知Webpack异步操作完成 }).catch(err => { callback(err); // 传递错误让Webpack捕获 }); }); // 或者用Promise风格(更推荐) compiler.hooks.afterEmit.tapPromise('prerender-plugin', async (compilation) => { await prerender(); // 直接await异步操作 });
如果还是用同步的 tap 处理异步逻辑,Webpack 会提前结束编译流程,导致你的预渲染代码没执行完就中断了。
2. Bundle 路径的正确获取
Webpack 4 里 compilation 对象的 assets 和 outputOptions 结构有细微变化,别直接硬拼路径,要从编译结果里动态获取正确的 bundle 文件名:
const path = require('path'); // 获取输出目录 const outputDir = compilation.outputOptions.path; // 找到目标bundle(这里假设是带hash的主js文件,可根据自己的配置调整筛选规则) const targetBundle = Object.keys(compilation.assets).find(fileName => fileName.match(/main\.[a-f0-9]+\.js$/) ); if (!targetBundle) { throw new Error('无法找到生成的目标bundle,请检查Webpack入口配置'); } const bundleFullPath = path.resolve(outputDir, targetBundle);
如果你的项目用了 splitChunks 或者多入口,还要注意筛选出你需要预渲染的那个 chunk,而不是随便拿一个 js 文件。
3. Node 环境下加载 Bundle 的兼容性
Webpack 4 默认的 target 是 web,生成的 bundle 里会包含浏览器环境的 API(比如 window、document),直接在 Node 里 require 会报错。解决办法有两种:
- 为预渲染的 bundle 单独配置
target: 'node':如果你的预渲染逻辑是单独的入口,可以在 Webpack 配置里给这个入口单独设置 target,让生成的 bundle 兼容 Node 环境。 - 在代码里加入环境判断:在你的 React 组件或入口文件里,对浏览器专属 API 做兼容处理:
if (typeof window !== 'undefined') { // 浏览器环境专属逻辑 }
4. 清除 Node 的模块缓存
Node 会缓存 require 加载的模块,如果你多次执行编译,可能会加载到旧的 bundle 内容。每次加载前记得清除缓存:
const bundleFullPath = path.resolve(outputDir, targetBundle); // 清除缓存 delete require.cache[bundleFullPath]; // 加载最新的bundle const AppComponent = require(bundleFullPath).default; // 注意导出是default还是具名导出
把这些点都调整完,你的预渲染插件应该就能在 Webpack 4 里正常运行了。
内容的提问来源于stack exchange,提问作者Vincent

