Skip to content

[plugin-legacy] finer control for modern/legacy target polyfills #6922

@Menci

Description

@Menci

Clear and concise description of the problem

The current implementation of plugin-legacy forces dynamic-import as the modern target (which means, browser supporting dynamic-import will load the modern bundle and others will load the legacy bundle).

But the proportion of browsers that support newer feature is gradually increasing so we can choose a higher feature as the modern target to decrease the bundle size of "modern" target. For the browsers have no support for the given feature, let them load the legacy bundle.

Additionally, we uses @babel/preset-env which generates too many polyfills (even for modern build). For example, if we used new URL in default target (esmodules: true), it generates URL polyfill since Safari < 14 implements URLSearchParams.prototype.delete wrongly. But in most cases we won't care of this so we won't need a 100% right polyfill.

Suggested solution

We already generate code to test legacy/modern browsers. We can change to provide an option to customize it:

interface Options {
  /**
   * JS code to test if the current running browser should use the modern build. Should throw exception if not.
   *
   * @default `"import('data:text/javascript,')"`
   */
  modernFeatureTestCode?: string

  /**
   * A browserslist of target we build the modern bundle for. Used for modern polyfills generation.
   *
   * @default A browserslist with support for ES6 dynamic import.
   */
  modernTargets?: string;
}

Then generate the testing and gate-keeping code like:

const defaultModernFeatureTestCode = "import('data:text/javascript,')"
const getFallbackInlineCode = (featureTestCode) => `!function(){try{new Function("",${JSON.stringify(featureTestCode)})()}catch(o){console.warn("vite: loading legacy build because required features are unsupported, errors above should be ignored");var e=document.getElementById("${legacyPolyfillId}"),n=document.createElement("script");n.src=e.src,n.onload=function(){${systemJSInlineCode}},document.body.appendChild(n)}}();`
const getModernGatekeepingCode = (featureTestCode) => `export function __vite_legacy_guard(){${featureTestCode}};__vite_legacy_guard();`

For example, if I want to target browsers supporting Object.fromEntries, we could use:

{
  modernTargets: browsersWithSupportForFeatures( // From package `browserslist-generator`
    "es6-module-dynamic-import",
    "javascript.builtins.Object.fromEntries"
  ),
  modernFeatureTestCode: "import('data:text/javascript,');Object.fromEntries([])"
}

Additionally, we could accept two functions to filter-out the polyfills we don't want to exclude:

interface Options {
  // Pass a function for auto detection, which returns false to exclude a polyfill.
  polyfills?: boolean | string[] | ((polyfill: string) => boolean)
  modernPolyfills?: boolean | string[] | ((polyfill: string) => boolean)
}

Alternative

Change the way of "testing if the client browser should use modern build" to just test if it matches a browserslist. But it may be hard to do this in a code snippet in index.html.

Additional context

I have implemented all I mentioned features and ready for creating a pull request.

Validations

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status

    Approved

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions