Skip to content

Commit

Permalink
Use getters to define live export bindings refresh (#35967)
Browse files Browse the repository at this point in the history
* use getters to define live export bindings

* fix scoping in export* helper

* Object.defineProperty cannot be used in ES3 target

* Accept changed baselines

* Use function expression, not arrow function

* Update importStarHelper to match export helper in binding-making

* Fix whitespace

* Adjust whitespace in edited helpers

* Use new helper for setting bindings, use unscoped __exportStar helper for exports so helpers get reused more

* Accept updated baselines

* Use __createBinding for individual reexports when target is es3

* Remove unneeded type assertion

* Singeline the helpers

* Add check for createBinding helper, accept updated baselines with shortened helper

Co-authored-by: Michael Rawlings <[email protected]>
  • Loading branch information
weswigham and mlrawlings committed Feb 24, 2020
1 parent 7a3b6b4 commit 65e7acc
Show file tree
Hide file tree
Showing 106 changed files with 1,480 additions and 426 deletions.
6 changes: 6 additions & 0 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33450,6 +33450,10 @@ namespace ts {
grammarErrorOnFirstToken(node, Diagnostics.An_export_declaration_cannot_have_modifiers);
}

if (node.moduleSpecifier && node.exportClause && isNamedExports(node.exportClause) && length(node.exportClause.elements) && languageVersion === ScriptTarget.ES3) {
checkExternalEmitHelpers(node, ExternalEmitHelpers.CreateBinding);
}

if (!node.moduleSpecifier || checkExternalImportOrExportDeclaration(node)) {
if (node.exportClause) {
// export { x, y }
Expand Down Expand Up @@ -35661,6 +35665,8 @@ namespace ts {
case ExternalEmitHelpers.MakeTemplateObject: return "__makeTemplateObject";
case ExternalEmitHelpers.ClassPrivateFieldGet: return "__classPrivateFieldGet";
case ExternalEmitHelpers.ClassPrivateFieldSet: return "__classPrivateFieldSet";
case ExternalEmitHelpers.CreateBinding: return "__createBinding";
case ExternalEmitHelpers.SetModuleDefault: return "__setModuleDefault";
default: return Debug.fail("Unrecognized helper");
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/compiler/transformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,11 @@ namespace ts {
Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the transformation context during initialization.");
Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed.");
Debug.assert(!helper.scoped, "Cannot request a scoped emit helper.");
if (helper.dependencies) {
for (const h of helper.dependencies) {
requestEmitHelper(h);
}
}
emitHelpers = append(emitHelpers, helper);
}

Expand Down
148 changes: 105 additions & 43 deletions src/compiler/transformers/module/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,11 +103,6 @@ namespace ts {
insertStatementsAfterStandardPrologue(statements, endLexicalEnvironment());

const updated = updateSourceFileNode(node, setTextRange(createNodeArray(statements), node.statements));
if (currentModuleInfo.hasExportStarsToExportValues && !compilerOptions.importHelpers) {
// If we have any `export * from ...` declarations
// we need to inform the emitter to add the __export helper.
addEmitHelper(updated, exportStarHelper);
}
addEmitHelpers(updated, context.readEmitHelpers());
return updated;
}
Expand Down Expand Up @@ -435,11 +430,6 @@ namespace ts {
insertStatementsAfterStandardPrologue(statements, endLexicalEnvironment());

const body = createBlock(statements, /*multiLine*/ true);
if (currentModuleInfo.hasExportStarsToExportValues && !compilerOptions.importHelpers) {
// If we have any `export * from ...` declarations
// we need to inform the emitter to add the __export helper.
addEmitHelper(body, exportStarHelper);
}
if (needUMDDynamicImportHelper) {
addEmitHelper(body, dynamicImportUMDHelper);
}
Expand Down Expand Up @@ -1001,20 +991,34 @@ namespace ts {
);
}
for (const specifier of node.exportClause.elements) {
const exportedValue = createPropertyAccess(
generatedName,
specifier.propertyName || specifier.name
);
statements.push(
setOriginalNode(
setTextRange(
createExpressionStatement(
createExportExpression(getExportName(specifier), exportedValue)
),
specifier),
specifier
)
);
if (languageVersion === ScriptTarget.ES3) {
statements.push(
setOriginalNode(
setTextRange(
createExpressionStatement(
createCreateBindingHelper(context, generatedName, createLiteral(specifier.propertyName || specifier.name), specifier.propertyName ? createLiteral(specifier.name) : undefined)
),
specifier),
specifier
)
);
}
else {
const exportedValue = createPropertyAccess(
generatedName,
specifier.propertyName || specifier.name
);
statements.push(
setOriginalNode(
setTextRange(
createExpressionStatement(
createExportExpression(getExportName(specifier), exportedValue, /* location */ undefined, /* liveBinding */ true)
),
specifier),
specifier
)
);
}
}

return singleOrMany(statements);
Expand Down Expand Up @@ -1343,7 +1347,7 @@ namespace ts {

case SyntaxKind.NamedImports:
for (const importBinding of namedBindings.elements) {
statements = appendExportsOfDeclaration(statements, importBinding);
statements = appendExportsOfDeclaration(statements, importBinding, /* liveBinding */ true);
}

break;
Expand Down Expand Up @@ -1453,12 +1457,12 @@ namespace ts {
* appended.
* @param decl The declaration to export.
*/
function appendExportsOfDeclaration(statements: Statement[] | undefined, decl: Declaration): Statement[] | undefined {
function appendExportsOfDeclaration(statements: Statement[] | undefined, decl: Declaration, liveBinding?: boolean): Statement[] | undefined {
const name = getDeclarationName(decl);
const exportSpecifiers = currentModuleInfo.exportSpecifiers.get(idText(name));
if (exportSpecifiers) {
for (const exportSpecifier of exportSpecifiers) {
statements = appendExportStatement(statements, exportSpecifier.name, name, /*location*/ exportSpecifier.name);
statements = appendExportStatement(statements, exportSpecifier.name, name, /*location*/ exportSpecifier.name, /* allowComments */ undefined, liveBinding);
}
}
return statements;
Expand All @@ -1476,8 +1480,8 @@ namespace ts {
* @param location The location to use for source maps and comments for the export.
* @param allowComments Whether to allow comments on the export.
*/
function appendExportStatement(statements: Statement[] | undefined, exportName: Identifier, expression: Expression, location?: TextRange, allowComments?: boolean): Statement[] | undefined {
statements = append(statements, createExportStatement(exportName, expression, location, allowComments));
function appendExportStatement(statements: Statement[] | undefined, exportName: Identifier, expression: Expression, location?: TextRange, allowComments?: boolean, liveBinding?: boolean): Statement[] | undefined {
statements = append(statements, createExportStatement(exportName, expression, location, allowComments, liveBinding));
return statements;
}

Expand Down Expand Up @@ -1518,8 +1522,8 @@ namespace ts {
* @param location The location to use for source maps and comments for the export.
* @param allowComments An optional value indicating whether to emit comments for the statement.
*/
function createExportStatement(name: Identifier, value: Expression, location?: TextRange, allowComments?: boolean) {
const statement = setTextRange(createExpressionStatement(createExportExpression(name, value)), location);
function createExportStatement(name: Identifier, value: Expression, location?: TextRange, allowComments?: boolean, liveBinding?: boolean) {
const statement = setTextRange(createExpressionStatement(createExportExpression(name, value, /* location */ undefined, liveBinding)), location);
startOnNewLine(statement);
if (!allowComments) {
setEmitFlags(statement, EmitFlags.NoComments);
Expand All @@ -1535,9 +1539,31 @@ namespace ts {
* @param value The exported value.
* @param location The location to use for source maps and comments for the export.
*/
function createExportExpression(name: Identifier, value: Expression, location?: TextRange) {
function createExportExpression(name: Identifier, value: Expression, location?: TextRange, liveBinding?: boolean) {
return setTextRange(
createAssignment(
liveBinding && languageVersion !== ScriptTarget.ES3 ? createCall(
createPropertyAccess(
createIdentifier("Object"),
"defineProperty"
),
/*typeArguments*/ undefined,
[
createIdentifier("exports"),
createLiteral(name),
createObjectLiteral([
createPropertyAssignment("enumerable", createLiteral(/*value*/ true)),
createPropertyAssignment("get", createFunctionExpression(
/*modifiers*/ undefined,
/*asteriskToken*/ undefined,
/*name*/ undefined,
/*typeParameters*/ undefined,
/*parameters*/ [],
/*type*/ undefined,
createBlock([createReturn(value)])
))
])
]
) : createAssignment(
createPropertyAccess(
createIdentifier("exports"),
getSynthesizedClone(name)
Expand Down Expand Up @@ -1813,21 +1839,55 @@ namespace ts {
}
}

export const createBindingHelper: UnscopedEmitHelper = {
name: "typescript:commonjscreatebinding",
importName: "__createBinding",
scoped: false,
priority: 1,
text: `
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));`
};

function createCreateBindingHelper(context: TransformationContext, module: Expression, inputName: Expression, outputName: Expression | undefined) {
context.requestEmitHelper(createBindingHelper);
return createCall(getUnscopedHelperName("__createBinding"), /*typeArguments*/ undefined, [createIdentifier("exports"), module, inputName, ...(outputName ? [outputName] : [])]);
}

export const setModuleDefaultHelper: UnscopedEmitHelper = {
name: "typescript:commonjscreatevalue",
importName: "__setModuleDefault",
scoped: false,
priority: 1,
text: `
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});`
};

// emit output for the __export helper function
const exportStarHelper: EmitHelper = {
const exportStarHelper: UnscopedEmitHelper = {
name: "typescript:export-star",
scoped: true,
importName: "__exportStar",
scoped: false,
dependencies: [createBindingHelper],
priority: 2,
text: `
function __export(m) {
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (!exports.hasOwnProperty(p)) __createBinding(exports, m, p);
}`
};

function createExportStarHelper(context: TransformationContext, module: Expression) {
const compilerOptions = context.getCompilerOptions();
return compilerOptions.importHelpers
? createCall(getUnscopedHelperName("__exportStar"), /*typeArguments*/ undefined, [module, createIdentifier("exports")])
: createCall(createIdentifier("__export"), /*typeArguments*/ undefined, [module]);
context.requestEmitHelper(exportStarHelper);
return createCall(getUnscopedHelperName("__exportStar"), /*typeArguments*/ undefined, [module, createIdentifier("exports")]);
}

// emit helper for dynamic import
Expand All @@ -1843,12 +1903,14 @@ namespace ts {
name: "typescript:commonjsimportstar",
importName: "__importStar",
scoped: false,
dependencies: [createBindingHelper, setModuleDefaultHelper],
priority: 2,
text: `
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};`
};
Expand Down
5 changes: 4 additions & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5794,6 +5794,7 @@ namespace ts {
readonly scoped: boolean; // Indicates whether the helper MUST be emitted in the current scope.
readonly text: string | ((node: EmitHelperUniqueNameCallback) => string); // ES3-compatible raw script text, or a function yielding such a string
readonly priority?: number; // Helpers with a higher priority are emitted earlier than other helpers on the node.
readonly dependencies?: EmitHelper[]
}

export interface UnscopedEmitHelper extends EmitHelper {
Expand Down Expand Up @@ -5834,8 +5835,10 @@ namespace ts {
MakeTemplateObject = 1 << 17, // __makeTemplateObject (used for constructing template string array objects)
ClassPrivateFieldGet = 1 << 18, // __classPrivateFieldGet (used by the class private field transformation)
ClassPrivateFieldSet = 1 << 19, // __classPrivateFieldSet (used by the class private field transformation)
CreateBinding = 1 << 20, // __createBinding (use by the module transform for exports and namespace imports)
SetModuleDefault = 1 << 21, // __setModuleDefault (use by the module transform for default exports)
FirstEmitHelper = Extends,
LastEmitHelper = ClassPrivateFieldSet,
LastEmitHelper = SetModuleDefault,

// Helpers included by ES2015 for..of
ForOfIncludes = Values,
Expand Down
22 changes: 18 additions & 4 deletions tests/baselines/reference/ambientShorthand_reExport.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,30 @@ x($);

//// [reExportX.js]
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
exports.__esModule = true;
var jquery_1 = require("jquery");
exports.x = jquery_1.x;
__createBinding(exports, jquery_1, "x");
//// [reExportAll.js]
"use strict";
function __export(m) {
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (!exports.hasOwnProperty(p)) __createBinding(exports, m, p);
}
exports.__esModule = true;
__export(require("jquery"));
__exportStar(require("jquery"), exports);
//// [reExportUser.js]
"use strict";
exports.__esModule = true;
Expand Down
1 change: 1 addition & 0 deletions tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2972,6 +2972,7 @@ declare namespace ts {
readonly scoped: boolean;
readonly text: string | ((node: EmitHelperUniqueNameCallback) => string);
readonly priority?: number;
readonly dependencies?: EmitHelper[];
}
export interface UnscopedEmitHelper extends EmitHelper {
readonly scoped: false;
Expand Down
1 change: 1 addition & 0 deletions tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2972,6 +2972,7 @@ declare namespace ts {
readonly scoped: boolean;
readonly text: string | ((node: EmitHelperUniqueNameCallback) => string);
readonly priority?: number;
readonly dependencies?: EmitHelper[];
}
export interface UnscopedEmitHelper extends EmitHelper {
readonly scoped: false;
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/commentsOnRequireStatement.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
// blah
// blah
var _0_1 = require("./0");
exports.subject = _0_1.subject;
Object.defineProperty(exports, "subject", { enumerable: true, get: function () { return _0_1.subject; } });
/* blah1 */
var _1_1 = require("./1");
exports.subject1 = _1_1.subject1;
Object.defineProperty(exports, "subject1", { enumerable: true, get: function () { return _1_1.subject1; } });
9 changes: 8 additions & 1 deletion tests/baselines/reference/constEnumPreserveEmitReexport.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ var ConstEnum_1 = require("./ConstEnum");
exports["default"] = ConstEnum_1.MyConstEnum;
//// [ReExport.js]
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
exports.__esModule = true;
var ConstEnum_1 = require("./ConstEnum");
exports["default"] = ConstEnum_1.MyConstEnum;
__createBinding(exports, ConstEnum_1, "MyConstEnum", "default");
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ exports.bar = bar;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var utils_1 = require("./utils");
exports.bar = utils_1.bar;
Object.defineProperty(exports, "bar", { enumerable: true, get: function () { return utils_1.bar; } });
utils_1.foo();
var obj;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,18 @@ var pkg2_1 = require("@raymondfeng/pkg2");
exports.ADMIN = pkg2_1.MetadataAccessor.create('1');
//// [index.js]
"use strict";
function __export(m) {
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (!exports.hasOwnProperty(p)) __createBinding(exports, m, p);
}
Object.defineProperty(exports, "__esModule", { value: true });
__export(require("./keys"));
__exportStar(require("./keys"), exports);


//// [keys.d.ts]
Expand Down
Loading

0 comments on commit 65e7acc

Please sign in to comment.