RequireJS Optimization with Play 2.1 and WebJars

RequireJS Optimization with Playframework 2.1 and WebJars

Motivation

In a previous post I showed how to use WebJars with RequireJS in Play to manage web dependencies.

What that post lacked was to explain how to make your app production-ready, i.e. combine all your files into one, minify the result, and to map WebJars resources to CDN URLs 1. Play currently 2 offers nothing to do this automatically, so we need to some of the work by hand.

I will also continue to show how to integrate AngularJS with Play. Angular is an excellent framework for building complete Single Page Applications, and using Play as a REST backend is the perfect companion.

RequireJS Configuration

When you run play stage or play dist, Play runs the RequireJS optimizer. The optimizer cannot resolve the WebJars RequireJS plugin (as in webjars!angular.js) and throws an error 3. As shown before, you usually specify a main JavaScript file that is loaded by RequireJS when your web app is loaded. Accordingly, if we want to use different paths for our WebJars in production, we need to use a separate main file (I’ll name these mainDev.js and mainProd.js). We can tell Play to use the production file by adding the requireJs and requireJsShim build parameters.

val main = play.Project(appName, appVersion, appDependencies).settings(
  requireJs += "mainProd.js",
  requireJsShim += "mainProd.js"
)

The first parameter adds the file to the optimizer, so it will resolve its dependencies and combine them into a single, uglified file. The second adds a file to the RequireJS configuration so the optimizer can resolve our non-filesystem dependencies. In this case we use the same file in both cases because we can’t differentiate between development and production mode, so there’s no point in putting this in two separate files. Your mileage may vary.

The first thing we need to do is to manually map our WebJars calls to module names in mainDev.

define("jquery", ["webjars!jquery.js"], function() {
  return $;
});
define("angular", ["webjars!angular.js"], function() {
  return angular;
});

This way, we can reference our WebJars in all subsequent require() and define() calls with their module names without hard-coding the WebJars plug-in.

Each main file uses the RequireJS shim to define inter-dependencies between the included JavaScript libraries. Thanks to the above remapping, we arrange our dependencies with simple module names.

requirejs.config({
  shim: {
    "jquery": { exports: "$" },
    "angular" : {
      exports : "angular",
      deps: ["jquery"]
    }
  }
});

The production main file additionally specifies the paths key to map the external libraries (WebJars and others) to CDN URLs.

requirejs.config({
  shim: ..., // as before
  paths: {
    "jquery": "//code.jquery.com/jquery-1.10.2.min",
    "angular": "//ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min"
  }
});

Note that you must leave out the ".js" at the end, RequireJS adds these automatically4.

Now whenever we add a new dependency, we have to change two files, so we should keep these files as simple as possible. Our require statement will only refer to the most basic modules, the rest should be loaded automatically by organizing our project in an intelligent manner.

Finally, when running the app we need to decide between the two main files. We can reuse Play’s requireJS helper and load it depending on the Application’s current mode.

@import play.api.Play.current
@if(Play.isDev) {
  @helper.requireJs(core = routes.WebJarAssets.requirejs.url, module = routes.Assets.at("javascripts/mainDev").url)
}
@if(Play.isProd) {
  @helper.requireJs(core = routes.WebJarAssets.requirejs.url, module = routes.Assets.at("javascripts/mainProd").url)
}
RequireJS Loading Scheme
RequireJS Files Organization

Organizing an AngularJS Application

The official play-angular-seed by Typesafe is pretty simple in that it defines one folder per AngularJS concept (controllers, directives, services, and so on). This approach doesn’t scale well with an increasing project size, as it becomes harder and harder to just find stuff 5. It could also become a bottle-neck when loading all modules at once.

I’ve adapted the practice of separating concerns and logical user interface groups (one or more screens that belong to the same category, e.g. Users, Shopping Cart, Common, and so on) into modules.

|- app.js
|- cart/
 |- directives/cart.js
 |- services/cartService.js
 |- routes.js
 |- controllers.js
 |- main.js
|- common/
 |- ...

Each module is defined in a RequireJS package (a folder with a main.js file). Other modules, such as app itself, only reference the package name. The package’s main module is thus responsible for loading all the files that are part of the package. These files constitute both a RequireJS module and an AngularJS sub-module.

