因Webpack/Vite构建是并发分阶段的,fs.writeFileSync在非产物阶段写入易被清理;须用emitFile或emitAsset注入虚拟模块,并标准化JSON键序、拼接多语言值后哈希,确保跨构建一致。

为什么不能直接用 fs.writeFileSync 在插件中写入哈希表文件
因为 Webpack/Vite 的构建是并发、分阶段的,插件钩子(如 buildStart 或 configResolved)触发时,源语言文件可能还没被读取或解析;而等真正拿到所有翻译内容(比如从 zh.json、en.json)时,往往已到 generateBundle(Vite)或 emitAssets(Webpack)阶段。此时直接 fs.writeFileSync 写文件,容易和后续的输出逻辑冲突,甚至被构建工具清理掉。
正确做法是把哈希表作为“虚拟模块”注入构建流程,或通过构建产物 API 显式添加:
- Vite:在
buildEnd钩子中调用this.emitFile({ type: 'asset', fileName: 'i18n-hash.json', source: JSON.stringify(hashMap) }) - Webpack:在
compilation.hooks.processAssets阶段(Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL)用compilation.emitAsset - 务必确保所有语言 JSON 文件已加载完成——建议监听
transform钩子,识别/locales\/.*\.json$/路径并缓存内容
如何保证哈希值对同一翻译内容稳定且跨构建一致
MD5 或 SHA-1 看似方便,但只要输入字符串顺序稍有不同(比如对象 key 排序不一致),哈希就变。多语言文件常由不同人维护,JSON.stringify 默认不排序 key,导致哈希漂移。
必须做两件事:
- 预处理语言文件:用
JSON.parse+JSON.stringify(obj, Object.keys(obj).sort(), 2)标准化格式后再哈希 - 哈希目标不是原始文件字节,而是归一化后的“语义内容”——即所有语言中相同 key 对应的 value 拼接成字符串(如
key1:zh_value|en_value|ja_value),再哈希该拼接串 - 避免使用
Date.now()或随机数参与哈希计算,否则每次构建结果不可复现
Vite 插件中读取多语言 JSON 文件的实际限制
Vite 默认不会把 src/locales/*.json 当作模块处理(除非显式 import),所以在 configResolved 阶段用 fs.readdirSync 读取文件列表是可行的,但直接 fs.readFileSync 读内容会绕过 Vite 的依赖追踪和 HMR,导致热更新失效或构建缓存误判。
更安全的做法是:
- 在
resolveId钩子中拦截/locales\/.*\.json$/请求,返回一个虚拟 ID(如virtual:i18n-locale-${lang}) - 在
load钩子中匹配该虚拟 ID,再用fs.readFileSync读真实文件,并设置watchFiles告诉 Vite 追踪这些 JSON - 这样既拿到内容,又保住了 HMR 和缓存有效性——否则改一个
zh.json,整个构建哈希全变
Webpack 插件里生成哈希表后怎么让运行时代码能 import 它
生成的 i18n-hash.json 是构建产物,不是源码,不能直接 import。常见错误是试图在源码里写 import hash from './i18n-hash.json'——这会报错,因为该文件只存在于 dist/ 目录。
真正可用的方案只有两个:
- 把哈希表内容注入到某个已存在的 JS 模块中,例如在
compilation.hooks.processAssets后,用compilation.updateAsset修改runtime.js,插入window.__I18N_HASH__ = { ... } - 或者生成一个独立的
i18n-hash.mjs(注意后缀是.mjs),并在 HTML 模板里加<script type="module" src="/i18n-hash.mjs"></script>,这样运行时可通过window.__I18N_HASH__访问 - 别用
require('./i18n-hash.json')—— CommonJS 在浏览器环境不原生支持,且 Webpack 不会为产物文件创建模块依赖图
最易被忽略的是路径问题:生成的哈希表文件名必须带哈希后缀(如 i18n-hash.[hash].json)才能利用 CDN 缓存,但运行时代码又得知道这个动态文件名——所以通常得配合 html-webpack-plugin 注入变量,或把哈希表内联进 HTML 的 <script> 标签里。


















