Processing with Babel

From the blog:
isomorphic-webpack - Universal module consumption using webpack - Interview with Gajus Kuizinas

Webpack processes ES6 module definitions by default and transforms them into code looks roughly like this:

build/app.js

webpackJsonp([1],{

/***/ 19:
/***/ function(module, exports, __webpack_require__) {

"use strict";
/* harmony default export */ exports["a"] = function () {
  const element = document.createElement('h1');

  element.className = 'pure-button';
  element.innerHTML = 'Hello world';
  element.onclick = () => {
    __webpack_require__.e/* import() */(0).then(__webpack_require__.bind(null, 36)).then((lazy) => {
      element.textContent = lazy.default;
    }).catch((err) => {
      console.error(err);
    });
  };

  return element;
};

...

It is important to note that it does not transform ES6 specific syntax, such as (lazy) => { in the example, to ES5! This can be a problem especially on older browsers. It is also problematic if you minify your code through UglifyJS as it doesn't support ES6 syntax yet and will raise an error when it encounters the syntax it doesn't understand.

One way to work around this problem is to process the code through Babel, a popular JavaScript compiler that supports ES6 features and more. It resembles ESLint in that it is built on top of presets and plugins. Presets are collections of plugins and you can define your own as well.

Given sometimes extending existing presets might not be enough, modify-babel-preset allows you to go a step further and configure the base preset in a more flexible way.

Using Babel with Webpack#

Even though Babel can be used standalone, as you can see in the Authoring Packages chapter, you can hook it up with webpack as well. During development, we actually might skip processing.

This is a good option especially if you don't rely on any custom language features and work using a modern browser. Processing through Babel becomes almost a necessity when you compile your code for production, though.

You can use Babel with webpack through babel-loader. It can pick up project level Babel configuration or you can configure it at the loader itself.

Connecting Babel with a project allows you to process webpack configuration through Babel if you name your webpack configuration following webpack.config.babel.js convention. This works with other solutions too as it relies on a package known as interpret.

Babel isn't the only option although it is the most popular one. Bublé by Rich Harris is another commpiler worth checking out. There's experimental buble-loader that allows you to use it with webpack. Bublé doesn't support ES6 modules, but that's not a problem as webpack provides that functionality.

Setting Up babel-loader#

The first step towards configuring Babel to work with webpack is to set up babel-loader. It will take our code and turn it into a format older browsers can understand. Install babel-loader and include its peer dependency babel-core:

npm i babel-loader babel-core --save-dev

As usual, let's define a part for Babel:

Here's the full loader configuration:

webpack.parts.js

...

exports.loadJavaScript = function(paths) {
  return {
    module: {
      rules: [
        {
          test: /\.js$/,
          include: paths,

          loader: 'babel-loader',
          options: {
            // Enable caching for improved performance during
            // development.
            // It uses default OS directory by default. If you need
            // something more custom, pass a path to it.
            // I.e., { cacheDirectory: '<path>' }
            cacheDirectory: true
          }
        }
      ]
    }
  };
};

Next, we need to connect this with the main configuration. I'll process the code through Babel only during production although it would be possible to do it both for development and production usage.

In addition, I'll constrain webpack to process only our application code through Babel as I don't want it to process files from node_modules for example. This helps with performance and it is a good practice with JavaScript files.

webpack.config.js

...

module.exports = function(env) {
  if (env === 'production') {
    return merge(
      common,
parts.loadJavaScript(PATHS.app),
parts.extractBundles([ { name: 'vendor', entries: ['react'] } ]), ... ); } ... };

Even though we have Babel installed and set up, we are still missing one bit - Babel configuration. I prefer to handle it using a dotfile known as .babelrc as then other tooling can pick it up as well.

There are times when caching Babel compilation can surprise you. This can happen particularly if your dependencies change in a way that babel-loader default caching mechanism doesn't notice. Override cacheIdentifier with a string that has been derived based on data that should invalidate the cache for better control. This is where Node.js crypto API and especially its MD5 related functions can come in handy.

Setting Up .babelrc#

At minimum you will need a package known as babel-preset-es2015. Given our project uses dynamic imports and the feature isn't in the standard yet, we need a specific plugin known as babel-plugin-syntax-dynamic-import for that. Install them:

npm i babel-plugin-syntax-dynamic-import babel-preset-es2015 --save-dev

To make Babel aware of them, we need to write a .babelrc. Given webpack supports ES6 modules out of the box, we can tell Babel to skip processing them:

.babelrc

{
  "plugins": ["syntax-dynamic-import"],
  "presets": [
    [
      "es2015",
      {
        "modules": false
      }
    ]
  ]
}

If you execute npm run build now and examine app.js, you should see something a little different:

webpackJsonp([1],{

/***/ 19:
/***/ function(module, exports, __webpack_require__) {

"use strict";
/* harmony default export */ exports["a"] = function () {
  var element = document.createElement('h1');

  element.className = 'pure-button';
  element.innerHTML = 'Hello world';
  element.onclick = function () {
    __webpack_require__.e/* import() */(0).then(__webpack_require__.bind(null, 36)).then(function (lazy) {
      element.textContent = lazy.default;
    }).catch(function (err) {
      console.error(err);
    });
  };

  return element;
};

...

Note especially how the function was transformed. This code should work in older browsers now. It would be also possible to push it through UglifyJS without any errors due to parsing.

There are other possible .babelrc options beyond the ones covered here.
Just like ESLint, Babel supports JSON5 as its configuration format. This means you can include comments in your source, use single quoted strings, and so on.
Sometimes you might want to use experimental features. Although you can find a lot of them within so called stage presets, I recommend enabling them one by one and even organizing them to a preset of their own unless you are working on a throwaway project. If you expect your project to live a long time, it's better to document the features you are using well.

Polyfilling Features#

Given it's not always enough to transform ES6 code to older format and expect it to work, polyfilling may be needed. The simplest way to solve this problem is to include babel-polyfill to your project. A simple way to achieve that in webpack is to either include it to an entry (app: ['babel-polyfill', PATHS.app]) or import 'babel-polyfill' from code to get it bundled.

Especially in bundle size sensitive environments babel-polyfill might not be the best option. If you know well which environment (browser versions, Node.js) you support, babel-preset-env provides a more granular way to achieve the same result with smaller size.

It is important to note that babel-polyfill pollutes the global scope with objects like Promise. Given this can be problematic for library authors, there's an option known as transform-runtime. It can be enabled as a Babel plugin and it will avoid the problem of globals by rewriting the code in such way that they won't be needed.

Useful Babel Presets and Plugins#

Perhaps the greatest thing about Babel is that it's possible to extend with presets and plugins. I've listed a few interesting ones below:

Conclusion#

Babel has become an indispensable tool for many developers given it bridges the standard with older browsers. Even if you targeted modern browsers, transforming through Babel may be a necessity if you use UglifyJS.

Previous chapterCode Splitting
Next chapterCleaning the Build

This book is available through Leanpub. By purchasing the book you support the development of further content. A part of profit (~30%) goes to Tobias Koppers, the author of Webpack.

Need help?