Now that we have clean code via a linter and can run our scripts courtesy of npm, let's work on the compilation / build of our code. In order to achieve this we are going to use two tools: Webpack and Babel.

Webpack

Webpack is an amazing tool that came out two or so years ago that captivated particularly the React crowd quick due to its ease and interesting features. We are going to only scratch the service of this powerful tool; you could give a whole workshop just on Webpack. We are going to lean on two of Webpack's core features: module compilation and the ability to plug in loaders.

As you saw with our code, it is pretty easy to put one or two components in one file. But what happens when you have an app as complex at Netflix that has hundreds, if not thousands, of components? Having just one file is untenable. You could just split them into separate files, make sure to load them in correct order, and keep a list of files in your HTML directory. But that sucks too, so we're going to introduce a build step that, while build steps suck, this will make things a bit easier.

For fun, split your MyTitle component from ClientApp.js into a new file, MyTitle.js. You will have to put the appropriate React.DOM helper methods in each file. At the bottom of MyTitle.js, add the line export default MyTitle;. At the top of ClientApp.js, put the line import MyTitle from './MyTitle'. Let's try to compile that.

You should have Webpack 2.3+ installed. Go to the directory of the project in the terminal . After that, run webpack js/ClientApp.js public/bundle.js. In your index.html, change the line of <&NegativeMediumSpace;script src="js/ClientApp.js"></script> to <&NegativeMediumSpace;script src="/public/bundle.js"></script>.

Try your browser again. bundle.js has all the stuff for both files now. Now we can keep components in their own files which is a huge win for organization. But wait, we can use Webpack for even greater code by using it to bring in node modules. In index.html, remove the other script tags so just bundle.js is left.

In ClientApp.js, remove the global comment at the top and add at the top

import React from 'react';
import { render } from ('react-dom');

// change render
render(React.createElement(MyFirstComponent), document.getElementById('app'));

In MyTitle, add:

// remove global comment, add at top
import React from 'react';

// add to bottom
export default MyTitle;

Run your Webpack command again. Try your browser again. Despite only including bundle.js, the whole app works! If you look at Webpack, you'll see it's 99% React code and some of yours. Now we can go forth making new files and including new libraries without worrying about if they are being included!

Let's add build to our npm scripts. Add to scripts in package.json "build": "Webpack js/ClientApp.js public/bundle.js",. Run yarn build from your terminal in the project root directory and see if it works. Magic!

We're also using ES6 modules here, if you're not familiar with that syntax. The import React from 'react' is semantically identical to var React = require('react'). The import { render } from 'react-dom' as far as you're concerned here is identical to var render = require('react-dom').render. For further reading, checkout 2ality's post on it.

Babel

Babel is an amazing, amazing tool that has fundamentally altered the landscape of JavaScript as we know. Though they didn't invent the idea of transpiling future syntax of JavaScript into current syntax (thanks Traceur,) they did make it so damn easy. Couple that with the fact it worked with React's JSX (which we'll talk about in a sec) and it was a perfect storm the be massively successful. Babel, in short, is amazing and should be something you consider for every project.

Babel 6 (the latest revision of Babel) took away a tiny bit of that ease of use but in return it became much easier to develop which is important when you're maintaining a massive project that's very important. In return it requires a bit of config now.

Create a new file called .babelrc. Inside .babelrc, put:

{
  "presets": [
    "react",
    ["env", {
      "targets": {
        "browsers": "last 2 versions"
      },
      "loose": true,
      "modules": false
    }]
  ]
}

Babel has the concept of plugins. Each transformation comes in the form a plugin. However ES6 and React each have a number of transformation and it's a huge pain to include each one. Thus Babel has the concept of presets, or in other words bundles of plugins. In this case, we're including all of the React and many ES6 transformations. If this were a production app we would definitely go through and only include the transformations we needed and not all of them. For example, this includes a fairly large library to get ES6 generators working. Few people these days are actually using generators and thus it's better to leave them off if you don't need them. In our case, this is a teaching app so page weight isn't a big deal to us. This will also allow us to start using JSX.

However, we've taken this a step further by using [babel-preset-env][bpe]. babel-preset-env let's you specify your target (in our case we've targeted the last two major versions of each browser) and it only includes the necessary plugins necessary. What's amazing about this is that as we go forward and browsers can support more and more, we can start shipping ES6+ code to browsers! Amazing. It updates itself.

Also, we're telling Babel to not transform modules. New to Webpack 2 is the ability to do tree shaking. Tree shaking (also known as live code inclusion, as opposed to dead code elimination) is where you start with the entry point to your program (usually an index.js) and begin working outwards, only including the parts of code your code uses. Usually this doesn't buy you much in terms of your code; we tend to not write much dead code for our own apps. However it's extremely helpful for modules you're including. If you use lodash for one function, you don't want to include the whole library. Instead, you want your bundler to just include that barebones of what you need for to work. This is what Webpack 2 and Uglify buy for us; Webpack 2 bundles and Uglify takes the dead code paths out.

Webpack loaders

So, we could use the Babel CLI to compile our code but we are already using Webpack and Webpack has a good way to send your code through Babel via its loader mechanism. Loaders are utilities that Webpack will pipe input into and accept output out of. You can use loaders to transpile things like CoffeeScript, TypeScript, or PureScript. Webpack loaders can do some other powerful things like allowing you to include CSS, inline images via encoding, and transform SVGs. We're just going to be using the JS transformation abilities for now. Run the command webpack --module-bind 'js=babel-loader' js/ClientApp.js public/bundle.js. Should take a bit longer since it's doing more. Since we're going to be using Webpack for a few other things, let's abstract that configuration from inline to a config file. Add a webpack.config.js with the following.

const path = require('path');

module.exports = {
  context: __dirname,
  entry: './js/ClientApp.js',
  devtool: 'source-map',
  output: {
    path: path.join(__dirname, 'public'),
    filename: 'bundle.js'
  },
  resolve: {
    extensions: ['.js', '.jsx', '.json']
  },
  stats: {
    colors: true,
    reasons: true,
    chunks: false
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        loader: 'babel-loader'
      }
    ]
  }
};

What you see is essentially a config version of what we were doing with CLI flags. Now it's a bit more resilient and guaranteed consistent. If you run just the command webpack from your project directory you should get the same output. Neat. Go change you npm script to just be "webpack" now. Go try it and make sure it still works. Great! Good? Good. This should get us to a point now where we can talk about JSX.

At this point we need to make sure Prettier and ESLint ignore our compiled files: we don't care if those files are well formatted or linted. Create a new file called .eslintignore and put this in there:

node_modules/
public/

So let's also make the scripts remember build. Add this to your package.json.

"build": "webpack"

Okay! Let's keep going!