John Hann @unscriptable
- co-founder of cujo.js
- engineer at SpringSource
- javascript barbarian everywhere else as @unscriptable
So awesome!
The future of modules is looking bright, too!
(Images adapted from http://instructables.com/, http://pocketprotectors.com/, and http://halloweencostumes.com/.)
export
a value with a name.import
a value exported by another
module by its name.Module
constructor
that represents a module. Its property names and values come
from the module's exports.(Shhhhhhh... I lifted most of this straight from @wycats.)
module.exports = ...
this == exports
Tutorials on AMD, CJS, and UMD modules: http://know.cujojs.com/
module "fu-ify" {
export function fuify (word) {
return word + '-fu';
};
let somethingElse = 5;
export somethingElse;
}
module "kung/fu" {
var kungfu;
import { fuify } from "fu-ify";
kungfu = fuify('kung');
export kungfu;
}
module "fubar" {
// #FAIL! SyntaxError
if (document.all) {
import { foolishness } from "uglyHacks/ie8";
export function addBubbler (node, event, listener) {
foolishness(event, listener);
}
}
else {
import { rainbows } from "shortcuts/w3c";
export function addBubbler (node, event, listener) {
rainbows(event, listener, false);
}
}
}
module "fu-ify" {
export function fuify (word) {
return word + '-fu';
};
}
module "kung/fu" {
var kungfu;
import { fuify: tofu } from "fu-ify";
kungfu = tofu('kung');
export kungfu;
}
// script "fu-ify.js"
export function fuify (word) {
return word + '-fu';
};
// script "kung/fu.js"
var kungfu;
import { fuify: tofu } from "fu-ify";
kungfu = tofu('kung');
export kungfu;
Why can't we export just a function or constructor, for instance? Why do we always have to export a "bag" of properties?
Here's how we already do it in AMD:
// script "fu-ify.js" in AMD format
define(function () {
return function (word) {
return word + '-fu';
};
});
// script "kung/fu.js" in AMD format
define(["fu-ify.js"], function (fuify) {
return fuify('kung');
});
And node:
// script "fu-ify.js" in node format
module.exports = function (word) {
return word + '-fu';
};
// script "kung/fu.js" in node format
var fuify = require("fu-ify");
module.exports = fuify('kung');
This is why it fails in ES6. Let's say that "fu-ify.js" is an AMD or node module that we want to use in an ES6 environment:
// script "fu-ify.js" in node format
module.exports = function (word) {
return word + '-fu';
};
// script "kung/fu.js"
var kungfu;
// nope:
import { ?????? } from "fu-ify";
kungfu = ??????('kung');
export kungfu;
Thankfully, TC39 has committed to making this work!
Here is one proposal for making this work in ES6:
// script "kung/fu.js"
var kungfu;
// oh look, a different import syntax!
import 'fu-ify' as fuify;
kungfu = fuify('kung');
export kungfu;
AMD | CommonJS | Node | AMD-wrapped CJS | ES6 |
---|---|---|---|---|
Can the imports/exports be determined with certainty without executing the code? |
||||
no | no | no | no | yes |
Factory / module "constructor" timing
When does the code inside the module execute? |
||||
just in time* | just in time | just in time | just in time* | just in time** |
Require / import timing
When is a dependency fetched? (Later is better.) |
||||
link time* | run time | run time | link time* | link time |
Anonymous modules
Modules without hard-coded ids are easier to configure and maintain. |
||||
allowed | always | always | allowed | allowed |
Named modules
Named modules are much simpler to concatenate. |
||||
allowed | nope | nope | allowed | allowed |
Bundling
Can modules be concatenated together into bundles? |
||||
yes | requires transport wrapper | requires transport wrapper | yes | yes |
<script>-friendly
Can modules be loaded with a script element? |
||||
yes, if named | nope | nope | yes, if named | yes, name == url? |
Inline modules
Can named modules be declared in the global scope or inside other modules? Useful for on-the-fly transpiling or mocks / stubs! |
||||
yes* | nope | nope | yes* | global only** |
Multiple versions / realms
Can modules be isolated somehow? Good for mocking or loading multiple versions of a package at one time. |
||||
yes* | PINF | nope? | yes* | yes** |
Circular dependencies
Can I write circular dependencies into my code? Oh don't. Just don't do this. |
||||
use CJS form | yes | use CJS form | use CJS form | of course! |
Exports
What types of modules can I create? |
||||
objects functions constructors arrays strings DOM nodes literals... |
objects | objects functions constructors arrays strings DOM nodes literals... |
objects functions constructors arrays strings DOM nodes literals... |
objects functions** constructors** arrays** strings** DOM nodes** literals**... |
Plugins
Can I directly load text, css, legacy javascript, or "foreign" modules? |
||||
yes, plugins | no | limited, by ext in node | yes, plugins | yes, loader overrides |
AMD | CommonJS | Node | AMD-wrapped CJS | ES6 |
* depends on AMD environment / tool
** tricky. needs more investigation
(I also stole this image from @wycats!)
***** NOTE: THE SPECS ARE A MOVING TARGET! THIS WILL ALL CHANGE! *****
A simple use case:
// load and run an app's "main" module
System.load("app/main",
function (main) {
main();
// get something that you know was loaded by "app/main":
System.get('app/socket').init();
},
function (ex) {
alert('drat! foiled again: ', ex.message);
}
);
Loader constructor:
var parent, loader;
parent = System;
loader = new Loader(parent, {
global: window,
baseURL: '../client/app',
linkedTo: null, // set the fundamental intrinsics of the modules
strict: true,
resolve: myResolver
fetch: myFetcher
translate: myTranslater
// etc.
});
Loader methods and properties:
Loader.prototype.global - the global object for all modules loaded with this loader. The default is not window!
Loader.prototype.baseURL - the "default location" for modules.
Loader.prototype.eval(src) - eval() the source code using this loader's scope and intrinsics.
Loader.prototype.evalAsync(src, callback, errback) eval() source code that may have remote dependencies.
Loader.prototype.get(id) - get a module that is already fetched / cached.
Loader.prototype.set(id, mod) - place a module into the loader's cache.
Loader.prototype.defineBuiltins({}) - define the fundamental intrinsics of all modules declared by this loader.
var loader = new Loader();
loader.load("app/main",
function (main) {
main();
// get something that you know was loaded by "app/main":
System.get('app/socket').init();
},
function (ex) {
alert('drat! foiled again: ', ex.message);
}
);
Each of the steps in the pipeline can be extended by "advising" the the methods:
var loader, origResolve;
loader = new Loader();
origResolve = loader.resolve;
loader.resolve = function (moduleId, options) {
if ('node' == options.type) {
// find in top-level node_modules folder
return { name: "node_modules/" + moduleId };
}
else {
return origResolve.apply(this, arguments);
}
};
normalize
should be async
resolve
step may have to communicate with a server processWhen can we expect to start using ES6 modules?
A: Whichever comes later: Fall 2016 or IE6-10 fade away
Ugh! Why so far away?
A: It'll take that long for implementations to work out the kinks and performance issues, unfortunately.
Also: the Loader
spec needs some work, imho.
What can I do now?
A: AMD or UMD
Our favorite UMD flavor:
(function (define) {
define(function (require) {
var awsm = require('something/awesome');
return somethingAwesomer;
}(
typeof define == 'function' and define.amd
? define
: function (factory) { module.exports = factory(require); }
));
Awww c'mon, can't I use ES6 modules now?
Yes. If you want to transpile and you don't mind making adjustments as the spec changes.
Note: The ES6 Module Transpiler offers zero transpilation from other ES5 or ES6
language features and syntax to ES3 or ES5. For instance, you cannot expect
Object.create()
to work in IE8. TypeScript offers much more transpilation of
these features, but comes at the cost of workflow and debugging complexity.
My suggestion: use a polyfill library such as cujo.js's poly.js (http://cujojs.com) or ES5 shim (https://github.com/kriskowal/es5-shim).
Fighting over .json
files
"Do it my way" attitude
Fanbois, fanbois, fanbois!
Competition is good, but so is a bit of certainty.
Mat Scales says it well: http://wibblycode.wordpress.com/2013/01/01/the-state-of-javascript-package-management/
A great gist by @wycats about ES6 modules and loaders: https://gist.github.com/wycats/51c96e3adcdb3a68cbc3
The original modules proposal (likely outdated, atm): http://wiki.ecmascript.org/doku.php?id=harmony:modules
Some examples of ES6 modules (some parts are outdated): http://wiki.ecmascript.org/doku.php?id=harmony:modules_examples
Here are some tutorials about current js modules (in raw form until the web site goes up): http://know.cujojs.com/
Here's another one "in the works": https://github.com/know-cujojs/know/blob/john/modules/004-consuming-amd-modules.md
A really great read by David Herman, the TC39 lead on modules: http://calculist.org/blog/2012/06/29/static-module-resolution/
You know you've got some.