项目开发中,我们经常面临这样的场景:如何方便地查看当前部署的版本信息,以便快速定位和解决问题?今天,我们写一个简单小巧的Vite插件,轻松实现这一需求。
首先梳理一下需求,希望在代码打包部署上线后,能够直接在控制台查看当前部署的版本信息,比如最后一次 commit 的 hash,最后一次 commit 的时间等信息。
那么我们的实现思路是,利用 git 相关命令,输出对应的信息,然后利用 vite 插件的 transformindexhtml
钩子的将信息添加到 dom 结构中。
使用 vite 插件钩子
接下来,我们将了解如何使用Vite插件钩子来实现这个功能。 先启动一个 vite 项目,在根目录创建一个文件(vitePluginGitRevisionInfo.ts
)写个插件的基础结构试试功能:
vitePluginGitRevisionInfo.ts
import { Plugin } from 'vite';
function vitePluginGitRevisionInfo(): Plugin {
return {
name: 'vite-plugin-git-revision-info',
transformIndexHtml() {
const HtmlStr = `const gitInfo = 'aa'`;
// 将htmlStr插到body里
return [
{
tag: 'script',
attrs: { defer: true },
children: HtmlStr,
injectTo: 'body',
},
];
},
};
}
export { vitePluginGitRevisionInfo as default };
然后在 vite.config.ts
里加入我们写的这个小插件:
vite.config.ts
import vitePluginGitRevisionInfo from './vitePluginGitRevisionInfo';
export default defineConfig(()=>{
return {
plugins:[vitePluginGitRevisionInfo(),]
}
})
打开控制台调试,可以看到我们添加的内容出现在了 dom 结构中,在控制台中也可以打印出相关信息:
接下来我们完善插件内容,以便获得git相关信息。
展示最近的 commit 等信息
transformIndexHtml
这个钩子允许使用异步函数,使我们可以输出 git 相关信息。
这里我们用到了 Node.js 的 child_process
模块执行 shell 命令,使用 promisify
将回调风格的 exec
函数转成了返回 Promise 的函数。
ts
import ChildProcess from 'child_process';
import { promisify } from 'util';
import { Plugin } from 'vite';
const { exec } = ChildProcess;
// 使用 promise 的方式执行命令
const execAsync = promisify(exec);
function vitePluginGitRevisionInfo(): Plugin {
return {
name: 'vite-plugin-git-revision-info',
async transformIndexHtml() {
const res = await execAsync('git log -1 --format=%cI');
const HtmlStr = `const gitInfo = ${JSON.stringify(res)}`;
// 将htmlStr插到body里
return [
{
tag: 'script',
attrs: { defer: true },
children: HtmlStr,
injectTo: 'body',
},
];
},
};
}
很顺利拿到了相关信息:
我们可以打印出更多有价值的信息,那我们将执行命令的函数提取出来,并且提取返回值中的 stdout,stdout 是我们需要的值。
ts
/**
* Executes a specified Git command on a given Git work tree
* @param gitWorkTree The path to the Git work tree
* @param command The Git command to be executed.
* @returns The standard output of the Git command
*/
async function runGitCommand(gitWorkTree: string | undefined, command: string) {
try {
const gitBaseCommand = gitWorkTree
? `git --git-dir=${path.join(
gitWorkTree,
'.git',
)} --work-tree=${gitWorkTree}`
: 'git';
const { stdout } = await execAsync(`${gitBaseCommand} ${command}`);
return removeEmptyLines(stdout);
} catch (error) {
console.error('Error executing git command:', error);
return `Error executing git command`;
}
}
/**
* Removes trailing empty lines and whitespace from a given string.
* @param string The input string.
* @returns A new string with trailing empty lines and whitespace removed.
*/
function removeEmptyLines(string: string) {
return string.replace(/[\s\r\n]+$/, '');
}
前面我们拿到了最后一次 commit 的时间,当然我们可以从 git 中拿到更多有用的信息,比如:
- 最后一次 commit 的 hash ---
git rev-parse HEAD
- 最后一次 commit 的时间 ---
log -1 --format=%cI
- 最后一次 commit 的信息 ---
git log -1 --format=%s
- 最近一次标签 ---
git describe --always
- 当前分支 ---
git rev-parse --abbrev-ref HEAD
将这些命令定义一下,并且给他们提供一个 key,使其更易理解和整洁:
ts
const COMMITHASH_COMMAND = 'rev-parse HEAD';
const VERSION_COMMAND = 'describe --always';
const BRANCH_COMMAND = 'rev-parse --abbrev-ref HEAD';
const LASTCOMMITTIME_COMMAND = 'log -1 --format=%cI';
const LASTCOMMITMSG_COMMAND = 'log -1 --format=%s';
const COMMITHASH_VAR = 'GIT_COMMITHASH';
const VERSION_VAR = 'GIT_VERSION';
const BRANCH_VAR = 'GIT_BRANCH';
const LASTCOMMITTIME_VAR = 'GIT_LASTCOMMITTIME';
const LASTCOMMITMSG_VAR = 'GIT_LASTCOMMITMSG';
那么如果我们想拿到最后一次 commit 的时间:
ts
function getLastCommitDateTime() {
return runGitCommand(undefined, LASTCOMMITMSG_COMMAND);
}
如果想拿到其他的值也都类似,并且我们把每个值是否要生成变成可以控制的,每个值的 key 也变成可以配置的,使插件更为灵活和通用。
ts
async function generateGitData(options) {
const data: { [key: string]: string } = {};
if (options.commitHash) {
data[options.commitHashVar || COMMITHASH_VAR] = JSON.stringify(
await getCommitHash(options),
);
}
if (options.version) {
data[options.versionVar || VERSION_VAR] = JSON.stringify(
await getVersion(options),
);
}
if (options.branch) {
data[options.branchVar || BRANCH_VAR] = JSON.stringify(
await getBranch(options),
);
}
if (options.lastCommitTime) {
data[options.lastCommitTimeVar||LASTCOMMITTIME_VAR] =
JSON.stringify(await getLastCommitDateTime(options));
return data
}
然后将 data 插入到 vite 的 transformIndexHtml
钩子里面就生成了我们想要的信息。
当然如果我们想要添加自己的自定义命令也是支持的,可以改写里面的命令。
我们也可以通过 vite 中的共享变量 define 将 git 信息共享为全局变量,只需要在返回的时候把需要的变量添加在 config.define
上就可以了,便可以在整个应用的任何地方读取这个全局变量。
这个变量在构建时会被静态替换为它的实际值,不会在运行时产生额外性能开销,而且这个变量会嵌入到构建的最终产物中,使得它们也可以在生产环境中被访问和使用。
ts
async function vitePluginGitRevisionInfo(
options: GitRevisionPluginOptions,
): Promise<Plugin> {
const res = JSON.stringify(await generateGitData(options));
return {
name: 'vite-plugin-git-revision-info',
apply: 'build', // 在构建时使用
config() {
return {
// 全局变量,可以在整个应用中使用
define: {
__GIT_REVISION_INFO__: res,
},
};
},
transformIndexHtml() {},
};
}
我们可以在项目的任意位置使用这些Git信息:
ts
console.log(__GIT_REVISION_INFO__)
总结
写一个小的 vite 插件是不是相当简单呢。在本文中我们学习了如何使用 git 命令获取和使用 git 信息,并可以根据需要和配置动态生成包含 git 信息的对象,另外可以通过 vite 的 define 选项将这些 git 信息定义为全局变量。
本文中的插件已上传至 github-vite-plugin-git-revision-info,并发布在 npm,欢迎使用。
特别感谢的是本文的实现主要来源于webpack 的 git 插件git-revision-webpack-plugin ,使用webpack的话可以使用该插件。
欢迎交流~