vite-plugin-singlefile: shipping a Vite app as one HTML file
Vite plugin for inlining JavaScript and CSS resources
At a glance
- What is it?
- vite-plugin-singlefile inlines every JavaScript and CSS asset Vite produces into the final dist/index.html, so the whole application becomes a single file you can open from disk with no server. The plugin is MIT licensed, the newest release is v2.3.0 from 2025-07-02, and the last push was on 2026-04-17.
- Who is it for?
- The plugin fits a team shipping an offline utility, an expert system tool, a documentation bundle or a demo as a single HTML file that a user can double-click, and it fits a team that wants to avoid a Cordova or Electron wrapper. It does not fit a normal hosted site, where the plugin itself says inlining is not recommended for performance.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 6 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One HTML file, and the plugin says plainly when not to use it
vite-plugin-singlefile is a Vite build plugin that inlines all JavaScript and CSS resources directly into the final `dist/index.html`, so that your entire web app can be embedded and distributed as a single HTML file.
What makes the readme worth reading is that it opens by telling you not to do this. Bundling your entire site into one file is not recommended for most situations, and in particular this is not a good idea, performance-wise, for hosting a site on a normal web server.
That honesty is the point, because the alternative to a single file is usually worse. The plugin's use case is offline web applications, apps bundled into a single HTML file that you can double-click and open directly in your web browser with no server needed. The examples it gives are utilities, expert system tools, documentation, and demos, and in each case the alternative would be the need for a Cordova or Electron wrapper, or the pain of normal application installation.
The single-file constraint is stated with a bold warning, and it is a hard one. This is a single file plugin, it creates one HTML file and no other files, so it either will not work or will not be optimized for apps that require multiple entry points. The documentation points at issue 51 for details and says that issues requesting multiple entry points will be closed as `wontfix`.
So the decision is: if your app is one page with one entry point and you want it to travel as a file, this is the tool. If you have a multi-page site, the project is not interested in your use case, and saying so up front saves everyone time.
What a file URL gives you, and what it takes away
The section on what works when running an HTML file locally is the most practically useful part of the documentation.
Local HTML files are now for most purposes considered to be a secure context, and so have far more capabilities than when the project started. The list of what you can use includes `localStorage`, newer experimental Persistent Storage APIs, the FileSystem API, requests for local files relative to the same folder, which for Vue means resources from your `public` folder, requests for images and fonts from external sites, requests to external APIs with `{ mode: 'no-cors'}` in your `fetch` call, following links to other files relative to the same folder, SPA hash-based routing, and WebXR.
The author is careful about the confidence level, saying he has only tested some of the above in Chromium-based browsers and that YMMV applies for WebKit and other browser engines, and that some may require explicit user permission. That caveat is worth taking seriously, because the entire feature set depends on which browser your user opens the file in.
The list of what does not work is the other half. SPA routing via the Web History API does not work. Cookies do not work, because they are passed via HTTP headers and those do not exist for `file:///` URIs. WebXR Immersive Mode and worklets are theoretically possible but not currently supported. And sourcemaps are useless, since inlining happens after they are generated.
That last one has a consequence for the plugin's own options, because `deleteViteModuleLoader` and the sourcemap handling in the configuration section are about exactly this. The cookies limitation in particular is the one most likely to surprise an application author, since any code that assumes a session cookie exists will fail in a way that looks like a bug in the app rather than a limitation of the file protocol.
Five options, and the recommended build config you can turn off
The configuration surface is small and each option answers a specific question. The plugin is installed as a dev dependency and added to the Vite plugin list:
npm install vite-plugin-singlefile --save-devimport { defineConfig } from "vite"
import vue from "@vitejs/plugin-vue"
import { viteSingleFile } from "vite-plugin-singlefile"
export default defineConfig({
plugins: [vue(), viteSingleFile()],
})`useRecommendedBuildConfig` defaults to `true` and this plugin will automatically adjust your Vite configuration to allow assets to be combined into a single file. To disable it, pass `useRecommendedBuildConfig: false`, and the documentation points at the `_useRecommendedBuildConfig` function in the repository's `index.ts` file so you can read the recommended configuration rather than guess at it.
`removeViteModuleLoader` defaults to `false`. Vite includes a function in your build to load other bundles, and since all bundles are being inlined, this option removes that bundle-loading function from the final build.
`inlinePattern` defaults to `[]`, which inlines all recognized JavaScript and CSS assets. You can provide an array of glob patterns to limit the inlining to certain assets, and any assets missed by your patterns will generate a warning, the same as any unrecognized assets. So a project with one large third-party library that you would rather leave external can be expressed as a pattern.
`deleteInlinedFiles` defaults to `true`, which deletes all inlined files that were inlined. The use case for turning it to `false` is if you would like sourcemaps to be generated so you can upload them to an error tracking platform like Sentry.io.
`overrideConfig` lets you put your own partial Rollup configuration in to override the plugin's defaults. The example given is that the default base URL for public non-inlined files is `./`, and if you need it to be absolute you use `overrideConfig: { base: "/" }`.
The recommended configuration is described as really recommended, but the override exists for when you need to tweak something it is doing. The plugin is doing a fair amount of configuration surgery on your build, and being able to see and adjust the result is reasonable.
The caveats are specific, and one of them is an admission
The caveat list mixes technical facts with a personal note, and the personal note is part of why the technical list is trustworthy.
Static resources in the `public` folder, like a favicon, are not inlined by Vite, and this plugin does not do that either, but the output single HTML file can work together with these resources using relative paths. Inlining of SVG is not supported directly by Vite, so it is not supported directly here either, and you will need something like vite-svg-loader or to put your SVG directly into the template. And there may be other situations where referenced files are not inlined by Vite and are not caught by this plugin either.
The last of those is the important one, because it is an admission that the plugin cannot detect every case. The inlining works on the assets Vite emits and recognises, and anything else silently stays external, which for a single-file build means a broken reference rather than a missing optimisation.
Then there is the sentence that explains a lot about the codebase: this is my first Vite and first Rollup plugin, I have no idea what I am doing, PRs welcome. Combined with the dual packages note, which explains that the plugin uses dual packages to support both ESM and CommonJS users and that this should work automatically, with links to the Node documentation on the dual package hazard and to a write-up on creating a single source npm module, it reads like a careful maintainer being candid about the edges of their own work.
The dual package approach is visible in the manifest, with `main` pointing at `dist/cjs/index.js`, `module` at `dist/esm/index.js`, and an `exports` map with separate `import` and `require` conditions, plus a `typings` entry. The build script compiles the ESM and CJS targets with two separate TypeScript configurations and then runs `fixup.cjs` to finalise the output.
How it is built and tested, including a matrix across Vite versions
The repository layout and the manifest describe a small TypeScript project with a testing setup that goes further than the size of the codebase suggests.
There is `src/` for the source, `__tests__/` for the tests, a `fixup.cjs` for the dual package fixup, `tsconfig.json` with `tsconfig-base.json` and `tsconfig-cjs.json`, an `eslint.config.mjs` and a `.markdownlint.json` for the documentation, a `CHANGELOG.md`, and a `test-vite-versions.sh` script.
That last script has its own npm script, `test:vite-versions`, which is the interesting one. The plugin's peer dependency range is `vite: ^5.4.21 || ^6.0.0 || ^7.0.0 || ^8.0.0`, spanning four major versions, and a plugin that rewrites the build configuration has to work across all of them. The script is how the maintainer checks that.
The test framework is Vitest, with `vitest --run` for the suite and `vitest watch` for the watch mode. The contributing section explains a workaround: the author was not able to persuade Jest to run on the bare source file, so it instead compiles and runs the CommonJS distribution file. That is a slightly unusual arrangement, testing the built artefact rather than the source, and it is a reasonable trade if the CJS distribution is what users actually load.
The contributing instructions also ask for a sister clone of the example repository, vite-plugin-singlefile-example, to test modifications against, and note that the author is happy to take pull requests for the example too if you want to add edge cases such as a large third-party dependency, and that the example is pretty barebones for now.
One more detail from the manifest that matters for anyone embedding this. The `engines` field requires Node greater than 18.0.0, and `rollup` is listed as an optional peer dependency, marked optional in `peerDependenciesMeta`, while `vite` is a required peer. The only runtime dependency is `micromatch` at ^4.0.8, which is the glob matcher used for the `inlinePattern` option. A plugin with one small dependency and no bundled copies of anything is quick to install and quick to audit.
Where vite-plugin-singlefile is the wrong choice
The first case is a normal hosted site, and the plugin says this itself: inlining is not recommended for performance on a normal web server. The reason is straightforward once you think about it. A single HTML file cannot be cached separately from its content, so any change to any asset invalidates the whole file, and a user who visits a second page of a multi-page app re-downloads everything. Split assets are what let a browser cache what has not changed.
The second case is a multi-page application. The plugin creates one HTML file and no other files, so an app with several entry points either will not work or will not be optimised, and requests for multiple entry points will be closed as `wontfix`. A docs site with one page per topic is the classic case that does not fit.
The third case is an app that needs the Web History API for routing. The list of what does not work is explicit, and history-based routing is on it, along with cookies, which means any session handling that relies on a cookie also fails. A single-page app that uses hash-based routing works; one that uses path-based routing does not.
The fourth is a project that needs source maps in production. The documentation says sourcemaps are useless since inlining happens after they are generated, and the `deleteInlinedFiles` option exists so you can keep the files around to upload to an error tracking platform. If source maps are a hard requirement rather than a nicety, this is a fight with the tool rather than a use of it.
A maintenance observation. The release history shows v2.1.0 from 2024-12-08, then v2.2.1 and v2.3.0 both on 2025-07-02, with the last push on 2026-04-17. The manifest version is 2.3.3, so the source is ahead of the last tag, and a project that rewrites other people's build configuration deserves a version you can pin to exactly.
Editorial conclusion
The plugin fits a team shipping an offline utility, an expert system tool, a documentation bundle or a demo as a single HTML file that a user can double-click, and it fits a team that wants to avoid a Cordova or Electron wrapper. It does not fit a normal hosted site, where the plugin itself says inlining is not recommended for performance. Before you adopt it, read the caveats about what does not work from a file URL, since SPA routing via the Web History API, cookies and sourcemaps all break, and note the plugin's version is 0.x-style in practice despite the 2.x tag, with the last push on 2026-04-17.
Frequently asked questions
How do I install vite-plugin-singlefile?
Run `npm install vite-plugin-singlefile --save-dev` or `yarn add vite-plugin-singlefile --dev`. Then add `viteSingleFile()` to the plugins array in your `vite.config.ts`, for example `plugins: [vue(), viteSingleFile()]`.
What does not work in a single-file build?
SPA routing via the Web History API, cookies because they are passed via HTTP headers that do not exist for file URIs, WebXR Immersive Mode and worklets, and sourcemaps since inlining happens after they are generated. Hash-based SPA routing does work.
What are the options of vite-plugin-singlefile?
`useRecommendedBuildConfig` (default true) adjusts your Vite config automatically, `removeViteModuleLoader` (default false) strips Vite's bundle loader, `inlinePattern` (default empty, inlines everything) limits inlining with glob patterns, `deleteInlinedFiles` (default true) can be set to false to keep sourcemaps, and `overrideConfig` lets you supply a partial Rollup config.
Which Vite versions does vite-plugin-singlefile support?
The peer dependency range covers Vite 5.4.21 and later, 6, 7 and 8, with Rollup as an optional peer dependency and Node greater than 18.0.0 required. The repository includes a `test-vite-versions.sh` script to check the plugin across those versions.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/richardtallent-vite-plugin-singlefile)Was this useful?