From c87e14f6a9c2ce9d0cf717116c11e54bb1146157 Mon Sep 17 00:00:00 2001 From: Douglas Duteil Date: Sun, 23 Nov 2014 02:22:24 +0100 Subject: [PATCH] feat(modules): add "register" module type Inspired by https://github.com/ModuleLoader/es6-module-loader/wiki/System.register-Explained --- doc/modules.md | 37 +++ lib/6to5/templates/register.js | 1 + lib/6to5/transformation/modules/register.js | 233 ++++++++++++++++++ lib/6to5/transformation/transform.js | 1 + .../exports-default/actual.js | 8 + .../exports-default/expected.js | 32 +++ .../exports-from/actual.js | 6 + .../exports-from/expected.js | 31 +++ .../exports-named/actual.js | 5 + .../exports-named/expected.js | 24 ++ .../exports-variable/actual.js | 8 + .../exports-variable/expected.js | 35 +++ .../hoist-function-exports/actual.js | 11 + .../hoist-function-exports/expected.js | 25 ++ .../imports-default/actual.js | 2 + .../imports-default/expected.js | 14 ++ .../imports-glob/actual.js | 1 + .../imports-glob/expected.js | 13 + .../imports-mixing/actual.js | 1 + .../imports-mixing/expected.js | 14 ++ .../imports-named/actual.js | 4 + .../imports-named/expected.js | 18 ++ .../es6-modules-register/imports/actual.js | 3 + .../es6-modules-register/imports/expected.js | 10 + .../es6-modules-register/options.json | 3 + .../es6-modules-register/overview/actual.js | 12 + .../es6-modules-register/overview/expected.js | 24 ++ 27 files changed, 576 insertions(+) create mode 100644 lib/6to5/templates/register.js create mode 100644 lib/6to5/transformation/modules/register.js create mode 100644 test/fixtures/transformation/es6-modules-register/exports-default/actual.js create mode 100644 test/fixtures/transformation/es6-modules-register/exports-default/expected.js create mode 100644 test/fixtures/transformation/es6-modules-register/exports-from/actual.js create mode 100644 test/fixtures/transformation/es6-modules-register/exports-from/expected.js create mode 100644 test/fixtures/transformation/es6-modules-register/exports-named/actual.js create mode 100644 test/fixtures/transformation/es6-modules-register/exports-named/expected.js create mode 100644 test/fixtures/transformation/es6-modules-register/exports-variable/actual.js create mode 100644 test/fixtures/transformation/es6-modules-register/exports-variable/expected.js create mode 100644 test/fixtures/transformation/es6-modules-register/hoist-function-exports/actual.js create mode 100644 test/fixtures/transformation/es6-modules-register/hoist-function-exports/expected.js create mode 100644 test/fixtures/transformation/es6-modules-register/imports-default/actual.js create mode 100644 test/fixtures/transformation/es6-modules-register/imports-default/expected.js create mode 100644 test/fixtures/transformation/es6-modules-register/imports-glob/actual.js create mode 100644 test/fixtures/transformation/es6-modules-register/imports-glob/expected.js create mode 100644 test/fixtures/transformation/es6-modules-register/imports-mixing/actual.js create mode 100644 test/fixtures/transformation/es6-modules-register/imports-mixing/expected.js create mode 100644 test/fixtures/transformation/es6-modules-register/imports-named/actual.js create mode 100644 test/fixtures/transformation/es6-modules-register/imports-named/expected.js create mode 100644 test/fixtures/transformation/es6-modules-register/imports/actual.js create mode 100644 test/fixtures/transformation/es6-modules-register/imports/expected.js create mode 100644 test/fixtures/transformation/es6-modules-register/options.json create mode 100644 test/fixtures/transformation/es6-modules-register/overview/actual.js create mode 100644 test/fixtures/transformation/es6-modules-register/overview/expected.js diff --git a/doc/modules.md b/doc/modules.md index ff655c5119..7e82c98fd2 100644 --- a/doc/modules.md +++ b/doc/modules.md @@ -135,6 +135,43 @@ function bar() { } ``` +### Register + +**In** + +```javascript +import foo from "foo"; + +export function bar() { + return foo("foobar"); +} +``` + +**Out** + +```javascript +System.register("bar", ["foo"], function ($__export) { + "use strict"; + + var __moduleName = "bar"; + + var foo; + function bar() { + return foo("foobar"); + } + return { + setters: [function (m) { + foo = m.default; + }], + execute: function () { + $__export("bar", bar); + } + }; +}); + + +``` + ## Custom You can alternatively specify module names instead of one of the built-in types. diff --git a/lib/6to5/templates/register.js b/lib/6to5/templates/register.js new file mode 100644 index 0000000000..267c8dad4e --- /dev/null +++ b/lib/6to5/templates/register.js @@ -0,0 +1 @@ +System.register(MODULE_NAME, MODULE_DEPENDENCIES, MODULE_BODY); diff --git a/lib/6to5/transformation/modules/register.js b/lib/6to5/transformation/modules/register.js new file mode 100644 index 0000000000..15d46abf51 --- /dev/null +++ b/lib/6to5/transformation/modules/register.js @@ -0,0 +1,233 @@ +module.exports = RegisterFormatter; + +var util = require("../../util"); +var t = require("../../types"); +var _ = require("lodash"); + +var EXPORT_IDENTIFIER = t.identifier("$__export"); +var DEFAULT_IDENTIFIER = t.identifier("default"); +var SETTER_MODULE_NAMESPACE = t.identifier("m"); +var NULL_SETTER = { + "type": "Literal", + "value": null, + "raw": "null" +}; + +function RegisterFormatter(file) { + this.file = file; + this.importedModule = {}; + this.exportedStatements = []; +} + +RegisterFormatter.prototype.transform = function(ast) { + var program = ast.program; + var body = program.body; + + // extract the module name + var moduleName = this.file.opts.filename + .replace(/^.*\//, "").replace(/\..*$/, ""); + + // build an array of module names + var dependencies = Object.keys(this.importedModule).map(t.literal); + + // generate the __moduleName variable + var moduleNameVariableNode = t.variableDeclaration("var", [ + t.variableDeclarator( + t.identifier("__moduleName"), + t.literal(moduleName) + ) + ]); + body.splice(1, 0, moduleNameVariableNode); + + // generate an array of import variables + + var declaredSetters = _(this.importedModule) + .map() + .flatten() + .pluck("VARIABLE_NAME") + .pluck("name") + .uniq() + .map(t.identifier) + .map(function(name) { + return t.variableDeclarator(name); + }) + .value(); + + if (declaredSetters.length) { + body.splice(2, 0, t.variableDeclaration("var", declaredSetters)); + } + + // generate the execute function expression + var executeFunctionExpression = t.functionExpression( + null, [], t.blockStatement(this.exportedStatements) + ); + + // generate the execute function expression + var settersArrayExpression = t.arrayExpression(this._buildSetters()); + + // generate the return statement + var moduleReturnStatement = t.returnStatement(t.objectExpression([ + t.property("init", t.identifier("setters"), settersArrayExpression), + t.property("init", t.identifier("execute"), executeFunctionExpression) + ])); + body.push(moduleReturnStatement); + + + // runner + var runner = util.template("register", { + MODULE_NAME: t.literal(moduleName), + MODULE_DEPENDENCIES: t.arrayExpression(dependencies), + MODULE_BODY: t.functionExpression( + null, + [EXPORT_IDENTIFIER], + t.blockStatement(body) + ) + }); + + program.body = [t.expressionStatement(runner)]; +}; + + +RegisterFormatter.prototype._buildSetters = function() { + // generate setters array expression elements + return _(this.importedModule) + .map(function(specifications) { + + if (!specifications.length) { + return NULL_SETTER; + } + + var expressionStatements = _.map(specifications, function(specification) { + return t.expressionStatement( + t.assignmentExpression( + "=", + specification.VARIABLE_NAME, + specification.IS_BATCH ? + SETTER_MODULE_NAMESPACE : + t.memberExpression( + SETTER_MODULE_NAMESPACE, + specification.KEY + ) + ) + ); + }); + + return t.functionExpression( + null, [SETTER_MODULE_NAMESPACE], t.blockStatement(expressionStatements) + ); + }.bind(this)) + .value(); +}; + +RegisterFormatter.prototype.import = function(node) { + var MODULE_NAME = node.source.value; + this.importedModule[MODULE_NAME] = + this.importedModule[MODULE_NAME] || []; +}; + +RegisterFormatter.prototype.importSpecifier = function(specifier, node) { + var variableName = t.getSpecifierName(specifier); + + // import foo from "foo"; + if (specifier.default) { + specifier.id = DEFAULT_IDENTIFIER; + } + + var MODULE_NAME = node.source.value; + + this.importedModule[MODULE_NAME] = + this.importedModule[MODULE_NAME] || []; + + this.importedModule[MODULE_NAME].push( + { + VARIABLE_NAME: variableName, + KEY: specifier.id, + IS_BATCH: specifier.type === "ImportBatchSpecifier" + } + ); +}; + +RegisterFormatter.prototype._export = function(name, identifier) { + this.exportedStatements.push(t.expressionStatement( + t.callExpression(EXPORT_IDENTIFIER, [t.literal(name), identifier]) + )); +}; + +RegisterFormatter.prototype.export = function(node, nodes) { + + var declar = node.declaration; + var variableName, identifier; + + if (node.default) { + // export default foo + variableName = DEFAULT_IDENTIFIER.name; + if (t.isClass(declar) || t.isFunction(declar)) { + + if (!declar.id) { + declar.id = this.file.generateUidIdentifier("anonymousFct"); + } + + nodes.push(t.toStatement(declar)); + declar = declar.id; + + } + + identifier = declar; + + } else if (t.isVariableDeclaration(declar)) { + // export var foo + variableName = declar.declarations[0].id.name; + identifier = declar.declarations[0].id; + + nodes.push((declar)); + + } else { + // export function foo () {} + variableName = declar.id.name; + identifier = declar.id; + + nodes.push(declar); + + } + + this._export(variableName, identifier); + +}; + +RegisterFormatter.prototype.exportSpecifier = function(specifier, node) { + var variableName = t.getSpecifierName(specifier); + + if (node.source) { + + if (t.isExportBatchSpecifier(specifier)) { + // export * from "foo"; + var exportIdentifier = t.identifier("exports"); + this.exportedStatements.push( + t.variableDeclaration("var", + [ + t.variableDeclarator( + exportIdentifier, + EXPORT_IDENTIFIER + ) + ] + ) + ); + + this.exportedStatements.push(util.template("exports-wildcard", { + OBJECT: t.identifier(node.source.value) + }, true)); + + } else { + // export { foo } from "test"; + this._export(variableName.name, t.memberExpression( + t.identifier(node.source.value), + specifier.id + )); + } + } else { + // export { foo }; + this._export(variableName.name, specifier.id); + } + + +}; diff --git a/lib/6to5/transformation/transform.js b/lib/6to5/transformation/transform.js index f3df8d952c..5de5354ba1 100644 --- a/lib/6to5/transformation/transform.js +++ b/lib/6to5/transformation/transform.js @@ -20,6 +20,7 @@ transform._ensureTransformerNames = function (type, keys) { transform.transformers = {}; transform.moduleFormatters = { + register: require("./modules/register"), common: require("./modules/common"), commonInterop: require("./modules/common-interop"), ignore: require("./modules/ignore"), diff --git a/test/fixtures/transformation/es6-modules-register/exports-default/actual.js b/test/fixtures/transformation/es6-modules-register/exports-default/actual.js new file mode 100644 index 0000000000..62923e5c15 --- /dev/null +++ b/test/fixtures/transformation/es6-modules-register/exports-default/actual.js @@ -0,0 +1,8 @@ +export default 42; +export default {}; +export default []; +export default foo; +export default function () {} +export default class {} +export default function foo () {} +export default class Foo {} diff --git a/test/fixtures/transformation/es6-modules-register/exports-default/expected.js b/test/fixtures/transformation/es6-modules-register/exports-default/expected.js new file mode 100644 index 0000000000..ce80287af0 --- /dev/null +++ b/test/fixtures/transformation/es6-modules-register/exports-default/expected.js @@ -0,0 +1,32 @@ +System.register("actual", [], function ($__export) { + "use strict"; + + var __moduleName = "actual"; + + function _anonymousFct() {} + var _anonymousFct2 = function _anonymousFct2() {}; + + function foo() {} + var Foo = function Foo() {}; + + return { + setters: [], + execute: function () { + $__export("default", 42); + + $__export("default", {}); + + $__export("default", []); + + $__export("default", foo); + + $__export("default", _anonymousFct); + + $__export("default", _anonymousFct2); + + $__export("default", foo); + + $__export("default", Foo); + } + }; +}); diff --git a/test/fixtures/transformation/es6-modules-register/exports-from/actual.js b/test/fixtures/transformation/es6-modules-register/exports-from/actual.js new file mode 100644 index 0000000000..60857f6542 --- /dev/null +++ b/test/fixtures/transformation/es6-modules-register/exports-from/actual.js @@ -0,0 +1,6 @@ +export * from "foo"; +export {foo} from "foo"; +export {foo, bar} from "foo"; +export {foo as bar} from "foo"; +export {foo as default} from "foo"; +export {foo as default, bar} from "foo"; diff --git a/test/fixtures/transformation/es6-modules-register/exports-from/expected.js b/test/fixtures/transformation/es6-modules-register/exports-from/expected.js new file mode 100644 index 0000000000..586a7f067c --- /dev/null +++ b/test/fixtures/transformation/es6-modules-register/exports-from/expected.js @@ -0,0 +1,31 @@ +System.register("actual", [], function ($__export) { + "use strict"; + + var __moduleName = "actual"; + + return { + setters: [], + execute: function () { + var exports = $__export; + (function (obj) { + for (var i in obj) { + exports[i] = obj[i]; + } + })(foo); + + $__export("foo", foo.foo); + + $__export("foo", foo.foo); + + $__export("bar", foo.bar); + + $__export("bar", foo.foo); + + $__export("default", foo.foo); + + $__export("default", foo.foo); + + $__export("bar", foo.bar); + } + }; +}); diff --git a/test/fixtures/transformation/es6-modules-register/exports-named/actual.js b/test/fixtures/transformation/es6-modules-register/exports-named/actual.js new file mode 100644 index 0000000000..8515ace759 --- /dev/null +++ b/test/fixtures/transformation/es6-modules-register/exports-named/actual.js @@ -0,0 +1,5 @@ +export {foo}; +export {foo, bar}; +export {foo as bar}; +export {foo as default}; +export {foo as default, bar}; diff --git a/test/fixtures/transformation/es6-modules-register/exports-named/expected.js b/test/fixtures/transformation/es6-modules-register/exports-named/expected.js new file mode 100644 index 0000000000..ae2884e02c --- /dev/null +++ b/test/fixtures/transformation/es6-modules-register/exports-named/expected.js @@ -0,0 +1,24 @@ +System.register("actual", [], function ($__export) { + "use strict"; + + var __moduleName = "actual"; + + return { + setters: [], + execute: function () { + $__export("foo", foo); + + $__export("foo", foo); + + $__export("bar", bar); + + $__export("bar", foo); + + $__export("default", foo); + + $__export("default", foo); + + $__export("bar", bar); + } + }; +}); diff --git a/test/fixtures/transformation/es6-modules-register/exports-variable/actual.js b/test/fixtures/transformation/es6-modules-register/exports-variable/actual.js new file mode 100644 index 0000000000..b4629cc731 --- /dev/null +++ b/test/fixtures/transformation/es6-modules-register/exports-variable/actual.js @@ -0,0 +1,8 @@ +export var foo = 1; +export var foo2 = function () {}; +export var foo3; +export let foo4 = 2; +export let foo5; +export const foo6 = 3; +export function foo7 () {} +export class foo8 {} diff --git a/test/fixtures/transformation/es6-modules-register/exports-variable/expected.js b/test/fixtures/transformation/es6-modules-register/exports-variable/expected.js new file mode 100644 index 0000000000..08f8c836c1 --- /dev/null +++ b/test/fixtures/transformation/es6-modules-register/exports-variable/expected.js @@ -0,0 +1,35 @@ +System.register("actual", [], function ($__export) { + "use strict"; + + var __moduleName = "actual"; + + var foo = 1; + var foo2 = function () {}; + var foo3; + var foo4 = 2; + var foo5; + var foo6 = 3; + function foo7() {} + var foo8 = function foo8() {}; + + return { + setters: [], + execute: function () { + $__export("foo", foo); + + $__export("foo2", foo2); + + $__export("foo3", foo3); + + $__export("foo4", foo4); + + $__export("foo5", foo5); + + $__export("foo6", foo6); + + $__export("foo7", foo7); + + $__export("foo8", foo8); + } + }; +}); diff --git a/test/fixtures/transformation/es6-modules-register/hoist-function-exports/actual.js b/test/fixtures/transformation/es6-modules-register/hoist-function-exports/actual.js new file mode 100644 index 0000000000..3c40b7d1c1 --- /dev/null +++ b/test/fixtures/transformation/es6-modules-register/hoist-function-exports/actual.js @@ -0,0 +1,11 @@ +import { isEven } from "./evens"; + +export function nextOdd(n) { + return isEven(n) ? n + 1 : n + 2; +} + +export var isOdd = (function (isEven) { + return function (n) { + return !isEven(n); + }; +})(isEven); diff --git a/test/fixtures/transformation/es6-modules-register/hoist-function-exports/expected.js b/test/fixtures/transformation/es6-modules-register/hoist-function-exports/expected.js new file mode 100644 index 0000000000..d142facb26 --- /dev/null +++ b/test/fixtures/transformation/es6-modules-register/hoist-function-exports/expected.js @@ -0,0 +1,25 @@ +System.register("actual", ["./evens"], function ($__export) { + "use strict"; + + var __moduleName = "actual"; + + var isEven; + function nextOdd(n) { + return isEven(n) ? n + 1 : n + 2; + } + + var isOdd = (function (isEven) { + return function (n) { + return !isEven(n); + }; + })(isEven);return { + setters: [function (m) { + isEven = m.isEven; + }], + execute: function () { + $__export("nextOdd", nextOdd); + + $__export("isOdd", isOdd); + } + }; +}); diff --git a/test/fixtures/transformation/es6-modules-register/imports-default/actual.js b/test/fixtures/transformation/es6-modules-register/imports-default/actual.js new file mode 100644 index 0000000000..e67418654c --- /dev/null +++ b/test/fixtures/transformation/es6-modules-register/imports-default/actual.js @@ -0,0 +1,2 @@ +import foo from "foo"; +import {default as foo} from "foo"; diff --git a/test/fixtures/transformation/es6-modules-register/imports-default/expected.js b/test/fixtures/transformation/es6-modules-register/imports-default/expected.js new file mode 100644 index 0000000000..ecc5b0b8b6 --- /dev/null +++ b/test/fixtures/transformation/es6-modules-register/imports-default/expected.js @@ -0,0 +1,14 @@ +System.register("actual", ["foo"], function ($__export) { + "use strict"; + + var __moduleName = "actual"; + + var foo; + return { + setters: [function (m) { + foo = m.default; + foo = m.default; + }], + execute: function () {} + }; +}); diff --git a/test/fixtures/transformation/es6-modules-register/imports-glob/actual.js b/test/fixtures/transformation/es6-modules-register/imports-glob/actual.js new file mode 100644 index 0000000000..e55c077500 --- /dev/null +++ b/test/fixtures/transformation/es6-modules-register/imports-glob/actual.js @@ -0,0 +1 @@ +import * as foo from "foo"; diff --git a/test/fixtures/transformation/es6-modules-register/imports-glob/expected.js b/test/fixtures/transformation/es6-modules-register/imports-glob/expected.js new file mode 100644 index 0000000000..73c8fbde65 --- /dev/null +++ b/test/fixtures/transformation/es6-modules-register/imports-glob/expected.js @@ -0,0 +1,13 @@ +System.register("actual", ["foo"], function ($__export) { + "use strict"; + + var __moduleName = "actual"; + + var foo; + return { + setters: [function (m) { + foo = m; + }], + execute: function () {} + }; +}); diff --git a/test/fixtures/transformation/es6-modules-register/imports-mixing/actual.js b/test/fixtures/transformation/es6-modules-register/imports-mixing/actual.js new file mode 100644 index 0000000000..ef78c95b1c --- /dev/null +++ b/test/fixtures/transformation/es6-modules-register/imports-mixing/actual.js @@ -0,0 +1 @@ +import foo, {baz as xyz} from "foo"; diff --git a/test/fixtures/transformation/es6-modules-register/imports-mixing/expected.js b/test/fixtures/transformation/es6-modules-register/imports-mixing/expected.js new file mode 100644 index 0000000000..9891592b72 --- /dev/null +++ b/test/fixtures/transformation/es6-modules-register/imports-mixing/expected.js @@ -0,0 +1,14 @@ +System.register("actual", ["foo"], function ($__export) { + "use strict"; + + var __moduleName = "actual"; + + var foo, xyz; + return { + setters: [function (m) { + foo = m.default; + xyz = m.baz; + }], + execute: function () {} + }; +}); diff --git a/test/fixtures/transformation/es6-modules-register/imports-named/actual.js b/test/fixtures/transformation/es6-modules-register/imports-named/actual.js new file mode 100644 index 0000000000..83a766c62d --- /dev/null +++ b/test/fixtures/transformation/es6-modules-register/imports-named/actual.js @@ -0,0 +1,4 @@ +import {bar} from "foo"; +import {bar, baz} from "foo"; +import {bar as baz} from "foo"; +import {bar as baz, xyz} from "foo"; diff --git a/test/fixtures/transformation/es6-modules-register/imports-named/expected.js b/test/fixtures/transformation/es6-modules-register/imports-named/expected.js new file mode 100644 index 0000000000..ddfb20d79a --- /dev/null +++ b/test/fixtures/transformation/es6-modules-register/imports-named/expected.js @@ -0,0 +1,18 @@ +System.register("actual", ["foo"], function ($__export) { + "use strict"; + + var __moduleName = "actual"; + + var bar, baz, xyz; + return { + setters: [function (m) { + bar = m.bar; + bar = m.bar; + baz = m.baz; + baz = m.bar; + baz = m.bar; + xyz = m.xyz; + }], + execute: function () {} + }; +}); diff --git a/test/fixtures/transformation/es6-modules-register/imports/actual.js b/test/fixtures/transformation/es6-modules-register/imports/actual.js new file mode 100644 index 0000000000..222b6885ac --- /dev/null +++ b/test/fixtures/transformation/es6-modules-register/imports/actual.js @@ -0,0 +1,3 @@ +import "foo"; +import "foo-bar"; +import "./directory/foo-bar"; diff --git a/test/fixtures/transformation/es6-modules-register/imports/expected.js b/test/fixtures/transformation/es6-modules-register/imports/expected.js new file mode 100644 index 0000000000..75c627ec56 --- /dev/null +++ b/test/fixtures/transformation/es6-modules-register/imports/expected.js @@ -0,0 +1,10 @@ +System.register("actual", ["foo", "foo-bar", "./directory/foo-bar"], function ($__export) { + "use strict"; + + var __moduleName = "actual"; + + return { + setters: [null, null, null], + execute: function () {} + }; +}); diff --git a/test/fixtures/transformation/es6-modules-register/options.json b/test/fixtures/transformation/es6-modules-register/options.json new file mode 100644 index 0000000000..110872cbc0 --- /dev/null +++ b/test/fixtures/transformation/es6-modules-register/options.json @@ -0,0 +1,3 @@ +{ + "modules": "register" +} diff --git a/test/fixtures/transformation/es6-modules-register/overview/actual.js b/test/fixtures/transformation/es6-modules-register/overview/actual.js new file mode 100644 index 0000000000..a77d4d5dfa --- /dev/null +++ b/test/fixtures/transformation/es6-modules-register/overview/actual.js @@ -0,0 +1,12 @@ +import "foo"; +import "foo-bar"; +import "./directory/foo-bar"; +import foo from "foo"; +import * as foo from "foo"; +import {bar} from "foo"; +import {foo as bar} from "foo"; + +export {test}; +export var test = 5; + +export default test; diff --git a/test/fixtures/transformation/es6-modules-register/overview/expected.js b/test/fixtures/transformation/es6-modules-register/overview/expected.js new file mode 100644 index 0000000000..75802c2a5c --- /dev/null +++ b/test/fixtures/transformation/es6-modules-register/overview/expected.js @@ -0,0 +1,24 @@ +System.register("actual", ["foo", "foo-bar", "./directory/foo-bar"], function ($__export) { + "use strict"; + + var __moduleName = "actual"; + + var foo, bar; + var test = 5; + + return { + setters: [function (m) { + foo = m.default; + foo = m; + bar = m.bar; + bar = m.foo; + }, null, null], + execute: function () { + $__export("test", test); + + $__export("test", test); + + $__export("default", test); + } + }; +});