Running Babel should be the one of the last stages of your front end build

18th Jul 2019

After recently upgrading from Babel 6 to 7.4 for a project, I decided to check over the outputted files to make sure all was well.

I started by looking for the basics - was const getting changed to var? Yes it was. Were ES6 classes being converted to functions? Yes they were. And were object spreads being transpiled down? Yes they were, but this was now being done in a different way to Babel 6.

In Babel 7.4, using an object spread operator will result in Babel injecting a polyfill function called _objectSpread into the top of the outputted JavaScript file. This function will then be called wherever you are using an object spread. E.g:

Input:

function someOtherTest () {
const p1 = {
name: 'p1'
};

const combinedP1 = {
height: 100,
...p1
}
}

Results in the following, after being run through Babel:

function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }

function \_objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments\[i\] != null ? arguments\[i\] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { \_defineProperty(target, key, source\[key\]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }

function \_defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj\[key\] = value; } return obj; }

function someOtherTest() {
var p1 = {
name: 'p1'
};

var combinedP1 = \_objectSpread({
height: 100
}, p1);
}

Here's a link to see this in action on the Babel repl.

As you can see, it's a lot more code, but is a tax that some of us have to pay in order to support some older browsers.

And this is where we need to be careful. Most front end build processes typically follow this flow:

  • Gather the source js files
  • Combine them into one file
  • Minify the single combined file
  • Write the output to disk

What you do not want to do, is to run Babel before the output has been combined into one file. If you follow this pattern:

  • Gather the source js files
  • Transpile them using Babel
  • Combine them into one file
  • Minify the single combined file
  • Write the output to disk

You will end up with some serious bloat! In every file that you have used the object spread operator in, Babel will inject a locally scoped _objectSpread function. So you could easily end up with multiple copies of the exact same polyfill function.

This is the web, and we don't like unnecessary bloat - so the correct workflow for your front end build is:

  • Gather the source js files
  • Combine them into one file
  • Transpile them using babel
  • Minify the single combined file
  • Write the output to disk

This way, you'll only get polyfill functions injected once into your outputted JS, even if the polyfill is used multiple times.