Chapter 8. Build Automation

We saw how to create an application from the ground up using tests with Jasmine. However, as the application grows and the number of files starts to increase, managing the dependencies between them can become a little difficult.

For instance, we have a dependency between the Investment and Stock models, and they must be loaded in a proper order to work. So, we do what we can; we order the loading of the scripts so that Stock is available once Investment is loaded. Here's how we do it:

<script type="text/javascript" src="src/Stock.js"></"script>
<script type="text/javascript" src="src/Investment.js"></"script>

However, that can soon become cumbersome and unmanageable.

Another problem is the number of requests the application uses to load all of its files; it can get up to hundreds once the application starts to grow.

So, we have a paradox here; although it is good to break it up into small modules for code maintainability, it is bad for the client performance, where a single file is much more desirable.

A perfect world would be to match the following two requirements at the same time:

  • In development, we have a bunch of small files containing different modules
  • In production, we have a single file with the content of all those modules

Clearly, what we need is some sort of build process. There are many different ways to achieve these goals with JavaScript, but we are going to focus on webpack.

Module bundler – webpack

Webpack is a module bundler created by Tobias Koppers to help create big and modular frontend JavaScript applications.

Its main difference from other solutions is its support for any type of module system (AMD and CommonJS), languages (CoffeeScript, TypeScript, and JSX) and even assets (images and templates) through loaders.

You read it right, even images; if in a React application, everything is a component, in a webpack project, everything is a module.

It builds a dependency graph of all your assets, serving them in a development environment and optimizing them for production.

Module definition

JavaScript is a language based on the ECMA Script specification that, until version 6, still didn't have a standard definition of a module. This lack of formal standards led to a number of competing community standards (AMD and CommonJS) and implementations (RequireJS and browserify).

Now, there is a standard to follow, but unfortunately there is no support for it in modern browsers, so which style should we use to write our modules?

The good news is that it is possible to use ES6 today through transpilers, which gives us a future-proof advantage.

A popular transpiler is Babel (http://babeljs.io/), which we are going to use with webpack through a loader.

We'll see how to use it with webpack in a moment, but first it is important to understand what makes an ES6 module. Here is a simple definition without any dependency:

function MyModule () {};
export default MyModule;

Let's compare it to the way we've been declaring modules until now. The next example shows how that code would be if written using the conventions presented in Chapter 3, Testing Frontend Code:

(function () {
  function MyModule() {};
  this.MyModule = MyModule;
}());

The biggest difference is the lack of an IIFE. An ES6 module, by default, has a scope of its own, so it is impossible to pollute the global namespace by accident.

The second difference is that the module value is no longer being attached to the global object, but instead being exported as the default module value:

function MyModule () {};
export default MyModule;

Regarding a module's dependencies, up until now, everything was globally available, so we passed the dependencies to the module as parameters to the IIFE, as shown here:

(function ($) {
  function MyModule() {};
  this.MyModule = MyModule;
}(jQuery));

However, as you start using ES6 modules on the project, there will be no more global variables. So, how do you get those dependencies into the module?

If you remember from before, the ES6 example was exporting the module value through the export default syntax. So, given a module has a value, all we have to do is ask for it as a dependency. Let's add the jQuery dependency to our ES6 module:

import $ from 'jQuery';
function MyModule () {};
export default MyModule;

Here, $ represents the name of the variable the dependency will be loaded into, and jQuery is the filename.

It is also possible to export multiple values as the result of a module and import these values into different variables, but for the scope of this book, default values will suffice.

The ES6 standard introduces a number of different constructs to the JavaScript language that are also beyond the scope of this book. For more information, check Babel's excellent documentation at http://babeljs.io/docs/learn-es6/.

Webpack project setup

Webpack is available as an NPM package, and its setup is very simple as it is going to be demonstrated in the next sections.

Tip

It is important to understand the difference between NPM and Node.js. NPM is both a package manager and a package format, while Node.js is a platform that NPM modules usually run.

Managing dependencies with NPM

We already got an embryo of a Node.js project, but as we are going to start using more dependencies throughout this chapter, we are going to need a formal definition of all the NPM packages that the project depends on.

To define the project as an NPM package, and at the same time all of its dependencies, we need to create a special file called package.json at the root folder of the application. It can be easily created through a single command:

npm init

It will prompt for a number of questions about the project that can all be left with their default values. In the end, you should have a file with content similar to the following output depending on your folder name:

{
  "name": "jasmine-testing-project",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts":" {
    "test": "echo "Error: no test specified" && exit 1"
  },
  "author": "",
  "license": "ISC"
}
  Is this ok? (Yes)

The next step is to install all of our dependencies, which, at the moment, is only express.

npm install --save express

The previous command will not only install express as described in Chapter 4, Asynchronous Testing – AJAX, but will also add it as a dependency to the package.json file. On running the npm init command as done previously, we get the following output showing the dependencies attribute:

{
  "name": "jasmine-testing-project",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo "Error: no test specified" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.12.0"
  }
}

