A modern Vite plugin for HTML — EJS templating, minification, MPA support, custom entry/template &
.envinjection. Framework-agnostic (Vue / React / Svelte). Built for Vite 8 (Rolldown + Oxc).
English | 中文
Rebuilt from vite-plugin-html for the Vite 8 era — ESM-first, zero-clutter runtime deps, powered by the same stack Vite itself uses (Rolldown + Oxc).
Core capabilities
- 🗜️ HTML minification — powered by
html-minifier-terser - 📝 EJS templating — variables, partials & loops directly in
index.html - 📄 Multi-page apps (MPA) — multiple entries & templates, first-class
- 🚪 Custom
entry/template— point to any path - 🔐
.envinjection — via Vite-nativeloadEnv - 🏷️ Tag injection — inject
<script>/<link>/ custom tags anywhere - 🧩 Framework-agnostic — Vue / React / Svelte / plain HTML
Built on a modern toolchain
- ⚡ Vite 8 (Rolldown + Oxc) — backward compatible to Vite 5
- 🪶 Only 7 runtime deps (was 12+) — dropped
fs-extra,dotenv,consola, etc. - 📦 Dual ESM / CJS export + full TypeScript types
- 🛠️ oxlint + oxfmt replacing eslint / prettier
node version: >=20.19
vite version: >=5.0.0
pnpm add @easy-vite/plugin-html -Dor
yarn add @easy-vite/plugin-html -Dor
npm i @easy-vite/plugin-html -D- Add EJS tags to
index.html, e.g.
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title><%- title %></title>
<%- injectScript %>
</head>- Configure in
vite.config.ts, this method can introduce the required functions as needed
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { createHtmlPlugin } from '@easy-vite/plugin-html'
export default defineConfig({
plugins: [
vue(),
createHtmlPlugin({
minify: true,
/**
* After writing entry here, you will not need to add script tags in `index.html`, the original tags need to be deleted
* @default src/main.ts
*/
entry: 'src/main.ts',
/**
* If you want to store `index.html` in the specified folder, you can modify it, otherwise no configuration is required
* @default index.html
*/
template: 'public/index.html',
/**
* Data that needs to be injected into the index.html ejs template
*/
inject: {
data: {
title: 'index',
injectScript: `<script src="./inject.js"></script>`,
},
tags: [
{
injectTo: 'body-prepend',
tag: 'div',
attrs: { id: 'tag' },
},
],
},
}),
],
})Multi-page application configuration
import { defineConfig } from 'vite'
import { createHtmlPlugin } from '@easy-vite/plugin-html'
export default defineConfig({
plugins: [
createHtmlPlugin({
minify: true,
pages: [
{
entry: 'src/main.ts',
filename: 'index.html',
template: 'public/index.html',
inject: {
data: {
title: 'index',
injectScript: `<script src="./inject.js"></script>`,
},
tags: [
{
injectTo: 'body-prepend',
tag: 'div',
attrs: { id: 'tag1' },
},
],
},
},
{
entry: 'src/other-main.ts',
filename: 'other.html',
template: 'public/other.html',
inject: {
data: {
title: 'other page',
injectScript: `<script src="./inject.js"></script>`,
},
tags: [
{
injectTo: 'body-prepend',
tag: 'div',
attrs: { id: 'tag2' },
},
],
},
},
],
}),
],
})v5 is a modernization release. It cleans up hidden behaviors and inconsistent naming, and ships a large test-suite expansion (10 → 37 tests, including end-to-end build/dev coverage).
Breaking changes
-
PageOption.injectOptionswas renamed toPageOption.inject— the same name as the top-level option, so page-level and global configuration read alike:pages: [ { filename: 'index.html', template: 'index.html', - injectOptions: { data: { title: 'index' } }, + inject: { data: { title: 'index' } }, }, ] -
The dev-server history fallback no longer hardcodes
/staticas a pass-through prefix. If your templates live understatic/(or you rely on any other prefix bypassing the fallback), list it explicitly:createHtmlPlugin({ template: 'static/index.html', + staticPathPrefixes: ['/static'], }) -
getHtmlInPages/getHtmlPath/readHtml(unused internal helpers that were accidentally exported) were removed from the public surface.
Bug fixes shipped in v5 (see CHANGELOG for the full list): page matching now uses the vite root instead of process.cwd(); the MPA fallback page keeps the global inject; pass-through prefixes match with a path boundary (/static no longer swallows /staticfoo) and return the original request path; the fallback rewrite regex is properly escaped; html documents without module scripts are no longer re-serialized when an entry is configured.
createHtmlPlugin(options: UserOptions)
| Parameter | Types | Default | Description |
|---|---|---|---|
| entry | string |
src/main.ts |
entry file path |
| template | string |
index.html |
relative path to the template |
| inject | InjectOptions |
- | Data injected into HTML |
| minify | boolean|MinifyOptions |
- | whether to compress html |
| pages | PageOption |
- | Multi-page configuration |
| staticPathPrefixes | string[] |
[] |
URL prefixes that bypass the dev-server history fallback |
| verbose | boolean |
false |
output warning log (e.g. when template entry scripts are removed) |
| Parameter | Types | Default | Description |
|---|---|---|---|
| data | Record<string, any> |
- | injected data |
| ejsOptions | EJSOptions |
- | ejs configuration OptionsEJSOptions |
| tags | HtmlTagDescriptor |
- | List of tags to inject |
data can be accessed in html using the ejs template syntax
By default, the contents of the .env file will be injected into index.html, similar to vite's loadEnv function
Warning
Every environment variable is injected (not only VITE_*-prefixed ones). Be careful not to render secrets into your HTML with <%- %> — treat inject.data as the safer, explicit channel.
| Parameter | Types | Default | Description |
|---|---|---|---|
| filename | string |
- | html file name |
| template | string |
index.html |
relative path to the template |
| entry | string |
src/main.ts |
entry file path |
| inject | InjectOptions |
- | Data injected into HTML |
Default compression configuration
collapseWhitespace: true,
keepClosingSlash: true,
removeComments: true,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
useShortDoctype: true,
minifyCSS: true,pnpm install
# SPA — auto build core (watch) and start the playground dev server
pnpm dev:basic
# MPA
pnpm dev:mpa
# Custom entry / template
pnpm dev:entry
# Non-root base (served under /app/)
pnpm dev:basepnpm install
pnpm build:core # build the plugin (vite lib + tsc type declarations)
pnpm test # run unit tests (vitest)
pnpm lint # lint with oxlint
pnpm format # format with oxfmtRequires Node
>=20.19and pnpm>=10. Linting is powered by oxlint and formatting by oxfmt.
MIT