Webpack Plugin 打包文件名写入 git commit hash

Published on

场景

问题点:因为最近开始模块拆分的缘故,要将子项目 build 出来的 JS 动态插入到主项目中加载,但是存在的一个问题就是测试环境子项目迭代较快,并没有打 tag 提发布的完整流程,需要将测试发布的 JS 和代码版本关联上。

在规范的 git 管理和发布流程下自然是没有这个问题的。但问题出现点本身就是子项目测试环境,代码提交更新频繁,再加上子项目是本地打包,流程长的发布耗时不太有必要,这就导致了每个测试版本的版本信息丢失。总的说是为了快速开发和提测迭代产生一个版本管理的问题。

要做的就是需要将打包产物 JS 文件和当前的 git 版本信息关联起来,然后回查问题就能快速找到文件名对应的代码版本进行排错。

怎么关联?思路肯定就是在打包产物中留下 git 信息,比如:

  • 将 git 提交信息写入到 JS (或者 html) 文件,然后页面上就能查找到
  • 更直接的,将 git 提交写到生成的 JS 文件名上

方案

1. git-revision-webpack-plugin

webpack 插件: git-revision-webpack-plugin

按照文档配置之后打包,却没有得到预期的文件名,查看源码后发现有些 API 的调用和文档有了小的差别,应该是个别 API 的小版本差异导致的。库功能很全,但我们需要的仅仅是 git commit hash,其实是几行脚本就可以完成的,就暂不引入第三方库。

2. node 执行 git 命令获取信息

在 webpack.config.js 中执行一段 node, 获取提交 commitId,然后写到生成文件名的配置。

const { execSync } = require('child_process')
/*
 * ...
 */
let fileName = '[hash:8]' // 默认8位hash
if (env != 'development') {
  fileName = execSync('git rev-parse --short HEAD', { encoding: 'utf-8' }).replace('\n', '')
}

const config = {
  // ...
  output: {
    filename: `${fileName}.js`,
    //...
  },
}
module.exports = config

git rev-parse --short HEAD 查看最近提交的短 commit id

如果是本地打包,还应该加上一个限制,只有提交之后才能build JS,否则很可能打包之后的内容包含了一些未提交的临时修改,然后这些修改后来又被还原的话就无从查证了(但已经被 build 进去生效了)。git 获取存在 diff 差异的文件,未提交代码产生提交记录之前不能build:

if (env != 'development') {
  let gitlocalDiff = execSync('git diff --name-only', { encoding: 'utf-8' }).replace('\n', '')
  if (gitlocalDiff) {
    console.error(new Error("you should commit your files before execuating 'npm run build'"))
    process.exit(-1)
  }
  commitID = execSync('git rev-parse --short HEAD', { encoding: 'utf-8' }).replace('\n', '')
}

git diff --name-only 查看本地修改文件名

然后就可以进行正常的流程了。commit ->build-> 产生的 JS 以commitID为文件名

查找当前生效代码就是:查看 JS 文件名(获取git提交id) -> 切到对应的 commitId 就是当前的打包代码了。

做成自定义插件 GitHashFileNamePlugin

做成插件的目的就是将git获取commitId的操作放在插件中进行,而不必在每个子项目的打包配置中写这些脚本。按照上面的思路,其实是通过插件修改webpack 配置中outputfilename

定义并实现:

const { execSync } = require('child_process')

class GitHashFileNamePlugin {
  constructor(appName) {
    this.appName = appName || 'app'
  }

  apply(compiler) {
    compiler.hooks.afterResolvers.tap('GitHashFileNamePlugin', (compilation, callback) => {
      // console.info("xxxx", compilation.options.output);  // 需要改的是filename
      const githash = execSync('git rev-parse --short HEAD', { encoding: 'utf-8' }).replace(
        '\n',
        ''
      )
      compilation.options.output.filename = `${this.appName}-${githash}.js`
    })
  }
}

module.exports = GitHashFileNamePlugin

在解析配置后的钩子afterResolvers中,获取到compilation对象,修改其输出配置的filename属性,值组成为${this.appName}-${githash}.js, appName 由外部调用传入,这样一来生成的就是项目名-Commit Hash.js的文件名。

webpack.config.js 引入后使用:

const GitHashFileNamePlugin = require('../plugins/githash')

module.exports = {
  // ...
  plugins: [
    // ...
    new GitHashFileNamePlugin('tars'),
  ],
}

这里传入的项目名为tars, 测试一下,打包名为tars-504273f.js

执行 git log 查看最近的 commit:

文件名和提交短信息是完全对上,bingo~