Directives and services can grow large in a module. Depending on their complexity, I’ll break these either into single files (directives.js and services.js), or into subfolders with a file per directive or service.

controller.js only defines a RequireJS module, not an AngularJS module because controllers are not injected. They are only referenced by the routes.js sub-module which associates each route with a template and a controller.

Finally, there is a basic app.js module (a simple file called app.js) that is responsible for bootstrapping the application and loading all of the required modules.

Angular Application Organization
Angular Application Organization

Wrap Up

Combining these practices should make developing Single Page Applications with Play a lot smoother, at least that’s what it did for me. I’d love to hear your feedback!

My own Play-Angular Seed illustrates all of the described techniques and also serves as a template for new apps. It already implements some concrete functionality that you’ll need in most of your applications.

Enjoy!


  1. Loading a file from has serveral benefits; CDNs are distributed around the globe, so the file should load faster; additionally, if the user surfed to a site before that used the same scripts, the files are already in his cache

  2. Play 2.3 is supposed to improve support for RequireJS and WebJars optimization

  3. Actually, the optimizer shows an error, but Play still builds the application as if nothing happened. The application works, but uses non-optimized files.

  4. This is different from the WebJars plug-in which expects the resource to end with .js. An URL like /routes also doesn’t automatically add a .js suffix. This can be very confusing and a common source of errors.

  5. The same is true for Play IMHO.

Posted in Programming
9 comments on “RequireJS Optimization with Play 2.1 and WebJars
  1. Artem Kozlov says:

    Very useful article. Thank you!

    btw, you can use RequireJs http://requirejs.org/docs/api.html#config-map to alias webjar module.

    requirejs.config({
    map: {
    ‘*': {
    ‘lodash': ‘webjars!lodash.js’
    }
    },
    shim: {
    app: {
    deps: [‘lodash’]
    }
    }

    This can be used when library already properly support AMD.

  2. Alejandro says:

    First of all, thanks for your article :) it was really helpful.

    Now that my application can build :D I have another question. Is there any way to add the application version number to the js minified files?

    Thanks again!

  3. Hiren says:

    I am looking for a tutorial on how to create a webJar programatically/using some task runner. Can you share any leads?

  4. Chris K says:

    Hi Marius,

    First thanks for all of the info and your hardwork/code base. Even to this date, still hard to find good examples like yours that integrate Play 2/Scala and Angular to include require.js.

  5. Peter says:

    Hi Marius,
    A very useful play template for angular and while I can see the value of modularising the app it would be great if we could use this with yeoman (i really like the automatic dynamic reloading), see http://yeoman.io and the yeoman template for play http://www.typesafe.com/activator/template/play-yeoman
    The yeoman template is structured differently where all the ui code is in a ui directory, which I must admit I like as it separates the single page app from the server side and should allow more independent development. But haven’t figured out if its possible to modularise it in the same way, which would be nice.
    The only thing I don’t like about yeoman is the need for separate and possible duplicate dependency management. Still looking into this.

    • marius says:

      Hi Peter,

      sure, you can always go the Yeoman/Grunt or Gulp route. But the point of this article is to show how you can use Play’s existing capabilities together with WebJars to create a nicely structured app. Also it seems impossible to adapt Yeoman generators to your needs.

      The automatic reloading can be achieved with James Ward’s Auto Refresh plugin (https://github.com/jamesward/play-auto-refresh/).

  6. mahendra singh says:

    Hi,
    I need to split my markup into multiple template files ,so I do not need to have routes for these templates and I will load them with ng-include.
    But I need to have corresponding controllers for these sub-templates,and so I need to use the angular controller instead of the normal require controllers.
    How can we use angular controllers in this pattern instead of the require controllers.

    Thanks

  7. pietro says:

    Dear Marius,
    thank you so much! I’ve digged through a lot of blogs, docs and examples but your template is definitely fantastic!
    I’m moving a huge web application from Play 2.2 to 2.3, implementing the whole production toolchain: your explanation is far more easy and useful than most of the official documentation.
    Thank you!
    pietro

Leave a Reply

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

*


eight × 8 =

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=""> <strike> <strong>