babel icon indicating copy to clipboard operation
babel copied to clipboard

What do we need to make `usage-global` injection work with `@babel/runtime`?

Open nicolo-ribaudo opened this issue 1 year ago • 2 comments

💻

  • [ ] Would you like to work on this feature?

What problem are you trying to solve?

@nyngwang has been going around a bit checking how our polyfill injection works (thank you very much!), and after going through their comments I realized that our injection story for usage-global is pretty bad when it comes to helpers.

If you have this config:

{
  "plugins": [
    "@babel/transform-runtime"
  ]
}

Babel will inject imports for helpers like import _typeof from "@babel/runtime/helpers/typeof".

Then you decide that you want to inject global polyfills through core-js, so you update your config to this:

{
  "plugins": [
    "@babel/transform-runtime",
    ["babel-plugin-polyfill-corejs3", { "method": "usage-global" }]
  ]
}

now Babel will inject imports like import "core-js/modules/es.array.flat.js";, but suddently your imports for helpers will become import "@babel/runtime-corejs3/helpers/typeof" (which depends on core-js-pure). This is to make sure that the helpers are also polyfilled, but:

  • you asked for global-modifying polyfills, and now in your dependencies you have both core-js and core-js-pure
  • injecting global polyfills forced you to change one of your dependencies.

If you still want to use @babel/runtime (because @babel/runtime-corejs3 includes polyfills down to ES5), you have to instead use this config:

{
  "plugins": [
    ["@babel/transform-runtime", { "moduleName": "@babel/runtime" }],
    ["babel-plugin-polyfill-corejs3", { "method": "usage-global" }]
  ]
}

NOTE: I think this behavior is good when it comes to usage-pure, because we are just changing the @babel/runtime imports to a different package that uses exactly the pure polyfill the user asked for.

Describe the solution you'd like

What if for each helper we maintained the list of built-ins that it relies on, and when injecting a import "@babel/runtime/typeof/helpers" we called the polyfill provider saying "hey, we included a helper that uses Symbol and Array.prototype.flat: if needed for the configured target, consider injecting imports to these polyfills".

Then, when compiling typeof foo and using ["babel-plugin-polyfill-corejs3", { "method": "usage-global" }], we would generate some code like

import "core-js/modules/es.symbol.js";
import _typeof from "@babel/runtime/helpers/typeof";

_typeof(foo);

rather than

import _typeof from "@babel/runtime-corejs3/helpers/typeof";

_typeof(foo);

This list would probably need to be manually-maintained by us for each helper, and:

  • we could try linting using our existing polyfill provider infrastructure, to make sure that we aren't missing any builtin in our list
  • even if we change the helper to not rely on X anymore, we still need to include X in the list of features for compatibility with older @babel/runtime versions

Describe alternatives you've considered

Do nothing

Documentation, Adoption, Migration Strategy

No response

nicolo-ribaudo avatar Mar 28 '24 17:03 nicolo-ribaudo

I don't remember how internally works @babel/transform-runtime, but what about a naive solution like this?

  1. Inject a helper as usually, without runtime
  2. Inject core-js polyfills via babel-plugin-polyfill-corejs3 for code and this helper as usually
  3. Just replace this helper to import from runtime

It looks slower than the predefined list of dependencies but simpler for maintenance.

zloirock avatar Mar 28 '24 17:03 zloirock

As I'm a naive user, I like what @zloirock proposed as it has the following two advantages:

  1. No more confusion about why "my code" does not include those extracted helpers when adding polyfills for my code. (If the link does not highlight the specific sentence, please search the page with: but with that config babel includes polyfills when you use that feature in your code!)
    • In short, they tried to teach people that the option useBuiltIns: 'usage' might miss some polyfills because "code of helpers" is not included in "my code" when determining what needs to be polyfilled. It was very frustrating when I tried to understand the entire thread. The thread is still open btw.
  2. It can be easily explained/documented in one simple sentence without leaky abstractions:
    • babel-plugin-polyfill-corejs3 will add all polyfills you need to run your app/library for your browser targets.
    • @babel/transform-runtime will extract helpers. Extracting it or not has nothing to do with whether my app/library will be polyfilled correctly.

nyngwang avatar Mar 28 '24 20:03 nyngwang