Skip to content

Instantly share code, notes, and snippets.

@induratized
Created June 27, 2025 12:16
Show Gist options
  • Save induratized/27358677f2a99f56000915de369e39e9 to your computer and use it in GitHub Desktop.
Save induratized/27358677f2a99f56000915de369e39e9 to your computer and use it in GitHub Desktop.
vite multi build for legacy + modern broswer

Ah — now I understand the nuance in your question. You're using Vite's native index.html handling, and yes:


✅ In Vite's default behavior:

When you run:

npm run build

And your index.html contains:

<script type="module" src="/src/main.jsx"></script>

Vite does transform the index.html during build, replacing that src path with the final, hashed file name — like:

<script type="module" src="/assets/index.abcd1234.js"></script>

This is because Vite uses index.html as part of its HTML entry build pipeline.


❌ However: It only transforms one build at a time

If you're doing two separate builds (e.g., modern + legacy) using different vite.config.js files, Vite processes index.html independently for each. That means:

🧨 Problem:

  • The second build will overwrite or regenerate index.html, based on its own config.
  • Vite does not merge the outputs or coordinate script type="module" and nomodule.

🔁 Solution Options:

✅ Option 1: Let Vite transform both separately, then post-process

  1. Run modern build (creates /dist/module/index.html with hashed modern script)
  2. Run legacy build (creates /dist/nomodule/main.js, does not modify index.html)
  3. Then inject both final filenames (module + nomodule) into a clean final index.html

Use a custom script like:

const fs = require('fs');
const path = require('path');

const moduleDir = 'dist/module/assets';
const nomoduleDir = 'dist/nomodule';

const moduleScript = fs.readdirSync(moduleDir).find(f => f.endsWith('.js'));
const nomoduleScript = fs.readdirSync(nomoduleDir).find(f => f.endsWith('.js'));

const html = `
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>Dual Build</title>
</head>
<body>
  <div id="root"></div>
  <script type="module" src="/assets/${moduleScript}"></script>
  <script nomodule src="/nomodule/${nomoduleScript}"></script>
</body>
</html>
`;

fs.writeFileSync('dist/index.html', html);

✅ Option 2: Use vite-plugin-legacy

If you don’t want to manually manage dual builds, you can use Vite’s official plugin:

npm install --save-dev @vitejs/plugin-legacy

Then in vite.config.js:

import legacy from '@vitejs/plugin-legacy';

export default {
  plugins: [
    react(),
    legacy({
      targets: ['defaults', 'not IE 11'], // or ['defaults', 'ie >= 11']
      modernPolyfills: true,
      renderLegacyChunks: true,
      polyfills: true,
    })
  ]
};

This plugin:

  • Creates both module and nomodule builds
  • Automatically injects both <script type="module"> and <script nomodule> into index.html
  • Handles polyfills

🟢 This is the simplest way to get dual builds with correct script tags — no post-processing needed.


✅ Final Recommendation:

Use Case Best Approach
Manual dual builds Use post-processing script
Prefer automatic handling Use @vitejs/plugin-legacy

Let me know if you want help wiring up either approach in your existing project.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment