Using ES6 and ES7 in the Browser, with Babel 6 and Webpack

This guide is part of The Complete Guide to ES6 with Babel 6 series. If you’re having trouble upgrading to Babel 6, start with Six Things You Need To Know About Babel 6.

The Babel CLI is great for compiling ES6 to ES5 on a file-by-file basis. However, when Babel encounters an import statement, it outputs a require call – which won’t get you very far in the browser.

To make our Babel output browser friendly, we’ll need to bundle it. My favourite tool for this is Webpack, and as it happens, Webpack has great Babel support through babel-loader

Installing packages

Before we start — if you’re currently using Babel 5, first remove its packages from your project’s package.json file and then npm uninstall them — babel, babel-core, babel-loader, etc.

Build-time packages

Once you’re ready, start by installing the babel-core and babel-loader packages:

npm install babel-core babel-loader --save-dev

Next, you’ll need to install any presets and plugins you need. Start with babel-preset-es2015 – Babel’s collection of ES6 transforms. If you’re using JSX, you’ll also want babel-preset-react. And if you want to play with fire, you can add babel-preset-stage-0 for access to ES7 decorators, async/await, etc.

# For ES6/ES2015 support
npm install babel-preset-es2015 --save-dev

# If you want to use JSX
npm install babel-preset-react --save-dev

# If you want to use experimental ES7 features
npm install babel-preset-stage-0 --save-dev

Runtime support

Babel can’t support all of ES6 with compilation alone — it also requires some runtime support. In particular, the new ES6 built-ins like Set, Map and Promise must be polyfilled, and Babel’s generator implementation also uses a number of runtime helpers. Given your app doesn’t have to share a JavaScript environment with other apps, you’ll be ok to use babel-polyfill to handle this:

npm install babel-polyfill --save

Babel also bakes a number of smaller helpers directly into your compiled code. This is OK for single files, but when bundling with Webpack, repeated code will result in a heavier file size. It is possible to replace these helpers with calls to the babel-runtime package by adding the transform-runtime plugin:

npm install babel-runtime --save
npm install babel-plugin-transform-runtime --save-dev

Configuring babel-loader

This guide assumes you know the basics of Webpack. To get up to scratch, read Webpack Made Simple: Building ES6 & LESS with autorefresh.

With Webpack, running your JavaScript and JSX through Babel is a simple as adding a loaders entry for babel-loader to your webpack.config.js:

module: {
  loaders: [
    {
      loader: "babel-loader",

      // Skip any files outside of your project's `src` directory
      include: [
        path.resolve(__dirname, "src"),
      ],

      // Only run `.js` and `.jsx` files through Babel
      test: /\.jsx?$/,

      // Options to configure babel with
      query: {
        plugins: ['transform-runtime'],
        presets: ['es2015', 'stage-0', 'react'],
      }
    },
  ]
}

In the above configuration, I’ve told Webpack to only apply Babel to files in my src directory. This speeds up the build by making sure that Babel isn’t applied to your entire node_modules directory – after all, NPM modules should be published as ES5. You could achieve the same effect with exclude:

exclude: [
  path.resolve(__dirname, "node_modules"),
],

The query object contains the options passed to Babel. I’ve assumed that you’ve installed every package from the previous section. If you don’t want to extract Babel’s runtime helpers or don’t want React or experimental ES7 support, just leave out the appropriate string from query.

Hint: .babelrc

Note that you could omit the presets option passed to Webpack’s query, and instead add a .babelrc file:

{
  "presets": ["es2015", "react"]
}

However, my recommendation is to keep the options to your build process in the build process configuration. It isn’t unheard of to want different configuration for tests, task runners, etc.

Hint: Babel 5

In Babel 5, the above query object would have been equivalent to the following:

query: {
  stage: 0,
  externalHelpers: true,
},

The difference is that with Babel 5, you didn’t need to install packages for each of these options.

Entry point scripts

