The Problem
- Increasing code complexity
- Developers want discrete JS files
- Need for optimized deployment
- Good debugging support
Modular JavaScript
- encapsulate a piece of code into a unit
- register its capability
- refer to other units as dependencies
The Old Way
- No support for modules in the language
- Scripts are loaded via <script> tags
- Dependencies are stated via the order of <script> tags
<script src="jquery.js"></script>
<script src="jquery.color-plugin.js"></script>
Global functions as event handlers
Avoiding Name Clashes
- Specially named (prefixed) global functions
Employing the object literal or the classic module pattern
Object Literal Pattern
var myModule = {
foo: function() {
console.log("foo!");
},
bar: function() {
console.log("bar!");
}
};
//...
myModule.foo();
Object Literal Pattern: Data
var myModule = {
options: {
name: "John Doe",
age: 30
},
init: function(name, age) {
this.options.name = name;
this.options.age = age;
},
foo: function() {
console.log("foo!");
}
};
Object Literal Pattern: Nesting
var Util = {
Logging: {
info: function() { ... },
debug: function() { ... }
},
Data: {
fetch: function() { ... },
save: function() { ... }
}
};
Util.Data.fetch();
Module Pattern
Anonymous Closures
(function() {
// private scope!
// private state!
})();
IIFE (Immediately Invoked Function Expression)
- The function creates a new scope with access to global variables
- The closure provides us private state for the application's lifetime
Module Pattern
Importing Globals
(function($, document, undefined) {
// $ refers to the jQuery global here
})(jQuery, document);
Module Pattern
Exporting a Module
var myModule = (function() {
var privateData = 42;
var privateMethod = function() { ... };
return {
name: "John Doe", // <= public
foo: function() { ... }, // <= public
bar: function() { ... } // <= public
};
})();
- Return a value from the anonymous function
- This is the module's public interface
- We can also maintain private state and have private methods
Object Literal And Module Patterns
Using them together
var Util = {
Data: (function() {
var privateData = 42;
return {
fetch: function() { ... },
save: function() { ... }
};
})()
};
Util.Data.fetch();
Object Literal And Module Patterns
Review
- Encapsulate pieces of code into units
- Pollute the global namespace
- Provide no dependency management
What can we do?
CommonJS
- A volunteer group for standardizing JavaScript APIs
- Write once, run everywhere
- e.g. web servers, command-line applications, desktop applications
CommonJS
Specifications
- Modules
- Packages
- Unit testing
- Binary data
- Text encoding
- Console logging
- File system
- ...
CommonJS Modules
- exports
- require()
- One module per file
- Top-level or relative module IDs
CommonJS Modules
Module Structure
var log = require("./logger");
function foo() {
log.write("foo!");
}
exports.foo = foo;
logger.js:
exports.write = function(msg) {
console.log(msg);
}
CommonJS Modules
Exporting Constructors
person.js:
function Person(name, age) {
this.name = name; this.age = age;
this.sayName = function() { console.log(name); }
this.sayAge = function() { console.log(age); }
}
exports.Person = Person;
main.js:
var Person = require("./person").Person;
var john = new Person("John", 30);
john.sayName(); // => John
CommonJS Modules
module
exports.write = function(msg) { console.log(msg); }
console.log(module);
{
id: '/Users/ashutosh/app/logger.js',
exports: { write: [Function] },
parent: { ... }, loaded: false, children: [],
filename: '/Users/ashutosh/app/logger.js',
paths: [
'/Users/ashutosh/app/node_modules',
'/Users/ashutosh/node_modules',
'/Users/node_modules',
'/node_modules'
]
}
CommonJS Modules
Support
- Node (server-side)
- Narwhal (server-side)
- curl.js (in-browser)
- SproutCore (in-browser)
- Browserify (in-browser)
- ...
Issues With CommonJS Modules
- require() is a synchronous call
- Only one module per file
- Additional steps required for modules to work in browsers
What can we do?
Asynchronous Module Definition
- Async loading of modules and dependencies
Asynchronous Module Definition
Asynchronous Module Definition
define()
define(
module_id, /* optional */
[ dependencies ], /* optional */
module_definition_function
);
Asynchronous Module Definition
define()
define(
['foo', 'bar'],
function(foo, bar) {
var myModule = {
foo: function() { /* ... */ }
};
return myModule;
}
);
Asynchronous Module Definition
require()
require(
[ dependencies ],
callback_function
);
require(
['foo', 'bar'],
function(foo, bar) {
foo.process(bar());
}
);
Asynchronous Module Definition
Dynamically Loading Dependencies
define(function(require) {
var ready = false, count = 0;
require(['foo', 'bar'], function(foo, bar) {
ready = true;
count = foo.count() + bar.count();
});
return {
ready: ready,
getCount: function() { return count; }
}
});
Asynchronous Module Definition
AMD Plugins
define(
['text!../templates/entry.html', 'foo'],
function (template, foo) {
// do something with the template text string.
}
);
Asynchronous Module Definition
Simplified CommonJS Wrapping
define(function(require, exports, module) {
// Traditional CommonJS module content here
});
define(function(require, exports, module) {
var foo = require('foo'),
bar = require('bar');
exports.foobar = function() { foo.count() + bar.count() };
});
Asynchronous Module Definition
Advantages
- Avoids pollution of the global namespace
- Load modules in the browser without a build process
- Multiple modules can be bundled in a single file
- Lazy loading of scripts is possible
Asynchronous Module Definition
Support
- RequireJS (in-browser)
- curl.js (in-browser)
- RequireJS (server-side)
- PINF (server-side)
- ...
RequireJS
Basic Usage
index.html:
<script data-main="scripts/main" src="require.js"></script>
scripts/main.js:
require(['helper/util'], function(util) {
// ...
});
RequireJS
Optimizer Tool
- Bundle JavaScript files and modules into a single file
- Minify the result
- Convert CommonJS modules to the AMD format
ES6 Modules
- Declarative syntax
- Loader API
ES6 Modules
Exporting
// circle.js
let function sq(n) { return n * n; } // private
export const PI = 3.14159
export function area(r) { return PI * r * r; }
// scripts/circle.js
const PI = 3.14159
function area(r) { return PI * r * r; }
export { PI, area };
export { PI as approximatePI, area };
ES6 Modules
Importing
// main.js
import { area } from 'scripts/circle';
console.log(area(5));
import { area, PI } from 'scripts/circle';
import 'scripts/circle' as circle;
console.log(circle.area(5));
import { area as areaOfCircle } from 'scripts/circle';
console.log(areaOfCircle(5));
ES6 Modules
Inline Modules
module 'scripts/circle' {
// ...
}
module 'scripts/rectangle' {
// ...
}
module 'foo' {
// ...
}
ES6 Modules
Module Loader API
System.import(
['foo', 'bar'],
function(foo, bar) { // success
// ...
},
function(error) { // failure
// ...
}
);
System.load(
'path/to/jquery.js',
function() {
// ...
}
);