Frontend Build

http://seattlejs.training.formidablelabs.com

@FormidableLabs | formidablelabs.com

Tip - space bar advances slides

Introduction

The frontend is now complicated enough to need its own build process and workflows.

What is the "Build"?

The technologies that enable you to more easily develop, test, and deploy your app.


  

Common Build Tasks

  • Downloading assets.
  • Checking code style.
  • Running tests & servers.
  • Moving, copying, deleting files.
  • Contatenating, minifying files.

Build Stack

  • Bower: Asset dependencies.
  • NPM: Grunt dependencies.
  • Grunt: Build tool.

Bower

Bower is a package manager for frontend assets.


bower.io

Bower Config

Let's look at the Notes bower.json

"dependencies": {
  "jquery": "2.1.0"
},
"devDependencies": {
  "sinonjs": "1.7.3"
}

Install

Install to "bower_components".

$ bower install

Add Libraries

Can add to bower.json with --save or --save-dev. Let's add d3:

$ bower install --save d3

NPM

NPM is a package manager for Node.js, which we use for Grunt.


www.npmjs.org

NPM Config

Let's look at the Notes package.json

"dependencies": { /* ... */  },
"devDependencies": { /* ... */ },
"scripts": { /* ... */ }

(npm install also bower installs in the Notes app.)

Install & Add

Similar to Bower.

# Install existing
$ npm install

# Install new libraries
$ npm install --save-dev nyancat

# NYANCAT
$ ./node_modules/.bin/nyancat

Grunt

Grunt is a robust and battle-tested JavaScript task runner.


gruntjs.com

Grunt Basics

  • Running tasks
  • Gruntfile & Plugins
  • Aggregating
  • Examples
  • Advanced topics

Running Grunt

When we say "grunt", we mean:

# Mac / Linux
$ ./node_modules/.bin/grunt
$ ./node_modules/.bin/grunt --help

# Windows
$ node_modules\.bin\grunt
$ node_modules\.bin\grunt --help

Running Grunt Tasks

  • grunt (default)
  • grunt TASK
  • grunt TASK TASK [...]

Examples

  • grunt jshint
  • grunt karma:mocha-fast
  • grunt build server (127.0.0.1:3000)

Gruntfile & Plugins

  • Plugins.
  • The Gruntfile.
  • Loading and configuring a plugin.
  • Running a task from a plugin.

Useful Plugins

Anatomy of a Gruntfile

Let's look at the Notes Gruntfile.js

/*global grunt, optionsForThatTask */
// Config
grunt.initConfig({
  theTaskYouWantToRun: optionsForThatTask
});

// Load NPM libraries
grunt.loadNpmTasks("npm-installed-grunt-plugin");

// Custom tasks
grunt.registerTask("taskName", [
  "subTask", "anotherSubTask"
]);

Follow Along

Use Gruntfile-workshop.js to code along with us.

# Should be in `my-app` or `skeleton/amd` already.

# Save the "finished" grunt file.
$ mv Gruntfile.js Gruntfile-finished.js

# Add in our workshop one!
# (Or download it from the web!)
$ mv Gruntfile-workshop.js Gruntfile.js

Test it out!

# See tasks.
$ grunt --help

# Run default.
$ grunt

# Should Error - we'll write this one!
$ grunt jshint:client

File Globbing

{
  "src": [
    // **Only** JS files at `my-dir` level.
    "my-dir/*.js",
    
    // JS files at `my-dir` level or **below**.
    "my-dir/**/*.js"
  ]
}

Our First Example!

Let's integrate grunt-contrib-clean in our Gruntfile.js to clean up build files.

Configuration Variables

Used in templates as "<%= VAR_NAME %>" and with grunt.config("VAR_NAME")

/*global grunt*/
grunt.initConfig({
  vendorPath: "app/js/vendor",
  distPath: "app/js-dist"
});

Configure, Include

/*global grunt*/
grunt.initConfig({
  vendorPath: "app/js/vendor",
  distPath: "app/js-dist",
  
  // Configure.
  clean: {
    vendor: "<%= vendorPath %>",
    dist: "<%= distPath %>"
  }
});

// ... Include.
grunt.loadNpmTasks("grunt-contrib-clean");

Running the Task

