Monday, December 5, 2011

Whole Project Optimization with Require.js

‹prev | My Chain | next›

I have been fiddling with require.js for the last couple of days. It really takes some effort to get started with, but seems to bring enough power to the table to make it worth the bother. Last night, for instance, I was able to figure out how to bundle my all the Javascript in me Backbone.js (backbone.js, jquery.js, etc) into a single, "built" Javascript files. That would certainly be quite powerful for deployment.

Tonight, I hope to build on that effort to not only build the the Javascript, but update all of my HTML such that it references the proper, built Javascript file. Currently, my node.js based application is laid out as:
.
├── app.build.js (require.js build configuration)
├── app.js       (node.js server)
└── public
    ├── index.html
    └── scripts
        ├── app.build.js
        ├── backbone.js
        ├── jquery-1.7.1.js
        ├── main.js
        ├── my-calendar
        │   ├── routers
        │   │   └── paginator.js
        │   └── views
        │       └── paginator.js
        ├── my-calendar.js
        ├── require.js
        └── underscore.js
To generate an optimized version of my backbone application, my app.build.js file contains the following:
({
  // Where my HTML and JS is stored:
  appDir: "public",
  // Top-level directory containing my JS:
  baseUrl: "scripts",
  // Filename containing my top-level requirejs application:
  name: "main",
  // Destination of the optimized version of my application:
  out: "public-build/scripts/main.js",
  // Resolve any 'jquery' dependencies to the versioned jquery file:
  paths: {
    'jquery': 'jquery-1.7.1'
  }
})
When I use that to optimize from my application directory (one level above the public sub-directory containing my backbone app), I get:
➜  backbone-requirejs-test git:(master) ✗ ./node_modules/requirejs/bin/r.js -o app.build.js

Tracing dependencies for: main
Uglifying file: /home/cstrom/src/backbone-requirejs-test/public-build/scripts/main-built.js

/home/cstrom/src/backbone-requirejs-test/public-build/scripts/main.js
----------------
/home/cstrom/src/backbone-requirejs-test/public/scripts/jquery-1.7.1.js
/home/cstrom/src/backbone-requirejs-test/public/scripts/underscore.js
/home/cstrom/src/backbone-requirejs-test/public/scripts/backbone.js
/home/cstrom/src/backbone-requirejs-test/public/scripts/my-calendar/views/paginator.js
/home/cstrom/src/backbone-requirejs-test/public/scripts/my-calendar/routers/paginator.js
/home/cstrom/src/backbone-requirejs-test/public/scripts/my-calendar.js
/home/cstrom/src/backbone-requirejs-test/public/scripts/main.js
The result is a 120k main.js file:
public-build
└── [4.0K]  scripts
    └── [119K]  main.js
That 120K comes from my application code (main.js, my-calendar.js, etc) as well as library code (jquery, backbone, etc.):
public
├── [ 416]  index.html
└── [4.0K]  scripts
    ├── [ 106]  app.build.js
    ├── [ 42K]  backbone.js
    ├── [242K]  jquery-1.7.1.js
    ├── [ 140]  main.js
    ├── [4.0K]  my-calendar
    │   ├── [4.0K]  routers
    │   │   └── [1.0K]  paginator.js
    │   └── [4.0K]  views
    │       └── [ 938]  paginator.js
    ├── [ 379]  my-calendar.js
    ├── [ 79K]  require.js
    └── [ 34K]  underscore.js
That seems to be working well enough, but how might I go about deploying this? I could have a post-optimize script that copies the built "main.js" over top of the original version. That is a pain, though.

Thankfully require.js has a mechanism for dealing with just this situation. In the optimization documentation, this is referred to as "whole project" optimization. What this does is copy over the entire appDir to another location, overwriting the main "module".

To accomplish this, I have to remove the "name" and "out" configuration options from app.build.js. Instead, I specify the name of the output directory with out and the name of the module being optimized with the modules option:
({
  // Where my HTML and JS is stored:
  appDir: "public",
  // Top-level directory containing my JS:
  baseUrl: "scripts",
  // Where to build the optimized project:
  dir: "public-build",
  // Modules to be optimized:
  modules: [
    {
      name: "main"
    }
  ],
  // Resolve any 'jquery' dependencies to the versioned jquery file:
  paths: {
    'jquery': 'jquery-1.7.1'
  }
})
Now, when I run the optimizer, I get:
➜  backbone-requirejs-test git:(master) ✗ ./node_modules/requirejs/bin/r.js -o app.build.js

Tracing dependencies for: main
Uglifying file: /home/cstrom/src/backbone-requirejs-test/public-build/scripts/my-calendar/views/paginator.js
Uglifying file: /home/cstrom/src/backbone-requirejs-test/public-build/scripts/my-calendar/routers/paginator.js
Uglifying file: /home/cstrom/src/backbone-requirejs-test/public-build/scripts/jquery-1.7.1.js
Uglifying file: /home/cstrom/src/backbone-requirejs-test/public-build/scripts/main.js
Uglifying file: /home/cstrom/src/backbone-requirejs-test/public-build/scripts/require.js
Uglifying file: /home/cstrom/src/backbone-requirejs-test/public-build/scripts/underscore.js
Uglifying file: /home/cstrom/src/backbone-requirejs-test/public-build/scripts/app.build.js
Uglifying file: /home/cstrom/src/backbone-requirejs-test/public-build/scripts/backbone.js
Uglifying file: /home/cstrom/src/backbone-requirejs-test/public-build/scripts/my-calendar.js

scripts/main.js
----------------
scripts/jquery-1.7.1.js
scripts/underscore.js
scripts/backbone.js
scripts/my-calendar/views/paginator.js
scripts/my-calendar/routers/paginator.js
scripts/my-calendar.js
scripts/main.js
Hrm... I could do without the copying / uglifying of the various library files. Still, it does seem to have copied over both the HTML and the optimized main.js:
public-build
├── [ 219]  build.txt
├── [ 416]  index.html
└── [4.0K]  scripts
    ├── [  77]  app.build.js
    ├── [ 14K]  backbone.js
    ├── [ 92K]  jquery-1.7.1.js
    ├── [119K]  main.js
    ├── [4.0K]  my-calendar
    │   ├── [4.0K]  routers
    │   │   └── [ 725]  paginator.js
    │   └── [4.0K]  views
    │       └── [ 679]  paginator.js
    ├── [ 225]  my-calendar.js
    ├── [ 14K]  require.js
    └── [ 11K]  underscore.js
Best of all, I only need point my node application to this public-build directory to serve up my Backbone application all bundled into a single main.js:


I think that about does it for optimizing with require.js. Up tomorrow: converting my existing Backbone app to use require.js

Day #226

No comments:

Post a Comment