Skip to content

Commit 05af8fa

Browse files
authored
Support tree-shakable imports for --target es2015 (microsoft#32742)
* Support tree-shakable imports for --target es2015 * Alias external helper imports for --module es2015
1 parent 85b8d27 commit 05af8fa

18 files changed

+293
-59
lines changed

src/compiler/emitter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1609,7 +1609,7 @@ namespace ts {
16091609
for (let i = 0; i < numNodes; i++) {
16101610
const currentNode = bundle ? i < numPrepends ? bundle.prepends[i] : bundle.sourceFiles[i - numPrepends] : node;
16111611
const sourceFile = isSourceFile(currentNode) ? currentNode : isUnparsedSource(currentNode) ? undefined : currentSourceFile!;
1612-
const shouldSkip = printerOptions.noEmitHelpers || (!!sourceFile && getExternalHelpersModuleName(sourceFile) !== undefined);
1612+
const shouldSkip = printerOptions.noEmitHelpers || (!!sourceFile && hasRecordedExternalHelpers(sourceFile));
16131613
const shouldBundle = (isSourceFile(currentNode) || isUnparsedSource(currentNode)) && !isOwnFileEmit;
16141614
const helpers = isUnparsedSource(currentNode) ? currentNode.helpers : getSortedEmitHelpers(currentNode);
16151615
if (helpers) {

src/compiler/factory.ts

Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3628,12 +3628,16 @@ namespace ts {
36283628

36293629
// Helpers
36303630

3631-
export function getHelperName(name: string) {
3631+
/**
3632+
* Gets an identifier for the name of an *unscoped* emit helper.
3633+
*/
3634+
export function getUnscopedHelperName(name: string) {
36323635
return setEmitFlags(createIdentifier(name), EmitFlags.HelperName | EmitFlags.AdviseOnEmitNode);
36333636
}
36343637

36353638
export const valuesHelper: UnscopedEmitHelper = {
36363639
name: "typescript:values",
3640+
importName: "__values",
36373641
scoped: false,
36383642
text: `
36393643
var __values = (this && this.__values) || function(o) {
@@ -3653,7 +3657,7 @@ namespace ts {
36533657
context.requestEmitHelper(valuesHelper);
36543658
return setTextRange(
36553659
createCall(
3656-
getHelperName("__values"),
3660+
getUnscopedHelperName("__values"),
36573661
/*typeArguments*/ undefined,
36583662
[expression]
36593663
),
@@ -3663,6 +3667,7 @@ namespace ts {
36633667

36643668
export const readHelper: UnscopedEmitHelper = {
36653669
name: "typescript:read",
3670+
importName: "__read",
36663671
scoped: false,
36673672
text: `
36683673
var __read = (this && this.__read) || function (o, n) {
@@ -3687,7 +3692,7 @@ namespace ts {
36873692
context.requestEmitHelper(readHelper);
36883693
return setTextRange(
36893694
createCall(
3690-
getHelperName("__read"),
3695+
getUnscopedHelperName("__read"),
36913696
/*typeArguments*/ undefined,
36923697
count !== undefined
36933698
? [iteratorRecord, createLiteral(count)]
@@ -3699,6 +3704,7 @@ namespace ts {
36993704

37003705
export const spreadHelper: UnscopedEmitHelper = {
37013706
name: "typescript:spread",
3707+
importName: "__spread",
37023708
scoped: false,
37033709
text: `
37043710
var __spread = (this && this.__spread) || function () {
@@ -3712,7 +3718,7 @@ namespace ts {
37123718
context.requestEmitHelper(spreadHelper);
37133719
return setTextRange(
37143720
createCall(
3715-
getHelperName("__spread"),
3721+
getUnscopedHelperName("__spread"),
37163722
/*typeArguments*/ undefined,
37173723
argumentList
37183724
),
@@ -3722,6 +3728,7 @@ namespace ts {
37223728

37233729
export const spreadArraysHelper: UnscopedEmitHelper = {
37243730
name: "typescript:spreadArrays",
3731+
importName: "__spreadArrays",
37253732
scoped: false,
37263733
text: `
37273734
var __spreadArrays = (this && this.__spreadArrays) || function () {
@@ -3737,7 +3744,7 @@ namespace ts {
37373744
context.requestEmitHelper(spreadArraysHelper);
37383745
return setTextRange(
37393746
createCall(
3740-
getHelperName("__spreadArrays"),
3747+
getUnscopedHelperName("__spreadArrays"),
37413748
/*typeArguments*/ undefined,
37423749
argumentList
37433750
),
@@ -4863,6 +4870,65 @@ namespace ts {
48634870
return emitNode && emitNode.externalHelpersModuleName;
48644871
}
48654872

4873+
export function hasRecordedExternalHelpers(sourceFile: SourceFile) {
4874+
const parseNode = getOriginalNode(sourceFile, isSourceFile);
4875+
const emitNode = parseNode && parseNode.emitNode;
4876+
return !!emitNode && (!!emitNode.externalHelpersModuleName || !!emitNode.externalHelpers);
4877+
}
4878+
4879+
export function createExternalHelpersImportDeclarationIfNeeded(sourceFile: SourceFile, compilerOptions: CompilerOptions, hasExportStarsToExportValues?: boolean, hasImportStar?: boolean, hasImportDefault?: boolean) {
4880+
if (compilerOptions.importHelpers && isEffectiveExternalModule(sourceFile, compilerOptions)) {
4881+
let namedBindings: NamedImportBindings | undefined;
4882+
const moduleKind = getEmitModuleKind(compilerOptions);
4883+
if (moduleKind >= ModuleKind.ES2015 && moduleKind <= ModuleKind.ESNext) {
4884+
// use named imports
4885+
const helpers = getEmitHelpers(sourceFile);
4886+
if (helpers) {
4887+
const helperNames: string[] = [];
4888+
for (const helper of helpers) {
4889+
if (!helper.scoped) {
4890+
const importName = (helper as UnscopedEmitHelper).importName;
4891+
if (importName) {
4892+
pushIfUnique(helperNames, importName);
4893+
}
4894+
}
4895+
}
4896+
if (some(helperNames)) {
4897+
helperNames.sort(compareStringsCaseSensitive);
4898+
// Alias the imports if the names are used somewhere in the file.
4899+
// NOTE: We don't need to care about global import collisions as this is a module.
4900+
namedBindings = createNamedImports(
4901+
map(helperNames, name => isFileLevelUniqueName(sourceFile, name)
4902+
? createImportSpecifier(/*propertyName*/ undefined, createIdentifier(name))
4903+
: createImportSpecifier(createIdentifier(name), getUnscopedHelperName(name))
4904+
)
4905+
);
4906+
const parseNode = getOriginalNode(sourceFile, isSourceFile);
4907+
const emitNode = getOrCreateEmitNode(parseNode);
4908+
emitNode.externalHelpers = true;
4909+
}
4910+
}
4911+
}
4912+
else {
4913+
// use a namespace import
4914+
const externalHelpersModuleName = getOrCreateExternalHelpersModuleNameIfNeeded(sourceFile, compilerOptions, hasExportStarsToExportValues, hasImportStar || hasImportDefault);
4915+
if (externalHelpersModuleName) {
4916+
namedBindings = createNamespaceImport(externalHelpersModuleName);
4917+
}
4918+
}
4919+
if (namedBindings) {
4920+
const externalHelpersImportDeclaration = createImportDeclaration(
4921+
/*decorators*/ undefined,
4922+
/*modifiers*/ undefined,
4923+
createImportClause(/*name*/ undefined, namedBindings),
4924+
createLiteral(externalHelpersModuleNameText)
4925+
);
4926+
addEmitFlags(externalHelpersImportDeclaration, EmitFlags.NeverApplyImportHelper);
4927+
return externalHelpersImportDeclaration;
4928+
}
4929+
}
4930+
}
4931+
48664932
export function getOrCreateExternalHelpersModuleNameIfNeeded(node: SourceFile, compilerOptions: CompilerOptions, hasExportStarsToExportValues?: boolean, hasImportStarOrImportDefault?: boolean) {
48674933
if (compilerOptions.importHelpers && isEffectiveExternalModule(node, compilerOptions)) {
48684934
const externalHelpersModuleName = getExternalHelpersModuleName(node);

src/compiler/transformers/destructuring.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,7 @@ namespace ts {
514514

515515
export const restHelper: UnscopedEmitHelper = {
516516
name: "typescript:rest",
517+
importName: "__rest",
517518
scoped: false,
518519
text: `
519520
var __rest = (this && this.__rest) || function (s, e) {
@@ -557,7 +558,7 @@ namespace ts {
557558
}
558559
}
559560
return createCall(
560-
getHelperName("__rest"),
561+
getUnscopedHelperName("__rest"),
561562
/*typeArguments*/ undefined,
562563
[
563564
value,

src/compiler/transformers/es2015.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4339,7 +4339,7 @@ namespace ts {
43394339
function createExtendsHelper(context: TransformationContext, name: Identifier) {
43404340
context.requestEmitHelper(extendsHelper);
43414341
return createCall(
4342-
getHelperName("__extends"),
4342+
getUnscopedHelperName("__extends"),
43434343
/*typeArguments*/ undefined,
43444344
[
43454345
name,
@@ -4351,7 +4351,7 @@ namespace ts {
43514351
function createTemplateObjectHelper(context: TransformationContext, cooked: ArrayLiteralExpression, raw: ArrayLiteralExpression) {
43524352
context.requestEmitHelper(templateObjectHelper);
43534353
return createCall(
4354-
getHelperName("__makeTemplateObject"),
4354+
getUnscopedHelperName("__makeTemplateObject"),
43554355
/*typeArguments*/ undefined,
43564356
[
43574357
cooked,
@@ -4362,6 +4362,7 @@ namespace ts {
43624362

43634363
export const extendsHelper: UnscopedEmitHelper = {
43644364
name: "typescript:extends",
4365+
importName: "__extends",
43654366
scoped: false,
43664367
priority: 0,
43674368
text: `
@@ -4383,6 +4384,7 @@ namespace ts {
43834384

43844385
export const templateObjectHelper: UnscopedEmitHelper = {
43854386
name: "typescript:makeTemplateObject",
4387+
importName: "__makeTemplateObject",
43864388
scoped: false,
43874389
priority: 0,
43884390
text: `

src/compiler/transformers/es2017.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -771,6 +771,7 @@ namespace ts {
771771

772772
export const awaiterHelper: UnscopedEmitHelper = {
773773
name: "typescript:awaiter",
774+
importName: "__awaiter",
774775
scoped: false,
775776
priority: 5,
776777
text: `
@@ -802,7 +803,7 @@ namespace ts {
802803
(generatorFunc.emitNode || (generatorFunc.emitNode = {} as EmitNode)).flags |= EmitFlags.AsyncFunctionBody | EmitFlags.ReuseTempVariableScope;
803804

804805
return createCall(
805-
getHelperName("__awaiter"),
806+
getUnscopedHelperName("__awaiter"),
806807
/*typeArguments*/ undefined,
807808
[
808809
createThis(),

src/compiler/transformers/es2018.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -995,6 +995,7 @@ namespace ts {
995995

996996
export const assignHelper: UnscopedEmitHelper = {
997997
name: "typescript:assign",
998+
importName: "__assign",
998999
scoped: false,
9991000
priority: 1,
10001001
text: `
@@ -1019,26 +1020,28 @@ namespace ts {
10191020
}
10201021
context.requestEmitHelper(assignHelper);
10211022
return createCall(
1022-
getHelperName("__assign"),
1023+
getUnscopedHelperName("__assign"),
10231024
/*typeArguments*/ undefined,
10241025
attributesSegments
10251026
);
10261027
}
10271028

10281029
export const awaitHelper: UnscopedEmitHelper = {
10291030
name: "typescript:await",
1031+
importName: "__await",
10301032
scoped: false,
10311033
text: `
10321034
var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }`
10331035
};
10341036

10351037
function createAwaitHelper(context: TransformationContext, expression: Expression) {
10361038
context.requestEmitHelper(awaitHelper);
1037-
return createCall(getHelperName("__await"), /*typeArguments*/ undefined, [expression]);
1039+
return createCall(getUnscopedHelperName("__await"), /*typeArguments*/ undefined, [expression]);
10381040
}
10391041

10401042
export const asyncGeneratorHelper: UnscopedEmitHelper = {
10411043
name: "typescript:asyncGenerator",
1044+
importName: "__asyncGenerator",
10421045
scoped: false,
10431046
text: `
10441047
var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
@@ -1062,7 +1065,7 @@ namespace ts {
10621065
(generatorFunc.emitNode || (generatorFunc.emitNode = {} as EmitNode)).flags |= EmitFlags.AsyncFunctionBody;
10631066

10641067
return createCall(
1065-
getHelperName("__asyncGenerator"),
1068+
getUnscopedHelperName("__asyncGenerator"),
10661069
/*typeArguments*/ undefined,
10671070
[
10681071
createThis(),
@@ -1074,6 +1077,7 @@ namespace ts {
10741077

10751078
export const asyncDelegator: UnscopedEmitHelper = {
10761079
name: "typescript:asyncDelegator",
1080+
importName: "__asyncDelegator",
10771081
scoped: false,
10781082
text: `
10791083
var __asyncDelegator = (this && this.__asyncDelegator) || function (o) {
@@ -1088,7 +1092,7 @@ namespace ts {
10881092
context.requestEmitHelper(asyncDelegator);
10891093
return setTextRange(
10901094
createCall(
1091-
getHelperName("__asyncDelegator"),
1095+
getUnscopedHelperName("__asyncDelegator"),
10921096
/*typeArguments*/ undefined,
10931097
[expression]
10941098
),
@@ -1098,6 +1102,7 @@ namespace ts {
10981102

10991103
export const asyncValues: UnscopedEmitHelper = {
11001104
name: "typescript:asyncValues",
1105+
importName: "__asyncValues",
11011106
scoped: false,
11021107
text: `
11031108
var __asyncValues = (this && this.__asyncValues) || function (o) {
@@ -1113,7 +1118,7 @@ namespace ts {
11131118
context.requestEmitHelper(asyncValues);
11141119
return setTextRange(
11151120
createCall(
1116-
getHelperName("__asyncValues"),
1121+
getUnscopedHelperName("__asyncValues"),
11171122
/*typeArguments*/ undefined,
11181123
[expression]
11191124
),

src/compiler/transformers/generators.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3176,7 +3176,7 @@ namespace ts {
31763176
function createGeneratorHelper(context: TransformationContext, body: FunctionExpression) {
31773177
context.requestEmitHelper(generatorHelper);
31783178
return createCall(
3179-
getHelperName("__generator"),
3179+
getUnscopedHelperName("__generator"),
31803180
/*typeArguments*/ undefined,
31813181
[createThis(), body]);
31823182
}
@@ -3242,6 +3242,7 @@ namespace ts {
32423242
// For examples of how these are used, see the comments in ./transformers/generators.ts
32433243
export const generatorHelper: UnscopedEmitHelper = {
32443244
name: "typescript:generator",
3245+
importName: "__generator",
32453246
scoped: false,
32463247
priority: 6,
32473248
text: `

src/compiler/transformers/module/es2015.ts

Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace ts {
99
context.enableEmitNotification(SyntaxKind.SourceFile);
1010
context.enableSubstitution(SyntaxKind.Identifier);
1111

12-
let currentSourceFile: SourceFile | undefined;
12+
let helperNameSubstitutions: Map<Identifier> | undefined;
1313
return chainBundle(transformSourceFile);
1414

1515
function transformSourceFile(node: SourceFile) {
@@ -18,18 +18,11 @@ namespace ts {
1818
}
1919

2020
if (isExternalModule(node) || compilerOptions.isolatedModules) {
21-
const externalHelpersModuleName = getOrCreateExternalHelpersModuleNameIfNeeded(node, compilerOptions);
22-
if (externalHelpersModuleName) {
21+
const externalHelpersImportDeclaration = createExternalHelpersImportDeclarationIfNeeded(node, compilerOptions);
22+
if (externalHelpersImportDeclaration) {
2323
const statements: Statement[] = [];
2424
const statementOffset = addPrologue(statements, node.statements);
25-
const tslibImport = createImportDeclaration(
26-
/*decorators*/ undefined,
27-
/*modifiers*/ undefined,
28-
createImportClause(/*name*/ undefined, createNamespaceImport(externalHelpersModuleName)),
29-
createLiteral(externalHelpersModuleNameText)
30-
);
31-
addEmitFlags(tslibImport, EmitFlags.NeverApplyImportHelper);
32-
append(statements, tslibImport);
25+
append(statements, externalHelpersImportDeclaration);
3326

3427
addRange(statements, visitNodes(node.statements, visitor, isStatement, statementOffset));
3528
return updateSourceFileNode(
@@ -74,9 +67,9 @@ namespace ts {
7467
*/
7568
function onEmitNode(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void): void {
7669
if (isSourceFile(node)) {
77-
currentSourceFile = node;
70+
helperNameSubstitutions = createMap<Identifier>();
7871
previousOnEmitNode(hint, node, emitCallback);
79-
currentSourceFile = undefined;
72+
helperNameSubstitutions = undefined;
8073
}
8174
else {
8275
previousOnEmitNode(hint, node, emitCallback);
@@ -95,21 +88,20 @@ namespace ts {
9588
*/
9689
function onSubstituteNode(hint: EmitHint, node: Node) {
9790
node = previousOnSubstituteNode(hint, node);
98-
if (isIdentifier(node) && hint === EmitHint.Expression) {
99-
return substituteExpressionIdentifier(node);
91+
if (helperNameSubstitutions && isIdentifier(node) && getEmitFlags(node) & EmitFlags.HelperName) {
92+
return substituteHelperName(node);
10093
}
10194

10295
return node;
10396
}
10497

105-
function substituteExpressionIdentifier(node: Identifier): Expression {
106-
if (getEmitFlags(node) & EmitFlags.HelperName) {
107-
const externalHelpersModuleName = getExternalHelpersModuleName(currentSourceFile!);
108-
if (externalHelpersModuleName) {
109-
return createPropertyAccess(externalHelpersModuleName, node);
110-
}
98+
function substituteHelperName(node: Identifier): Expression {
99+
const name = idText(node);
100+
let substitution = helperNameSubstitutions!.get(name);
101+
if (!substitution) {
102+
helperNameSubstitutions!.set(name, substitution = createFileLevelUniqueName(name));
111103
}
112-
return node;
104+
return substitution;
113105
}
114106
}
115107
}

0 commit comments

Comments
 (0)