Skip to content
This repository was archived by the owner on May 22, 2025. It is now read-only.

Commit 7950404

Browse files
evmarcopybara-github
authored andcommitted
simplify emit of side effect imports
Imagine code like ``` import 'foo'; // #1 import * as foo from 'foo'; // #2 ``` tsickle currently produces something like: ``` var tsickle_module_1_ = goog.require('foo'); // #1 var foo_1 = tsickle_module_1_; // #2 ``` Using this alias ends up confusing some JSCompiler optimization (see bug). We can avoid it by producing the simpler emit done in this change, which looks instead like: ``` goog.require('foo'); // #1 var foo_1 = goog.require('foo'); // #2 ``` PiperOrigin-RevId: 317957124
1 parent 8335738 commit 7950404

File tree

10 files changed

+163
-46
lines changed

10 files changed

+163
-46
lines changed

src/googmodule.ts

Lines changed: 78 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -291,8 +291,11 @@ export function commonJsToGoogmoduleTransformer(
291291

292292
let moduleVarCounter = 1;
293293
/**
294-
* Creates a new unique variable to assign side effect imports into. This allows us to re-use
295-
* the variable later on for other imports of the same namespace.
294+
* Creates a new unique variable name for holding an imported module. This
295+
* is used to split places where TS wants to codegen code like:
296+
* someExpression(require(...));
297+
* which we then rewrite into
298+
* var x = require(...); someExpression(x);
296299
*/
297300
function nextModuleVar() {
298301
return `tsickle_module_${moduleVarCounter++}_`;
@@ -305,23 +308,28 @@ export function commonJsToGoogmoduleTransformer(
305308
const namespaceToModuleVarName = new Map<string, ts.Identifier>();
306309

307310
/**
308-
* maybeCreateGoogRequire returns a `goog.require()` call for the given CommonJS `require`
309-
* call. Returns null if `call` is not a CommonJS require.
311+
* maybeCreateGoogRequire returns a `goog.require()` call for the given
312+
* CommonJS `require` call. Returns null if `call` is not a CommonJS
313+
* require.
314+
*
315+
* @param newIdent The identifier to assign the result of the goog.require
316+
* to, or undefined if no assignment is needed.
310317
*/
311318
function maybeCreateGoogRequire(
312-
original: ts.Statement, call: ts.CallExpression, newIdent: ts.Identifier): ts.Statement|
313-
null {
319+
original: ts.Statement, call: ts.CallExpression,
320+
newIdent: ts.Identifier|undefined): ts.Statement|null {
314321
const importedUrl = extractRequire(call);
315322
if (!importedUrl) return null;
316323
const imp = importPathToGoogNamespace(host, sf, importedUrl);
317324
modulesManifest.addReferencedModule(sf.fileName, imp.text);
318-
const ident: ts.Identifier|undefined = namespaceToModuleVarName.get(imp.text);
325+
const existingImport: ts.Identifier|undefined =
326+
namespaceToModuleVarName.get(imp.text);
319327
let initializer: ts.Expression;
320-
if (!ident) {
321-
namespaceToModuleVarName.set(imp.text, newIdent);
328+
if (!existingImport) {
329+
if (newIdent) namespaceToModuleVarName.set(imp.text, newIdent);
322330
initializer = createGoogCall('require', imp);
323331
} else {
324-
initializer = ident;
332+
initializer = existingImport;
325333
}
326334

327335
// In JS modules it's recommended that users get a handle on the
@@ -332,18 +340,34 @@ export function commonJsToGoogmoduleTransformer(
332340
// In a goog.module we just want to access the global `goog` value,
333341
// so we skip emitting that import as a goog.require.
334342
// We check the goog module name so that we also catch relative imports.
335-
if (newIdent.escapedText === 'goog' && imp.text === 'google3.javascript.closure.goog') {
343+
if (newIdent && newIdent.escapedText === 'goog' &&
344+
imp.text === 'google3.javascript.closure.goog') {
336345
return createNotEmittedStatementWithComments(sf, original);
337346
}
338347

339-
const varDecl = ts.createVariableDeclaration(newIdent, /* type */ undefined, initializer);
340-
const newStmt = ts.createVariableStatement(
341-
/* modifiers */ undefined,
342-
ts.createVariableDeclarationList(
343-
[varDecl],
344-
// Use 'const' in ES6 mode so Closure properly forwards type aliases.
345-
host.es5Mode ? undefined : ts.NodeFlags.Const));
346-
return ts.setOriginalNode(ts.setTextRange(newStmt, original), original);
348+
if (newIdent) {
349+
// Create a statement like one of:
350+
// var foo = goog.require('bar');
351+
// var foo = existingImport;
352+
const varDecl = ts.createVariableDeclaration(
353+
newIdent, /* type */ undefined, initializer);
354+
const newStmt = ts.createVariableStatement(
355+
/* modifiers */ undefined,
356+
ts.createVariableDeclarationList(
357+
[varDecl],
358+
// Use 'const' in ES6 mode so Closure properly forwards type
359+
// aliases.
360+
host.es5Mode ? undefined : ts.NodeFlags.Const));
361+
return ts.setOriginalNode(
362+
ts.setTextRange(newStmt, original), original);
363+
} else if (!newIdent && !existingImport) {
364+
// Create a statement like:
365+
// goog.require('bar');
366+
const newStmt = ts.createExpressionStatement(initializer);
367+
return ts.setOriginalNode(
368+
ts.setTextRange(newStmt, original), original);
369+
}
370+
return createNotEmittedStatementWithComments(sf, original);
347371
}
348372

349373
/**
@@ -526,35 +550,53 @@ export function commonJsToGoogmoduleTransformer(
526550
stmts.push(...exportStarAsNs);
527551
return;
528552
}
529-
// Check for:
530-
// "require('foo');" (a require for its side effects)
553+
554+
// The rest of this block handles only some function call forms:
555+
// goog.declareModuleId(...);
556+
// require('foo');
557+
// __exportStar(require('foo'), ...);
531558
const expr = exprStmt.expression;
532559
if (!ts.isCallExpression(expr)) break;
533560
let callExpr = expr;
561+
562+
// Check for declareModuleId.
534563
const declaredModuleId = maybeRewriteDeclareModuleId(exprStmt, callExpr);
535564
if (declaredModuleId) {
536565
statements.push(declaredModuleId);
537566
return;
538567
}
539-
// Handle export * in ES5 mode (in ES6 mode, export * is dereferenced already).
540-
// export * creates either a pure top-level '__export(require(...))' or the imported
541-
// version, 'tslib.__exportStar(require(...))'. The imported version is only substituted
542-
// later on though, so appears as a plain "__exportStar" on the top level here.
543-
const isExportStar =
544-
(ts.isIdentifier(expr.expression) && expr.expression.text === '__exportStar') ||
545-
(ts.isIdentifier(expr.expression) && expr.expression.text === '__export');
546-
if (isExportStar) callExpr = expr.arguments[0] as ts.CallExpression;
547-
const ident = ts.createIdentifier(nextModuleVar());
548-
const require = maybeCreateGoogRequire(exprStmt, callExpr, ident);
568+
569+
// Check for __exportStar, the commonjs version of 'export *'.
570+
// export * creates either a pure top-level '__export(require(...))'
571+
// or the imported version, 'tslib.__exportStar(require(...))'. The
572+
// imported version is only substituted later on though, so appears
573+
// as a plain "__exportStar" on the top level here.
574+
const isExportStar = ts.isIdentifier(expr.expression) &&
575+
(expr.expression.text === '__exportStar' ||
576+
expr.expression.text === '__export');
577+
let newIdent: ts.Identifier|undefined;
578+
if (isExportStar) {
579+
// Extract the goog.require() from the call. (It will be verified
580+
// as a goog.require() below.)
581+
callExpr = expr.arguments[0] as ts.CallExpression;
582+
newIdent = ts.createIdentifier(nextModuleVar());
583+
}
584+
585+
// Check whether the call is actually a require() and translate
586+
// as appropriate.
587+
const require =
588+
maybeCreateGoogRequire(exprStmt, callExpr, newIdent);
549589
if (!require) break;
550590
statements.push(require);
551-
// If this is an export star, split it up into the import (created by the maybe call
552-
// above), and the export operation. This avoids a Closure complaint about non-top-level
553-
// requires.
591+
592+
// If this was an export star, split it up into the import (created
593+
// by the maybe call above), and the export operation. This avoids a
594+
// Closure complaint about non-top-level requires.
554595
if (isExportStar) {
555-
const args: ts.Expression[] = [ident];
596+
const args: ts.Expression[] = [newIdent!];
556597
if (expr.arguments.length > 1) args.push(expr.arguments[1]);
557-
statements.push(ts.createStatement(ts.createCall(expr.expression, undefined, args)));
598+
statements.push(ts.createStatement(
599+
ts.createCall(expr.expression, undefined, args)));
558600
}
559601
return;
560602
}

test/googmodule_test.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -133,21 +133,22 @@ console.log(mod_1.x);
133133
expectCommonJs('a.ts', `import 'req/mod';`).toBe(`goog.module('a');
134134
var module = module || { id: 'a.ts' };
135135
goog.require('tslib');
136-
var tsickle_module_1_ = goog.require('req.mod');
136+
goog.require('req.mod');
137137
`);
138138
});
139139

140-
it('converts imports to goog.require calls without assignments after comments', () => {
141-
expectCommonJs('a.ts', `
140+
it('converts imports to goog.require calls without assignments after comments',
141+
() => {
142+
expectCommonJs('a.ts', `
142143
// Comment
143144
import 'req/mod';`)
144-
.toBe(`goog.module('a');
145+
.toBe(`goog.module('a');
145146
var module = module || { id: 'a.ts' };
146147
goog.require('tslib');
147148
// Comment
148-
var tsickle_module_1_ = goog.require('req.mod');
149+
goog.require('req.mod');
149150
`);
150-
});
151+
});
151152

152153
it('keeps fileoverview comments before imports', () => {
153154
expectCommonJs('a.ts', `/** @modName {mod_a} */
@@ -368,7 +369,7 @@ console.log(sym, es6RelativeRequire, es6NonRelativeRequire);
368369
expect(output).toBe(`goog.module('a.b');
369370
var module = module || { id: 'a/b.ts' };
370371
goog.require('tslib');
371-
var tsickle_module_1_ = goog.require('foo.bare_require');
372+
goog.require('foo.bare_require');
372373
var goog_foo_bar_1 = goog.require('foo.bar');
373374
var relative_1 = goog.require('a.relative');
374375
var relative_2 = goog.require('non.relative');
@@ -455,7 +456,7 @@ tslib_1.__exportStar(tsickle_module_1_, exports);
455456
var namedExport_js_1 = goog.require('project.namedExport');
456457
exports.namedRexport = namedExport_js_1.namedRexport;
457458
exports.renamedExportTo = namedExport_js_1.renamedExportFrom;
458-
var tsickle_module_2_ = goog.require('google3.workspace.rooted.file');
459+
goog.require('google3.workspace.rooted.file');
459460
var starImportWorkspaceRooted = goog.require('google3.workspace.rooted.otherFile');
460461
console.log(starImport, file_js_1.namedImport, file_js_1.renamedFrom, starImportWorkspaceRooted);
461462
`);

test_files/file_comment.puretransform/side_effect_import.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@
66
goog.module('test_files.file_comment.puretransform.side_effect_import');
77
var module = module || { id: 'test_files/file_comment.puretransform/side_effect_import.ts' };
88
goog.require('tslib');
9-
const tsickle_module_1_ = goog.require('test_files.file_comment.puretransform.file_comment');
9+
goog.require('test_files.file_comment.puretransform.file_comment');

test_files/file_comment/side_effect_import.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@
1010
goog.module('test_files.file_comment.side_effect_import');
1111
var module = module || { id: 'test_files/file_comment/side_effect_import.ts' };
1212
goog.require('tslib');
13-
const tsickle_module_1_ = goog.require('test_files.file_comment.file_comment');
13+
goog.require('test_files.file_comment.file_comment');
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/**
2+
*
3+
* @fileoverview A module for importing from the main test.
4+
*
5+
* Generated from: test_files/side_effect_import/module1.ts
6+
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
7+
*/
8+
goog.module('test_files.side_effect_import.module1');
9+
var module = module || { id: 'test_files/side_effect_import/module1.ts' };
10+
goog.require('tslib');
11+
class Mod1 {
12+
}
13+
exports.Mod1 = Mod1;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/**
2+
* @fileoverview A module for importing from the main test.
3+
*/
4+
5+
export class Mod1 {}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/**
2+
*
3+
* @fileoverview A module for importing from the main test.
4+
*
5+
* Generated from: test_files/side_effect_import/module2.ts
6+
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
7+
*/
8+
goog.module('test_files.side_effect_import.module2');
9+
var module = module || { id: 'test_files/side_effect_import/module2.ts' };
10+
goog.require('tslib');
11+
class Mod2 {
12+
}
13+
exports.Mod2 = Mod2;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/**
2+
* @fileoverview A module for importing from the main test.
3+
*/
4+
5+
export class Mod2 {}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
*
3+
* @fileoverview Use some side-effect imports and verify that tsickle generates
4+
* proper module code from them.
5+
*
6+
* Generated from: test_files/side_effect_import/side_effect_import.ts
7+
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
8+
*/
9+
// tslint:disable
10+
goog.module('test_files.side_effect_import.side_effect_import');
11+
var module = module || { id: 'test_files/side_effect_import/side_effect_import.ts' };
12+
goog.require('tslib');
13+
const tsickle_module1_1 = goog.requireType("test_files.side_effect_import.module1");
14+
const tsickle_module2_2 = goog.requireType("test_files.side_effect_import.module2");
15+
goog.require('test_files.side_effect_import.module1');
16+
goog.require('test_files.side_effect_import.module2');
17+
const module1_1 = goog.require('test_files.side_effect_import.module1');
18+
// Use one as a type and the other as a value.
19+
/** @type {!tsickle_module1_1.Mod1} */
20+
let x = new module1_1.Mod1();
21+
/** @type {!tsickle_module2_2.Mod2} */
22+
let y;
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/**
2+
* @fileoverview Use some side-effect imports and verify that tsickle generates
3+
* proper module code from them.
4+
*/
5+
6+
// tslint:disable
7+
8+
import './module1';
9+
import './module2';
10+
11+
import {Mod1} from './module1';
12+
import {Mod2} from './module2';
13+
14+
// Use one as a type and the other as a value.
15+
let x = new Mod1();
16+
let y: Mod2;

0 commit comments

Comments
 (0)