Now that we understand how to manage the dependencies of our project, we can install webpack and Babel as development dependencies to start bundling our modules, as follows:

npm install --save-dev babel-loader webpack webpack-dev-server

The final step is to add a script in package.json to start the development server:

"scripts": {
  "test": "echo "Error: no test specified" && exit 1",
  "dev": "webpack-dev-server"
}

This allows us to start the development server with a simple command:

npm run dev

The actual location of the webpack-dev-server executable is in the ./node_modules/.bin folder. So, npm run dev is the same as:

./node_modules/.bin/webpack-dev-server

It works because when you run npm run <scriptName>, NPM adds the ./node_modules/.bin folder to the path.

Webpack configuration

Next, we need to configure webpack so that it knows what files to bundle. This can be achieved by creating a webpack.config.js file at the root folder of the project. Its content should be:

module.exports = {
  context: __dirname,
  entry: {
    spec: [
      './spec/StockSpec.js',
      './spec/InvestmentSpec.js',
      './spec/components/NewInvestmentSpec.jsx',
      './spec/components/InvestmentListItemSpec.jsx',
      './spec/components/InvestmentListSpec.jsx'
    ]
  },

  output: {
    filename: '[name].js'
  },

  module: {
    loaders: [
      {
        test: /(.js)|(.jsx)$/,
        exclude: /node_modules/,
        loader: 'babel-loader'
      }
    ]
  }
};

There are a few key points about this configuration file:

  • The context directive tells webpack to look for modules in __dirname, meaning the project's root folder.
  • The entry directive specifies the application's entry points. Since we are only doing testing at the moment, there is a single entry point named spec that refers to all of our spec files.
  • The output.filename directive is here to inform the filename of each of the entry points. The [name] pattern will be replaced by an entry point name on compilation. So spec.js will actually contain all of our spec code.
  • The module.loaders final directive tells webpack how to deal with different file types. We are using the babel-loader parameter here to add support for ES6 modules and the JSX syntax on our source files. The exclude directive is important so as not to waste compiling any dependency from the node_modules folder.

With this setup completed, you can start the development server and check what the transpiled bundle looks like at http://localhost:8080/spec.js (the filename defined in the configuration file).

At this point, the webpack configuration is complete, and we can move to adapt the Jasmine runner to run the specs.

The spec runner

As stated previously, we are using webpack to compile and bundle the source files, so the Jasmine spec is about to become a lot simpler:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Jasmine Spec Runner v2.1.3</title>

  <link rel="shortcut icon" type="image/png" href="lib/jasmine-2.1.3/jasmine_favicon.png">
  <link rel="stylesheet" href="lib/jasmine-2.1.3/jasmine.css">

  <script src="lib/jasmine-2.1.3/jasmine.js"></script>
  <script src="lib/jasmine-2.1.3/jasmine-html.js"></script>
  <script src="lib/jasmine-2.1.3/boot.js"></script>

  <script src="lib/jquery.js"></script>
  <script src="lib/jasmine-jquery.js"></script>

  <script src="lib/mock-ajax.js"></script>

  <script src="spec/SpecHelper.js"></script>

  <script src="spec.js"></script>
</head>
<body>
</body>
</html>

There are a few takeaways:

First, we no longer need the JSX transformer hack explained in the previous chapter; the transformation is now done by webpack and the babel-loader. As a result, we can use the default Jasmine boot just fine.

Second, we've chosen to leave the test runner dependencies as global (Jasmine, Mock Ajax, Jasmine JQuery, and the Spec helper). Leaving them global makes things a lot simpler for our test runner, and we don't hurt our code as far as modularity is concerned.

At this moment, trying to run the tests at http://localhost:8080/SpecRunner.html should produce a lot of fails due to missing references. That is because we still need to convert our specs and sources into ES6 modules.

Testing a module

To be able to run all the tests requires that all the source and spec files be converted into ES6 modules. At the specs, it means adding, as dependencies all the source modules:

import Stock from '../src/Stock';
describe("Stock", function() {
  // the original spec code
});

At the source files, it means declaring all the dependencies as well as exporting its default value, as follows:

import React from 'react';
var InvestmentListItem = React.createClass({
  // original code
});
export default InvestmentListItem;

Once all the code is converted, the tests should work upon starting the development server and pointing the browser once again to the runner URL.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset