Skip to content
This repository was archived by the owner on May 22, 2025. It is now read-only.
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions src/googmodule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,33 @@ function isEsModuleProperty(stmt: ts.ExpressionStatement): boolean {
return prop.initializer.kind === ts.SyntaxKind.TrueKeyword;
}

function isHoistedExportAssignment(stmt: ts.ExpressionStatement): boolean {
function checkVoid0Assignment(expr: ts.Expression): boolean {
// Ensure this looks something like `exports.abc = exports.xyz = void 0;`.
if (!ts.isBinaryExpression(expr)) return false;
if (expr.operatorToken.kind !== ts.SyntaxKind.EqualsToken) return false;

// Ensure the left side of the expression is an access on `exports`.
if (!ts.isPropertyAccessExpression(expr.left)) return false;
if (!ts.isIdentifier(expr.left.expression)) return false;
if (expr.left.expression.escapedText !== 'exports') return false;

// If the right side is another `exports.abc = ...` check that to see if we eventually hit a
// `void 0`.
if (ts.isBinaryExpression(expr.right)) {
return checkVoid0Assignment(expr.right);
}

// Ensure the right side is exactly "void 0";
if (!ts.isVoidExpression(expr.right)) return false;
if (!ts.isNumericLiteral(expr.right.expression)) return false;
if (expr.right.expression.text !== '0') return false;
return true;
}

return checkVoid0Assignment(stmt.expression);
}

/**
* Returns the string argument if call is of the form
* require('foo')
Expand Down Expand Up @@ -289,6 +316,11 @@ export function commonJsToGoogmoduleTransformer(
return sf;
}

// TypeScript will create at most one `exports.abc = exports.def = void 0` per file. We keep
// track of if we have already seen it here. If we have seen it already that probably means
// there was some code like `export const abc = void 0` that we don't want to erase.
let didRewriteHoistedExportsAssignment = false;

let moduleVarCounter = 1;
/**
* Creates a new unique variable to assign side effect imports into. This allows us to re-use
Expand Down Expand Up @@ -467,6 +499,61 @@ export function commonJsToGoogmoduleTransformer(
return [require, exportStmt];
}

function rewriteObjectDefinePropertyOnExports(stmt: ts.ExpressionStatement): ts.Statement|
null {
if (!ts.isCallExpression(stmt.expression)) return null;

const callExpr = stmt.expression;
if (!ts.isPropertyAccessExpression(callExpr.expression)) return null;

const propAccess = callExpr.expression;
if (!ts.isIdentifier(propAccess.expression)) return null;
if (propAccess.expression.text !== 'Object') return null;
if (propAccess.name.text !== 'defineProperty') return null;

const [objDefArg1, objDefArg2, objDefArg3] = callExpr.arguments;
if (callExpr.arguments.length !== 3) return null;
if (!ts.isIdentifier(objDefArg1)) return null;
if (objDefArg1.text !== 'exports') return null;
if (!ts.isStringLiteral(objDefArg2)) return null;
if (!ts.isObjectLiteralExpression(objDefArg3)) return null;

function findPropConfigFor(name: string) {
return (p: ts.ObjectLiteralElementLike) => {
return ts.isPropertyAssignment(p) && ts.isIdentifier(p.name) && p.name.text === name;
};
}

const enumerableConfig = objDefArg3.properties.find(findPropConfigFor('enumerable'));
if (!enumerableConfig) return null;
if (!ts.isPropertyAssignment(enumerableConfig)) return null;
if (enumerableConfig.initializer.kind !== ts.SyntaxKind.TrueKeyword) return null;

const getConfig = objDefArg3.properties.find(findPropConfigFor('get'));
if (!getConfig) return null;
if (!ts.isPropertyAssignment(getConfig)) return null;
if (!ts.isFunctionExpression(getConfig.initializer)) return null;

const getterFunc = getConfig.initializer;
if (getterFunc.body.statements.length !== 1) return null;

const getterReturn = getterFunc.body.statements[0];
if (!ts.isReturnStatement(getterReturn)) return null;

const realExportValue = getterReturn.expression;
if (!realExportValue) return null;

const exportStmt = ts.setOriginalNode(
ts.setTextRange(
ts.createExpressionStatement(ts.createAssignment(
ts.createPropertyAccess(ts.createIdentifier('exports'), objDefArg2.text),
realExportValue)),
stmt),
stmt);

return exportStmt;
}

/**
* visitTopLevelStatement implements the main CommonJS to goog.module conversion. It visits a
* SourceFile level statement and adds a (possibly) transformed representation of it into
Expand Down Expand Up @@ -502,6 +589,13 @@ export function commonJsToGoogmoduleTransformer(
stmts.push(createNotEmittedStatementWithComments(sf, exprStmt));
return;
}

if (!didRewriteHoistedExportsAssignment && isHoistedExportAssignment(exprStmt)) {
didRewriteHoistedExportsAssignment = true;
stmts.push(createNotEmittedStatementWithComments(sf, exprStmt));
return;
}

// Check for:
// module.exports = ...;
const modExports = rewriteModuleExportsAssignment(exprStmt);
Expand All @@ -526,6 +620,11 @@ export function commonJsToGoogmoduleTransformer(
stmts.push(...exportStarAsNs);
return;
}
const exportFromObjDefProp = rewriteObjectDefinePropertyOnExports(exprStmt);
if (exportFromObjDefProp) {
stmts.push(exportFromObjDefProp);
return;
}
// Check for:
// "require('foo');" (a require for its side effects)
const expr = exprStmt.expression;
Expand Down