| This chapter is outdated and basically kept for historical reasons. |
There exist several types of dependencies between modules, distinguishable by the time when the dependency is relevant. We first define these dependencies lazily to give an impression of the problem, at more rigorously later on.
Dependency needed at compile time. These type of dependency is removed by the compiler. These are basically type references used in variable or function declarations.
Runtime dependencies are to be handled at runtime in general. We distinguish two special types of runtime dependencies:
A loadtime dependency is a special runtime dependency that needs to be resolved before a module is initialized, that is, when all top-level statements of a module, containing class declarations, are executed. This usually is a types super type (e.g., super class), or a call to a function (defined in a different module) in a static initializer or module top level statement.
An execution time dependency is a non-initialization runtime dependency. That is, when a method is called (from another module), this is execution time.
Of course, before a module can be loaded, it needs to be fetched (i.e., the actual code has to be retrieved by the browser).
We can define sets containing modules which a given module depends on. Note that these sets contain each other, as shown in Euler Dependencies. However, we define disjoint sets in which a dependency to another type is only contained in one of the sets.
Given a code sequence , we define the set of accessed modules in it as .
describes all function calls happening in code block , i.e. . In case calls on functions , we define a function’s body code sequence as .
The complete set of accessed modules for a particular code sequence is then defined as ]
We explicitly allow a function to be excluded from being incorporated in the above algorithm by annotating it.
The set of load-time-dependencies for a module with initializer code is then defined as math:[\[\begin{aligned} load-time-deps := AccessdModules( SuperClass(M) ) + AccessdModules( IC(M) ) \end{aligned}\]]
Before ES6, Javascript had no built in support for modules. To overcome this hurdle, the two widely accepted formats have been :
CommonJS : Primarily aimed at synchronous module loading. The main implementation of this format is seen in Node.js
var value = 100;
function inc() {
value++;
}
module.exports = {
value : value,
inc : inc
}
Import via require.
AMD : Primarily aimed at asynchronous module loading in browsers. The main implementation of this format is seen in RequireJS.
define('myModule', ['mod1', 'mod2'], function (mod1, mod2) {
return {
myFunc: function(x, y) {
..
}
};
};
});
passive format
The ES6 spec introduces modules. ES6 modules resemble CommonJS syntax with AMD like asynchronous loading support.
Apart from the syntactic details, the highlights of ES6 modules are :
ES6 modules support (multiple) named exports.
export const VERSION = "1.0.1";
export function inc(x) {
return x + 1;
}
ES6 modules support default exports.
export default function (x) {
return x+1;
}
As specified here, ES6 modules export live immutable bindings (instead of values).
This behaviour is different from that of CommonJS and AMD modules where a snapshot of the value is exported.
|
An example demonstrating the behavioural difference :
//-------------src.js------------
var value = 100;
function inc() {
value++;
}
module.exports = {
value : value,
inc : inc
}
//-------------main.js------------
var src = require("./src"); //import src
console.log(src.value); //prints 100
src.inc();
console.log(src.value); //prints 100 <--- The value does not update.
src.value = 65;
console.log(src.value); //prints 65 <--- The imported value is mutable.
The same example with ES6 modules :
//-------------src.js------------
export var value = 100; // <--- ES6 syntax
export function inc() { // <--- ES6 syntax
value++;
}
//-------------main.js------------
import {value, inc} from "src" // <--- ES6 syntax
console.log(value); //prints 100
inc();
console.log(value); //prints 101 <--- The value is a live binding.
value = 65; // <--- throws an Error implying the binding is immutable.
ES6 modules impose a static module structure i.e. the imports and exports can be determined at compile time (statically).
For resolving module dependencies and loading modules, the JS landscape provides a few different module loaders.
RequireJS is the loader of choice for in browser, AMD style modules. We currently transpile our code into an AMD-style format to allow it running in both Browser and Node.js environments.
Node.js provides a native loader implementation for CommonJS style modules.
For browsers (primarily), tools like Webpack and Browserify exist. These tools analyse the dependency graph of the entire project and then bundle up all the dependencies in a single file. Browserify only supports CommonJS modules where as Webpack works with both CommonJS & AMD style modules.
At the time of writing this document (August 2015), there does not exist any native implementation for ES6 modules by any Javascript host environments i.e. ES6 modules are not natively supported by browsers or Node.js, as of now.
[fig:moduelLoader] shows an overview.
The ES6 spec started out with ES6 Module Loader details as part of the spec. However the Working Group later decided to not proceed with it. The specification for ES6 Module Loader is now a separate specification [WhatWGLoader].
The aim of this specification is:
This specification describes the behavior of loading JavaScript modules from a JavaScript host environment. It also provides APIs for intercepting the module loading process and customizing loading behavior.
The Implementation status of the spec states :
It is too early to know about the Loader, first we need ES2015 modules implemented by the various engines.
Although there is no native support for ES6 module loading, there are a few attempts to polyfill this gap.
The es6-module-loader project provides a polyfill for the ES6 Module Loader implementation. It dynamically loads ES6 modules in browsers and Node.js with support for loading existing and custom module formats through loader hooks.
Building upon es6-module-loader, SystemJS supports loading ES6 modules along with AMD, CommonJS and global scripts in the browser and Node.js.
A demonstration of how to how to use ES6 modules with Babel and SystemJS in Node.js as of today (August 2015).
Create an ES6 module as shown:
export var value = 100; // <--- named export of a variable
export function inc() { // <--- named export of a function
value++;
}
Import the bindings from the module as shown:
import {value, inc} from "src"
var importedValue = value; // <--- using the imported value
inc(); // <--- using the imported function
Transpile these two files using Babel to ES5 with the target module format as system, as shown:
$ babel <inputdir> --out-dir <outputdir> --modules system
The transpiled output should be resemble the following:
System.register([], function (_export) {
"use strict";
var value;
_export("inc", inc);
function inc() {
_export("value", value += 1);
}
return {
setters: [],
execute: function () {
value = 100;
_export("value", value);
}
};
});
System.register(["src"], function (_export) {
"use strict";
var value, inc, importedValue;
return {
setters: [function (_src) {
value = _src.value;
inc = _src.inc;
}],
execute: function () {
importedValue = value;
inc();
}
};
});
Finally run the above transpiled files, as shown:
var System = require('systemjs'); // <--- Require SystemJS
System.transpiler = 'babel'; // <--- Configure SystemJS
System.import('main'); // <--- Import the transpiled "main" module.
This section is NOT an exhaustive introduction to Microsoft’s TypeScript, but a narrowed down analysis of certain aspects of TypeScript.
|
TypeScript language has recently added support for ES6 modules. From the wiki :
TypeScript1.5 supportsECMAScript 6(ES6) modules. ES6 modules are effectivelyTypeScriptexternal modules with a new syntax: ES6 modules are separately loaded source files that possibly import other modules and provide a number of externally accessible exports. ES6 modules feature several new export and import declarations.
TypeScript does not concern itself with providing a module loader. It is the responsibility of the host environment. However TypeScript’s compiler provides options to transpile the modules to different formats like AMD, CommonJS, ES6 etc. It is the developer’s responsibility to choose an appropriate format and then use the modules with a correct module loader.
From the wiki again :
TypeScript supports down-level compilation of external modules using the new ES6 syntax. When compiling with
-t ES3or-t ES5a module format must be chosen using-m CommonJSor-m AMD. When compiling with-t ES6the module format is implicitly assumed to beECMAScript 6and the compiler simply emits the original code with type annotations removed. When compiling down-level forCommonJSorAMD, named exports are emitted as properties on the loader supplied exports instance. This includes default exports which are emitted as assignments to exports.default.
Consider the following module src.ts :
export var value = 100; //<--- ES6 syntax
export function inc() { //<--- ES6 syntax
value++;
}
Compiling it to SystemJS format produces :
System.register([], function(exports_1) {
var value;
function inc() {
(exports_1("value", ++value) - 1);
}
exports_1("inc", inc);
return {
setters:[],
execute: function() {
exports_1("value", value = 100); //<--- ES6 syntax
}
}
});
Compiling it to CommonJS format produces :
exports.value = 100; //<--- ES6 syntax
function inc() {
exports.value++;
}
exports.inc = inc;
Compiling it to AMD format produces :
define(["require", "exports"], function (require, exports) {
exports.value = 100; //<--- ES6 syntax
function inc() {
exports.value++;
}
exports.inc = inc;
});
Compiling it to UMD format produces :
(function (deps, factory) {
if (typeof module === 'object' && typeof module.exports === 'object') {
var v = factory(require, exports); if (v !== undefined) module.exports = v;
}
else if (typeof define === 'function' && define.amd) {
define(deps, factory);
}
})(["require", "exports"], function (require, exports) {
exports.value = 100; //<--- ES6 syntax
function inc() {
exports.value++;
}
exports.inc = inc;
});
NOTE :
Visual Studio 2015 does not support ES6 modules at this time.
SystemJS supports TypeScript as a compiler. This implies TypeScript modules can be transpiled to be used with SystemJS as the module loader.
To better analyse and evaluate SystemJS module loader and different module formats, let’s look at a cyclic dependency example from a (extremely simplified) stdlib task FixedPoint6. The outline for the example is :
Prepare 2 ES6 modules with a circular dependency.
Then transpile these modules to different module formats (e.g. AMD, & SystemJS).
With SystemJS as the module loader, execute the test for every transpiled module format.
Consider the following ES6 listings:
RoundingMode
export default {
FLOOR : "FLOOR",
CEILING : "CEILING"
}
MathContext
import { default as FixedPoint6 } from "FixedPoint6";
import { default as RoundingMode } from "RoundingMode";
let MathContext = class {
constructor(mode) {
this.mode = mode;
}
divide(fp1, fp2) {
var quotient = FixedPoint6.getQuotient(fp1, fp2);
if(this.mode === RoundingMode.CEILING) {
return new FixedPoint6(Math.ceil(quotient));
} else if(this.mode === RoundingMode.FLOOR) {
return new FixedPoint6(Math.floor(quotient));
} else {
throw new Error("Incorrect RoundingMode");
}
}
}
MathContext.FLOOR = new MathContext(RoundingMode.FLOOR);
MathContext.CEILING = new MathContext(RoundingMode.CEILING);
export default MathContext;
FixedPoint6
import { default as MathContext } from "MathContext";
export default class FixedPoint6 {
constructor(number) {
this.value = number;
}
static getQuotient(fp1, fp2) {
return fp1.value/fp2.value;
}
divide(fp) {
return FixedPoint6.defaultContext.divide(this, fp);
}
}
FixedPoint6.defaultContext = MathContext.FLOOR;
Test
import { default as FixedPoint6 } from "FixedPoint6";
import { default as MathContext } from "MathContext";
import { default as RoundingMode } from 'RoundingMode';
var fp1 = new FixedPoint6(20.5);
var fp2 = new FixedPoint6(10);
var fp3 = fp1.divide(fp2);
console.log(fp1, fp2, fp3);
Runner : This is the runner file to execute the test (after transpilation).
var System = require('systemjs');
System.transpiler = 'babel';
System.config({
baseURL: './build',
"paths": {
"*": "*.js"
}
});
System.import('test').catch(function(e) {
console.log(e);
})
Clearly MathContext & FixedPoint6 have a circular dependency upon each other.
Transpile the above setup to different formats and execute the code using SystemJS module loader :
Compile the previous set up using babel as the transpiler with AMD modules :
babel src -w --out-dir build --modules amd
Run the transpiled test.js with :
node runner.js
The execution would fail with an error like the following :
Error: _FixedPoint62.default.getQuotient is not a function
Compile the previous set up using babel as the transpiler with CommonJS modules :
babel src -w --out-dir build --modules common
Run the transpiled test.js with :
node runner.js
The execution is successful and logs the following results :
{ value: 20.5 } { value: 10 } { value: 2 }
Compile the previous set up using babel as the transpiler with SystemJS modules :
babel src -w --out-dir build --modules system
Run the transpiled test.js with :
node runner.js
The execution is successful and logs the following results :
{ value: 20.5 } { value: 10 } { value: 2 }
As observed, the test is executed successfully with CommonJS & SystemJS module formats. It however fails with AMD format (due to the circular dependency).
In order to integrate SystemJS as the module loader, the recommended module format is System.register. This section serves as a guide (& implementation hint) to transpile N4JS modules with System.register as the module format.
This format is best explained from its documentation :
System.register can be considered as a new module format designed to support the exact semantics of ES6 modules within ES5. It is a format that was developed out of collaboration and is supported as a module output in Traceur (as instantiate), Babel and TypeScript (as system). All dynamic binding and circular reference behaviors supported by ES6 modules are supported by this format. In this way it acts as a safe and comprehensive target format for the polyfill path into ES6 modules.
To run the format, a suitable loader implementation needs to be used that understands how to execute it. Currently these include SystemJS, SystemJS Self-Executing Bundles and ES6 Micro Loader. The ES6 Module Loader polyfill also uses this format internally when transpiling and executing ES6.
The System.register format is not very well documented. However, this format is supported by all major transpilers out there i.e. BabelJS, Traceur & TypeScript transpilers. In fact, the primary resource of this documentation has been the outputs generated by these transpilers.
In order to follow along, it will be best to try out different ES6 syntax being transpiled to System.register format by these transpilers.
The following instructions will be useful :
Transpile with Traceur
traceur --dir <SOURCE_DIR> <OUTPUT_DIR> --experimental --modules=instantiate
Transpile with Babel
babel <SOURCE_DIR> --out-dir <OUTPUT_DIR> --modules system
Transpile with TypeScript
Create a file by the name of tsconfig.json in the project folder, with the following contents :
{
"compilerOptions": {
"module": "system",
"target": "ES5",
"outDir": <OUTPUT_DIR>,
"rootDir": <SOURCE_DIR>
}
}
Then transpile with :
tsc
For the following ES6 code :
import { p as q } from './dep';
var s = 'local';
export function func() {
return q;
}
export class C {}
The Babel transpiler generates the following code (w/ System.register format):
System.register(['./dep'], function (_export) {
'use strict';
var q, s, C;
_export('func', func);
function _classCallCheck(instance, Constructor) { .. }
function func() {
return q;
}
return {
setters: [function (_dep) {
q = _dep.p;
}],
execute: function () {
s = 'local';
C = function C() {
_classCallCheck(this, C);
};
_export('C', C);
}
};
});
Broadly speaking, a System.register module has the following structure :
System.register(<<DEPENDENCIES ARRAY>>, function(<<exportFn>>) {
<<DECLARATION SCOPE>>
return {
setters: <<SETTERS ARRAY>>,
execute: function() {
<<EXECUTABLES>>
}
};
});
Highlights :
System.register(…) is called with 2 arguments :
<<DEPENDENCIES ARRAY>> : an array of dependencies (similar to AMD) extracted from the ES6 import statements.
a function (FN) :
accepts a parameter called <<exportFn>>. This <<exportFn>> (provided by SystemJS) keeps a track of all the exports of this module & will inform all the importing modules of any changes.
contains a <<DECLARATION SCOPE>> where all the functions and variables (from the source code) get hoisted to.
returns an object with 2 properties :
setters : The <<SETTERS ARRAY>> is simply an array of functions. Each of these functions represents the imported-bindings from the <<DEPENDENCIES ARRAY>>. The <<SETTERS ARRAY>> and <<DEPENDENCIES ARRAY>> follow the same order.
execute : <<EXECUTABLES>> is the rest of the body of the source code.
By observing the existing transpilers’ output, this sub-section provides insights into the process of transpiling to this format :
The following are ES6 code snippets with some import statements :
Simple Import Statement
import {b1} from 'B';
A valid System.register output for this snippet would look like :
System.register(['B'], function (<<exportFn>>) { //(1.)
var b1; //(2.)
return {
//(3.)
setters: [function (_B) {
b1 = _B.b1; //(4.)
}],
execute: function () {}
};
});
| highlights |
|
| Takeaway |
|
Multiple Import Statements
import { a1 as a0 } from 'A';
import {b1} from 'B';
import { c1 as c0 } from 'C';
import {b2, b3} from 'B';
import {default} from 'C';
import {a2} from 'A';
A valid System.register output for this snippet would look like :
System.register(['A', 'B', 'C'], function (<<exportFn>>) { //(1.)
var a0, a2, b1, b2, b3, c0, default; //(2.)
return {
//(3.)
setters: [function (_A) {
a0 = _A.a1; //(4.1.)
a2 = _A.a2; //(4.2.)
}, function (_B) {
b1 = _B.b1;
b2 = _B.b2;
b3 = _B.b3;
}, function (_C) {
c0 = _C.c1;
default = _C['default'];
}],
execute: function () {}
};
});
| highlights |
|
| Takeaway |
|
Before moving on to handling exports, let’s focus on the SystemJS provided <<exportFn>>.
This function looks similar to the following :
function (name, value) { //(1.)
module.locked = true;
if (typeof name == 'object') {
for (var p in name)
exports[p] = name[p]; //(2.1.)
}
else {
exports[name] = value; //(2.2.)
}
for (var i = 0, l = module.importers.length; i < l; i++) {
var importerModule = module.importers[i];
if (!importerModule.locked) {
var importerIndex = indexOf.call(importerModule.dependencies, module);
importerModule.setters[importerIndex](exports); //(3.)
}
}
module.locked = false;
return value; //(4.)
}
| highlights |
|
| Takeaway |
This |
Now let’s focus on handling export statements.
Simple Exports Statement
export var v =1;
export function f(){}
A valid System.register output for this snippet would look like :
System.register([], function (_export) { //(1.)
//(2.)
var v;
function f() {}
_export("f", f); //(4.1)
return {
setters: [],
//(3.)
execute: function () {
v = 1; //(3.1.)
_export("v", v); //(4.2.)
}
};
});
| highlights |
|
| Takeaway |
|
Tracking Exported Bindings
To maintain live bindings, SystemJS needs to track any changes to exported bindings in order to call the setter functions of importing modules. Let’s look at an example for that :
export var v1 = 1;
export var v2 = 2;
export var v3 = 3;
export function f() {}
v1++; //(1.)
++v2; //(2.)
v3 += 5; //(3.)
f = null; //(4.)
Babel output for this snippet looks like :
System.register([], function (_export) {
var v1, v2, v3;
_export("f", f);
function f() {}
return {
setters: [],
execute: function () {
v1 = 1;
_export("v1", v1);
v2 = 2;
_export("v2", v2);
v3 = 3;
_export("v3", v3);
_export("v1", v1 += 1); //(1.)
_export("v2", v2 += 1); //(2.)
_export("v3", v3 += 5); //(3.)
_export("f", f = null); //(4.)
}
};
});
Traceur output for this snippet looks like :
System.register([], function($__export) {
var v1, v2, v3;
function f() {}
$__export("f", f);
return {
setters: [],
execute: function() {
v1 = 1;
$__export("v1", v1);
v2 = 2;
$__export("v2", v2);
v3 = 3;
$__export("v3", v3);
($__export("v1", v1 + 1), v1++); //(1.)
$__export("v2", ++v2); //(2.)
$__export("v3", v3 += 5); //(3.)
$__export("f", f = null); //(4.)
}
};
});
TypeScript output for this snippet looks like :
System.register([], function(exports_1) {
var v1, v2, v3;
function f() { }
exports_1("f", f);
return {
setters:[],
execute: function() {
exports_1("v1", v1 = 1);
exports_1("v2", v2 = 2);
exports_1("v3", v3 = 3);
(exports_1("v1", ++v1) - 1); //(1.)
exports_1("v2", ++v2); //(2.)
exports_1("v3", v3 += 5); //(3.)
f = null; //(4.)
}
}
});
| highlights |
|
| Takeaway |
|
Exporting a Class extending an imported Class.
Let’s look at the following class declaration :
import {A} from "A"; //<-- import class A
export class C extends A {}
Babel output for this snippet looks like :
System.register(["A"], function (_export) {
var A, C;
var _get = function get(_x, _x2, _x3) { ... };
function _classCallCheck(instance, Constructor) { ... }
function _inherits(subClass, superClass) { ... }
return {
setters: [function (_A2) {
A = _A2.A;
}],
execute: function () { //(1.)
C = (function (_A) {
_inherits(C, _A);
function C() {
_classCallCheck(this, C);
_get(Object.getPrototypeOf(C.prototype), "constructor", this).apply(this, arguments);
}
return C;
})(A);
_export("C", C);
}
};
});
Traceur output for this snippet looks like :
System.register(["A"], function($__export) {
var A, C;
return {
setters: [function($__m) {
A = $__m.A;
}],
execute: function() { //(1.)
C = $traceurRuntime.initTailRecursiveFunction(function($__super) {
return $traceurRuntime.call(function($__super) {
function C() {
$traceurRuntime.superConstructor(C).apply(this, arguments);
}
return $traceurRuntime.continuation($traceurRuntime.createClass, $traceurRuntime, [C, {}, {}, $__super]);
}, this, arguments);
})(A);
$__export("C", C);
}
};
});
TypeScript output for this snippet looks like :
System.register(["A"], function(exports_1) {
var __extends = function(){ ... }
var A_1;
var C;
return {
setters:[
function (A_1_1) {
A_1 = A_1_1;
}],
execute: function() { //(1.)
C = (function (_super) {
__extends(C, _super);
function C() {
_super.apply(this, arguments);
}
return C;
})(A_1.A);
exports_1("C", C);
}
}
});
| highlights |
|
| Takeaway |
|
This section focuses on circular dependencies. The goal is to see how the transpiled output looks like and if the execution is possible.
Source files:
import B from "B";
export default class A {}
A.b = new B(); //<---
import A from "A";
export default class B {}
B.a = new A(); //<---
Transpiled Outputs (w/ Babel):
System.register(["B"], function (_export) {
"use strict";
var B, A;
function _classCallCheck(instance, Constructor) {...}
return {
setters: [function (_B) {
B = _B["default"];
}],
execute: function () {
A = function A() {
_classCallCheck(this, A);
};
_export("default", A);
A.b = new B();
}
};
});
System.register(["A"], function (_export) {
"use strict";
var A, B;
function _classCallCheck(instance, Constructor) {...}
return {
setters: [function (_A) {
A = _A["default"];
}],
execute: function () {
B = function B() {
_classCallCheck(this, B);
};
_export("default", B);
B.a = new A();
}
};
});
Execution Result
var System = require('systemjs');
System.import('A', 'B').then(function(resp) {
var a = new A();
var b = new B();
}).catch(function(e) {
console.log(e);
});
Babel : [Error: undefined is not a function]
Source files:
import {B} from "B";
export class A extends B{}
import {A} from "A";
export class B{}
class C extends A{}
Transpiled Outputs (w/ Babel) :
System.register(["B"], function (_export) {
"use strict";
var B, A;
var _get = function get(_x, _x2, _x3) { ... };
function _classCallCheck(instance, Constructor) { ... }
function _inherits(subClass, superClass) {...}
return {
setters: [function (_B2) {
B = _B2.B;
}],
execute: function () {
A = (function (_B) {
_inherits(A, _B);
function A() {
_classCallCheck(this, A);
_get(Object.getPrototypeOf(A.prototype), "constructor", this).apply(this, arguments);
}
return A;
})(B);
_export("A", A);
}
};
});
System.register(["A"], function (_export) {
"use strict";
var A, B, C;
var _get = function get(_x, _x2, _x3) { ... };
function _inherits(subClass, superClass) { ... }
function _classCallCheck(instance, Constructor) { ... }
return {
setters: [function (_A2) {
A = _A2.A;
}],
execute: function () {
B = function B() {
_classCallCheck(this, B);
};
_export("B", B);
C = (function (_A) {
_inherits(C, _A);
function C() {
_classCallCheck(this, C);
_get(Object.getPrototypeOf(C.prototype), "constructor", this).apply(this, arguments);
}
return C;
})(A);
}
};
});
Execution Result
var System = require('systemjs');
System.import('A','B').then(function(resp) {
var a = new A();
}).catch(function(e) {
console.log(e);
});
TypeScript : [Error: Cannot read property 'prototype' of undefined]
Babel : [Error: Super expression must either be null or a function, not undefined]
Source files:
import B from "B";
class A extends B {}
export default class X {}
import X from "A";
export default class B {}
class Y extends X {}
Transpiled Outputs (w/ Babel):
System.register(["B"], function (_export) {
"use strict";
var B, A, X;
var _get = function get(_x, _x2, _x3) { ... };
function _classCallCheck(instance, Constructor) { ... }
function _inherits(subClass, superClass) { ... }
return {
setters: [function (_B2) {
B = _B2["default"];
}],
execute: function () {
A = (function (_B) {
_inherits(A, _B);
function A() {
_classCallCheck(this, A);
_get(Object.getPrototypeOf(A.prototype), "constructor", this).apply(this, arguments);
}
return A;
})(B);
X = function X() {
_classCallCheck(this, X);
};
_export("default", X);
}
};
});
System.register(["A"], function (_export) {
"use strict";
var X, B, Y;
var _get = function get(_x, _x2, _x3) { ... };
function _inherits(subClass, superClass) { ... }
function _classCallCheck(instance, Constructor) { ... }
return {
setters: [function (_A) {
X = _A["default"];
}],
execute: function () {
B = function B() {
_classCallCheck(this, B);
};
_export("default", B);
Y = (function (_X) {
_inherits(Y, _X);
function Y() {
_classCallCheck(this, Y);
_get(Object.getPrototypeOf(Y.prototype), "constructor", this).apply(this, arguments);
}
return Y;
})(X);
}
};
});
Execution Result
var System = require('systemjs');
System.import('A').then(function(resp) {
var a = new A();
}).catch(function(e) {
console.log(e);
});
TypeScript : [Error: Cannot read property 'prototype' of undefined]
Babel : [[Error: Super expression must either be null or a function, not undefined]]
In order to improve our precision in conversing and discussing about different kinds of circular dependencies, this section provides the most basic examples of different kinds.
Below examples demonstrate cases when cyclic dependency cannot be resolved at all and will cause runtime errors.
import b from "B"
export public var number a = 1;
export public var number a2 = b + 1;
import a from "A"
export public var number b = a + 1;
import a2 from "A"
console.log(a2); //<-- should be 3. not NaN.
import B from "B"
export public class A {
static a = B.b + 1;
}
import A from "A"
export public class B {
static b = 1;
}
export public class B2 {
static b2 = A.a;
}
import B2 from "B"
console.log(B2.b2); //should log 2
import B from "B"
export public class A {
B b = new B();
}
import A from "A"
export public class B {
A a = new A();
}
import A from "A"
new A(); // should not cause a runtime error.
import b_fun from "B"
export public var a2 = b_fun();
export public var a = 1;
import a from "A"
export public function b_fun() {
return a + 1;
}
import a2 from "A"
console.log(a2); //<-- should be 2. not NaN.
import B from "B"
export public class A {
static a1 = 1;
static a2 = B.b1;
}
import A from "A"
export public class B {
static b1 = A.a1;
}
import A from "A"
console.log(A.a1); //should log 1. not an error.
import B from "B"
export public class A {
static a1 = 1;
static a2 = B.b1;
}
import A from "A"
export public class B {
static b1 = -1;
static b2 = A.a1;
}
import A from "A"
console.log(A.a1);//should log 1. not an error.
import B from "B"
export public class A {
static a = new B();
}
import A from "A"
export public class B {
static b = new A();
}
import A from "A"
new A(); //should succeed.
import B from "B"
export public class A {}
export public class C extends B {}
import A from "A"
export public class B extends A{}
import C from "A"
new C();//should succeed.
import B from "B"
export public class A {}
export public class C {
c = new B();
}
import A from "A"
export public class B extends A{}
import C from "A"
new C(); //should succeed.
import B from "B"
export public class A {}
new B();
import A from "A"
export public class B {}
new A();
import A from "A"
new A() //should succeed.
import B from "B"
export public class A {}
B.b1;
import A from "A"
export public class B {
static b1;
}
new A();
import A from "A"
new A() //should succeed.
To provide better compatibility with npm eco-system, we want to transpile N4JS code to CommonJS module format.
A sample CommonJS module :
var lib1 = require("/lib1"); //<-- require
var lib2 = require("/lib2"); //<-- require
function fn() {
//...something using 'lib1' & 'lib2'
}
exports.usefulFn = fn; //<--exports
exports.uselessValue = 42; //<--exports
The CommonJS spec describes the salient features of module format as (quoted verbatim) :
Module Context
In a module, there is a free variable "require", that is a function.
The "require" function accepts a module identifier.
"require" returns the exported API of the foreign module.
If there is a dependency cycle, the foreign module may not have finished executing at the time it is required by one of its transitive dependencies; in this case, the object returned by "require" must contain at least the exports that the foreign module has prepared before the call to require that led to the current module’s execution.
If the requested module cannot be returned, "require" must throw an error.
In a module, there is a free variable called "exports", that is an object that the module may add its API to as it executes.
modules must use the "exports" object as the only means of exporting.
Module Identifiers
A module identifier is a String of "terms" delimited by forward slashes.
A term must be a camelCase identifier, ".", or "..".
Module identifiers may not have file-name extensions like ".js".
Module identifiers may be "relative" or "top-level". A module identifier is "relative" if the first term is "." or "..".
Top-level identifiers are resolved off the conceptual module name space root.
Relative identifiers are resolved relative to the identifier of the module in which "require" is written and called.
This section examines how Babel transpiles ES6 modules to CommonJS format. By observing the transpiled output from Babel, we can gather insights for transpiling N4JS modules to CommonJS format.
import "B";
console.log(B);
"use strict";
require("B");
console.log(B);
import {b1} from "B";
b1;
"use strict";
var _B = require("B");
_B.b1;
import {b1, b2} from "B";
b1;
b2;
"use strict";
var _B = require("B");
_B.b1;
_B.b2;
import {b3 as b4} from "B";
b4 + 1;
"use strict";
var _B = require("B");
_B.b3 + 1;
import {b3 as b4, b5 as b6} from "B";
b4 + 1;
b6 + 1;
"use strict";
var _B = require("B");
_B.b3 + 1;
_B.b5 + 1;
import * as B from "B";
console.log(B);
"use strict";
function _interopRequireWildcard(obj) {
//Babel internally tracks ES6 modules using a flag "__esModule".
if (obj && obj.__esModule) {
return obj;
} else {
//Copy over all the exported members.
var newObj = {};
if (obj != null) {
for (var key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key];
}
}
//Set the "default" as the obj itself (ES6 default export)
newObj["default"] = obj;
return newObj;
}
}
var _B = require("B");
var B = _interopRequireWildcard(_B);
console.log(B);
import B from "B";
console.log(B);
"use strict";
//For importing a default export,
//Babel checks if the obj is an ES6 module or not.
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { "default": obj };
}
var _B = require("B");
var _B2 = _interopRequireDefault(_B);
console.log(_B2["default"]);
let a = 1;
export {a};
"use strict";
//Babel makes a note that this is as an ES6 module.
//This information is later used when this module is imported.
Object.defineProperty(exports, "__esModule", {
value: true
});
var a = 1;
exports.a = a;
let a = 1;
let b = true;
export {a, b};
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var a = 1;
var b = true;
exports.a = a;
exports.b = b;
let a =1;
export {a as b};
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var a = 1;
exports.b = a;
let a = 1, b = 2;
export {a as A, b as B};
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var a = 1,
b = 2;
exports.A = a;
exports.B = b;
export default 42;
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = 42; //<-- default export is treated as a special named export
module.exports = exports["default"]; //<-- IMPORTANT
let x =10;
export {x as default};
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var x = 10;
exports["default"] = x;
module.exports = exports["default"];
let a = 1;
export {a};
export default 42;
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var a = 1;
exports.a = a;
exports["default"] = 42;
export default class A {}
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
function _classCallCheck(...) { ... }
var A = function A() {
_classCallCheck(this, A);
};
exports["default"] = A;
module.exports = exports["default"];
export * from "A"
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
function _interopExportWildcard(obj, defaults) {
var newObj = defaults({}, obj);
delete newObj["default"]; //<-- A module's default export can not be re-exported.
return newObj;
}
function _defaults(obj, defaults) {
var keys = Object.getOwnPropertyNames(defaults);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var value = Object.getOwnPropertyDescriptor(defaults, key);
if (value && value.configurable && obj[key] === undefined) {
Object.defineProperty(obj, key, value);
}
}
return obj;
}
var _A = require("A");
_defaults(exports, _interopExportWildcard(_A, _defaults));
export {a1, a2} from "A";
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var _A = require("A");
Object.defineProperty(exports, "a1", {
enumerable: true,
get: function get() {
return _A.a1;
}
});
Object.defineProperty(exports, "a2", {
enumerable: true,
get: function get() {
return _A.a2;
}
});
export {a1 as A1, a2 as A2} from "A";
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var _A = require("A");
Object.defineProperty(exports, "A1", {
enumerable: true,
get: function get() {
return _A.a1;
}
});
Object.defineProperty(exports, "A2", {
enumerable: true,
get: function get() {
return _A.a2;
}
});
As specified in the section about ES6 Modules (ES6 Modules), ES6 Modules export live immutable bindings. The following listings demonstrate how Babel achieves this.
export var a = 1;
a++;
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var a = 1;
exports.a = a;
exports.a = a += 1; //<-- Exported value is tracked.
The following listings present a simple but complete example of ES6 export, import and live-binding concepts. It uses 3 simple ES6 modules called A.js, B.js and Main.js. The modules are listed alongside their CommonJS versions generated by Babel.
export var a = 1; //<-- exports a number
export function incA() { //<-- exports a function
a++;
}
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.incA = incA;
var a = 1;
exports.a = a;
function incA() {
exports.a = a += 1;
}
import {incA} from "./A"; //<-- Imports the function from A.js
export function incB() { //<-- Exports a function that calls the imported function from A.js
incA();
}
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.incB = incB;
var _A = require("./A");
function incB() {
_A.incA();
}
import {a} from "./A"; //<-- Imports the exported number from A.js
import {incB} from "./B"; //<-- Imports the exported function from B.js
console.log(a); //<-- Prints "1"
incB(); //<-- This will call the "incA" function of A.js
console.log(a); //<--Prints "2". The imported value "a" is updated.
"use strict";
var _A = require("./A");
var _B = require("./B");
console.log(_A.a);
_B.incB();
console.log(_A.a);