From cfac8aaf4aea434ff6ba54091b108665ac332c5a Mon Sep 17 00:00:00 2001 From: Richard Eames Date: Sat, 20 Sep 2014 12:44:14 -0600 Subject: [PATCH 1/3] Module transpilation based upon `andreypopp/es6-module-jstransform` --- es6-transpiler.js | 1 + tests/es6-modules-out.js | 17 ++++ tests/es6-modules.js | 13 +++ transpiler/core.js | 8 +- transpiler/modules.js | 166 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 202 insertions(+), 3 deletions(-) create mode 100644 tests/es6-modules-out.js create mode 100644 tests/es6-modules.js create mode 100644 transpiler/modules.js diff --git a/es6-transpiler.js b/es6-transpiler.js index e0213ee..46c3350 100644 --- a/es6-transpiler.js +++ b/es6-transpiler.js @@ -53,6 +53,7 @@ let plugins = [ , require("./transpiler/RegExp") , require("./transpiler/unicode") , require("./transpiler/polyfills") + , require("./transpiler/modules") ]; let extensions = [ diff --git a/tests/es6-modules-out.js b/tests/es6-modules-out.js new file mode 100644 index 0000000..ad84e29 --- /dev/null +++ b/tests/es6-modules-out.js @@ -0,0 +1,17 @@ +var MD$0;var MD$1;var MD$2;require('spam'); +var assert = require('assert'); +MD$0 = require('foo'); +var bar = MD$0; +MD$1 = require('basket'); +var eggs = MD$1.eggs; +MD$2 = require('functions'); +var fn1 = MD$2.fn1; +var fn2 = MD$2.fn2; +var Function1 = MD$2.fn1; + +var foo = module.exports.foo = 1; +var Bob = (function(){"use strict";var PRS$0 = (function(o,t){o["__proto__"]={"a":t};return o["a"]===t})({},{});var DP$0 = Object.defineProperty;var GOPD$0 = Object.getOwnPropertyDescriptor;var MIXIN$0 = function(t,s){for(var p in s){if(s.hasOwnProperty(p)){DP$0(t,p,GOPD$0(s,p));}}return t};function Bob() {}DP$0(Bob,"prototype",{"configurable":false,"enumerable":false,"writable":false});;return Bob;})(); +module.exports.Bob = Bob;; +module.exports.Bob = Bob; + +module.exports = foo; diff --git a/tests/es6-modules.js b/tests/es6-modules.js new file mode 100644 index 0000000..33d079e --- /dev/null +++ b/tests/es6-modules.js @@ -0,0 +1,13 @@ +import 'spam'; +import assert from 'assert'; +module bar from 'foo'; +import {eggs} from 'basket'; +import {fn1, fn2} from 'functions'; +import {fn1 as Function1} from 'functions'; + +export var foo = 1; +export class Bob {}; +export {Bob}; + +export default foo; + diff --git a/transpiler/core.js b/transpiler/core.js index c7ce4a0..33dfcce 100644 --- a/transpiler/core.js +++ b/transpiler/core.js @@ -245,15 +245,17 @@ let core = module.exports = extend({}, require('./core/is.js'), require('./core/ addParamToScope(node.rest) } - } else if (node.type === "ImportDeclaration") { + } else if (node.type === "ImportDeclaration" && node.kind) { // Variable declarations names in import's assert( node.kind === "default" || node.kind === "named" ); node.specifiers.forEach(function(declarator) { assert(declarator.type === "ImportSpecifier"); - addVariableToScope(declarator.id, "var"/*, node.kind*/, declarator, void 0, declarator); + addVariableToScope(declarator.name ? declarator.name : declarator.id, "var"/*, node.kind*/, declarator, void 0, declarator); }, this); - + } else if (node.type === "ModuleDeclaration") { + addVariableToScope(node.id, "var", node, void 0, node.source); + } else if (node.type === "VariableDeclaration") { // Variable declarations names goes in current scope assert(this.is.isVarConstLet(node)); diff --git a/transpiler/modules.js b/transpiler/modules.js new file mode 100644 index 0000000..945fe18 --- /dev/null +++ b/transpiler/modules.js @@ -0,0 +1,166 @@ +"use strict" + +const assert = require("assert"); +const stringmap = require("stringmap"); +const core = require("./core"); + +let plugin = module.exports = { + reset: function() { + this.__statistic = { + requires: {} + }; + } + + , setup: function(alter, ast, options) { + if( !this.__isInit ) { + this.reset(); + this.__isInit = true; + } + + this.alter = alter; + this.options = options; + + core.registerVar('MD', {name:'MD'}); + core.registerVar('i', {persistent: true}); + } + + , isKnownModule: function(node) { + return node.source.raw in this.__statistic.requires; + } + + , getModule: function(node) { + return this.__statistic.requires[node.source.raw]; + } + + , getOrAddModule: function(node) { + let requirePath = node.source.raw; + + if (!(this.isKnownModule(node))) { + let modName = this.__statistic.requires[requirePath] = core.getScopeTempVar(node, node.$scope, null, 'MD'); + this.alter.insertBefore(node.range[0], modName + ' = require(' + node.source.raw + ');\n'); + } + return this.getModule(node); + } + + , ':: ImportDeclaration': function replaceImportDeclaration(node, astQuery) { + var specifier, name, replaceString, modName; + + switch (node.kind) { + // import "module" + case undefined: + this.alter.replace(node.range[0], node.range[1], 'require(' + node.source.raw + ');\n'); + break; + + // import name from "module" + case 'default': + specifier = node.specifiers[0]; + assert(specifier, "default import without specifier: " + node); + name = specifier.name ? specifier.name.name : specifier.id.name; + replaceString = 'var ' + name + ' = require(' + node.source.raw + ');\n'; + this.alter.replace(node.range[0], node.range[1], replaceString); + break; + + // import {name, name2 as name3} from "module" + case 'named': + modName = this.getOrAddModule(node); + + for (let i = 0, l = node.specifiers.length; i < l; i++) { + specifier = node.specifiers[i]; + name = specifier.name ? specifier.name.name : specifier.id.name; + replaceString = 'var ' + name + ' = ' + modName + '.' + specifier.id.name + ';\n'; + this.alter.insertAfter(node.range[1], replaceString); + } + this.alter.replace(node.range[0], node.range[1], ''); + } + } + + , '::ExportDeclaration': function replaceExportDeclaration(node) { + var specifier, name, len, i, modName, exportString; + + if (node.declaration) { + // export default name = value + if (Array.isArray(node.declaration)) { + assert(node.declaration.length === 1, "cannot export more than a single declaration"); + assert(node.declaration[0].id.name === "default", "invalid export format `export name = value1`"); + + name = node.declaration[0].id.name; + i = node.range[0]; // start of the "export default" + exportString = "module.exports ="; + + if (node.declaration[0].init) { + this.alter.replace(i, node.declaration[0].init.range[0], exportString); + } + else { + this.alter.replace(i, node.declaration[0].range[1], exportString); + } + } + else { + // export var name = value -> var name = module.exports.name = value + // export function name() {} -> function name() {} module.exports.name = name; + // export class name() {} -> class name {} module.exports.name = name; + + // replace "export " + this.alter.replace(node.range[0], node.declaration.range[0], ''); + + switch (node.declaration.type) { + case "VariableDeclaration": + node.declaration.declarations.forEach(function(declaration) { + this.alter.insertAfter(declaration.id.range[1], " = module.exports." + declaration.id.name); + }, this); + break; + case "FunctionDeclaration": + case "ClassDeclaration": + name = node.declaration.id.name; + this.alter.insertAfter(node.declaration.range[1], "\nmodule.exports." + name + " = " + name + ";"); + break; + default: + assert(false, "Unknown declaration type: " + node.declaration.type); + } + } + } + else if (node.source) { + + // export * from "module" + if (node.specifiers.length === 1 && node.specifiers[0].type === "ExportBatchSpecifier") { + modName = this.getOrAddModule(node); + let keyId = core.createVars(node, 'i'); + this.alter.replace(node.range[0], node.range[1], + "for (let " + keyId + " in " + modName + " ) " + + "module.exports[" + keyId + "] = " + modName + "[" + keyId + "];" + ) + } + // export {name, name2 as name3} from "module" + else { + modName = this.getOrAddModule(node); + this.alter.replace(node.range[0], node.range[1], ''); + node.specifiers.forEach(function(specifier) { + let name = specifier.name ? specifier.name.name : specifier.id.name; + this.alter.insertAfter(node.range[1], "module.exports." + name + " = " + modName + "." + specifier.id.name + ";"); + }, this); + } + } + else if (node.specifiers) { + // export {name, name2 as name3} + + node.specifiers.forEach(function(specifier) { + let name = specifier.name ? specifier.name.name : specifier.id.name; + this.alter.insertAfter(node.range[1], "module.exports." + name + " = " + specifier.id.name + ";"); + }, this); + this.alter.replace(node.range[0], node.range[1], ''); + } + else { + assert(false, "unknown export declaration syntax"); + } + } + + , ':: ModuleDeclaration': function replaceModuleDeclaration(node) { + var modName = this.getOrAddModule(node), replaceString; + + replaceString = 'var ' + node.id.name + ' = ' + modName +';\n'; + this.alter.replace(node.range[0], node.range[1], replaceString); + } +}; + +for(let i in plugin) if( plugin.hasOwnProperty(i) && typeof plugin[i] === "function" ) { + plugin[i] = plugin[i].bind(plugin); +} From 2c6a32ae06ec16ad5161993323b6295c1d9822a3 Mon Sep 17 00:00:00 2001 From: Richard Eames Date: Mon, 22 Sep 2014 13:54:59 -0600 Subject: [PATCH 2/3] Fixed `export default` via upstream esprima/pull/216 --- lib/esprima_harmony.js | 7 ++++++- tests/es6-modules-out.js | 2 ++ tests/es6-modules.js | 3 ++- transpiler/modules.js | 2 +- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/esprima_harmony.js b/lib/esprima_harmony.js index e539812..3eadb86 100644 --- a/lib/esprima_harmony.js +++ b/lib/esprima_harmony.js @@ -3520,6 +3520,8 @@ parseYieldExpression: true } expect('='); init = parseAssignmentExpression(); + } else if (kind === 'default') { + init = parseAssignmentExpression(); } else if (match('=')) { lex(); init = parseAssignmentExpression(); @@ -3645,8 +3647,11 @@ parseYieldExpression: true if (isIdentifierName(lookahead)) { previousAllowKeyword = state.allowKeyword; state.allowKeyword = true; - decl = parseVariableDeclarationList('let'); + decl = parseVariableDeclarationList('default'); state.allowKeyword = previousAllowKeyword; + + consumeSemicolon(); + return markerApply(marker, delegate.createExportDeclaration(decl, null, null)); } diff --git a/tests/es6-modules-out.js b/tests/es6-modules-out.js index ad84e29..d4af70d 100644 --- a/tests/es6-modules-out.js +++ b/tests/es6-modules-out.js @@ -15,3 +15,5 @@ module.exports.Bob = Bob;; module.exports.Bob = Bob; module.exports = foo; +module.exports = { a:1, b: 2 }; +module.exports = function fn3() {} diff --git a/tests/es6-modules.js b/tests/es6-modules.js index 33d079e..b9b16a4 100644 --- a/tests/es6-modules.js +++ b/tests/es6-modules.js @@ -10,4 +10,5 @@ export class Bob {}; export {Bob}; export default foo; - +export default { a:1, b: 2 }; +export default function fn3() {} diff --git a/transpiler/modules.js b/transpiler/modules.js index 945fe18..14c838c 100644 --- a/transpiler/modules.js +++ b/transpiler/modules.js @@ -85,7 +85,7 @@ let plugin = module.exports = { name = node.declaration[0].id.name; i = node.range[0]; // start of the "export default" - exportString = "module.exports ="; + exportString = "module.exports = "; if (node.declaration[0].init) { this.alter.replace(i, node.declaration[0].init.range[0], exportString); From 25e98d308d138056104d18d9518b36d4af92a5b0 Mon Sep 17 00:00:00 2001 From: Richard Eames Date: Mon, 22 Sep 2014 14:08:08 -0600 Subject: [PATCH 3/3] Better test for `export default {obj:literal}` --- tests/es6-modules-out.js | 2 +- tests/es6-modules.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/es6-modules-out.js b/tests/es6-modules-out.js index d4af70d..d48090b 100644 --- a/tests/es6-modules-out.js +++ b/tests/es6-modules-out.js @@ -15,5 +15,5 @@ module.exports.Bob = Bob;; module.exports.Bob = Bob; module.exports = foo; -module.exports = { a:1, b: 2 }; +module.exports = { f: function() { return 2; }, a:1 }; module.exports = function fn3() {} diff --git a/tests/es6-modules.js b/tests/es6-modules.js index b9b16a4..4c63de7 100644 --- a/tests/es6-modules.js +++ b/tests/es6-modules.js @@ -10,5 +10,5 @@ export class Bob {}; export {Bob}; export default foo; -export default { a:1, b: 2 }; +export default { f() { return 2; }, a:1 }; export default function fn3() {}