As discussed above, Babel requires some helper code to be run before your application. To achieve this, add the polyfill to the entry section of your webpack.config.js:

entry: [
  // Set up an ES6-ish environment
  'babel-polyfill',

  // Add your application's scripts below
  './src/main',
],
Hint: Babel 5

In Babel 5, you needed two entry point scripts. Both were available in the babel-core package:

'babel-core/external-helpers',
'babel-core/polyfill',

Examples

For a simple example of a Webpack-bundled app configured with Babel 6, see webpack-black-triangle.

For a more full-featured example of a React-based app bundled with Webpack, see the Unicorn Standard Starter Kit.

More ways to Babel

Now that you’ve got your app building with ES6, why not test it with ES6 too? And write your libraries with it. And write your tasks with it…

Keeping up to date

So you’ve made Babel 6 work. But the JavaScript world moves fast — how long will it stay working?

If trying to keep up feel overwhelming, I’d love to help! Just subscribe to my free Newsletter to receive news and guides on the most important tools for people making small apps with React. And in return for your e-mail, you’ll immediately receive three bonus print-optimised PDF cheatsheets – on React (see preview), ES6 and JavaScript promises. All for free!

I will send you useful articles, cheatsheets and code.

I won't send you useless inbox filler. No spam, ever.
Unsubscribe at any time.

One more thing – I love hearing your questions, offers and opinions. If you have something to say, leave a comment or send me an e-mail at james@jamesknelson.com. I’m looking forward to hearing from you!

Read More

