A simple Babel optimization I recently learned

AD, please don't block.

Babel relies on a few internal functions to generate the transpiled code.

These functions, when needed, are placed at the top of the generated code, so they are not inlined multiple times across a single file.

For example, a class declaration class Foo {} gets transpiled as:

"use strict";

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var Foo = function Foo() {
  _classCallCheck(this, Foo);
};

Since Babel performs transpilation on single file basis, there’s still the risk that these functions get duplicated across different files. Of course this is not an optimal solution.

Recently I’ve learned that it’s possible to instruct Babel to do not place any declaration at the top of a file, and instead point them to a reference declared in a single shared module.

In this post we’ll learn how to achieve this goal, and what are the caveats. I’ve published a working example on Github.

Babel’s runtime to the rescue

The easiest solution comes under the name of the @babel/plugin-transform-runtime plugin.

We have to install the plugin, and @babel/runtime standalone module. Then configure Babel to use it.

{
  "presets": [
    "@babel/preset-es2015"
  ],
  "plugins": [
    "@babel/transform-runtime"
  ]
}

With this configuration in place, Babel’s output becomes:

"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));

module.exports = function Foo() {
  (0, _classCallCheck2.default)(this, Foo);
};

Babel’s helpers implementation is not duplicated; each module only requires what it needs.

In the next session we’ll go even further. Let’s forget about @babel/runtime, and its companion plugin; we’re going to remove also those initial requires.

Generate Babel’s helpers

First thing we’ve to do is to generate the helpers functions for our personal use.

To build the helpers, we’ll need @babel/cli. When we install it, it adds babel-external-helpers into node_modules/.bin.

The executable comes with a couple of useful options, which permit to customize the module format of the generated output, or filter out the list of the generated helpers.

Below the formal cli reference, and a few examples you might would like to try yourself.

  • -t, –output-type [type] Set output format: global (default choice), umd or var

  • -l, –whitelist Whitelist of helpers to ONLY include

babel-external-helpers -t umd > ./helpers.js

babel-external-helpers -t umd -l createClass,classCallCheck > ./helpers.js

Use the helpers

Once we’ve the helpers into our working directory, we have to tell Babel to do not include those helpers in the transpiled code.

This is done using the @babel/plugin-external-helpers plugin. You may have to install it, and reference it in the .babelrc file.

{
  "presets": [
    "@babel/preset-es2015"
  ],
  "plugins": [
    "@babel/external-helpers"
  ]
}

With this configuration, the generated output of the initial example becomes:

"use strict";

var Foo = function Foo() {
  babelHelpers.classCallCheck(this, Foo);
};

In which babelHelpers is a global reference to Babel’s internal helpers.

Sadly, the proposal to have a configurable namespace didn’t gain traction so far.

Anyway, when we run new Foo we get a ReferenceError, because babelHelpers isn’t defined.

If we target only browsers, the solution is straightforward; we’ve to include the helpers before application code.

<script type="text/javascript" src="path/to/babel-helpers.js"></script>
<script type="text/javascript" src="app.js"></script>

In node we could follow a similar strategy, introducing a middleware that pollutes the global scope with the babelHelpers, and then launch the main application.

Alternatively, in case you already rely on webpack, you can use webpack-provide-plugin to inject the helpers where needed.

module.exports = {
  ...
  plugins: [
    new webpack.ProvidePlugin({
      babelHelpers: [path.resolve(__dirname, "dist", "helpers.js")],
    }),
  ],