vite-plugin-virtual-mpa is a Vite plugin that generates multiple HTML entries for multi-page applications from a single EJS template using virtual files.
What is vite-plugin-virtual-mpa?
vite-plugin-virtual-mpa is an open-source Vite plugin (by emosheeep) that adds out-of-the-box MPA support with a built-in EJS template engine and virtual entry files. It takes a pages configuration (each page with a name, an optional entry JS file, optional EJS data, and an optional custom filename like fruits/apple.html) and produces built HTML files at those configured paths. The plugin runs inside Vite's build and dev server, automatically populating rollupOptions.input and the dev/preview server history-fallback rewrites.
Key Features
- EJS Template Capability — Uses a single EJS template file to render every page; page-specific data is passed via the
dataoption, and VITE-prefixed environment variables are automatically injected. - Virtual Entry Files — Entry HTML files are kept in memory during development and only written to disk at build time, so no per-page
.htmlfiles need to exist in the source tree. - Custom Output Paths — Each page can define a
filenamelikefruits/apple.html, which is relative tobuild.outDir, letting you control exactly where the built file lands. - History Fallback API — The plugin generates default rewrite rules for both dev and preview servers based on the Vite
baseand the page-name-to-filename map; you can override them withrewritesandpreviewRewrites. - scanOptions — Automatically generate pages by scanning directories with
scanDirs; subdirectory names become page names, andentryFileand afilenamefunction customize the output. - watchOptions — Reload the
pagesconfig or restart the dev server on file add/change/remove events, with include/exclude filters followingRollup.createFilter. - transformHtml — A dedicated hook that inherits Vite's
transformIndexHtmlto modify generated HTML, receiving the page context so you can inject tags or alter markup per page. - htmlMinify — Optional HTML minification powered by
html-minifier-terser, gated on thehtmlMinifyoption (defaultfalse).
Who should use vite-plugin-virtual-mpa?
- Vite developers building multi-page applications — Replace dozens of static HTML files with one EJS template and a page list, cutting duplication and maintenance overhead.
- React, Vue, or framework-agnostic teams — The plugin doesn't care about your UI stack; it only receives an entry JS path and a template, so it works with any framework.
- Projects that need a structured output layout — If you want build artifacts in folders like
fruits/apple.html, thefilenameoption gives you that control without post-build file-moving scripts.
What can you do with vite-plugin-virtual-mpa?
- Create a family of landing pages — Define pages named
apple,banana,strawberries, each with its ownentryanddata(e.g. atitle), and generate all of them from one template with different content. - Auto-generate pages from a directory structure — Point
scanOptions.scanDirsat a folder likesrc/fruits, setentryFile: 'index.js', and every subdirectory instantly becomes a page, with output paths controlled by thefilenamecallback. - Customize dev-server navigation — For routes that don't match a real file, provide your own
rewritesorpreviewRewritesin the connect-history-api-fallback format so refreshes reach the right virtual HTML file.
How does vite-plugin-virtual-mpa work?
Install the plugin with pnpm add -D vite-plugin-virtual-mpa, then add createMpaPlugin to your vite.config.ts and pass a pages array. The plugin generates a virtual module for each page using the template and the page's data, wires up rollupOptions.input from the filename values, and injects default history-fallback rules derived from base and the page map. During development, these virtual files are served in memory; during vite build, they are emitted to the configured output paths.
Pros and cons
- Pros — Combines template engine, virtual entries, and history fallback in a single plugin; supports automatic page discovery via directory scanning; fully typed APIs with
createPageshelper. - Cons — If you supply custom
rewrites, they override the automatically generated default rules, so you must maintain them manually; pages created throughscanOptionscannot receive per-pagedatainjection through that feature (you'd need to define those pages manually for custom data).
Alternatives
- vite-plugin-mpa — Automatically configures entries and dev-server proxy, but requires a conventional directory structure and doesn't provide a template engine or virtual entries.
- vite-plugin-html-template — Adds template engine support but lacks virtual entries; it's designed to be used in combination with another MPA plugin.
- vite-plugin-html — Supports template engines, but you need separate entry templates for each file you want to generate.
- vite-plugin-virtual-html — Supports virtual entries and lets you plug in a custom rendering function, but it has no built-in template engine, making it more cumbersome.
FAQ
Is vite-plugin-virtual-mpa free?
Yes, it's an open-source Vite plugin distributed on npm and publicly hosted on GitHub, with no paid tiers mentioned. You can install it with pnpm add -D, npm install, or yarn add.
Does vite-plugin-virtual-mpa work with React or Vue?
Yes, the README explicitly states the plugin doesn't limit the technology stack and works with Vue, React, or any other framework, because it only handles the HTML entry generation and the entry JS script.
How do I customize the output path of a page?
Set the filename property in that page's configuration, for example filename: 'fruits/apple.html'. It defaults to ${name}.html and is resolved relative to build.outDir.
Can I generate pages by scanning directories?
Yes, through the scanOptions option. You provide scanDirs, and optionally entryFile and a filename function; the subdirectory names become page names. This auto-generates pages but cannot pass per-page data to the template.
What happens if I set my own rewrites?
Your rewrites value replaces the default history-fallback rule that the plugin would otherwise generate from your pages configuration. You must make sure your rules cover all the routes you want to resolve.








