A Changing of the Guard in Web Technology (ES6 For The Win Part 2)

This post is the followup of the introduction on Javascript programming with ES6 (ES2015) - Part 1.

First Things First

At MYOB we started writing Javascript with the ES6 (ES2015) syntax a while ago and choose to go with Babel to transform it back to old Javascript. Babel comes as a command line tool or as a plugin for all the various task runners and so you have to choose which way you are going to go. At the time we had an elaborate Grunt pipeline and so our first foray into ES6 territory was using the grunt-babel plugin. However, Grunt showed its limits to us, mainly in term of build performance (we were doing more than transpiling), and after evaluating alternatives from Gulp to plain command line and everything in-between we found a diamond in the rough: Webpack.

In this post we will see how to setup Webpack to use Babel and will build a simple React + Flux page using some of the new features of Javascript like classes with the very latest React syntax (as of version 0.13.x).

If you want to learn more about React, Flux and Webpack, stay tuned as the next articles will get into the meat of those libraries and tools we use at MYOB to create our new generation of web based products.

Introducing The Stars of the Show

Let's start with a short introduction of Webpack, React and Flux.

  • Webpack is part of this new generation of packager / module loader for your Javascript project. It is one of the fastest rising solutions alongside jspm, and helps you manage your project dependencies as well as replacing most of Grunt in building your assets into a deployable product. It borrows many ideas from Browserify but does more out the box in my opinion. In our present case, it not only works with AMD and CommonJS modules but also with ES6 imports which is what we want.
  • React probably rings a bell. Latest trend figures among developers show it is poised to overtake Angular in popularity in the coming year. It is very different from the aforementioned framework however as it is a library with a very small API surface targeting the view layer only and is now famous for introducing the idea of using a virtual DOM to speed up rendering.
  • Flux is the natural companion of React. Both have been imagined and implemented by the folks at Facebook and Instagram. In MVC parlance, Flux role is to provide the Model and Controller layers for your application. However Flux uses terms such as stores, actions, plus a dispatcher. Flux promotes a uni-directional flow for your data, i.e., one-way databinding with your view. This contrasts to Angular which by default has two-way databinding. We could write a whole post about the Flux architecture, so we will let the code speak for itself today.

Getting The Basics In Place

First we need a folder structure and a build pipeline for our project. As we are going to use Webpack, we will need to install it and then create a webpack.config.js file that describes our application and how it should get built.

We are using here Node's package manager NPM getting our dependencies and installing them, so we will also create a package.json file.

Many projects we develop at MYOB rely on NPM not just for bringing in third party dependencies, but also internal dependencies, such as other Javascript projects that get published to our private NPM registry.

The structure

We will create and put our code in a src folder organised like so:

src/ +  
     | actions/
     | components/
     | stores/
     index.html
     module.js

When writing Flux applications it is a common choice to separate the different layers into different directories. The entry point to our application will be module.js and index.html will be a simple HTML page using the components we will build.

The stores/ folder contains our model and stores our data. The components/ folder contain the React components responsible for interacting with this data and the actions/ folder will contain the actions your component can perform to mutate the data in the store.
Last but not least module.js will bootstrap our application, what Webpack calls the entry point.

Getting The Dependencies Right

Before we can start writing and testing our code, we need to install Webpack, Babel, React and everything else. The best option for this is to use our good old NPM friend. So we create a package.json file at the root of our project as follows:

{
  "name": "webpack-example",
  "scripts": {
    "build": "webpack --progress --profile --colors",
    "watch": "webpack-dev-server --hot --inline --progress --colors",
    "test": "echo \"No unit tests and e2e tests yet\" && exit 1"
  },
  "license": "MIT",
  "dependencies": {
    "alt": "0.17.1",
    "babel-runtime": "5.6.18",
    "react": "0.13.3",
    "react-router": "0.13.3"
  },
  "devDependencies": {
    "babel-core": "5.6.18",
    "babel-loader": "5.3.1",
    "html-webpack-plugin": "1.6.0",
    "webpack": "1.10.1",
    "webpack-dev-server": "1.10.1"
  }
}

