π¨π³ δΈζζζ‘£ | π¬π§ English README
unplugin-version-injector is a lightweight unplugin-based plugin that automatically injects version and build timestamp into all HTML files. It supports Vite, Webpack 4/5, Rspack, Rollup and Rolldown, and works seamlessly with both SPA and MPA projects.
β
Injects <meta name="version"> and <meta name="project"> into the <head>
β
Injects <script> into the <body> to log version, name, and build time
β
Supports Vite, Webpack 4/5, Rspack, Rollup, Rolldown
β
Fully compatible with Multi-Page Applications (MPA)
β
Customizable version, project name, date format, and theme-based console styling
β
Date format supports dayjs-style patterns (YYYY-MM-DD HH:mm:ss) with zero extra dependencies
β
Optional: attach a version header (X-Client-Version) to fetch / XMLHttpRequest requests to identify clients in backend logs
# Using npm
npm install -D unplugin-version-injector
# Using yarn
yarn add -D unplugin-version-injector
# Using pnpm
pnpm add -D unplugin-version-injector// vite.config.ts
import versionInjector from 'unplugin-version-injector/vite';
export default {
plugins: [versionInjector()],
};// webpack.config.js
const versionInjector = require('unplugin-version-injector/webpack');
module.exports = {
plugins: [
versionInjector({
version: '1.2.3', // Optional: manually specify version
}),
],
};// rspack.config.js
const versionInjector = require('unplugin-version-injector/rspack');
module.exports = {
plugins: [versionInjector()],
};// rollup.config.js
import versionInjector from 'unplugin-version-injector/rollup';
export default {
plugins: [versionInjector()],
};// rolldown.config.js
import versionInjector from 'unplugin-version-injector/rolldown';
export default {
plugins: [versionInjector()],
};In your final HTML output:
<head>
<meta name="version" content="1.2.3">
<meta name="project" content="my-project">
</head>
<body>
<script data-injected="unplugin-version-injector">
// console badges:
// my-project@1.2.3
// Build Time: 2024-04-01T12:00:00.000Z
</script>
</body>| Option | Type | Description | Default |
|---|---|---|---|
version |
string |
Custom version number | Read from package.json |
name |
string |
Custom project name | Read from package.json |
log |
boolean |
Whether to inject the console log script | true |
formatDate |
string | ((date: Date) => string) |
Custom build time format: a dayjs-style pattern (e.g. 'YYYY-MM-DD HH:mm:ss') or a function |
ISO 8601 format |
requestHeaders |
boolean | RequestHeadersOptions |
Attach version/build-time headers to outgoing requests | false |
nonce |
string |
CSP nonce added to the injected inline <script> tags |
β |
versionandnamecan be provided independently β whichever is missing falls back to the nearestpackage.json.
formatDate accepts two forms, and applies to both the console banner and the X-Client-Build-Time header:
// 1) dayjs-style pattern (built-in lightweight impl, no dayjs needed)
versionInjector({ formatDate: 'YYYY-MM-DD HH:mm:ss' }); // 2024-04-01 12:30:45
// 2) custom function
versionInjector({ formatDate: (date) => date.getTime().toString() }); // timestampSupported tokens: YYYY YY MMMM MMM MM M DD D dddd ddd dd d HH H hh h mm m ss s SSS SS S A a.
Enable requestHeaders to patch window.fetch and XMLHttpRequest so requests carry the client version and build time β making it trivial to tell which client build produced a request in backend/API logs. Two headers are injected by default:
X-Client-Version: my-app/1.2.3
X-Client-Build-Time: 2024-04-01 12:00:00
Because it patches
fetch/XMLHttpRequest, axios and most HTTP clients work automatically.navigator.sendBeaconand WebSocket connections cannot carry custom headers and are not covered.
| Option | Type | Description | Default |
|---|---|---|---|
versionHeaderName |
string |
Version header name; value is ${name}/${version} |
'X-Client-Version' |
buildTimeHeaderName |
string |
Build-time header name; value is the formatDate output |
'X-Client-Build-Time' |
include |
(string | RegExp)[] |
Extra cross-origin allowlist: string = URL prefix, RegExp = full-URL test. Same-origin requests are always injected | [] |
Page and API share an origin, or you use a dev-server proxy (requests hit /api, which the browser treats as same-origin):
versionInjector({ requestHeaders: true }); // true = defaults, same-origin onlyversionInjector({
requestHeaders: {
versionHeaderName: 'X-App-Version',
buildTimeHeaderName: 'X-App-Build',
},
});Front end and API are on different origins β add the API origin to include (string = URL prefix):
versionInjector({
requestHeaders: { include: ['https://api.example.com'] },
});versionInjector({
requestHeaders: {
include: [
'https://api.example.com',
'https://auth.example.com',
/^https:\/\/[^/]*\.example\.com\//, // any *.example.com subdomain
],
},
});Many packages share one build config, and each app talks to cross-origin APIs that differ per environment (dev / sandbox / prod). Two keys:
1. Write include once in the shared root config β every package inherits it (put the plugin in the shared configureWebpack / vite config).
2. Don't hardcode origins β build them from env vars. Each app's .env.* usually already defines its API origins:
// shared root build config (webpack example)
const versionInjector = require('unplugin-version-injector/webpack');
// provided by each app / each environment's .env
const apiOrigins = [
process.env.VUE_APP_API_ORIGIN,
process.env.VUE_APP_SDK_API_ORIGIN,
process.env.VUE_APP_USER_API_ORIGIN,
].filter(Boolean);
module.exports = {
configureWebpack: {
plugins: [
versionInjector({ requestHeaders: { include: apiOrigins } }),
],
},
};dev / sandbox / prod each get the right origins automatically β no giant hardcoded list.
If all APIs live under a few fixed base domains, a single RegExp also works (new subdomains match automatically):
versionInjector({
requestHeaders: {
include: [/^https:\/\/[^/]*\.(example\.io|example\.dev|sandbox-example\.com)\//],
},
});Only include API origins you actually
fetch/XHRand whose CORS you control. Don't add CDNs or third-party SDK script hosts β that only triggers preflights and can break asset loading.
Custom headers on cross-origin requests trigger an OPTIONS preflight. Every API service matched by include must allow the two headers, or the browser blocks the request:
Access-Control-Allow-Headers: X-Client-Version, X-Client-Build-Time
This is exactly why cross-origin injection is opt-in via include rather than on by default.
Open the browser Network tab and look at the request's real URL:
http://localhost:9040/api/...(via dev proxy) β same-origin,requestHeaders: trueis enough, noinclude.https://api.xxx.com/...(direct) β cross-origin, must be ininclude+ backend must allow the headers.
- π Plug and play
- π Helps identify deployed versions
- π Track build timestamps
- π― Works across major bundlers
- π§© Easily configurable
MIT License Β© 2024 Nian YI
π₯ unplugin-version-injector β the simplest way to track version and build info!