# Run **all** cleans.
$ grunt clean

# Run specific clean tasks.
$ grunt clean:vendor
$ grunt clean:dist

Grunt Examples

  • JsHint
  • Development server
  • JS build

JsHint

Check your JavaScript code for errors / conventions. Rules are defined in .jshint-frontend.json.

Challenge

Look at the server: config and write a client: one.

/*global grunt, _readJsonCfg */
grunt.initConfig({
  jshint: {
    options: _readJsonCfg(".jshint-frontend.json"),
    // TODO: Add a `client` configuration for:
    // - JS files in `app/js/` directory.
    // - JS files **anywhere in** `app/js/app/` dir.
    server: { /* ... */ }
  }
});

grunt.loadNpmTasks("grunt-contrib-jshint");

Solution

/*global grunt, _readJsonCfg */
grunt.initConfig({
  jshint: {
    options: _readJsonCfg(".jshint-frontend.json"),
    client: {
      files: {
        src: ["app/js/*.js", "app/js/app/**/*.js"]
      }
    }
  }
});

grunt.loadNpmTasks("grunt-contrib-jshint");

Nodemon

Runs and reloads a development server.

  • Plugin: grunt-nodemon
  • Invoke: grunt nodemon:dev (aliased as: grunt server)

Challenge

Load and configure nodemon to: run the server defined in server/index.js, and restart every time index.js is modified.

/*global grunt*/
grunt.initConfig({
  nodemon: {
  }
});

Solution

/*global grunt*/
grunt.initConfig({
  nodemon: {
    dev: {
      script: "server/index.js"
    },
    options: {
      watch: ["server"]
    }
  }
});

grunt.loadNpmTasks("grunt-nodemon");

JS Build

  • Clean old builds.
  • Copy dependencies.
  • Bundle our JS app.

Clean

Already have that!

$ grunt clean

Copy

Automate file copies.

Copy

Use copy to make bower dependencies accessible for distribution

/*global grunt*/
grunt.initConfig({
  copy: {
    dist: { // Copy dependencies from bower.
      files: [{
          cwd: "<%= bowerPath %>",
          dest: "<%= distPath %>",
          expand: true, flatten: true,
          src: ["requirejs/require.js"]
      }]
}}});

grunt.loadNpmTasks("grunt-contrib-copy");

RequireJS

Build RequireJS dependencies into one file.

RequireJS

/*global grunt*/
grunt.initConfig({
  requirejs: {
    app: {
      options: {
        name: "app/app", baseUrl: "app/js/vendor",
        mainConfigFile: "app/js/config.js",
        out: "<%= distPath %>/bundle.js",
        optimize: "uglify2"
      }
    }
  }
});

grunt.loadNpmTasks("grunt-contrib-requirejs");

Challenge

/*global grunt*/
grunt.registerTask("build:vendor", [
  // TODO: Need to `clean` and `copy` the vendor files.
]);
grunt.registerTask("build:dist", [
  // TODO: Need to `copy` the distribution files and build
  //       the RequireJS bundle (hint: `requirejs`)
]);
grunt.registerTask("build", [
  // TODO: Need to aggregate the two previous tasks ;)
]);

Solution

Aggregating and Aliasing

/*global grunt*/
grunt.registerTask("build:vendor", [
  "clean:vendor",
  "copy:vendor"
]);
grunt.registerTask("build:dist", [
  "clean:dist",
  "copy:dist",
  "requirejs"
]);
grunt.registerTask("build", [
  "build:vendor",
  "build:dist"
]);
grunt.registerTask("build:dev", ["build"]);

Advanced

Other Build Plugins

Our Lifecycles

These basic tools are the starting point for a (much bigger) discussion about development and release lifecycles...

Development Lifecycle

  • Write new code.
  • Experiment in dev. server (grunt server)
  • Write and run tests. (grunt karma:mocha-fast)
  • Check code quality. (grunt jshint)
  • Commit!
  • ... rinse, and repeat ...

Release Lifecycle

  • Check code quality. (grunt jshint)
  • Run multi-browser tests. (grunt karma:mocha-ci)
  • Bundle for production. (grunt build)
  • Deploy!
  • ... rinse, and repeat ...

Build It!

gruntjs.com | bower.io