Using webpack 2 and NPM to bundle various resources of a web application part 5: adding babel.js for ES5 transformation

Introduction

In the previous post we first briefly discussed the output that webpack generates in the bundle.js file. Simply put, webpack extracts the dependency graph from the various JS input files and organises the elements into functions that in turn are placed into an array. The functions in the array are invoked in a sequence that reflects the dependency graph. Then we also built a minimal HTML page to execute the bundle in various browsers. Our ES6 code was successfully executed in all of them except for IE11 which is obviously too old to correctly interpret the new ES6 features. We would see the same in older versions of Chrome and FF as well.

In this post we’ll explore another great feature of webpack, namely the ease at which we can add plug-ins to the bundling process. We’ll specifically add babel.js to transform ES6 code to ES5. The point here is that JS developers want to be able to use the latest and greatest of ES6 and other future versions of JavaScript without having to worry about JS support across various browsers.

Adding more ES6 features

Let’s add some more ES6 language features to our index.js and see how well the most current web browsers, mentioned in the previous post, have implemented them:

import greeting from './utils'

let myGreeting = greeting('John', 'Smith')
console.log(myGreeting)

//Testing the map array helper
console.log('-----------------')
console.log('Map array helper:')
console.log('-----------------')
const numbers = [1, 2, 3, 4, 5, 6]

const squares = numbers.map(function(number) {
 return number * number
});

console.log(`Squares with map function: ${squares}`)

//Testing an ES6 class
console.log('--------')
console.log('Classes:')
console.log('--------')
class Animal {
    constructor(options) {
      this.name = options.name
      this.age = options.age
    }
     
    makeSound() {
      return 'WHOOAAA'
    }
     
    move() {
      console.log('I\'m moving forward')
    }
}

let myAnimal = new Animal({name: 'Simba', age: 10})
let sound = myAnimal.makeSound()
myAnimal.move()
myAnimal.makeSound()

//Destructuring
console.log('--------------')
console.log('Destructuring:')
console.log('--------------')
let customer = {
    name: 'Great Customer Inc',
    address: 'Los Angeles'
}

let {name, address} = customer
console.log(name)
console.log(address)

//Rest and spread operator
console.log('-------------------------')
console.log('Rest and spread operator:')
console.log('-------------------------')
let strictOopLanguages = ['C#', 'Java', 'C++']
let looseOopLanguages = ['Python', 'Ruby']
let funcLanguages = ['F#', 'Scala', 'Erlang']
let frontEndLanguages = ['JavaScript', 'HTML', 'CSS']
let allLanguages = [...strictOopLanguages, ...looseOopLanguages, ...funcLanguages, ...frontEndLanguages]
for (let lang of allLanguages) {
    console.log(lang)
}

//Generator functions
console.log('--------------------')
console.log('Generator functions:')
console.log('--------------------')
function* programmingLanguages() {
    yield 'C#'
    yield 'Java'
    yield 'JavaScript'
    yield 'F#'
}

for (let lang of programmingLanguages()) {
    console.log(lang)
}

//Promises
console.log('---------')
console.log('Promises:')
console.log('---------')
function executePromise() {
    let promise = new Promise((resolve, reject) => {
      setTimeout(() => {
        let success = {'message': 'all good'}
        resolve(success)
      }, 5000)
    })
    promise.then(response => console.log(response.message))
      .catch(e => console.log(e.exception))
}
executePromise()

That’s a collection of ES6 features including the following:

  • The map array helper
  • Classes
  • Destructuring
  • Rest and spread operator
  • Generator functions
  • Promises

If you don’t know these features then consult this page where you can find links to examples and explanations.

Then open a command prompt, navigate to the project root and run the following command to generate the bundle.js file:

npm run build

…and then open index.html in a web browser with Development Tools open (F12). If you have the latest of Chrome, FF and Edge then you’ll see that all of them managed to run our code. Here’s the console output in Chrome 60:

Chrome output of ES6 features in webpack NPM project

That’s very positive but we cannot assume that all clients have updated their browsers. Also, later on when ES7, ES8 etc. features come along then browser manufacturers will always require some time to implement them in their product. Also, JS developers want to be able to use new features of the language without worrying much about browser support. This is where babel.js enters the scene.

Configuring Babel

Babel.js is a JS compiler that basically transforms “new” JS into “old” JS. Babel has a number of NPM packages to make it work with webpack. We’ll need to install 3 of them to be exact. Run the following NPM command in the command prompt:

npm install –save-dev babel-loader babel-core babel-preset-env

The loader package ensures that Babel can work with webpack. It is a so-called module loader in webpack. A module loader is a pre-processor that is applied on all or some subset of the source files in the webpack project. Pre-processors are executed before webpack creates the bundle. Make sure you understand the distinction between these pre-processing libraries and webpack itself. We can instruct webpack to execute a pre-processor but it’s not webpack’s responsibility to make them work properly. It is up to the pre-processor to execute its work. When it’s done then webpack takes over and finishes its own main task. Babel-core is a JS parser and the preset package contains the rules how to transform ES6 specific features to ES5 equivalent code.

