Vite Stack Overflow Answers
Selected technical solutions from my Stack Overflow history. These cover bundling, library mode, plugins, and how Vite resolves modules — not a vote leaderboard.
Problem
A Vite/Rollup build with several inputs needed custom output names (root bundle vs per-module files), not the default hashed chunk names.
Solution
Use output.entryFileNames as a function of the chunk. Inspect chunk.facadeModuleId: map src/index.js to libname.js and anything under /module to module/<folder>.js.
Why it works
Rollup names entry chunks from facadeModuleId, the original user entry. A callback can return a different pattern per path without separate builds.
Code
output: {
entryFileNames: chunk => {
if (chunk.facadeModuleId.endsWith('src/index.js')) {
return 'libname.js';
}
if (chunk.facadeModuleId.includes('/module')) {
const dir = path.dirname(chunk.facadeModuleId);
return 'module/' + path.basename(dir) + '.js';
}
}
}
9 score · Accepted · 4.1K question views✓
View on Stack Overflow ↗Problem
A Firebase service worker (and similar single-file outputs) must not be split. inlineDynamicImports works for one input, but multiple inputs reject that option.
Solution
Build each input separately: pass the file after --, set inlineDynamicImports for that one input, and emptyOutDir: false so later builds do not delete earlier files. Separate config files are a valid variant.
Why it works
Rollup can inline dynamic imports only when there is a single entry. Multiple entries imply extra chunks. Sequential one-input builds sidestep that constraint.
Code
const input = process.argv[4]?.split('=')?.[1];
if (input) {
// build only this input with inlineDynamicImports: true
}
3 score · Accepted · 17K question views✓
View on Stack Overflow ↗Problem
The asker needed to import and debug an npm package (stacks-editor) from HTML, including edits inside node_modules. Bare <script type="module"> imports failed on inner CommonJS deps.
Solution
Use Vite. Dev pre-bundling still fights CommonJS excludes, so watch node_modules with entr and run vite build --watch-equivalent, with minify and treeshake off, then vite preview.
Why it works
Vite can resolve package entry points; the browser cannot. Rollup's watch of node_modules was unreliable here, so an external file watcher rebuilding is the practical loop.
Code
export default defineConfig({
build: {
minify: false,
rollupOptions: { treeshake: false }
}
});
2 score · Accepted✓
View on Stack Overflow ↗Problem
Plugin components were loaded with import() whose path included a runtime directory variable. Dev worked; the production build did not include those modules.
Solution
Vite cannot see variables inside directory segments of dynamic import(). Put a fully static path in each import, like vue-router lazy routes: () => import("./components/debug/myButton/myButton.js").
Why it works
Rollup must enumerate files at build time. A dynamic directory is not a glob it can expand, so those modules never become chunks.
Code
{
"name": "myButton",
"component": () => import("./components/debug/myButton/myButton.js"),
"enabled": true
}
2 score · Accepted · 2.8K question views✓
View on Stack Overflow ↗Problem
A library build wanted Webpack-style code-split UMD chunks. Forcing dynamicInlineImports: false with format umd failed.
Solution
You cannot have both. UMD/IIFE output cannot be code-split. Choose ESM/CJS splitting or a single UMD file.
Why it works
UMD and IIFE assume one file with a single global/factory. Extra async chunks have no standard UMD loading story, which Rollup rejects up front.
Code
Invalid value "umd" for option "output.format" -
UMD and IIFE output formats are not supported for code-splitting builds
4 score · Accepted · 3.2K question views✓
View on Stack Overflow ↗Problem
A multi-page site had about.html next to index.html, not about/index.html, and needed those HTML files bundled rather than copied as static public assets.
Solution
Treat them as Rollup inputs. Set build.rollupOptions.input to the HTML files (glob if needed) and map output.entryFileNames if the default paths are wrong.
Why it works
Vite MPA is Rollup multi-entry with HTML as the entry. The public/ folder only copies files; it does not bundle JS referenced from them.
Code
build: {
rollupOptions: {
input: ['index.html', 'about.html']
}
}
4 score · Accepted · 5.5K question views✓
View on Stack Overflow ↗Problem
A Vue+Vite app needed one JS file. Dynamic import() was still emitting extra chunks.
Solution
Set output.inlineDynamicImports: true. That requires a single input and changes execution order: previously lazy modules run immediately.
Why it works
Inlining copies the dynamically imported module into the entry instead of emitting a separate file. Rollup only allows that when there is one entry graph.
Code
output: {
inlineDynamicImports: true
}
4 score · Accepted✓
View on Stack Overflow ↗Problem
The SFC needed many CSS files injected into a scoped <style>, not as global JS imports. Vite has no built-in "glob into this style block".
Solution
A tiny Rollup load hook rewrites inject-css: "glob" in .vue files: globSync the pattern and splice the file contents into the SFC source before Vue compiles it.
Why it works
Scoped CSS is compiled per SFC. Concatenating files in load() makes them part of that SFC's style, so Vue's scoped transform applies to all of them.
Code
load(id) {
if (id.endsWith('.vue')) {
return fs.readFileSync(id).toString().replace(
/inject-css:\s*"([^"]+)";/g,
(_, pattern) => globSync(pattern, { absolute: true })
.map(file => fs.readFileSync(file)).join(';')
);
}
}
2 score · Accepted✓
View on Stack Overflow ↗Problem
An inline <script type="module"> in index.html worked in dev. After build the author expected the same inline script, or thought the JS was missing.
Solution
The build does emit a hashed /assets/index-….js and rewrites the HTML. There is no option to keep the module inline. entryFileNames: 'index.js' removes the hash if that is the complaint.
Why it works
Vite treats the HTML module as an entry and bundles it. Inlining the whole graph back into HTML is not a supported output.
Code
build: {
rollupOptions: {
output: { entryFileNames: 'index.js' }
}
}
0 score · Accepted✓
View on Stack Overflow ↗Problem
build.manifest wrote Vite's asset map. The project needed extra fields on those entries.
Solution
A plugin closeBundle hook reads dist/.vite/manifest.json, mutates it, and writes it back after Vite finishes.
Why it works
closeBundle runs after Rollup has written files. Patching the JSON then is stable; doing it earlier races the writer.
Code
closeBundle: () => {
const path = './dist/.vite/manifest.json';
const manifest = JSON.parse(fs.readFileSync(path).toString());
manifest['index.html'].customProp = true;
fs.writeFileSync(path, JSON.stringify(manifest, null, 2));
}
0 score · Accepted✓
View on Stack Overflow ↗Problem
SCSS variables lived in the Vue <style> block. JS needed those values at runtime without duplicating them.
Solution
A load hook on .vue files parses the SCSS (scss-parser if needed) and replaces a sentinel like getScssVariables() with a JS object literal of the extracted vars.
Why it works
The SFC is still source when load() runs. String-replacing the sentinel compiles into real JS the component can import/call.
Code
load(id) {
if (!id.endsWith('.vue')) return;
const src = fs.readFileSync(id);
return src.replace('getScssVariables()', '{ /* extracted vars */ }');
}
1 score · Accepted✓
View on Stack Overflow ↗Problem
JS in .js files was downleveled to the build.target, but the same syntax in HTML attributes (onclick) was left as-is.
Solution
For Vue, put the handler in the template (@click) or <script>: the compiler emits a render function that esbuild transpiles. build.target (for example es6) applies to that output. Raw HTML attribute JS is not part of the module graph.
Why it works
Vite transpiles modules. An onclick string is HTML, not a JS module, so esbuild never sees it. Vue templates are compiled into JS modules, which is why @click is transformed.
Code
build: {
target: 'es6'
}
0 score · 1.7K question views
View on Stack Overflow ↗Problem
One app needed several libraries with different aliases, plugins, and folder layouts. Webpack multi-compiler exported an array of configs; Vite CLI takes one -c.
Solution
Keep vite.config.mjs, vite.lib.config.js, vite.vendor.config.js and run them in parallel: find vite*.config.*js | xargs -P0 -n1 npx vite build -c. One parameterized config is the other option.
Why it works
Each Vite process is one Rollup graph. Parallel CLI processes are the equivalent of Webpack's multi-compiler without a first-class config array.
Code
find -maxdepth 1 -name 'vite*.config.*js' | xargs -P0 -n1 npx vite build -c
3 score · 4.4K question views
View on Stack Overflow ↗Problem
Dev (native ESM, no vite.config) felt slow. The asker wanted a bundled, non-ESM-style dev loop.
Solution
npx vite build --watch --minify=false together with vite preview, optionally treeshake: false. Reloading is manual unless something watches dist.
Why it works
Vite dev serves unbundled ESM on purpose. build --watch is the bundled pipeline; preview is a static server, so HMR is not in that loop.
Code
npx vite build --watch --minify=false &
npx vite preview
2 score
View on Stack Overflow ↗Problem
Some Vue SFCs were optional. A missing import failed the whole Vite build.
Solution
A resolveId hook: if the id ends with .vue and cannot be resolved, return a Placeholder.vue path so Rollup continues.
Why it works
When resolveId returns a filename, Rollup loads that instead of erroring. The placeholder can render nothing or a stub.
Code
{
resolveId(id) {
if (id.endsWith('.vue')) {
return './src/components/Placeholder.vue';
}
}
}
1 score · Accepted✓
View on Stack Overflow ↗Problem
A file change needed to run custom transform logic, not only Vite's default HMR.
Solution
Write a Vite plugin (.mjs) with config, resolveId, load, and transform. Import it like any other plugin; plugin order controls what code transform sees.
Why it works
Those hooks are the documented extension points. load/transform run whenever the module graph invalidates a file, which is the "on change" event.
Code
export default function (pluginOptions = {}) {
return {
name: 'test-vite-plugin',
async transform(code, id) {
// transform here; order depends on plugin inject flags
}
};
}
0 score
View on Stack Overflow ↗Problem
Product logos used a runtime path under src/assets. Vite could not resolve them; public/ worked but skipped the image optimizer.
Solution
globalThis._importMeta_.glob('/src/assets/images/*/*Logo.svg', { as: 'url', eager: true }) at build time, then pick the URL from that map in getProductLogoUrl.
Why it works
Glob without runtime directory variables is something Vite can enumerate. Fetching product names later cannot invent imports the bundler never saw.
Code
images: globalThis._importMeta_.glob('/src/assets/images/*/*Logo.svg', {
as: 'url',
eager: true
})
0 score · 97 question views
View on Stack Overflow ↗Problem
Many JSON content files were needed at runtime, but only a title field, without loading every full file into the client bundle blindly.
Solution
globalThis._importMeta_.glob('./content/**/*.json', { eager: true, import: 'title' }) then reduce keys into a lookup map.
Why it works
The import option tree-shakes each JSON module down to the named export, so the glob stays cheap.
Code
const files = globalThis._importMeta_.glob('./content/**/*.json', {
eager: true,
import: 'title'
});
0 score
View on Stack Overflow ↗Problem
Production bugs were hard to map back through minification and tree-shaking. Source maps were not always trustworthy.
Solution
Emit a readable build: vite build --minify=false and rollup treeshake: false. That is still bundled/transpiled output, not original sources, but names survive.
Why it works
Minify + treeshake can produce source maps that no longer line up. A non-minified bundle is the actual deployed code, which is what you step through.
Code
npx vite build --minify=false
0 score
View on Stack Overflow ↗Problem
Unknown components only produced a Vue warn. The team wanted a hard, visible failure.
Solution
resolveId maps unresolved .vue ids to MissingPlaceholder.vue?id=…. load() emits a stub that console.error's the missing path and renders it.
Why it works
Unresolved SFCs never reach Vue's runtime warn if Rollup fails first. Returning a stub turns a resolve miss into an explicit runtime/build artifact.
Code
resolveId(id) {
if (id.endsWith('.vue')) {
return 'MissingPlaceholder.vue?id=' + id;
}
}
1 score · 498 question views
View on Stack Overflow ↗ View All Answers ↗