Make sure you have NodeJS and NPM installed or you will not be able to use this package.json file (note: NodeJS should install NPM as well). When you have this file ready, go ahead and run npm install in your project root folder. It will fetch the dependencies we need for development and what Webpack needs to package the project.

This package.json file is made of 3 important parts:

  • dependencies contain the list of the libraries we rely on for the project to run. You will find React, React-router, Alt.js (Flux) and the Babel runtime. The runtime provides polyfills and utility functions for the ES6 and ES7 features your browser might not yet support. When code is transpiled, Babel injects those into your generated code so it can run on everyday browsers.
  • devDependencies contain the libraries used to build the project, in particular Webpack and Babel. html-webpack-plugin is a handy Webpack plugin that automatically generates the import scripts we need in our HTML page.
  • scripts define a few commands we can run from the command line with NPM to build and test our project.
Now For Some Skeletal Code

The first piece of code we need is a simple HTML page, just to provide a container for our example. Something like this:

<!DOCTYPE html>  
<html>  
  <head>
    <title>Webpack example</title>
  </head>
  <body>
    <div id="content"></div>
  </body>
</html>  

This is the bare minimum you need. We will render our component inside of the content div.

Next, we need our Javascript entry point module.js to bootstrap our application. This is now finally our opportunity to use some of those ES6 features we have been talking about:

import React from 'react';

React.render(  
  <h1>Example</h1>,
  document.getElementById('content')
);

You will notice in the above that we seem to embed HTML in our code: <h1>Example</h1>. What is this trickery? Well this is JSX syntax and Babel recognises JSX syntax in your code and translates it into React function calls. JSX makes it easier to work with components when you are used to HTML. However JSX is not HTML and the features and syntax can differ.

The interesting bit here for us is import React from 'react';. Welcome to the future! This is how Javascript modules are imported in the ES6+ world, like require() is in the current NodeJS universe (unless you use ES6+ as well for the backend ;-)).

If you are with me so far you have your package.json at hand, installed your NPM dependencies, a minimal index.html file and the above cute little Javascript file in module.js.

Its now time to bring it all together.

Enter Webpack

Webpack is going to be our Swiss Army knife of sorts. It will be in charge of transpiling our code using Babel, building our project and even providing us with an incredibly fast and powerful hot reloading development server to test our application.

Webpack is driven from the config file webpack.config.js which lives in the root folder:

'use strict';

var webpack = require('webpack'),  
  HtmlWebpackPlugin = require('html-webpack-plugin'),
  path = require('path'),
  srcPath = path.join(__dirname, 'src');

module.exports = {  
  target: 'web',
  cache: true,
  entry: {
    module: path.join(srcPath, 'module.js'),
    common: ['react', 'react-router', 'alt']
  },
  resolve: {
    root: srcPath,
    extensions: ['', '.js'],
    modulesDirectories: ['node_modules', 'src']
  },
  output: {
    path: path.join(__dirname, 'tmp'),
    publicPath: '',
    filename: '[name].js',
    library: ['Example', '[name]'],
    pathInfo: true
  },

  module: {
    loaders: [
      {test: /\.js?$/, exclude: /node_modules/, loader: 'babel?cacheDirectory'}
    ]
  },
  plugins: [
    new webpack.optimize.CommonsChunkPlugin('common', 'common.js'),
    new HtmlWebpackPlugin({
      inject: true,
      template: 'src/index.html'
    }),
    new webpack.NoErrorsPlugin()
  ],

  debug: true,
  devtool: 'eval-cheap-module-source-map',
  devServer: {
    contentBase: './tmp',
    historyApiFallback: true
  }
};

