AMD & RequireJS

cf.Objective() 2013

Who Am I

Ryan Anklam

Agenda

Why AMD?

The Beginning...

<table cellpadding="0" cellspacing="0" border="0">
   <tr>
      <td>
         <img src="link1.jpg" 
          onmouseover="this.src='link1_over.jpg'"
          onmouseout="this.src='link1.jpg'">
      </td>
      <td>
         <blink>
         <a href="javascript:void(0)" 
          onclick="alert('Are you sure.')">
            Click Here
          </a>
        </blink>
      </td>
   </tr>
</table>

Confession: I used to write code like this on my IBM Aptiva while watching the Fresh Prince of Bel Air.

The Middle...

<script type="text/javascript" >
   function OnMouseOver(image, newSrc){
      image.src = newSrc;
   }
   
   var ele = document.getElementById( 'myImage' );
   
   if(document.attachEvent){
     ele.attachEvent( 'click', ...);
   }
   else{
    ele.addEventListener( 'click', ...);
   }
   
</script>

The Not So Distant Past...

    <script src="da.js"></script>
    <script src="bears.js"></script>
    <script src="still.js"></script>
    <script src="suck.js"></script>
    <script src="so.js"></script>
    <script src="do.js"></script>
    <script src="the.js"></script>
    <script src="cubs.js"></script>
  </body>
</html>

The Not So Distant Past...

Then some hot shot designer who is a Bears fan edits your page...

<script src="still.js"></script> 
<!--oops this relied on the bears.js!-->
<script src="suck.js"></script> 
<!--and this relied on still.js working-->
<script src="the.js"></script>
<script src="bears.js"></script>

And breaks EVERYTHING!

Script Loaders!

head.js("/path/to/jquery.js", 
        "jquery.plugin.js",
        "/js/site.js");
$LAB.script("/path/to/jquery.js")
  .script("jquery.plugin.js").wait()
  .script("/js/site.js");

Script Loaders Are Great But...

The Present...

RequireJS to the Rescue!

<!DOCTYPE html>
<html>
    <head>
        <title>Awesome Stuff</title>
        <script 
            data-main="scripts/main.js" 
            src="scripts/require.js">
       </script>
    </head>
    <body>
        ...
    </body>
</html>

But Wait, Theres More!

Writing AMD Modules

The AMD Spec

"The Asynchronous Module Definition (**AMD**) API specifies a mechanism for defining modules such that the module and its dependencies can be asynchronously loaded. This is particularly well suited for the browser environment where synchronous loading of modules incurs performance, usability, debugging, and cross-domain access problems. "

https://github.com/amdjs/amdjs-api/wiki/AMD

define( )

define( < id, > < [ dependencies ], > factory );

A Simple Module

define( function( ){ 

   return {};
} );

A More Useful Example

define( 'navView',
        [ 'jquery',
          'underscore',
          'backbone',
          'models/user' ],
        
      function( $, _, Backbone, user ){
        var NavView = 
          Backbone.View.extend( {
            ...
         } );

         return NavView;
      }
);

Thinking Modular

A good module...

The more modules you write, the better you'll become at determining what belongs in a module.

Thinking Modular

Typical jQuery code
$( function(){
  
  $( '#addEvent' ).on( 'click', function(){
     //add a event
  });
  
  $( '#events' ).on( 'click', '.viewEvent', function(){
    //show the event
  })
  .on( 'click', 'delete', function(){
    //delete the event
  });

  function validateEntry(){
    //validate
  }

});

Thinking Modular

More Modular
define( 'calendar', function(){
  
  var calendar = {
    addEvent : function(){
    },
    viewEvent : function(){
    },
    deleteEvent : function(){
    },
    validateEvent : function(){
    }
  };
  
  return {
    addEvent : calendar.addEvent,
    editEvent : calendar.editEvent,
    deleteEvent : calendar.deleteEvent
  };
});

Thinking Modular

More Modular
require( [ 'calendar', function( cal ){

 $( '#addEvent' ).on( 'click', cal.addEvent );
 
 $( '#events' )
    .on( 'click', '.viewEvent', cal.viewEvent)
    .on( 'click', 'delete', cal.deleteEvent ); 
    
} );

RequireJS

RequireJS

RequireJS

The Simplest Implementation.

<script 
  data-main="main.js" 
  src="require.js"></script>

The data-main attribute tells require to run the main.js to initialize the application.

Inside main.js

require( [ 'app' ], function( app ){

  app.init( );

} );

The require function is used as a shortcut by RequireJS when a script has no exports. The AMD spec defines that the require function is optional for an AMD loader to implement.

app.js can have its own dependencies that will be resolved when it is included into main script.

Config Options

Configuring Require

require.config( {
   baseUrl : '',
   paths : { 'jquery', 'vendor/jquery-1.9.1.js'},
   shim: { ... }, //added in RequireJS 2.1
   config : { version : '0.9.1'},
   deps : [ 'main' ]
} );

To see all config options go to the require website: requirejs.org

Common Config

index.html

<script 
  data-main="config.js" 
  src="require.js"></script>

config.js

require.config( {
    deps : [ 'main' ],
    ...
} );

Using a CDN

require.config({
    paths:{
        'jquery': 
          'http://ajax.googleapis.com/.../jquery.min.js'
    }
});

Adding Non-AMD Libraries

paths + shim

paths : {
   underscore : 'vendor/underscore',
   backbone : 'vendor/backbone'
},

shim : {
  underscore : {
    exports : 'underscore'
  },
  backbone : {
    deps : [ 'underscore', 'jquery' ],
    exports : 'Backbone'
  }
}

Shimming jQuery Plugins

paths : {
   jquery : 'vendor/jquery-1.9.1',
   'jquery.validation' : 'vendor/jquery.validate.js',
},

shim : {
   'jquery.validation' : {
      deps : [ 'jquery' ],
      exports : 'jQuery.fn.validate' //checks of module is loaded
   }
}

Exporting Your Library

From the Lo-Dash source code:

if (typeof define == 'function' &&
    typeof define.amd == 'object' && define.amd) {

    window._ = lodash;
    
    define(function() {
      return lodash;
    });
  }

Plugins

Require Plugins

https://github.com/jrburke/requirejs/wiki/Plugins

Require Plugins

To use a plugin just add the plugin source file to the base directory of your application.

require(
    [
        'text!myViewTemplate.html'
    ],
    function( template ){
        ...
    }
});