The terminology is a bit confusing because a module loader is called a rule in webpack 2 and rules make part of the webpack module system. We can also tell webpack which files to apply the rule on. This is called a test. E.g. we want the Babel rule to be applied on JS files only and not e.g. CSS files. That wouldn’t make any sense.

Rules – or module loaders – make webpack even more exciting than what it is. They show the pluggable nature of webpack where we can add any number of modules to perform some action on the source files. So by default webpack is “only” responsible for creating the JS bundle but it can be extended with rules.

Here’s the extended webpack.config.js with the new section called “module”:

const path = require('path')

const config = {
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'build'),
        filename: 'bundle.js'
    },
    module: {
        rules: [
            { test: /\.js$/, exclude: /node_modules/, use: 'babel-loader' }
        ]
    }
}

module.exports = config

“module” stands for the module system of webpack mentioned above. Then comes the array of rules with a single entry with 3 properties. We declare the loader to be the Babel loader. We want to apply it to all files ending with .js which is declared through a regular expression of the test property. Finally we want the files in the node_modules folder to be excluded.

We’re not done yet. We have to tell the Babel loader to apply the babel-preset-env transpiler on each input file. We don’t do that in the webpack config file though. Recall that it’s not webpack that performs the JS code transformation but Babel. As soon as webpack calls upon the Babel rule we’re on Babel’s turf, not webpack’s. At this moment the Babel loader will need to know what we want it to do and we can achieve it through Babel’s own configuration file called .babelrc :

Add Babel configuration file to webpack NPM project visual studio code

The Babel loader will be looking for this file. Add the following JSON object to .babelrc:

{
    "presets": ["env"]
}

We tell Babel to use the preset called “env” which stands for the babel-preset-env processor.

All right, let’s see if this thing works. Run the usual npm command again…

npm run build

Run index.html in your web browser and… I don’t know about you but I got an exception:

Error received after Babel code transformation

The exception is complaining about the following line of code:

var _marked = /*#__PURE__*/regeneratorRuntime.mark(programmingLanguages);

…where “regeneratorRuntime” is not defined. This is a case of where webpack and the various loaders are sort of out of sync and we need to find a solution. This thread on StackOverflow gives the necessary hints. First we need to install the babel-polyfill processor as well:

npm install –save-dev babel-polyfill

…and add it to the first position of the entry point array in webpack.config.js:

const config = {
    entry: ['babel-polyfill', './src/index.js'],
    //rest of the document ignored
}

Build the project with npm run build again, test it in Chrome and it should work again.

The code also works in IE11:

Babel generated code works in internet explorer 11 in NPM demo project

Babel stages

Another important component related to Babel is called a stage. This website comes with a good overview of Babel including stages:

JavaScript has some proposals for new features that are not yet finalized. They are separated into 5 stages (0 to 4). As proposals gain more traction and are more likely to be accepted into the standard they proceed through the various stages, finally being accepted into the standard at stage 4. There is no babel-preset-stage-4 as it’s simply babel-preset-es2015.

These stages imply that if a new JS feature comes along that has not yet been fully accepted developers can still use it and have Babel transpile it into ES5. The requirement is to install an extra package that corresponds to the stage where the feature is located. Here’s how stage 2 would be installed:

npm install –save-dev babel-preset-stage-2

…and then we can tell Babel to apply the stage 2 level code additions as follows in .babelrc:

{
    "presets": ["env", "stage-2"]
}

View all posts related to JavaScript here.

Advertisements

About Andras Nemes
I'm a .NET/Java developer living and working in Stockholm, Sweden.

2 Responses to Using webpack 2 and NPM to bundle various resources of a web application part 5: adding babel.js for ES5 transformation

  1. luckyure says:

    Awesome explanation!! Thank you.

  2. JeffE says:

    This tutorial was helpful to me. Thank you for the careful step-by-step explanations.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

ultimatemindsettoday

A great WordPress.com site

Elliot Balynn's Blog

A directory of wonderful thoughts

Robin Sedlaczek's Blog

Developer on Microsoft Technologies

HarsH ReaLiTy

A Good Blog is Hard to Find

Softwarearchitektur in der Praxis

Wissenswertes zu Webentwicklung, Domain-Driven Design und Microservices

the software architecture

thoughts, ideas, diagrams,enterprise code, design pattern , solution designs

Technology Talks

on Microsoft technologies, Web, Android and others

Software Engineering

Web development

Disparate Opinions

Various tidbits

chsakell's Blog

Anything around ASP.NET MVC,WEB API, WCF, Entity Framework & AngularJS

Cyber Matters

Bite-size insight on Cyber Security for the not too technical.

Guru N Guns's

OneSolution To dOTnET.

Johnny Zraiby

Measuring programming progress by lines of code is like measuring aircraft building progress by weight.

%d bloggers like this: