Vite Plugin Generate HTML
June 18, 2026 ยท View on GitHub
A Vite plugin that writes generated <script> and <link> tags to separate files during the build.
The plugin reads Vite/Rollup output entry chunks, uses their final hashed asset filenames, and writes:
- one file containing
<script>tags for JavaScript entry chunks - one file containing
<link>tags for CSS imported by those entry chunks
This is useful when your application shell is rendered outside Vite, for example in .html, .cshtml (Razor), .php, or another server-rendered template, but you still need references to Vite's hashed build assets.
It is inspired by Webpack's HtmlWebpackPlugin and is intended for similar use cases in Vite projects where generated asset tags need to be written into external template files.
Installation
npm install --save-dev vite-plugin-generate-html
Requirements
- Node.js 18 or newer
- npm 8 or newer
Usage
// vite.config.ts
import { resolve } from "node:path";
import { defineConfig } from "vite";
import generateHtmlFiles from "vite-plugin-generate-html";
export default defineConfig({
build: {
rollupOptions: {
input: {
app: resolve(__dirname, "src/main.ts"),
},
},
},
plugins: [
generateHtmlFiles({
publicDir: "/dist/",
jsEntryFile: resolve(__dirname, "../templates/javascript.html"),
cssEntryFile: resolve(__dirname, "../templates/css.html"),
}),
],
});
If src/main.ts imports CSS:
import "./styles.css";
the generated files can look like this after a Vite build.
javascript.html:
<script type="module" src="/dist/app.abc123.js"></script>
css.html:
<link href="/dist/app.abc123.css" rel="stylesheet" media="all" />
The plugin overwrites both configured output files on each build.
Options
interface VitePluginGenerateHtmlOptions {
/**
* URL prefix added before generated JavaScript and CSS filenames.
*
* @default "/dist/"
*/
publicDir?: string;
/**
* File path where generated `<script>` tags are written.
*/
jsEntryFile: string;
/**
* File path where generated `<link>` tags are written.
*/
cssEntryFile: string;
/**
* Custom attributes for generated tags by entry chunk name.
*/
output?: Array<
Record<
string,
{
attrs: string[];
linkAttrs: string[];
}
>
>;
/**
* Limit generation to specific entry chunk names.
* By default, all entry chunks are handled.
*/
chunks?: string[];
}
publicDir
publicDir is used as a URL prefix for generated asset references. It is not read from disk.
For example, with publicDir: "/assets/" and an output chunk named app.abc123.js, the generated script tag uses:
<script type="module" src="/assets/app.abc123.js"></script>
jsEntryFile
Required. The file path where generated <script> tags are written.
cssEntryFile
Required. The file path where generated <link> tags are written.
Only CSS imported by handled Vite entry chunks is included. If a handled entry chunk has no imported CSS, no link tag is generated for that chunk.
output
Use output to customize attributes for specific entry chunk names.
generateHtmlFiles({
publicDir: "/dist/",
jsEntryFile: resolve(__dirname, "../templates/javascript.html"),
cssEntryFile: resolve(__dirname, "../templates/css.html"),
output: [
{
app: {
attrs: ['type="module"', "defer", 'data-entry="app"'],
linkAttrs: ['rel="stylesheet"', 'media="all"'],
},
},
],
});
Generated output:
<script type="module" defer data-entry="app" src="/dist/app.abc123.js"></script>
<link href="/dist/app.abc123.css" rel="stylesheet" media="all" />
When output is provided, each handled entry chunk must have a matching key for script generation. If you only want to configure some entries, combine output with chunks.
Default attributes are used when output is omitted:
<script>:type="module"<link>:rel="stylesheet" media="all"
chunks
Use chunks to generate files for only specific entry chunk names.
generateHtmlFiles({
publicDir: "/dist/",
jsEntryFile: resolve(__dirname, "../templates/admin-javascript.html"),
cssEntryFile: resolve(__dirname, "../templates/admin-css.html"),
chunks: ["admin"],
});
This is helpful when a Vite build has multiple entry points and you want separate generated files for different pages or templates.
Multiple Entry Points
You can use the plugin more than once to write separate files for different entry chunks.
plugins: [
generateHtmlFiles({
publicDir: "/dist/",
jsEntryFile: resolve(__dirname, "../templates/app-js.html"),
cssEntryFile: resolve(__dirname, "../templates/app-css.html"),
chunks: ["app"],
}),
generateHtmlFiles({
publicDir: "/dist/",
jsEntryFile: resolve(__dirname, "../templates/admin-js.html"),
cssEntryFile: resolve(__dirname, "../templates/admin-css.html"),
chunks: ["admin"],
output: [
{
admin: {
attrs: ['type="module"', 'data-entry="admin"'],
linkAttrs: ['rel="stylesheet"', 'media="all"'],
},
},
],
}),
];
Errors
The plugin throws an error when:
jsEntryFileis missingcssEntryFileis missingoutputis not an array- no handled entry chunks are found
outputis provided but a handled entry chunk has no matching configurationattrsorlinkAttrsis not an array for a matching entry
License
This project is licensed under the MIT License.