Note the use of the !, this is the syntax for loading a plugin.

Best Practices & Gotchas

Best Practice

One Module Per File

define( 'userModel' , 
  [ 'underscore', 'backbone' ], 
  function(){
    ...
} );

define( 'userView' , 
  [ 'userModel', 'underscore', 'backbone' ], 
  function(){
    ...
} );

Better

userModel.js

define( 'userModel' , 
  [ 'underscore', 'backbone' ], 
  function(){
    ...
} );

userView.js

define( 'userView' , 
  [ 'userModel', 'underscore', 'backbone' ], 
  function(){
    ...
} );

Be careful when using ID's

Module with an id not in main app directory.

views/appView.js

define( 'appView', [], function(){
} );

config.js

{
    paths : {
        'appView' : 'views/AppView'
    }
}

Be careful when using ID's

ID not the same as js file name.

appView.js

define( 'app-view', [], function(){
} );

config.js

{
    paths : {
        'app-view' : 'appView'
    }
}

Watch Out!

Circular Dependencies

One module has a dependency on a module that has a dependency on it.

define( 'a', [ 'b' ], function(){
   ...
} );
define('b', [ 'a' ], function(){
   ...
} );

Optimization With R.js

What Is It?

Requirements

Node is the preferred environment in a CLI

Build Options

Build Options

Using build.js

({
   baseUrl : 'js', 
   out : 'main-min.js' 
   paths : { } 
   shim : { } 
   name : 'main' //
   optimize : 'uglify'
})
node r.js -o build.js

Build Options

Command Line

  node r.js -o baseUrl=. name=main out=main-built.js

In the CLI dots are viewed as property seperators!

Multi Page Site

Page 1

<!DOCTYPE html>
<html>
    <head>
        <title>Page 1</title>
        <script data-main="js/page1" src="js/lib/require.js"></script>
    </head>
    <body>
        <a href="page2.html">Go to Page 2</a>
    </body>
</html>
 
//Load common code that includes config, then load the app logic for this page.
require(['./common'], function (common) {
    require(['app/main1']);
});

Multi Page Site

Page 2

<!DOCTYPE html>
<html>
    <head>
        <title>Page 2</title>
        <script data-main="js/page2" src="js/lib/require.js"></script>
    </head>
    <body>
        <a href="page1.html">Go to Page 1</a>
    </body>
</html>
//Load common code that includes config, then load the app logic for this page.
require(['./common'], function (common) {
    require(['app/main2']);
});

Multi Page Site

common.js

requirejs.config({
    baseUrl: 'js/lib',
    paths: {
        app: '../app'
    }
}); 

Multi Page Site Build

({
   modules: [
    //First set up the common build layer.
    {
      name: '../common',
      include: ['jquery',
         'app/controller/Base',
         'app/model/Base']
    },
    {
      name: '../page1',
      include: ['app/main1'],
      exclude: ['../common']
    },
    {
      name: '../page2',
      include: ['app/main2'],
      exclude: ['../common']
    }
  ]
})

https://github.com/requirejs/example-multipage

Multi Page Site Build

After the build:

Why not AMD?

Why AMD is Not the Answer by Tom Dale, one of the Ember.js creators.

The Future

ES Harmony Modules

Warning! The spec is still in process

3 new keywords

Using module And export

module person{
   var name = ''; //private

   export function sayHello(){
      return 'Hello, ' + name;
   }
}

Multiple Exports

module carFactory{

   export var ford = {
      make : 'Ford'
   };

   export var chevy = {
      make : 'Chevrolet'
   };
}

import

module fusion{
    
   import ford from carFactory;

   var model = 'Fusion';

   export function getFullName(){
      return ford.make + ' ' + model;
   }
}

Traceur

You can use ECMAScript.Harmony code today!

Google's Traceur compiler compiles Harmony code to ECMAScript 5 code!

Project home page: https://code.google.com/p/traceur-compiler/

Online Compiler: http://traceur-compiler.googlecode.com/git/demo/repl.html

Playing with Harmony Modules and Traceur: http://blog.bittersweetryan.com/2013/02/playing-with-ecmascriptharmony-modules.html

Questions?

The End.

http://bittersweetryan.github.com/introduction-to-amd/

/

#