Boilerplate: An Empty Javascript Project Structure with Gulp+Traceur+Karma

This post describes what I learned about setting up a typical Javascript application in nowadays, including project structure, tools, dependency management, test automation.

Different people would have different preferences. Hopefully my experience can inspire others.

1. Purpose of Using Boilerplate

One of the key benefits of this setup is to help easily structure the code in terms of extendable application, manage dependency (loading required libraries), run application as well as test cases, and eventually facilitate the final distribution and deployment process.

By suing such boilerplate, you should focus more on the coding itself, leaving all other stuff handled by the underlying tools and pre-defined flow.

2. Tools

In order to help mange the code structure, dependency, as well as the flow, we need tools & libraries.

Here is the list of tools & libraries I’m using for building up this boilerplate.

ToolsAnnotationAlternatives
Node.jsNode.js is the server-side runtime environment for JavaScript based on the V8 engine by Google. Normally, its package manager, NPM, will be used to mange all “development” related dependency, like Gulp and its plugins, Karma and its tools, etc.
BowerBower is another package and dependency manager mainly for front-end JavaScript libraries like AngularJS, jQuery, etc. Normally, Bower is used to mange all the libraries that will be used in the final production env.NPM
GulpGulp is an intuitive, code-over-configuration, streaming build system. Gulp uses Node.js streams, making it faster to build as it doesn’t need to write temporary files/folders to disk.Grunt or NPM
KarmaKarma is a JavaScript test-runner built with Node.js, and mainly meant for unit testing.QUnit
JasmineJasmine is a Behavior-Driven JavaScript library that helps write the test case. Then Karma runs these test cases.Mocha or QUnit
TraceurTraceur is a JavaScript.next-to-JavaScript-of-today compiler that allows you to use features from the future today.Babel

Later, I will add tools for doing “End-to-end” (E2E) testing.

2.1 Dev Machine Environment Setup

Among those tools / libraries, Node.js and Bower should be installed under machine environment. Once installed, all other projects should benefit from it.

2.1.1 Node.js & NPM

Go to the download page and follow the instruction based on different OS platforms.

If you are running Mac OS, you can try brew to install Node.js. It is much more convenient.

  • Run ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" in a Terminal to install brew.
  • Then run brew install node to install Node.js as well as NPM.

2.1.2 Bower

Use NPM to install Bower:

npm install -g bower

3. The Basic Structure

The directory structure of the project start something like this:

project
|
+-- bower_components        // The packages got by Bower
|   |
|
+-- node_modules            // The packages got by NPM
|   |
|
+-- src                     // The source code of your application
|   |
|
+-- test                    // All test cases
|   |
|   +-- test-main.js        // The "main" JavaScript file used by RequireJS
|
+-- dist                    // The "Distinction" folder contains the shippable code
|   |
|
+-- .gitignore              // The "ignore" file used by Git
+-- .npmignore              // The "ignore" file used by NPM
+-- package.json            // The "config" file of NPM
+-- bower.json              // The "config" file of Bower
+-- gulpfile.js             // The "config" file of Gulp
+-- karma.conf.js           // The "config" file of Karma
+-- README.md               // The default README file (in markdown)

3.1 package.json

The first file you need to edit is package.json to define the the local dependencies of the project, and call script to invoke Bower to load all front-end packages.

The file is “JSON” based and should be like:

{
  "name": "gulp-traceur-karma-boilerplate",
  "version": "0.0.1",
  "description": "An Boilerplate project built with Gulp, Traceur and Karma.",
  "readme": "README.md",
  "repository": {
    "type": "git",
    "url": "git@git.example.com:example.git"
  },  
   "dependencies": {
  },
  "devDependencies": {
    "gulp": "^3.8.6",  
    "karma": "~0.12.0",
    "karma-jasmine": "^0.2.2",
    "karma-chrome-launcher": "^0.1.3",
    "karma-requirejs": "~0.2.2",
    "karma-traceur-preprocessor": "~0.4.0"
  },  
  "scripts": {
    "postinstall": "bower install"
  }
}

