babel-preset-modules

0.0.2 • Public • Published

babel-preset-modules

A carefully crafted Babel preset that targets browsers with ES Modules support.

Use this instead of @babel/preset-env's target.esmodules option for smaller bundle size and improved startup performance.

Features Supported

  • JSX uses native Object Spread properties for JSX spread attributes 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+.

How?

@babel/preset-env is great, since it lets you define which Babel features are needed based on a browser support target. However, in order to make that plumbing work the preset has to group all of the possible JavaScript syntax features you might be using into fairly large groups. It enables or disables these groups based on the browser support target you specify, including targets.esmodules which is effectively an alias for the set of browsers that support ES Modules.

The problem is that one browser that supports ES Modules can spoil a whole set of features, since they're all grouped together. 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 like a shame to transpile all those great features down to ES5 just for Edge 16, right? I thought so.

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 for the above, at 147 bytes minified & gzipped:

const foo = function foo(_ref, b) {
 let { a = 1 } = _ref;
 
 if (=== 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 dramatically improved bundle size and performance, while supporting the same browsers. All from removing grouping.

Example Usage

{
  "env": {
    "modern": {
      "presets": ["babel-preset-modules"]
    },
    "legacy": {
      "presets": {
        ["@babel/preset-env", {
          "targets": {
            "browsers": ">1%, not dead"
          }
        }]
      }
    }
  }
}

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 Node API:

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

With Terser CLI:

terser --ecma 8 --safari10 ...

With terser-webpack-plugin:

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

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

Bundle Size Comparison

Bundling an unmodified copy of the redux-todos codebase.

Babel Preset / Configuration Gzipped Bundle Size
babel-preset-modules 4.07kB
@babel/preset-env target.esmodules 4.75kB
@babel/preset-env (default) 5.33kB

The code is small because no dependencies are bundled, but imagine how this scales up. For an app is 10x this size, would you rather ship 40kB of JS, or 53kB?

Package Sidebar

Install

npm i babel-preset-modules

Weekly Downloads

111

Version

0.0.2

License

Apache 2.0

Unpacked Size

48.6 kB

Total Files

23

Last publish

Collaborators

  • developit