babel: transform-runtime is not removing regenerator-runtime import added by preset-env

Hello!

Thank you for this great tool!

Problem

However, I’m looking at situation when the transform-runtime plugin is not removing global regenerator-runtime/runtime import, which is introduced by the preset-env preset.

Use case

We were working on some libraries and were using preset-env to actually transpile the code to the required target execution environment as well as to add needed global core-js and regenerator-runtime imports (usage mode). We were also using transform-runtime to optimize babel helper imports.

And now we would like to migrate from using global regeneratorRuntime usages to the explicitly imported ones (pure), so we’ve enabled regenerator option of the transform-runtime.

More details

Source code

// src/main.js

export async function foo() {
  return await new Promise(resolve => setTimeout(resolve, 1000));
}

Babel config

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": "IE 11",
        "useBuiltIns": "usage",
        "corejs": 3,
        "modules": false,
        "debug": true
      }
    ]
  ],
  "plugins": [
    [
      "@babel/plugin-transform-runtime",
      {
        "helpers": true,
        "regenerator": true
      }
    ]
  ],
  "exclude": "node_modules/**"
}

Transpiled result

// dist/main.js

import "core-js/modules/es.object.to-string";
import "core-js/modules/es.promise";

import "regenerator-runtime/runtime";
import _regeneratorRuntime from "@babel/runtime/regenerator";

export function foo() {
  return _regeneratorRuntime.async(function foo$(_context) {
    while (1) {
      switch (_context.prev = _context.next) {
        case 0:
          _context.next = 2;
          return _regeneratorRuntime.awrap(new Promise(function (resolve) {
            return setTimeout(resolve, 1000);
          }));

        case 2:
          return _context.abrupt("return", _context.sent);

        case 3:
        case "end":
          return _context.stop();
      }
    }
  });
}

As you can see, the generated file contains both pure and global imports for regenerator-runtime, however, only explicit (pure) import should be left:

import "regenerator-runtime/runtime";
import _regeneratorRuntime from "@babel/runtime/regenerator";

I believe, that transform-runtime should remove such global imports (isn’t it it’s purpose in the first place?), or at least there should be an option for the preset-env to skip adding the global import for regenerator-runtime (I couldn’t find any).

  1. What do you think about this issue?

  2. Do you know of any workarounds I can use right now to actually remove such superfluous imports?

Thank you.

About this issue

  • Original URL
  • State: open
  • Created 5 years ago
  • Reactions: 4
  • Comments: 23 (10 by maintainers)

Most upvoted comments

No, it won’t. Adding import to core-js will pollute the global environment, and the single line import will convert to more granular imports from the core-js library based on your targets, thus not adding polyfills that are not needed. The useBuiltIns: 'usage' option also pollutes the global environment, but it adds polyfills based on whether you use a feature that is not supported on your targets in your code or not (this is problematic if some of the dependencies use a feature that you don’t use in your code, because it won’t be polyfilled)

The only way to work around this that I’ve found so far is to use babel-plugin-transform-remove-imports plugin:

[
  'babel-plugin-transform-remove-imports', {
    test: /^regenerator-runtime\/runtime/,
  },
],

Yes, that’s true. But the benefit is that you will have the polyfills bundled with your library/app (its more useful for libraries, not so much for apps, because you can allow global pollution in your app, the problem is that you don’t want your library users to have global scope pollution), and avoid global scope pollution.

The main thing and the main point of confusion is how to handle polyfills, i.e mixing @babel/preset-env with useBuiltIns with @babel/transform-runtime. You can remember it like this: Whenever you use @babel/preset-env for polyfill handling you pollute, whenever you use @babel/transform-runtime for polyfill handling you are safe, although including useless polyfills.

@JMarkoski where is preset-env config?

@dangreen You don’t need it, moreover, you shouldn’t use @babel/preset-env with useBuiltIns with @babel/transform-runtime (although you can use it in a config where useBuiltIns: 'entry' and @babel/transform-runtime is used only for helpers, but here we talk about useBuiltIns: 'usage'). I’ll link again my answer that answers exactly the question why you shouldn’t use it: https://github.com/babel/babel/issues/9853#issuecomment-619587386

EDIT: To correct myself about what I said above, you need @babel/preset-env to transpile the syntax though. I was talking about handling polyfills (about useBuiltIns to be specific). See the corrected answer above.

@JMarkoski in your answer no word about preset-env. I want to know how to combine preset-env with transform-runtime.

@dangreen I’m not sure I understood correctly what is the problem you are trying to solve, but I’ll try to explain the philosophy of using the @babel/preset-env with useBuiltIns: 'usage' and @babel/transform-runtime.

So, we should note the main important difference between the two: you use @babel/preset-env with {useBuiltIns: 'usage', corejs: *} when you want globally polluting polyfilling, using the babel built-ins for that, and you use @babel/transform-runtime when you want to bundle the polyfills with your app, so you don’t pollute the global environment.

Now, having that philosophy in mind about the above mentioned usage of @babel/preset-env, the polyfilling process also includes the regenerator-runtime/runtime polyfill, together with the babel helpers like classCallCheck and the feature polyfills from the core-js library. In other words, with this config you say:

I want globally polluting polyfilling with babel built-ins that are included on every usage of the polyfillable feature.

Now, when you include the @babel/transform-runtime in the context, you are somewhat doing the same stuff but differently. First you said that you want globally polluting polyfills for everything, and now you say, I want polyfills/helpers for everything bundled with my app, not polluting the global environment.

Actually you want to have this scenario, which I will explain why @nicolo-ribaudo says here that it doesn’t make much sense: https://github.com/babel/babel/pull/10768#pullrequestreview-324863979:

When @babel/preset-env with {useBuiltIns: 'usage', corejs: *} or @babel/transform-runtime is used, each one of them adds a set of things:

  1. Helpers like classCallCheck
  2. Polyfills from core-js
  3. Instance of regenerator-runtime/runtime

This set is the runtime your app needs in order to work in your target environments. It is a set of features that have to be included in order for your app to work, and it can be done in two ways: global pollution and globally safe manner.

In the case of @babel/preset-env this is globally polluting, and in the case of @babel/transform-runtime is not, it adds references to the @babel/runtime or @babel/runtime-corejs*. But the important note is:

The set is included as a whole.

You can’t add globally polluting polyfills from core-js and add regenerator-runtime/runtime that is not globally polluting. You can’t pick things from both sets and mix them, because how something includes/uses other thing is connected, and AFAIK mixing will lead to some unpredictability, or is hard to reason about. So, as @nicolo-ribaudo says in the comment, it doesn’t even make sense to mix globally polluting with globally safe pollyfilling, you either are fully globally polluting or fully globally safe.

EDIT: An answer about mixing both @babel/preset-env and @babel/transform-runtime for polyfill handling: https://github.com/babel/babel/issues/9853#issuecomment-619587386

This is my limited understanding of the matter, @nicolo-ribaudo feel free to correct me if I am wrong somewhere.

Another solution is to switch from useBuiltIns: 'usage' to useBuiltIns: 'entry', since you are okay with global core-js pollution. You can add only import 'core-js' in the entry point, and leave the regenerator: true option on the @babel/transform-runtime.