The file above does a bit more than the basics but there are some extra goodies in there for you. Here's what it all means:

  • The declarations at the top are importing Webpack and html-webpack-plugin. It also stores the absolute path to our src folder for reuse.
  • Next we specify that our build target is for the web and that we want to leverage the cache.
  • entry is the most important and we will be a bit clever here and define two entry points because Webpack is capable of working with multiple: module will point to our module.js file and common will bundle together our React, React-router and Alt.js dependencies.
  • resolve allows us to define the places where the code we use resides, namely our src and node_modules folders. Setting up the moduleDirectories is a nifty trick to help Webpack locate your modules using imports no matter where you are in the code. For example import components/example instead of something like ../../components/example. extensions lists file types that can have optional extensions in your imports; instead of import components/example.js you can then write import components/example.
  • output is the second most important section after entry. path maps to the path in which you want to generate the project. filename is the target filename; as we have 2 entry points [name] acts as a placeholder and Webpack will replace it with either module or common. library can help you define some namespacing if you export this project and the other fields are not critical at this point.
  • module is where most of the magic happen and is the land of Webpack loaders. Every time you require/import an asset, the loaders will pick them up and if needed will apply some transformation. In our case we define that all .js files have to be transformed by Babel using babel-loader (-loader part optional). Since transformations are costly we make sure to exclude node_nodules from it.
  • plugins are the other side of the magic. You will find on the Webpack website that there are tons of plugins out there to choose from. In this instance we use just a few. CommonsChunkPlugin is specifically for bundling our separate dependencies together in one common bundle. HtmlWebpackPlugin will take our index.html as a template and automatically inject the scripts tags to load our common.js and module.js bundles. NoErrorsPlugin is a little plugin used when running webpack-dev-server just to make sure it doesn't refresh your browser if your latest change throws an error when rebuilding.
  • During development we want debug information and devtool will use the best (and super fast) Webpack sourcemap generator.
  • Last but not least, devServer provides some configuration options to our webpack-dev-server to use for realtime testing. contentBase will serve the resources from our target tmp folder and historyApiFallback allows us to use page routing in our application without those ugly hash characters in the URL.

So what now ? How do I run my app ?

Dependencies - check
Webpack config file - check
src/index.html - check
src/module.js - check

Congratulations, you are now ready to use the scripts we defined in our package.json file:

  • To only build your project: npm run build - check your tmp/ folder, you should find common.js, module.js and index.html.
  • To build and run webpack-dev-server: npm run watch - open http://localhost:8080 and voila ! Your React application is up and running. Try to make a change to the module.js file and enjoy the watching and hot-reloading features of Webpack. It will quickly rebuild, creating a diff and a patch of your changes and refresh your browser.

And now we can sprinkle some real code

We have a very basic React example in place. What if we take this opportunity to write a bit more code and get some more interesting ES6 features in there ?

First let's see how routing works

React is lucky to have a great community and a great ecosystem. One natural companion of your React projects is React-router. With it you can manage your routes in your application with an easy to use declarative syntax and concepts borrowed from the best router of all; the Ember router.

In our src folder, we are going to create a new file called routes.js alongside the existing index.html and module.js files.

import React from 'react';  
import {Route} from 'react-router';

import Main from 'components/main';  
import Example from 'components/example';

const routes = (  
  <Route handler={Main}>
    <Route name='example' handler={Example}/>
  </Route>
);

export default routes;  

In its pure glory, this file introduces us a few more ES6 features.

We start by importing React, but in react-router we only want to import the Route object. The import {Route} from 'react-router' syntax basically says to only import one of the exported objects/functions of the module, namely Router.

At the end of the file the export default routes is how you can define what gets exported from your module. In our case we are going to export the routes we constructed so they can be consumed elsewhere. The default keyword means that if you import that module without specifying anything in brackets (like we did for react-router), what you get by default will be that routes object.

Let's skim quickly over the 2 imports that will get the 2 React components we are going to write, and let's look at how we define routing.

