Pixel Pixel use Cookies.

We use cookies to enable essential services and functionality and to collect data on how visitors interact with our site. By clicking Accept Cookies, you agree to our use of all relevant cookies.

Certain cookies are unfortunately fundamental to the operation of the site and are thus required - you can find out more by viewing our Cookie Policy.

You can manage your preferences at any time via our Cookie Preferences manager.


Manage Preferences

Manage Cookie Preferences for Pixel Pixel

You can find out more about by checking out our Cookie Policy

  • Essential

    Unfortunately certain cookies are essential to the operation of our site and are a condition of its use.

  • Performance

    Collects anonymous data about how visitors use our site and how it performs.

  • Support

    These cookies assist us in providing a great service every time. They may include CRM or feedback tools.

Get in touch
Featured image for Compiling ES6 v2 - this time with NPM

Compiling ES6 v2 - this time with NPM

Posted on 27th July 2018 at 8:00am by Chris Snowden in Code, Tutorials, Web

We recently discussed how to use Gulp and the gulp-babel plugin to compile your ES6 javascript into browser-friendly code. It seemed to strike a chord with developers wanting to use ES6 while keeping their tooling simple, but what if you don't use Gulp and don't fancy adding an extra step to your workflow? Good news - you don't have to!

Enter NPM and the Babel CLI

If you are comfortable with using a task runner like Gulp then chances are you're quite happy tooling around with NPM scripts as well. Even if you don't consider yourself an expert, you probably use them for kicking off development scripts or test packages, and if you use React/Vue etc. scaffolding tools then you definitely use a script to kick off the inner Webpack workings. 

Getting NPM set up to run the Babel CLI is pretty simple, but I've also included an example package.json which has all the relevant code, so feel free to ignore all the words and just copy-paste that :)

Recap on ES6 - why?

Here's what we said last time...

"ES6/ES2015 is the current hotness of ECMAScript (Javascript), and adds some new and juicy functionality for us to play with. This is what the big boys in the industry all use, but sadly for the rest of us, browser support isn't quite there yet ( IE >:[ ). This is where Babel comes in. Babel is a Javascript compiler which takes ES6/ES7 and beyond and converts them to Javascript which we can safely use in production, even for those pesky legacy browsers."

Sounds good to me!


Using Babel-CLI with NPM

First things first, we need to get set up and install the relevant packages. 

  1. Check that you have NPM installed and create a project

    npm init
  2. Install the packages we need, saving them to our package.json 

    npm install --save babel-cli
    npm install --save babel-preset-env
    npm install --save babel-core
  3. Configure our babel preset

    Presets are how Babel knows which new features to handle for you. There are a few popular presets which are frequently used - 'ES2015' and 'react' are two of the biggest, but Babel have recently moved away from using iterative, defined presets in favour of using the 'babel-preset-env' preset. 

    The env preset is great because it automatically fetches all the latest and greatest features, but it also allows for a deep level of configuration via your config file. Use of env is being pushed by Babel as they begin to phase out the older presets - ES2015 is now deprecated on the plugin page with this message: "This is deprecated. If you want to stay up to date, use the env preset".

    To use the preset, we need to add it to our config file. We are adding our Babel config directly into our package.json, but you could also do this via babel.rc or using Webpack.
    Here, we are also telling Babel to target support for the last 5 browser versions and not to bother with anything less then IE9. You can get the full list of available options from browserlist.

     "babel": {
        "presets": [
          [
            "env",
            {
              "targets": {
                "browsers": [
                  "last 5 versions",
                  "not ie <= 8"
                ]
              }
            }
          ]
        ]
     }
  4. Add a script command to run Babel

    Add the following script command to your package.json:

    "babel": "babel dev-javascript --watch --out-file compiled-javascript/app.js --source-maps inline"

    Let's break this down a bit:

    • The 'babel' command triggers the Babel CLI.
    • The next part tells the CLI which folder to compile javascript from, and using the --watch flag will keep the process running and update whenever a change is seen within that folder.
    • Next, we specify a file or folder to dump the complied javascript in. You can use either --out-file or --out-dir for compiling everything into a specific file or moving to a new folder.
    • We also have the option of using source maps. Using the --source-maps flag will create a source map file in the directory, and adding the inline flag will create source maps inline within the destination file.
  5. Run it!

    npm run babel
    Note: you can also use trigger the CLI via the terminal instead. Check out the docs.


Done! 

You should now see a shiny new file in the compiled-javascript folder, full of nicely compiled javascript ready for use :) 

But we can do better. 

Stay with me and we will cover adding plugins and polyfills plus creating a separate production build script.


Filling in the gaps

While using the env preset will handle most of the new syntax features from ES6/ES7, Babel sadly can't handle certain features which do not have corresponding ES5 counterparts, or which are in the proposal or early-adoption stage. This is usually because the new feature makes use of new primitive properties which simply didn't exist when IE9 etc. were released.

Two good examples of this are new primitive methods like Array.from() and new additions like rest-spread. For example, this function would error if we tried to use it without a plugin as it contains both rest arguments and Array.from().

function allToPower(power,...params) {
    return Array.from(params).map((a) => Math.pow(a,power) );
}

But, with the addition of plugins, this becomes usable in an older browser (if a bit less readable...)