Several annotations:

  • The “dependencies” section is empty since all “production” related packages will be loaded by Bower.
  • The “postinstall” script (bower install) under scripts section is to get all Bower packages after NPM packages are set up.
  • Traceur can also be load through Gulp (by gulp-traceur plugin). When using it in the way, it normally utilizes gulp-watch to watch the file change and re-run all Gulp flow to do transpilation. However, since Karma will do file watching while working with Gulp or Grunt, we have to use Traceur upon Karma. This is why karma-traceur-preprocessor and karma-requirejs are used (Requirehttp://requirejs.org/JS is used by karma-traceur-preprocessor plugin).
    > Check here for more details about how to line Karma up with RequireJS.
  • The plugin karma-chrome-launcher is the “browser launcher” used to launch specific browser. You can find other choices from here.

You can check the documentation to learn more about the sections of package.json.
Besides, an interactive guide is also helpful.

3.2 bower.json

Similar to package.json file (also in JSON format), bower.json is used to define all packages that Bower needs to load.

{
  "name": "es6-adventure",
  "version": "0.0.1",
  "dependencies": {
  },
  "analytics": false
}

Under “dependencies” section, you can add packages that the project needs, like angular, bootstrap, etc.

You can check the documentation for more details about the sections.

After setting up package.json and bower.json, you can now run the following command in the console to get all packages (including NPM and Bower) to your project folder.

npm install

it will automatically generate the folders bower_components and node_modules to place these packages.

Be sure to add these two folders into .gitignore so that they will not be included into the code repo.

3.3 karma.conf.js

The configuration file of Karma should be like this:

module.exports = function(config) {

    config.set({

        // base path that will be used to resolve all patterns (eg. files, exclude)
        basePath: '',

        // frameworks to use
        // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
        frameworks: ['jasmine', 'requirejs', 'traceur'],

        files: [

            {pattern: 'test/**/*.spec.es6', included: false},

            'test/test-main.js'
        ],

        // list of files to exclude
        exclude: [

        ],

        // preprocess matching files before serving them to the browser
        // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
        preprocessors: {
            'test/**/*.spec.es6': ['traceur']
        },

        traceurPreprocessor: {
            // options passed to the traceur-compiler
            // see traceur --longhelp for list of options
            options: {
                experimental: true,
                sourceMaps: true,
                modules: 'amd'
            },
            // custom filename transformation function
            transformPath: function(path) {
                return path.replace(/.es6$/, '.js');
            }
        },

        // test results reporter to use
        // possible values: 'dots', 'progress'
        // available reporters: https://npmjs.org/browse/keyword/karma-reporter
        reporters: ['progress'],

        // web server port
        port: 9876,

        // enable / disable colors in the output (reporters and logs)
        colors: true,

        // level of logging
        // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
        logLevel: config.LOG_INFO,

        // enable / disable watching file and executing tests whenever any file changes
        autoWatch: true,

        // Start these browsers, currently available:
        // - Chrome
        // - ChromeCanary
        // - Firefox
        // - Opera (has to be installed with `npm install karma-opera-launcher`)
        // - Safari (only Mac; has to be installed with `npm install karma-safari-launcher`)
        // - PhantomJS
        // - IE (only Windows; has to be installed with `npm install karma-ie-launcher`)
        // @see https://npmjs.org/browse/keyword/karma-launcher
        browsers: ['Chrome'],

        // Continuous Integration mode
        // if true, Karma captures browsers, runs the tests and exits
        singleRun: false

    });
};

Please refer to the documentation for more details.

Here, we also load traceur and requirejs into frameworks section. Once requirejs is used, you will need to use RequireJS to help load all test files.

The files section includes all the files that karma will deal with. In the above example, the following files / file pattern will be watched by Karma:

{pattern: 'test/**/*.spec.es6', included: false}  // All test cases in ES6 JavaScript files
test/test-main.js

But only test/test-main.js will be loaded into the browser.

About more details on how to use “files” section, please check here.

In the preprocessors section, add “file pattern” and specify the preprocessor with traceur, meaning it needs Traceur to compile all the files listed to ES5 files serving them to the browser.

And in the traceurPreprocessor section, you will need to specify the “options” as well as “callback” functions of Traceur.

  • Add experimental: true into “Traceur options” will enable all “experimental” features of Traceur, which usually means it will support cutting-edge ES6 features.
  • In the above example, the callback function transformPath is used to change the file extension from es6 to the normal js so that the browser can correctly identify them.

Changing autoWatch to true and singleRun to false will open the auto-watch of Karma and keep running the test case whenever the “watched” files modified and saved.

3.4 gulpfile.js

Since Traceur and auto-watch of files are handled by Karma, Gulp only needs to do the following two things:

  • Call Karma to run the test cases.
  • Run the flow of building deployment.

In order to call Karma, you need to include the folowing component:

var karma = require('karma').server;

And then the code of calling Karma should be like:

/**
 * Watch for file changes and re-run tests on each change
 * TDD: Test-Driven Development
 */
gulp.task('tdd', [],function (done) {
    karma.start({
        configFile: __dirname + '/karma.conf.js'
    }, done);
});

About using Gulp to do projet deployment, I will create an separate post to discuss about it.

3.5 test-main.js

This file is mainly for configuring RequireJS.

it should be like:

var tests = [];
for (var file in window.__karma__.files) {
    if (window.__karma__.files.hasOwnProperty(file)) {
        if (/.spec.js$/.test(file)) {
            tests.push(file);
        }
    }
}

/**
 * RequireJS config
 */
requirejs.config({
    // Karma serves files from '/base'
    baseUrl: '/base/src',

    paths: {
    },

    shim: {
    },

    // ask Require.js to load these files (all our tests)
    deps: tests,

    // start test run, once Require.js is done
    callback: window.__karma__.start
});

Array tests contains all “test” JavaScript files. Then in RequireJs config, we ask require.js to load these files to browser.

Once RequireJs is done its job, use callback window.__karma__.start to start the test run.

You can check here for more details about how to config RequireJs.

4. Example CodeBases

My ES6 learning repo is built based on this Boilerplate structure. You can check there for more details about the setup.

I will add more examples here later.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>