ReqUire.js

Using AMD to fight for the good and against the bad and ugly parts of javascript

Mikael Karon <mikael.karon@ef.com>
EF Labs (Shanghai)

Meet the family

The problem

We don't build web pages any more
We build web applications

The ugly:Manual deps

<script src="jquery.js"></script>
<script src="jquery.easing.js"></script>
<script src="slideshow.js"></script>
...
<script type="text/javascript">
    var _gaq = _gaq || [];
    _gaq.push(['_setAccount', 'UA-12345678-1']);
</script>

We know that JavaScript tags block page rendering

Not every JavaScript file is needed when the page first loads

Multi-page applications start copying & pasting script tags

As the site grows, so does complexity

No encapsulation of export of value = namespace pollution

The bad:Inlined closures

(function () {
    var $ = this.jQuery;
    var slideshow = $('#id').slideme();

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

The good:AMD

define(['widget'], function (Widget) {
    return Widget.extend({
        "method" : function () {
...
        }
    });
});

Modules are encapsulated and sand-boxed

Create and reuse code from different products

Structured, clean code base

Async loading, managed dependencies

Lazy load modules (stuff not used by the user yet can be loaded in background when needed on demand)

Not a technology, but a specification proposal

define() API

  • Used to define a module
  • Specifies all module dependencies
  • Optionally name your module
  • Export your module interface

define() API

// naming you module is optional, and in fact, you should avoid it
// naming your modules makes your code less portable
// dependencies listed in an array, same rules as require()
// mapped to arguments in the callback
define('name', ['dependency', 'bar'], function(dependency, bar) {
  // Export the interface for our module.
  return {
    win: dependency.win,
    lose: bar.lose
  };
});

dEFINE() API

define(['dependency', 'bar'], function(dependency, bar) {
  // Export the interface for our module.
  return {
    win: dependency.win,
    lose: bar.lose
  };
});

dEFINE() API

define(function() {
  // Export the interface for our module.
  return function(){
    //functions can exported to!
  };
});

rEQUIRE() API

  • Used to load code in a top level JavaScript file
  • An entry point of sorts
  • Used to lazily load code

rEQUIRE() API

// foo is an external module
// foo can be a path or an alias to a path using require configuration
// the exports or return of foo will be mapped 
// to the corresponding argument.
require(['foo'], function(foo) {
  foo.win()
});

rEQUIRE() API

// Multiple dependencies
require(['foo', 'bar'], function(foo, bar) {
  foo.win();
  bar.lose();
});

rEQUIRE() API

// Dynamic loading of dependencies
define(['require', 'jquery'], function(require, $) {
  // lots of awesome code

  $('#chat-start').on('click', function(){ 
    // dependencies are paths so this will grab features/chat.js
    require(['features/chat'], function(chat) {
      chat.start();
    });
  });
  return //awesome api;
});

rEQUIRE() API

// multiple loaders!
var reqOne = require.config({
  context: "version1",
  baseUrl: "version1"
});

require.config() API

  • Configuration of the Require.js Loader
  • Plugin configuration
  • Paths, packages, shims, map and more

require.config() API

require.config({
  baseUrl: '/js',
  paths: {
    // ;-)
    'underscore': 'lodash'
  }
});

REQUIRE.CONFIG() API

require.config({
  shim: {
    'backbone': {
      // These script dependencies should be loaded before 
      // loading backbone.js
      deps: ['underscore', 'jquery'],

      //Once loaded, use the global 'Backbone' as the
      //module value.
      exports: 'Backbone'
    }
  }
});

require.config() API

requirejs.config({
  map: {
    // When some/newmodule requires foo it gets the newer version.
    'some/newmodule': {
       'foo': 'foo1.2'
    },
    // When some/oldmodule requires foo it gets the older version.
    'some/oldmodule': {
        'foo': 'foo1.0' 
     }
  }
});

Plugins:using

  • Transparent middle ware for your modules!
  • Load different kinds of assets. Assets are dependencies too! (CSS, Templates, etc)
  • Can be used to pre-process module contents or load strategies. (CoffeeScript, Non-AMD Scripts)
  • text!, css!, i18n!, cs!, json!, mdown!, jade! & lots more!

PLUGINS:USING

// the <plugin>!<resource> is the syntax to use a plugin 
// plugins are just modules that implement a specific api
define(['foo!bar'], function(bar) {
  // Export the interface for our module.
});

PLUGINS:USING

define(['cs!module.coffee'], function(module) {
  // module was compiled for me!
});

PLUGINS:USING

define([
  'text!mytemplate.handlebars',
  'handlebars'
], function(template, handlebars){
  // template is just a string
  return handlebars.compile(template);
});

PLUGINS:creating

  • We can significantly reduce boilerplate, and further separate our concerns with plugins!
  • The plugins will actually write to file in the build, this means your whole application could be a single request

PLUGINS:creating

Plugins are just modules

That honour a specified API

write: function (pluginName, name, write) { ... },
load: function (name, parentRequire, load, config) {
  // Require the intended dependency
  parentRequire([name], function (val) {
    // Add extra functionality
    val.extra = function () { alert('extra!'); };
    // resolve the dependency manually
    load(val);
  });
}
require(['plugin!module'], function (module) {
  module.extra(); // alerts 'extra!'
});

Questions?

Require.js

By Mikael Karon