Skip to content

Instantly share code, notes, and snippets.

@unscriptable
Last active February 12, 2024 00:36
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save unscriptable/5304442 to your computer and use it in GitHub Desktop.
Save unscriptable/5304442 to your computer and use it in GitHub Desktop.
"slides" for Boston Javascript Meetup Group, "The future of JS modules", April 4, 2013

What's the future look like?

Can you picture it?

  • a container
  • anonymous or tagged
  • protection from leaky things
  • totally customizable

Show of hands

o |o \o o o| \o o |o o/ \o

Terms

  • module: a unit of source code with optional imports and exports.
  • export: a module can export a value with a name.
  • imports: a module can import a value exported by another module by its name.
  • module instance object: an instance of the Module constructor that represents a module. Its property names and values come from the module's exports.
  • loader: an object that defines how modules are fetched, translated, and compiled into a module instance object. Each JavaScript environment (the browser, node.js) defines a default loader that defines the semantics for that environment.
  • package: a collection of related modules that are part of a larger solution.
  • realm: an isolated "universe of modules" within a loader.
  • TC39: the team that has been nominated to develop and specify ES6!

(Shhhhhhh... I lifted most of this straight from @wycats.)

Existing module formats

  • AMD: Asynchronous Module Definition
    • RequireJS, curl.js, Inject.js, etc.
    • dojo, MooTools
  • CJS: CommonJS Modules/1.1
    • node.js, RingoJS, Ember, etc.
  • node: CJS with extensions
    • module.exports = ...
    • this == exports
  • UMD: Universal Module Definition
    • AMD + CJS in one file
  • Others: YUI, Ext, etc.

Tutorials on AMD, CJS, and UMD modules: http://know.cujojs.com/

ES6 Module format

The basics

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 names are just string literals
    • realistically, they should be compatible with file names and urls
  • import { names } from name;
  • export thing;

Static analysis

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);
		}
	}
}

Renaming imports

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;
}

Anonymous modules

// 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;

Function-modules or anonymous exports???

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!

Screen Shot 2013-04-04 at 12.05.31 PM

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;

Module semantics

Comparison

AMDCommonJSNodeAMD-wrapped CJSES6

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
AMDCommonJSNodeAMD-wrapped CJSES6

* depends on AMD environment / tool

** tricky. needs more investigation

Module loaders

Why?

  • Dynamically load chunks of an app
  • Load non-ES6 modules and text-based resources
    • AMD, CJS
    • HTML templates, CSS
    • CoffeeScript, LESS, others

Loader pipeline

(I also stole this image from @wycats!)

Using the System loader

***** 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.

Creating a new 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);
	}
);

Extending a loader

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);
	}
};

What's still under-specified or missing? (IMHO)

  • Entire pipeline after normalize should be async
    • resolve step may have to communicate with a server process
    • scan "node_modules" folders
  • No clear way to specify how non-browser-friendly things are wrapped
    • node and CJS modules
    • LESS and SASS files

Timeline

When 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).

cujojs/curl demo?

More info

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/

Questions!

You know you've got some.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment