前端项目上线后,版本追溯往往依赖人工记录或者流水线日志,一旦排查线上故障需要确认当前部署代码对应的 Git 提交和构建时间,效率很低。Webpack 作为打包入口,完全可以在构建阶段把这些信息自动写入产物。本文会介绍两种主流实现方式:生成独立 version.json 文件,以及通过 DefinePlugin 注入 JavaScript 常量,并给出完整的插件代码和配置示例。

一、版本信息文件要记录哪些字段
版本信息文件的核心作用是让开发者在任何环境下都能快速识别当前运行的构建产物对应哪个代码版本。如果只记录一个 Git Hash,信息仍然不够直观,因此建议至少包含短提交哈希、完整提交哈希、当前分支名、构建时间和构建环境。短哈希适合展示在页面上,完整哈希用于精确核对,分支名可以帮助区分主干发布和热修复分支,构建时间则可以快速判断发布是否成功以及是否有缓存问题。
字段选择还需要结合项目的发布流程。例如接入持续集成时,可能还需要记录流水线编号、构建序号或者触发人。这些数据通常由 CI 环境变量提供,在插件里读取即可。对于纯前端项目,package.json 中的 version 也是一个很好的人工可见版本号,建议一并写入文件。信息越完整,后续排查时越省力。
值得注意的是,版本信息一旦生成就要随构建产物一起部署,不能依赖源码目录中的临时文件。因此输出位置最好在 Webpack 的 output.path 根目录,或者指定的静态资源目录,这样拷贝产物时不会遗漏。生成的内容使用 JSON 格式最方便,前端既可以异步请求展示,也可以在构建工具中读取。
二、编写自定义 Webpack 插件生成 version.json
Webpack 插件本质上是一个带有 apply 方法的类,可以在 compilation 钩子阶段向产物添加文件。我们通过 Node.js 的 child_process.execSync 执行 git 命令获取提交哈希和分支,再将结果组合成对象,最后使用 compilation.emitAsset 输出 version.json。下面是一段可直接使用的插件代码。
const { execSync } = require('child_process');
const { sources } = require('webpack');
class VersionFilePlugin {
apply(compiler) {
compiler.hooks.thisCompilation.tap('VersionFilePlugin', (compilation) => {
compilation.hooks.processAssets.tap(
{
name: 'VersionFilePlugin',
stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL
},
() => {
const info = {
shortHash: this.getCommandOutput('git rev-parse --short HEAD'),
fullHash: this.getCommandOutput('git rev-parse HEAD'),
branch: this.getCommandOutput('git rev-parse --abbrev-ref HEAD'),
buildTime: new Date().toISOString(),
version: require('./package.json').version
};
const content = JSON.stringify(info, null, 2);
compilation.emitAsset('version.json', new sources.RawSource(content));
}
);
});
}
getCommandOutput(command) {
try {
return execSync(command, { encoding: 'utf-8' }).trim();
} catch (err) {
return 'unknown';
}
}
}
module.exports = VersionFilePlugin;
上面代码挂载的是 thisCompilation 和 processAssets 钩子。processAssets 的 stage 设为 ADDITIONAL,可以确保在其他主要资源处理完成后再添加版本文件,不会干扰正常的 JS、CSS 产物。getCommandOutput 方法对命令执行做了异常兜底,当本地没有 Git 环境或者不在 Git 仓库中时返回 unknown,避免构建直接失败。
在 webpack.config.js 中引入并实例化该插件即可。执行打包后,输出目录里会出现 version.json,内容类似短哈希、完整哈希、分支名和 ISO 时间字符串。这个文件独立于业务代码,前端可以在运行时通过 fetch 获取并展示,也可以由运维脚本在发布后读取进行验证。
如果项目使用 Webpack 5,RawSource 可以从 webpack 包的 sources 对象中获取;如果是 Webpack 4,需要改为 import { RawSource } from 'webpack-sources'。这一点在升级构建工具时需要留意。
三、通过 DefinePlugin 注入版本常量
有些场景不需要单独的 JSON 文件,而是希望版本号直接嵌入 JavaScript 包中,方便在控制台输出。DefinePlugin 可以在编译阶段将指定的代码片段替换为常量,适合把 Git Hash 和构建时间注入到业务代码中。配置方式是在 webpack.config.js 里先读取版本信息,再传给 DefinePlugin。
const webpack = require('webpack');
const { execSync } = require('child_process');
function getGitInfo() {
try {
return {
hash: execSync('git rev-parse --short HEAD', { encoding: 'utf-8' }).trim(),
branch: execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf-8' }).trim()
};
} catch (err) {
return { hash: 'unknown', branch: 'unknown' };
}
}
const gitInfo = getGitInfo();
const buildTime = new Date().toISOString();
module.exports = {
plugins: [
new webpack.DefinePlugin({
__APP_VERSION__: JSON.stringify({
...gitInfo,
buildTime
})
})
]
};
DefinePlugin 的替换发生在编译阶段,它会把代码中出现的 __APP_VERSION__ 直接替换成对应的 JSON 对象字面量。因此前端源码中可以直接使用这个全局常量,不需要任何运行时请求。比如在入口文件里写 console.log('版本信息:', __APP_VERSION__),打包后就能看到完整信息。
这种方式的好处是零异步请求,版本信息一定随 JS 代码一起加载,不会出现单独文件丢失或者缓存不同步的问题。缺点是每修改一次版本信息都会让整个 JS 包的内容变化,不利于长期缓存。对于需要精细化缓存策略的项目,建议把版本信息放在独立文件中;对于内部管理系统或者对缓存不敏感的页面,直接注入会更简单。
还需要注意,DefinePlugin 只做静态替换,不能用它注入运行时才能确定的数据。例如构建完成后的产物大小、部署服务器 IP 等都必须采用其他方式获取。此外,注入的对象如果包含函数或者特殊类型,JSON.stringify 会丢失信息,所以通常只放字符串、数字和布尔值。
四、Windows 兼容、CI 环境与常见避坑
在 Windows 上使用 execSync 执行 git 命令时,可能会因为命令解析差异或换行符问题导致结果带有多余的回车符。上面代码已经在返回结果上调用了 trim 方法,可以处理大部分情况。如果项目需要在 Windows 和 Linux 同时构建,建议统一使用 Node.js 的 cross-spawn 或者写一个平台判断方法,避免直接依赖系统 shell。
CI 环境中经常出现 detached HEAD 状态,此时 git rev-parse --abbrev-ref HEAD 会返回 HEAD,而不是真实分支名。可以从环境变量里读取 CI 提供的分支信息作为补充,例如 Jenkins 的 GIT_BRANCH、GitLab CI 的 CI_COMMIT_REF_NAME、GitHub Actions 的 GITHUB_REF_NAME。把这些数据整合进版本信息文件,比单纯依赖本地 Git 命令更可靠。
另一个常见问题是 Webpack 的持久化缓存。如果只在 Node 进程启动时获取一次 Git 信息,后续增量构建可能因为缓存没有变化而跳过插件执行,导致版本文件不更新。对于持续集成场景,每次构建通常都是全新环境,很少遇到这个问题;但本地调试时如果修改了代码但 Git 信息没变,可能看不到新的构建时间。解决方法是在插件中把构建时间放到 processAssets 阶段实时生成,而不是在配置读取阶段固定下来。
最后,生成版本文件时可以顺便输出一个构建序列号,或者对文件内容做哈希命名,防止浏览器缓存旧版本。例如生成 version-
Webpack版本信息Git Hash构建时间修改时间:2026-09-18 05:58:03