Skip to content
A Babel preset that targets modern browsers by fixing engine bugs (will be merged into preset-env eventually)
JavaScript
Branch: master
Clone or download
developit Merge pull request #6 from wmertens/patch-1
Fix requiring own dependencies
Latest commit 7afe042 Nov 26, 2019
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.github/workflows Delete nodejs.yml Nov 12, 2019
src Fix requiring own dependencies Nov 24, 2019
test Use Object.assign() for JSX spread Nov 18, 2019
.babelrc initial commit Nov 12, 2019
.editorconfig initial commit Nov 12, 2019
.eslintrc.json initial commit Nov 12, 2019
.gitignore Add remainder of tests Nov 12, 2019
.prettierignore initial commit Nov 12, 2019
.prettierrc initial commit Nov 12, 2019
README.md Revise JSX Spread statement Nov 19, 2019
package.json 0.1.1 Nov 19, 2019
yarn.lock initial commit Nov 12, 2019

README.md

@babel/preset-modules

A Babel preset that enables async/await, Tagged Templates, arrow functions, destructured and rest parameters, and more in all modern browsers (88% of traffic).

It works around bugs and inconsistencies in modern JavaScript engines by converting broken syntax to the closest non-broken modern syntax. Use this in place of @babel/preset-env's target.esmodules option for smaller bundle size and improved performance.

Features Supported

  • JSX spread attributes are compiled to Object.assign() instead of a helper.
  • Default, destructured and optional parameters are all natively supported.
  • Tagged Templates are fully supported, patched for Safari 10+ and Edge 16+.
  • async/await is supported without being transpiled to generators.
  • Function name inference works as expected, including Arrow Functions.

Installation & Usage

Install the preset from npm:

npm install @babel/preset-modules --save-dev

To use the preset, add it to your Babel Configuration:

{
  "presets": [
    "@babel/preset-modules"
  ]
}

If you're implementing the module/nomodule pattern, your configuration might look something like this:

{
  "env": {
    "modern": {
      "presets": [
        "@babel/preset-modules"
      ]
    },
    "legacy": {
      "presets": [
        "@babel/preset-env"
      ]
    }
  }
}

Options

There's a single Boolean loose option, which defaults to false. Passing true further reduces output size.

The loose setting turns off a rarely-needed function name workaround for older versions of Edge. If you're not relying on Function.prototype.name, it's worth enabling loose mode.

How does it work?

Babel’s preset-env is great, since it lets you define which Babel features are needed based on a browser support target. In order to make that plumbing work automatically, the preset has configuration that groups all of the new JavaScript syntax features into collections of related syntax transforms. These groups are fairly large, for example "function arguments" includes destructured, default and rest parameters. The groupings come from the fact that Babel’s transforms often rely on other transforms, so they can’t always be applied in isolation.

From this grouping information, Babel enables or disables each group based on the browser support target you specify to preset-env’s targets option. For modern output, the targets.esmodules option is effectively an alias for the set of browsers that support ES Modules: Edge 16+, Safari 10.1+, Firefox 60+ and Chrome 61+.

Here's the problem: if any version of any browser in that list contains a bug triggered by modern syntax, the only solution we have is to enable the corresponding transform group that fixes that bug. This means that fundamentally, preset-env converts code to ES5 in order to get around syntax bugs in ES2017. Since that's the only solution at our disposal, eventually it becomes overused.

For example, all of the new syntax features relating to function parameters are grouped into the same Babel plugin (@babel/plugin-transform-function-parameters). That means because Edge 16 & 17 support ES Modules but have a bug related to parsing shorthand destructured parameters with default values within arrow functions, all functions get compiled from the new compact argument syntaxes down to ES5:

// this breaks in Edge 16:
const foo = ({ a = 1 }) => {};

// .. but this doesn't:
function foo({ a = 1, b }, ...args) {}

// ... and neither does this:
const foo = ({ a: a = 1 }) => {};

In fact, there are 23 syntax improvements for function parameters in ES2017, and only one of them is broken in ES Modules-supporting browsers. It seems unfortunate to transpile all those great features down to ES5 just for one browser!

This plugin takes a different approach than we've historically taken with JavaScript: it transpiles the broken syntax to the closest non-broken modern syntax. In the above case, here's what is generated to fix all ES Modules-supporting browsers:

input:

const foo = ({ a = 1 }, b = 2, ...args) => [a,b,args];

output:

const foo = ({ a: a = 1 }, b = 2, ...args) => [a,b,args];

That output works in all ES Modules-supporting browsers, and is only 59 bytes minified & gzipped.

Compare this to @babel/preset-env's targets.esmodules output (147 bytes minified & gzipped):

const foo = function foo(_ref, b) {
 let { a = 1 } = _ref;

 if (b === void 0) { b = 2; }

 for (
   var _len = arguments.length,
     args = new Array(_len > 2 ? _len - 2 : 0),
     _key = 2;  _key < _len; _key++
 ) {
   args[_key - 2] = arguments[_key];
 }

 return [a, b, args];
};

The result is improved bundle size and performance, while supporting the same browsers.

Important: Minification

The output generated by this preset includes workarounds for Safari 10, however minifiers like Terser sometimes remove these workarounds. In order to avoid shipping broken code, it's important to tell Terser to preserve the workarounds, which can be done via the safari10 option.

It's also generally the case that minifiers are configured to output ES5 by default, so you'll want to change the output syntax to ES2017.

With Terser's Node API:

terser.minify({
  ecma: 8,
  safari10: true
})

With Terser CLI:

terser --ecma 8 --safari10 ...

With terser-webpack-plugin:

module.exports = {
  optimization: {
    minimizer: [
      new TerserPlugin({
        terserOptions: {
          ecma: 8,
          safari10: true
        }
      })
    ]
  }
};

All of the above configurations also apply to uglify-es. UglifyJS (2.x and prior) does not support modern JavaScript, so it cannot be used in conjunction with this preset.

You can’t perform that action at this time.