Skip to content

Instantly share code, notes, and snippets.

@wycats
Last active December 23, 2015 05:19
Show Gist options
  • Save wycats/22a6105f2fa0b0bec235 to your computer and use it in GitHub Desktop.
Save wycats/22a6105f2fa0b0bec235 to your computer and use it in GitHub Desktop.

Module Interoperability

The goal of this specification is to define a subset of AMD-, Node- and Globals-style modules that can interoperate with each other, and to define a static semantics for reliably extracting the imports from modules written using this subset.

In general, this means limiting conditional or dynamic dependencies, so that all imports can be determined by parsing. The vast majority of existing modules are written using this subset.

A companion spec, AMD Wrapping for Interoperability, describes how to wrap these modules so that they can interoperate using the AMD definition and AMD-compatible loaders.

Definitions

  • relative dependency: a dependency that begins with ./ or ../.
  • top-level variable: the lexical binding to a free variable at the top-level of a module file. If the same name is re-bound in a child scope, references to the newly bound variable are not references to the top-level variable.

AMD example:

// this is a top-level call to `define`
define(["definitions", "b"], function(definitions, b) {
  definitions.forEach(function(define) {
    // `define` is re-bound by the parameter, so
    // this `define` is not a top-level call to `define`.
    define(1);
  });
}));
if (typeof define !== 'undefined') {
  // this is a top-level call to `define`
  define(["a", function(a) { /* factory body */ })
}

Node example:

// this is a top-level call to `require`
var requirements = require("requirements");

// this is an assignment to the top-level `module`
module.exports = requirements.map(function(require) {
  // `require` is re-bound by the parameter, so
  // this `require` is not a top-level call to `require`
  var module = require("test-suite");

  // `module` has been rebound, so this is not a top-level
  // assignment to `module.exports`
  module.exports = ["a", "b", "c"];
});

"amd"

Syntax

An AMD module contains a single call to the module's top-level define function.

If there is more than one call to the top-level define function, it is a SyntaxError.

If there are more than two arguments to the define function, it is a SyntaxError.

If the first argument to the define function (dependencies) is not an Array Literal, or any element of the Array Literal is not a String Literal, it is a SyntaxError.

If the second argument to the define function (factory) is not a Function Expression, it is a SyntaxError.

Static Semantics

If a module specifies relative dependencies, each of those dependencies MUST resolve to a file in the same package that meets these requirements.

Imports

The imports of an AMD module is the Array of Strings passed to the define function.

Exports
  1. Let exports be an empty list.
  2. If factory has at least one Return Statement, add default to exports.
  3. If dependencies does not have an exports element, return exports
  4. Let exportsIndex be the index of the exports dependency in dependencies.
  5. Let exportsBinding be the variable binding created by the argument at exportsIndex in the arguments list.
  6. Search factory for Assignment Expressions whose Reference is exportsBinding. For each such expression:
    1. Add the expression's Property Name to exports
  7. Return exports.

"node"

Any modules provided by the package MUST be valid Node modules. The name of the module will be derived from the location in the package.

Syntax

If a node module contains a call to the top-level require function, and it has more than one parameter, or the parameter is not a String Literal, it is a SyntaxError.

Imports
  1. Let imports be an empty list
  2. Let requires be a list of all Call Expressions that are top-level calls to the require function.
  3. For each require in requires:
    1. Add the first parameter to the Call Expression to imports
  4. Return imports
Exports
  1. Let exports be an empty list
  2. Let script be the script representing the node module
  3. If script has a top-level assignment to module.exports, add default to exports
  4. Let exportsBinding be the top-level binding with the name exports.
  5. Search script for Assignment Expressions whose Reference is exportsBinding. For each such expression:
    1. Add the expression's Property Name to exports
  6. Return exports.

"es6"

ES6 modules MUST be valid ModuleBodys.

The static semantics for imports and exports are the same as the static semantics in ES6.

@KidkArolis
Copy link

What about support for CJS sugar syntax in AMD, i.e.:

define(function (require) {
  var foo = require("foo");
  return "bar";
});

and

define(function (require, exports, module) {
  var foo = require("foo");
  module.exports = "bar";
});

That's the only way we author all our modules in my company.

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