function allToPower(power) {
    for (var _len =
arguments.length, params = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
        params[_key - 1= arguments[_key];
    }
    return (0, _from2.default)(params).map(function (a) {
        return Math.pow(a, power);
    });
}

If we want to use these features in our code then we'll need to add some plugins. We have two options here, transform plugins and polyfills/runtime plugins.


Adding transform plugins:

As an example, we are going to add the 'transform-object-rest-spread' plugin to allow us to use rest-spread properties like in the example above.

  1. Install the plugin.

    npm install --save babel-plugin-transform-object-rest-spread
  2. Update the Babel config.

    Add the desired plugins to the config at the same level as the 'presets' option, under the name 'plugins'. See the full example below for proper placement if uncertain.

    "plugins": [
      "transform-object-rest-spread"
    ]

Adding polyfill/runtime plugins:

You have multiple options here, specifically babel-polyfill vs babel-runtime. These two approaches achieve basically the same result with a couple of key differences, so which you choose comes down to the type of application you are building and to personal preference. 

The main difference between the two is that babel-polyfill adds missing methods etc. to prototypes in the global scope as well as working with instance methods, whereas babel-runtime is self-contained and does not affect global prototypes. This means that runtime unfortunately lacks certain methods such as Array.includes(), forcing you to avoid it or to use a specific polyfill. It is however also slightly lighter than babel-polyfill, and does not require you to import anything within your actual code.

I'm going to stick with babel-runtime because I like the drop in-drop out nature of the plugin implementation and because I like that it does not pollute the global scope. 

  1. Install the babel-runtime plugin and transform-runtime plugin.

    npm install --save babel-runtime
    npm install --save babel-plugin-transform-runtime
  2. Update the CLI command in the build script.

    All we need to do here is add an extra command to our build script:

    --plugins transform-runtime
    e.g.
    "babel": "babel dev-javascript --watch --out-file compiled-javascript/app.js --source-maps inline --plugins transform-runtime"


Note: If you want to use the Polyfill the approach is similar, but you must remember to import the polyfill into your code using import or require e.g. import "babel-polyfill";


Minify/Production build:

We're pretty much done! The last (optional) step is to improve our tooling so that we can have different outputs for dev work and production code. For production, I would like to drop the source maps option and minify the code.

  1. Install the babel-preset-minify package.

    npm install --save babel-preset-minify
  2. Add a 'production' option in our babel config.

    Add a new 'env' option to the config - this reads Babel environment variables and applies presets etc. if a certain environment variable is set. We want any presets within here to only be applied as part of our build script if the BABEL_ENV env variable is set to 'production'. Add this at the same level as the 'presets' and 'plugins' options, under the name 'plugins'. See the full example below for proper placement if uncertain.

    "env": {
      "production": {
        "presets": [
          "minify"
        ]
      }
    }
  3. Split our build script into dev and production scripts.

    We want to change our current 'babel' script and add another called 'babel-production'. We are removing the --source-maps and --watch flags from our production script, and adding a new command to set the Babel env variable - BABEL_ENV=production.

    "babel": "babel dev-javascript --watch --out-file compiled-javascript/app.js --source-maps inline --plugins transform-runtime"
    "babel-production": "BABEL_ENV=production babel dev-javascript --out-file compiled-javascript/app.min.js --plugins transform-runtime"
  4. Try it! 

    Try running both scripts. You should get two outputs - one un-minified and with source-maps included, the other optimised for production.


That's all there is to it!

Phew, you've now learnt two different methods of compiling ES6 with Babel - gulp-babel and babel-cli/npm, so there is really no excuse now for not using ES6 in your daily javascript workflow! 

Get practising and then try out some of the more advanced configuration options with Babel. There are a multitude of options available but they can tend to be quite obtuse, so check out the official docs and then probably fall back to Stack Overflow ;)

Get in contact if you get stuck or need any other web development advice.


Extra credit

It's possible to implement Babel in a wide range of ways, including NPM and Gulp as you've just seen, as well as through more complicated bundlers like Webpack and Parcel. However, there are also implementations which completely avoid tooling!

Babel Standalone and Polyfill.io are two options which allow you to compile your code on the fly by including a script in your html. Not so great performance wise because the compiling is obviously being achieved client-side, but definitely worth checking out and could be useful for some edge cases or unusual projects where something like Webpack might not appropriate.


Example package.json

This package.json is set up for using the babel-runtime and the rest-spread plugin. It also has separate dev and production script commands with different outputs. If you don't want or need these then you can remove most of the dependencies included. You just need babel-cli + babel-preset-env !

{
  "scripts": {
    "babel": "babel dev-javascript --watch --out-file compiled-javascript/app.js --source-maps inline --plugins transform-runtime",
    "babel-production": "BABEL_ENV=production babel dev-javascript --out-file compiled-javascript/app.min.js --plugins transform-runtime"
  },
  "dependencies": {
    "babel-cli""^6.26.0",
    "babel-core": "^6.26.3",
    "babel-plugin-transform-object-rest-spread""^6.26.0",
    "babel-plugin-transform-runtime": "^6.23.0",
    "babel-preset-env": "^1.7.0",
    "babel-runtime""^6.26.0",
    "babel-preset-minify: "^0.4.3"
  },
  "babel": {
    "presets": [
      [
        "env",
        {
          "targets": {
            "browsers": [
              "last 5 versions",
              "not ie <= 8"
            ]
          }
        }
      ]
    ],
    "env": {
      "production": {
        "presets": ["minify"]
      }
    },
    "plugins": [
      "transform-object-rest-spread"
    ]
  }
}

Reference


We love what we do! You will too. Speak to us