First you will notice we seem to embed HTML in the code. It is actually not, but JSX syntax. JSX is the declarative alternative offered by React to build your components.
Second, you have this new ES6 keyword: const which you can imagine declare the following as a constant, meaning an error will trigger if you try to change its content afterwards.
Finally, the routing itself simply declares a shell component called Main with a child route represented by the component Example. Let me stress out here that contrary to the traditional concept of template pages found in most frameworks, in React, a page is in fact a component itself, and components can be composed of myriad of child components.

So we have defined routes, but we don't have created those components yet !

Time to write proper components

We need 2 for our example. The first one below will be the shell component Main that we will store in components/main.js.

import React from 'react';  
import {RouteHandler, Link} from 'react-router';

class Main extends React.Component {  
  render() {
    return (
      <div>
        <h1>Example</h1>
        <Link to='example'>Go to the Example page...</Link>
        <RouteHandler/>
      </div>
    );
  }
}

export default Main;  

Here we go ! Our first ES6 class, using the recommended new React syntax to declare your components. Since React v0.13.0, you can create a component by inheriting from the React.Component base class. Is it not that beautiful ?

The minimum we need to make this component useful is to define a render() function. This function written again using JSX, will display a title and a link to our example page (or component). The <RouteHandler/> tag determine where the example component will be loaded in the page. Of course you noticed in our imports that we specifically asked for two objects: RouteHandler and Link that we use here.

Pretty simple. Is there more ?

Brace yourself for a more exciting component with a lot more awesomeness: Example. We will also store it in components/example.js.

import React from 'react';  
import connectToStores from 'alt/utils/connectToStores';  
import DummyStore from 'stores/dummyStore';  
import DummyActions from 'actions/dummyActions';

@connectToStores
class Example extends React.Component {  
  constructor(props) {
    super(props);
    this.state = {
      name: props.name
    }
  }

  static getStores(props) {
    return [DummyStore];
  }

  static getPropsFromStores(props) {
    return DummyStore.getState();
  }

  render() {
    return (
      <div>
        <input type="text" value={this.state.name} onChange={this.onChange}/>
        <h1>It works: {this.props.name}</h1>
      </div>
    );
  }

  onChange = evt => {
    this.setState({name: evt.target.value});
    DummyActions.updateName(evt.target.value);
  }
}

export default Example;  

We have a good example here of a Flux connected component that uses Alt.js, imports a store and actions and plug everything together.

We also have here an example of the other very popular ES6 feature besides classes that everyone is raving about, arrow functions (usually called lambdas elsewhere).

Let's proceed from top to bottom:

  • We import a Flux store we call DummyStore and actions we call DummyActions (more on this later). The only thing that matters is for the store to have a name property. We also use a nifty feature of Alt.js called connectToStores. This is a higher order function (the new big thing replacing mixins) that we will use to augment our component with store support plumbing.
  • connectToStores can be used as a function wrapper you call on your class or you can use another fantastic ES6 (ES7 actually) feature called decorators (like in Python). To decorate our Example component with store capabilities, we just add @connectToStores before the class declaration. Please note that like in many other languages, you can use decorators on class, functions and so on... This allows elegant and flexible composition strategies instead of abusing inheritance
  • Using @connectToStores means we need to override two functions (ES6 static functions): getStores() and getPropsFromStores(). The first one is to define the store(s) we are going to use, in that case DummyStore. The second one returns the current state of your store(s) properties. As a full introduction on Flux and Alt.js is out of scope here, you can learn more about how Alt.js makes your Flux life easier on their documentation website but I recommend looking at up to date documentation on their Github README.
  • Since we now use ES6 classes, we now have access to a constructor function. This is the right place to declare the state objects you need and seed them with the equivalent store properties. State objects retain a local copy of your store properties and are mutable. Any change in data in the component will happen on state objects not on the original properties.
  • So to propagate changes to data from a component back to a store you use actions. The onChange() member function role is exactly that one in this example. It will read the changed value from the React event object and update the state object, as well as calling the updateName() function from DummyActions to also notify the store(s) of the change - behind the scenes, the change goes through a Dispatcher, a fundamental piece of the flux architecture.

