Skip to content

Commit

Permalink
Setup module.exports first (#2129)
Browse files Browse the repository at this point in the history
This gives some much needed love to the CommonJS module output.

Most importantly it mutates the `module.exports` object before the
code is executed which allows some cyclic dependencies to work
(function declarations).

This also changes the transformer to work on the original code
instead of the already transformed code (from `ModuleTransformer`).

Things have been refactored so that more code is shared between
`CommonJsModuleTransformer` and `ModuleTransformer`.

Also, the require variable binding (with `__esModule`) has changed
so that we do not need temp variables (outside what is needed for
destructuring).
  • Loading branch information
arv committed Jun 9, 2016
1 parent 79fa394 commit 2ddade7
Show file tree
Hide file tree
Showing 11 changed files with 158 additions and 172 deletions.
2 changes: 1 addition & 1 deletion src/codegeneration/AmdTransformer.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export class AmdTransformer extends ModuleTransformer {
getExportProperties() {
let properties = super.getExportProperties();

if (this.exportVisitor_.hasExports())
if (this.exportVisitor.hasExports())
properties.push(parsePropertyDefinition `__esModule: true`);
return properties;
}
Expand Down
2 changes: 1 addition & 1 deletion src/codegeneration/ClosureModuleTransformer.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export class ClosureModuleTransformer extends ModuleTransformer {
return statements;
}

appendExportStatement(statements) {
addExportStatement(statements) {
if (!this.hasExports()) return statements;
let exportObject = this.getExportObject();
statements.push(parseStatement `exports = ${exportObject}`);
Expand Down
195 changes: 90 additions & 105 deletions src/codegeneration/CommonJsModuleTransformer.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,44 +13,30 @@
// limitations under the License.

import {ModuleTransformer} from './ModuleTransformer.js';
import {
CALL_EXPRESSION,
GET_ACCESSOR,
OBJECT_LITERAL,
PROPERTY_NAME_ASSIGNMENT,
RETURN_STATEMENT
} from '../syntax/trees/ParseTreeType.js';
import {
ArgumentList,
CallExpression,
ExpressionStatement
} from '../syntax/trees/ParseTrees.js';
import {assert} from '../util/assert.js';
import globalThis from './globalThis.js';
import {NAMED_EXPORT} from '../syntax/trees/ParseTreeType.js';
import {AnonBlock} from '../syntax/trees/ParseTrees.js';
import {
parseExpression,
parsePropertyDefinition,
parseStatements
parseStatement,
} from './PlaceholderParser.js';
import {
createEmptyParameterList,
createFunctionExpression,
createIdentifierExpression,
createExpressionStatement,
createObjectLiteral,
createObjectLiteralForDescriptor,
createPropertyNameAssignment,
createVariableStatement,
createVariableDeclaration,
createVariableDeclarationList
} from './ParseTreeFactory.js';
import {VAR} from '../syntax/TokenType.js';
import {prependStatements} from './PrependStatements.js';
import {FindVisitor} from './FindVisitor.js';

export class CommonJsModuleTransformer extends ModuleTransformer {

constructor(identifierGenerator, reporter, options = undefined) {
super(identifierGenerator, reporter, options);
this.moduleVars_ = [];
this.anonymousModule =
options && !options.bundle && options.moduleName !== true;
this.namedExportsWithModuleSpecifiers_ = [];
this.isImportingDefault_ = false;
this.needsInteropRequire_ = false;
}

getModuleName(tree) {
Expand All @@ -59,106 +45,105 @@ export class CommonJsModuleTransformer extends ModuleTransformer {
return tree.moduleName;
}

moduleProlog() {
let statements = super.moduleProlog();

// declare temp vars in prolog
if (this.moduleVars_.length) {
let tmpVarDeclarations = createVariableStatement(createVariableDeclarationList(VAR,
this.moduleVars_.map((varName) => createVariableDeclaration(varName, null))));

statements.push(tmpVarDeclarations);
wrapModule(statements) {
if (this.needsInteropRequire_) {
const req = parseStatement `function $__interopRequire(id) {
id = require(id);
return id && id.__esModule && id || {default: id};
}`;
return prependStatements(statements, req);
}

return statements;
}

wrapModule(statements) {
let last = statements[statements.length - 1];
statements = statements.slice(0, -1);
assert(last.type === RETURN_STATEMENT);
let exportExpression = last.expression;

// If the module doesn't use any export statements, nor global "this", it
// might be because it wants to make its own changes to "exports" or
// "module.exports", so we don't append "module.exports = {}" to the output.
if (this.hasExports()) {
let exportStatement =
this.transformExportExpressionToModuleExport(exportExpression);
statements = statements.concat(exportStatement);
addExportStatement(statements) {
if (!this.hasExports()) {
return statements;
}
return statements;
}

transformExportExpressionToModuleExport(tree) {
let expression;

// $traceurRuntime.exportStar({}, ...)
if (tree.type === CALL_EXPRESSION) {
let descriptors =
this.transformObjectLiteralToDescriptors(tree.args.args[0]);
let object = parseExpression
`Object.defineProperties(module.exports, ${descriptors})`;
let newArgs = new ArgumentList(tree.args.location,
[object, ...tree.args.args.slice(1)])
expression = new CallExpression(tree.location, tree.operand, newArgs);
} else {
let descriptors = this.transformObjectLiteralToDescriptors(tree);
expression = parseExpression
`Object.defineProperties(module.exports, ${descriptors})`;
const descr = this.getExportDescriptors();
let exportObject = parseExpression
`Object.defineProperties(module.exports, ${descr})`;
if (this.hasStarExports()) {
exportObject = this.getExportStar(exportObject);
}

return new ExpressionStatement(expression.location, expression);
// Mutate module.exports immediately after all the export star are
// imported, before any module code is executed, to allow some cyclic
// dependencies to work.
return prependStatements(statements,
...this.namedExportsWithModuleSpecifiers_,
createExpressionStatement(exportObject));
}

transformObjectLiteralToDescriptors(literalTree) {
assert(literalTree.type === OBJECT_LITERAL);

let props = literalTree.propertyNameAndValues.map((exp) => {
let descriptor;

switch (exp.type) {
case GET_ACCESSOR: {
let getterFunction =
createFunctionExpression(createEmptyParameterList(), exp.body);
descriptor =
parseExpression `{get: ${getterFunction}, enumerable: true}`;
break;
}
getExportDescriptors() {
// {
// x: {
// enumerable: true,
// get: function() { ... },
// },
// ...
// }

const properties = this.exportVisitor.getNonTypeNamedExports().map(exp => {
const f = parseExpression `function() { return ${
this.getGetterExportReturnExpression(exp)
}; }`;
return createPropertyNameAssignment(exp.name,
createObjectLiteralForDescriptor({enumerable: true, get: f}));
});
properties.unshift(parsePropertyDefinition `__esModule: {value: true}`);
return createObjectLiteral(properties);
}

case PROPERTY_NAME_ASSIGNMENT:
descriptor = parseExpression `{value: ${exp.value}}`;
break;
transformExportDeclaration(tree) {
this.checkForDefaultImport_(tree);
this.exportVisitor.visitAny(tree);
const transformed = this.transformAny(tree.declaration);

// Need to output the require for export ? from moduleSpecifier before
// the call to exportStar.
if (tree.declaration.type == NAMED_EXPORT &&
tree.declaration.moduleSpecifier !== null) {
this.namedExportsWithModuleSpecifiers_.push(transformed);
return new AnonBlock(null, []);
}

default:
throw new Error(`Unexpected property type ${exp.type}`);
}
return transformed;
}

return createPropertyNameAssignment(exp.name, descriptor);
});
transformImportDeclaration(tree) {
this.checkForDefaultImport_(tree);
return super.transformImportDeclaration(tree);
}

return createObjectLiteral(props);
checkForDefaultImport_(tree) {
const finder = new FindDefault();
finder.visitAny(tree);
this.isImportingDefault_ = finder.found;
}

transformModuleSpecifier(tree) {
let moduleName = tree.token.processedValue;
let tmpVar = this.getTempVarNameForModuleSpecifier(tree);
this.moduleVars_.push(tmpVar);
let tvId = createIdentifierExpression(tmpVar);

// require the module, if it is not marked as an ES6 module, treat it as { default: module }
// this allows for an unlinked CommonJS / ES6 interop
// note that future implementations should also check for native Module with
// Reflect.isModule or similar
return parseExpression `(${tvId} = require(${moduleName}),
${tvId} && ${tvId}.__esModule && ${tvId} || {default: ${tvId}})`;
if (this.isImportingDefault_) {
this.needsInteropRequire_ = true;
return parseExpression `$__interopRequire(${moduleName})`;
}
return parseExpression `require(${moduleName})`;
}
}

getExportProperties() {
let properties = super.getExportProperties();

if (this.exportVisitor_.hasExports())
properties.push(parsePropertyDefinition `__esModule: true`);
return properties;
class FindDefault extends FindVisitor {
visitImportSpecifier(tree) {
this.found = tree.name !== null && tree.name.value === 'default';
}
visitNameSpaceImport(tree) {
this.found = true;
}
visitNameSpaceExport(tree) {
this.found = true;
}
visitExportSpecifier(tree) {
this.found = tree.lhs !== null && tree.lhs.value === 'default';
}
}
8 changes: 4 additions & 4 deletions src/codegeneration/InlineES6ModuleTransformer.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export class InlineES6ModuleTransformer extends ModuleTransformer {
if (this.isRootModule)
return tree;

this.exportVisitor_.visitAny(tree);
this.exportVisitor.visitAny(tree);
return this.transformAny(tree.declaration);
}

Expand Down Expand Up @@ -154,11 +154,11 @@ export class InlineES6ModuleTransformer extends ModuleTransformer {
*
* @returns {Array} statements
*/
appendExportStatement(statements) {
addExportStatement(statements) {
let exportProperties = this.getExportProperties();
let exportObject = createObjectLiteral(exportProperties);
if (this.exportVisitor_.starExports.length) {
let starExports = this.exportVisitor_.starExports;
if (this.exportVisitor.starExports.length) {
let starExports = this.exportVisitor.starExports;
let starIdents = starExports.map((moduleSpecifier) => {
return createIdentifierExpression(
this.getTempVarNameForModuleSpecifier(moduleSpecifier));
Expand Down
2 changes: 1 addition & 1 deletion src/codegeneration/InstantiateModuleTransformer.js
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ export class InstantiateModuleTransformer extends ModuleTransformer {
* });
*
*/
appendExportStatement(statements) {
addExportStatement(statements) {
let declarationExtractionTransformer = new DeclarationExtractionTransformer();

// convert __moduleName identifiers into $__moduleContext.id
Expand Down
Loading

0 comments on commit 2ddade7

Please sign in to comment.