26 Comments Using ES6 and ES7 in the Browser, with Babel 6 and Webpack

  1. Frank

    I didn’t see ‘babel-runtime’ in your entry for your ‘unicorn-standard/starter-kit’ project.

    wondering why not?

    Reply
    1. James K Nelson

      Thanks for mentioning this.

      So it turns out that you need to have babel-runtime installed in node_modules, but don’t need it in entry. I’ll update the guide to mention this.

      Reply
  2. AJ

    Following the steps I made a async/await function in my entry file. But it pops out with this error:
    “`
    ERROR in ./~/babel-runtime/regenerator/runtime.js
    Module not found: Error: Cannot resolve module ‘babel-runtime/helpers/typeof’ in /Users/AJ/Desktop/WebLab/node_modules/babel-runtime/regenerator
    @ ./~/babel-runtime/regenerator/runtime.js 15:15-54
    “`
    Some help?

    Reply
    1. James K Nelson

      This seems to be a problem with the versions of Babel which have been released since I wrote the article (a whopping 3 days ago).

      If you *need* async, you’re going to have a bit of trouble at the moment, because it doesn’t play nice with the latest version’s babel-plugin-transform-runtime, *or* with the register script. Unfortunately, the best course of action is probably to just wait for the Babel team to fix this.

      Reply
        1. Jonathan

          Same, I figured I’d give babel 6 a few months to iron out the bugs but it’s still the most broken npm release I’ve ever seen.

          Reply
  3. Paddy

    Thanks James for this excellent guide 🙂

    I’ve temporarily had to disable transform-runtime due to https://github.com/babel/babel/issues/2954

    I’m then having an issue with IE8 which can’t handle the compiled code’s use of the word ‘default’

    e.g. in the line:

    function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

    Would this be resolved by having transform-runtime? Or is IE8 not supported? (Googling suggests it is supported, but I can’t find a solution to the issue I’m experiencing).

    Reply
    1. James K Nelson

      I doubt it would be solved by transform-runtime, somehow. But I’ve not had the misfortune of needing to target IE8, so I can’t help you, sorry.

      Reply
  4. Eli

    Hello James,

    I’ve followed the entire tutorial and still when I try to use decorators like this:

    @connect((state) => {
    return {

    };
    })
    export default class Movie extends Component {

    I get the following error in the terminal:
    “Decorators are not supported yet in 6.x pending proposal update”

    Thank you upfront.

    Reply
    1. James K Nelson

      That’s bizarre. I’m using decorators in a number of places, so I doubt they’re plain not supported. Possibly they’re not supported in combination with something else. Perhaps try looking in Babel’s GitHub issues?

      Reply
      1. Eli

        Already went through Babel’s GitHub issues, no solution has yet fixed this.

        Nevertheless, thank you for publishing great tutorials.

        Reply
      2. Lonelind

        Have the same issue with decorators, but I use ‘stage-0’ and have used it before I decided to update my project deps.

        Reply
      3. Kiki

        Hey! First, thank you for your guide.

        I’ve followed it and I get the following error in the terminal too:
        “Decorators are not supported yet in 6.x pending proposal update”

        You said “I’m using decorators in a number of places”.

        How do you use decorators? For me it’s like that with Redux and React:

        @connect(
        state => ({
        foo: state.foo,
        })
        )
        class Indicator extends Component {

        }

        Thanks for your help.

        Reply
      4. James K Nelson

        Turns out I the decorators of mine which were working were in Babel 5 projects. An upgrade caused them to fail.

        Babel 6 doesn’t support decorators yet. For more information, see this issue on the Babel 6 tracker

        Reply
  5. Jovica Aleksic

    It seems that babel has internal issues with handling decorators – which does worry me personally – and it seems we must stick with 5.8 until babel 6 supports decorators.

    Reply
  6. Rafael

    Great writing.

    I think you should add to your list:
    1) Babel6 changes how export default xxx works. There was a hack in the babel5 that is removed in babel6 chat causes cold that was doing require(‘function_exported_as_default’)() to not work anymore. (let me know if you need more details on that).
    2) that decorators were dropped in babel6, so e anyone using is out of luck with babel6 for now. (I’m working in a plugin to port decorators form 5 to 6, but in the mean time there is nothing.

    Reply
    1. Dtothefp

      Rafael I’d love more info on the exports default issue and how you’ve dealt with it. I’ve been using https://www.npmjs.com/package/babel-plugin-add-module-exports for my build code but unfortunately for my client code I test with karma and require plugin and these two plugins seem to conflict. Looks like I may have to go through all my client code and change syntax.

      On another note generators have really strange behavior when using const and they will yell at you if you don’t. I added to phabricator issue here so please add on if you experience it as well https://phabricator.babeljs.io/T2843#68845

      This whole Babel 6 upgrade has been an incredible nightmare:-((

      Reply
  7. Apurva B

    I am getting following error when I do npm run build

    >npm run build

    > @0.0.1 build
    > babel-node tools/run build

    build
    [16:15:30] Starting ‘undefined’…
    TypeError: object is not a function
    at _callee$ (run.js:8:9)
    at tryCatch ({path}\node_modules\babel-regenerator-runti
    me\runtime.js:61:40)
    at GeneratorFunctionPrototype.invoke [as _invoke] ({path}\node_modules\babel-regenerator-runtime\runtime.js:329:22)
    at GeneratorFunctionPrototype.prototype.(anonymous function) [as next] ({path}\node_modules\babel-regenerator-runtime\runtime.js:94:21)
    at step ({path}\tools\run.js:7:191)
    at {path}\tools\run.js:7:423
    at new Promise ({path}\node_modules\core-js\modules\es6.
    promise.js:197:7)
    at {path}\tools\run.js:7:99
    at run (run.js:5:19)
    at Object. (run.js:18:3)

    Reply
  8. loop

    hi,James
    when i used babel-runtime to replace the helpers in my code like _extends ,_interopRequireDefault(obj) eg,but it does not work.what’s the reason?

    this babel version i used:
    “`
    “babel-core”: “^6.10.4”,
    “babel-eslint”: “^6.0.0”,
    “babel-loader”: “^6.2.4”,
    “babel-polyfill”: “^6.9.1”,
    “babel-preset-es2015”: “^6.9.0”,
    “babel-runtime”: “^6.6.1”,
    “`

    can you help me?thx

    Reply

Leave a Reply

Your email address will not be published.