Vite + @vitejs/plugin‑legacy: Balancing Legacy Target and Polyfills for Zero‑Downtime Migration
0 reputation · 08 Apr 2024, 18:09 UTC
0 reputation · 08 Apr 2024, 18:09 UTC
Migrating a lightweight application to Vite while preserving uninterrupted service requires careful handling of the legacy bundle. The @vitejs/plugin‑legacy module produces a separate ES5‑compatible script that is loaded via a <script nomodule> tag, allowing modern browsers to consume the module‑based bundle.
The core of the decision rests on the legacy.target option. A broader target such as es2015 reduces polyfill imports and bundle size, but may break in older engines. Narrowing to es5 injects many polyfills, increasing size but maximizing compatibility.
Additionally, the noExternal flag can exclude large dependencies from the legacy build, lowering duplication but risking runtime errors if the library is unavailable in the legacy context. The plugin also respects the project’s browserslist; an absent or misconfigured entry can lead to inconsistent polyfill sets between releases.
Because Vite’s defaults and the plugin’s polyfill logic evolve, the exact trade‑off between bundle size, build time, and browser coverage is not fixed. Determining the appropriate configuration requires balancing these factors against the target audience’s browser profile.
legacy.target value to achieve acceptable bundle size while maintaining support for IE11 in a small app?noExternal setting affect polyfill injection and duplication across the legacy bundle?browserslist be explicitly defined or rely on Vite defaults to ensure consistent polyfill coverage?For a small application that must still serve IE11, configure the legacy bundle for the IE11/ES5 class of browsers rather than es2015. Modern browsers consume the type='module' bundle, while legacy browsers consume the separate nomodule fallback. Do not assume legacy.target exists in your installed plugin; option names vary by @vitejs/plugin-legacy version. Use the plugin's targets or browserslist entry and verify it against the plugin's README or type definitions before deploying.
| Item | Established behavior | Assumption or uncertainty |
|---|---|---|
| Bundle split | Vite emits a module bundle; plugin-legacy emits a separate nomodule bundle. | Standard for Vite 4 and 5. |
| IE11 target | IE11 requires ES5-class syntax and runtime polyfills; es2015 alone is insufficient. | Polyfills cannot supply every missing DOM or JavaScript feature. |
| noExternal | Controls whether dependencies are bundled into legacy output or left external; it is not a polyfill switch. | Size and duplication effects depend on your dependency tree. |
| browserslist | Explicit queries produce more stable polyfill sets than Vite defaults. | Invalid queries may be ignored by some tooling versions. |
package.json:"browserslist": [
"ie 11",
"last 2 Chrome versions",
"last 2 Firefox versions",
"last 2 Safari versions"
]
targets or browserslist rather than a bare legacy.target scalar:import legacy from '@vitejs/plugin-legacy'
export default {
plugins: [
legacy({
targets: ['ie 11'],
// additionalLegacyPolyfills: ['promise-polyfill'] // add only after auditing missing APIs
})
]
}
noExternal sparingly. Leave a dependency external only if it is present and compatible in the legacy runtime; otherwise bundle it into the legacy output to avoid missing-module errors.Promise, fetch, or Object.assign. Do not import every available polyfill.type='module' and nomodule entries. Use short cache control for HTML and long cache control for assets. Retire the old build only after the new HTML is fully served.npx vite build and inspect dist/index.html: confirm one script uses type='module' and the fallback uses nomodule.const/let, and optional chaining should not appear for an IE11 target.Promise, fetch, or script-selection errors.noExternal entries before finalizing.Which legacy browser versions and traffic share actually require the nomodule bundle? If the requirement is strictly IE11, target the IE11/ES5 class. If the audience is only older evergreen browsers, es2015 may be sufficient and smaller. This detail changes the target recommendation.
Use comments to ask for clarification. Post a solution as an answer.
6,250 reputation · 08 Apr 2024, 21:39 UTC
A distinction worth emphasizing is that @vitejs/plugin-legacy creates two separate compilation paths: the modern bundle follows Vite's build.target, while the legacy bundle follows legacy.target. An explicit .browserslistrc helps synchronize Autoprefixer and other CSS transforms with the legacy baseline, but it does not override an explicit legacy.target value. If IE11 support is required, es5 is the safe choice because it triggers SystemJS and core-js polyfills; moving to es2015 reduces bundle size but leaves arrow functions and classes untranspiled, which IE11 cannot parse. Because plugin defaults evolve across Vite versions, verify the exact polyfill set in your installed release rather than assuming fixed behavior.
For practical verification, inspect the generated HTML to confirm both <script type="module"> and <script nomodule> tags are present, then load the legacy bundle in an IE11 environment to catch syntax errors before cutover.