You might be surprised by how this function is declared, because it is an arrow function. Not only does the function keyword disappear but the important piece here is that arrow functions always inherit the context of their enclosing object (this is like having .bind(this) appended to your function call). Why is that useful here? Because we want to use the function in the rendered JSX content. Look at render(). We can write onChange={this.onChange} and be sure it will run our function in the right context.

More information about this and more tips from the Facebook people in this post.

Bringing It All Together

We now need to change our entry point file module.js so it renders our shell components, our routes and does any initialisation that we might need to do.

// Bootstrapping module
import React from 'react';  
import Router from 'react-router';  
import routes from 'routes';

Router.run(routes, Router.HistoryLocation, (Root, state) => {  
  React.render(<Root {...state}/>, document.getElementById('content'));
});

Here we import react, react-router and our routes; that you remember we exported as a constant.

Bootstrapping is as simple as calling router.run() and passing our routes to that function. Remember we have that <div> called "content" in index.html, so we just locate the DOM node and there will go our Root component.

Notice that {...state} code? Thats another great ES6 time saver. It means unpack the state object properties into arguments applied to that component. The ... is called the spread operator.

You might have noticed so far that we used Alt.js features in components but we never really instantiated anything. Well this is a good point, and time has come to create a new file to do that as we will need the store and actions to work together. Let's call it control.js.

import Alt from 'alt';

const flux = new Alt();  
export default flux;  

That module simply instantiate a new Alt object we call flux, a constant we use as a singleton in this project. Please note that Alt.js allows you to create several instances of Alt if you want to - useful for isomorphic apps for example, but this would be the purpose of a whole new article. Flux reference implementation from Facebook works with singletons, but Alt.js doesn't force you to do so.

Armed with our flux instance we can now create the store and actions that deal with our name property.

First our actions, actions/dummyActions.js:

import flux from 'control';  
import {createActions} from 'alt/utils/decorators';

@createActions(flux)
class DummyActions {  
  constructor() {
    this.generateActions('updateName');
  }
}

export default DummyActions;  

An actions class is a simple ES6 class where you declare the actions you want to be able to perform. We are showing here another condensed version as Alt.js give us great boilerplate killers:

  • generateActions() is a great shortcut to create an action that simply takes a property value as a parameter and dispatches it to the store so it is notified of that properties new value.
  • createActions is a higher order function we use again in its decorator flavour to turn our POJC (Plain Old Javascript Class) into an actions class living under our flux instance of Alt.js.

Next our store, stores/dummyStore.js:

import flux from 'control';  
import {createStore, bind} from 'alt/utils/decorators';  
import actions from 'actions/dummyActions';

@createStore(flux)
class DummyStore {  
  name = 'awesome';

  @bind(actions.updateName)
  updateName(name) {
    this.name = name;
  }
}

export default DummyStore;  

The store is also a simple class that gets decorated. It imports the action we just declared in dummyActions. By binding our member function updateName to the updateName function from our actions class the value that we get from the action replaces the value of our name property. Again we used the decorator version of bind for elegance and less boilerplate. An alternative would be to write equivalent code in the class constructor.

The last ES6 (ES7 again actually) feature we will use today is represented in that simple statement: name = 'awesome'. ES6 doesn't let you declare member variable like this and you will find numerous ES6 example declaring variables inside the constructor instead like so:

class dummyStore {  
  constructor() {
    this.name = 'awesome';
  }
}
Are we there yet ?

I think that's enough for now! If you run npm run watch you will be able to see our simple project and see how changing the value in the input box changes the property we display below it.

In this article we showed how one could use Webpack to build and run an ES6 project transpiled by Babel. We introduced a simple React application for the occasion with React router routes and using an implementation of Flux called Alt.js.

The project created for this article is available here on Github. Feel free to fork it.

In the next article of the series we will start exploring a more advanced use of React and Flux.

Stay tuned for part 3 of the series. Watch this space for the link.

Cover image by Chris Williams [Public domain], via Wikimedia Commons (modified)