diff --git a/scripts/processDiagnosticMessages.ts b/scripts/processDiagnosticMessages.ts index eeb6debfa56..414e7e6e1b9 100644 --- a/scripts/processDiagnosticMessages.ts +++ b/scripts/processDiagnosticMessages.ts @@ -8,6 +8,7 @@ interface DiagnosticDetails { reportsDeprecated?: {}; isEarly?: boolean; elidedInCompatabilityPyramid?: boolean; + retired?: boolean; // indicates the code has been retired and is present only to prevent reuse } type InputDiagnosticMessageTable = Map; @@ -34,6 +35,7 @@ function main(): void { const diagnosticMessages: InputDiagnosticMessageTable = new Map(); for (const key in diagnosticMessagesJson) { if (Object.hasOwnProperty.call(diagnosticMessagesJson, key)) { + if (diagnosticMessagesJson[key].retired) continue; diagnosticMessages.set(key, diagnosticMessagesJson[key]); } } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 003bc879639..eb3435ce377 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -323,6 +323,7 @@ namespace ts { const compilerOptions = host.getCompilerOptions(); const languageVersion = getEmitScriptTarget(compilerOptions); const moduleKind = getEmitModuleKind(compilerOptions); + const umdExport = getSingleFileUmdExport(compilerOptions); const allowSyntheticDefaultImports = getAllowSyntheticDefaultImports(compilerOptions); const strictNullChecks = getStrictOptionValue(compilerOptions, "strictNullChecks"); const strictFunctionTypes = getStrictOptionValue(compilerOptions, "strictFunctionTypes"); @@ -924,6 +925,7 @@ namespace ts { const diagnostics = createDiagnosticCollection(); const suggestionDiagnostics = createDiagnosticCollection(); + let hasReportedUmdExportDiagnostic = false; const typeofTypesByName: ReadonlyESMap = new Map(getEntries({ string: stringType, @@ -965,7 +967,7 @@ namespace ts { if (jsxFragmentPragma) { const chosenPragma = isArray(jsxFragmentPragma) ? jsxFragmentPragma[0] : jsxFragmentPragma; file.localJsxFragmentFactory = parseIsolatedEntityName(chosenPragma.arguments.factory, languageVersion); - visitNode(file.localJsxFragmentFactory, markAsSynthetic); + markAsSynthetic(file.localJsxFragmentFactory); if (file.localJsxFragmentFactory) { return file.localJsxFragmentNamespace = getFirstIdentifier(file.localJsxFragmentFactory).escapedText; } @@ -984,7 +986,7 @@ namespace ts { if (jsxPragma) { const chosenPragma = isArray(jsxPragma) ? jsxPragma[0] : jsxPragma; file.localJsxFactory = parseIsolatedEntityName(chosenPragma.arguments.factory, languageVersion); - visitNode(file.localJsxFactory, markAsSynthetic); + markAsSynthetic(file.localJsxFactory); if (file.localJsxFactory) { return file.localJsxNamespace = getFirstIdentifier(file.localJsxFactory).escapedText; } @@ -996,7 +998,7 @@ namespace ts { _jsxNamespace = "React" as __String; if (compilerOptions.jsxFactory) { _jsxFactoryEntity = parseIsolatedEntityName(compilerOptions.jsxFactory, languageVersion); - visitNode(_jsxFactoryEntity, markAsSynthetic); + markAsSynthetic(_jsxFactoryEntity); if (_jsxFactoryEntity) { _jsxNamespace = getFirstIdentifier(_jsxFactoryEntity).escapedText; } @@ -1010,9 +1012,12 @@ namespace ts { } return _jsxNamespace; - function markAsSynthetic(node: Node): VisitResult { + } + + function markAsSynthetic(node: Node | undefined) { + if (node) { setTextRangePosEnd(node, -1, -1); - return visitEachChild(node, markAsSynthetic, nullTransformationContext); + forEachChild(node, markAsSynthetic); } } @@ -36076,6 +36081,16 @@ namespace ts { if (isExternalOrCommonJsModule(node)) { checkExternalModuleExports(node); } + else if (!node.isDeclarationFile && umdExport && !hasReportedUmdExportDiagnostic) { + hasReportedUmdExportDiagnostic = true; + const umdExportNamespace = parseIsolatedEntityName(umdExport, languageVersion); + if (umdExportNamespace) { + markAsSynthetic(umdExportNamespace); + if (!resolveEntityName(umdExportNamespace, SymbolFlags.Value, /*ignoreErrors*/ true, /*dontResolveAlias*/ false, node)) { + error(undefined, Diagnostics.Cannot_find_namespace_0_specified_by_umdExport_option, umdExport); + } + } + } if (potentialThisCollisions.length) { forEach(potentialThisCollisions, checkIfThisIsCapturedInEnclosingScope); diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 26b8641e2d7..0fb86e01e69 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -999,6 +999,27 @@ namespace ts { category: Diagnostics.Advanced_Options, description: Diagnostics.Resolve_keyof_to_string_valued_property_names_only_no_numbers_or_symbols, }, + { + name: "umdExport", + type: "string", + affectsEmit: true, + category: Diagnostics.Advanced_Options, + description: Diagnostics.Specifies_the_sole_export_for_a_single_file_output_UMD_module + }, + { + name: "umdGlobal", + type: "string", + affectsEmit: true, + category: Diagnostics.Advanced_Options, + description: Diagnostics.Wraps_a_single_file_output_as_a_UMD_module_with_specified_name_used_as_a_global_value_for_the_sole_export + }, + { + name: "umdGlobalAlways", + type: "boolean", + affectsEmit: true, + category: Diagnostics.Advanced_Options, + description: Diagnostics.Always_set_the_global_value_for_a_single_file_output_UMD_module + }, { // A list of plugins to load in the language service name: "plugins", diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 82f3cb4ce57..296cb097dd9 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3016,7 +3016,10 @@ "category": "Error", "code": 2792 }, - + "Cannot find namespace '{0}' specified by 'umdExport' option.": { + "category": "Error", + "code": 2793 + }, "Import declaration '{0}' is using private name '{1}'.": { "category": "Error", @@ -3467,7 +3470,7 @@ "category": "Error", "code": 5058 }, - "Invalid value for '--reactNamespace'. '{0}' is not a valid identifier.": { + "Invalid value for '{0}'. '{1}' is not a valid identifier.": { "category": "Error", "code": 5059 }, @@ -3499,7 +3502,7 @@ "category": "Error", "code": 5066 }, - "Invalid value for 'jsxFactory'. '{0}' is not a valid identifier or qualified-name.": { + "Invalid value for '{0}'. '{1}' is not a valid identifier or qualified-name.": { "category": "Error", "code": 5067 }, @@ -3587,6 +3590,10 @@ "category": "Error", "code": 5088 }, + "Option '{0} can only be specified when module code generation is 'umd'.": { + "category": "Error", + "code": 5089 + }, "Generates a sourcemap for each corresponding '.d.ts' file.": { "category": "Message", @@ -4482,6 +4489,18 @@ "category": "Error", "code": 6236 }, + "Specifies the sole export for a single-file output UMD module.": { + "category": "Message", + "code": 6237 + }, + "Wraps a single-file output as a UMD module with specified name used as a global value for the sole export.": { + "category": "Message", + "code": 6238 + }, + "Always set the global value for a single-file output UMD module.": { + "category": "Message", + "code": 6239 + }, "Projects to reference": { "category": "Message", @@ -5981,8 +6000,9 @@ "category": "Message", "code": 18034 }, - "Invalid value for 'jsxFragmentFactory'. '{0}' is not a valid identifier or qualified-name.": { + "retired:18035": { "category": "Error", - "code": 18035 + "code": 18035, + "retired": true } } diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index f976c38ea6a..72d287e1ac1 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1550,7 +1550,9 @@ namespace ts { case SyntaxKind.JSDocComment: return emitJSDoc(node as JSDoc); - // Transformation nodes (ignored) + // Transformation nodes + case SyntaxKind.EmbeddedSourceFileStatement: + return emitEmbeddedSourceFileStatement(node as EmbeddedSourceFileStatement); } if (isExpression(node)) { @@ -3707,6 +3709,10 @@ namespace ts { // Transformation nodes + function emitEmbeddedSourceFileStatement(node: EmbeddedSourceFileStatement) { + print(EmitHint.SourceFile, node.sourceFile, node.sourceFile); + } + function emitPartiallyEmittedExpression(node: PartiallyEmittedExpression) { emitExpression(node.expression); } diff --git a/src/compiler/factory/nodeFactory.ts b/src/compiler/factory/nodeFactory.ts index a9679f453fe..af497efdfec 100644 --- a/src/compiler/factory/nodeFactory.ts +++ b/src/compiler/factory/nodeFactory.ts @@ -415,6 +415,8 @@ namespace ts { createSyntheticExpression, createSyntaxList, createNotEmittedStatement, + createEmbeddedSourceFileStatement, + updateEmbeddedSourceFileStatement, createPartiallyEmittedExpression, updatePartiallyEmittedExpression, createCommaListExpression, @@ -4954,6 +4956,20 @@ namespace ts { return node; } + // @api + function createEmbeddedSourceFileStatement(sourceFile: SourceFile) { + const node = createBaseNode(SyntaxKind.EmbeddedSourceFileStatement); + node.sourceFile = sourceFile; + return node; + } + + // @api + function updateEmbeddedSourceFileStatement(node: EmbeddedSourceFileStatement, sourceFile: SourceFile) { + return sourceFile !== node.sourceFile + ? update(createEmbeddedSourceFileStatement(sourceFile), node) + : node; + } + /** * Creates a synthetic expression to act as a placeholder for a not-emitted expression in * order to preserve comments or sourcemap positions. diff --git a/src/compiler/factory/utilities.ts b/src/compiler/factory/utilities.ts index 770600d94b7..89c48536c3f 100644 --- a/src/compiler/factory/utilities.ts +++ b/src/compiler/factory/utilities.ts @@ -546,7 +546,8 @@ namespace ts { return factory.createStringLiteral(file.moduleName); } if (!file.isDeclarationFile && outFile(options)) { - return factory.createStringLiteral(getExternalModuleNameFromPath(host, file.fileName)); + const moduleName = getExternalModuleNameFromPath(host, file.fileName); + return moduleName ? factory.createStringLiteral(moduleName) : undefined; } return undefined; } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 87f4637258c..f7f0a37012c 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -3091,6 +3091,35 @@ namespace ts { createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "out", "outFile"); } + const languageVersion = getEmitScriptTarget(options); + + if (options.umdExport) { + if (!options.out && !options.outFile) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1_or_option_2, "umdExport", "out", "outFile"); + } + if (options.module !== ModuleKind.UMD) { + createDiagnosticForOptionName(Diagnostics.Option_0_can_only_be_specified_when_module_code_generation_is_umd, "umdExport"); + } + if (!isEntityNameText(options.umdExport, languageVersion)) { + createOptionValueDiagnostic("umdExport", Diagnostics.Invalid_value_for_0_1_is_not_a_valid_identifier_or_qualified_name, options.umdExport); + } + } + + if (options.umdGlobal) { + if (!options.umdExport) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "umdGlobal", "umdExport"); + } + if (!isIdentifierText(options.umdGlobal, languageVersion)) { + createOptionValueDiagnostic("umdGlobal", Diagnostics.Invalid_value_for_0_1_is_not_a_valid_identifier, options.umdGlobal); + } + } + + if (options.umdGlobalAlways) { + if (!options.umdGlobal) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "umdGlobalAlways", "umdGlobal"); + } + } + if (options.mapRoot && !(options.sourceMap || options.declarationMap)) { // Error to specify --mapRoot without --sourcemap createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1_or_option_2, "mapRoot", "sourceMap", "declarationMap"); @@ -3117,8 +3146,6 @@ namespace ts { createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "noImplicitUseStrict", "alwaysStrict"); } - const languageVersion = options.target || ScriptTarget.ES3; - const firstNonAmbientExternalModuleSourceFile = find(files, f => isExternalModule(f) && !f.isDeclarationFile); if (options.isolatedModules) { if (options.module === ModuleKind.None && languageVersion < ScriptTarget.ES2015) { @@ -3139,7 +3166,7 @@ namespace ts { // Cannot specify module gen that isn't amd or system with --out if (outputFile && !options.emitDeclarationOnly) { - if (options.module && !(options.module === ModuleKind.AMD || options.module === ModuleKind.System)) { + if (options.module && !(options.module === ModuleKind.AMD || options.module === ModuleKind.System || options.module === ModuleKind.UMD)) { createDiagnosticForOptionName(Diagnostics.Only_amd_and_system_modules_are_supported_alongside_0, options.out ? "out" : "outFile", "module"); } else if (options.module === undefined && firstNonAmbientExternalModuleSourceFile) { @@ -3200,20 +3227,20 @@ namespace ts { if (options.reactNamespace) { createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "reactNamespace", "jsxFactory"); } - if (!parseIsolatedEntityName(options.jsxFactory, languageVersion)) { - createOptionValueDiagnostic("jsxFactory", Diagnostics.Invalid_value_for_jsxFactory_0_is_not_a_valid_identifier_or_qualified_name, options.jsxFactory); + if (!isEntityNameText(options.jsxFactory, languageVersion)) { + createOptionValueDiagnostic("jsxFactory", Diagnostics.Invalid_value_for_0_1_is_not_a_valid_identifier_or_qualified_name, options.jsxFactory); } } else if (options.reactNamespace && !isIdentifierText(options.reactNamespace, languageVersion)) { - createOptionValueDiagnostic("reactNamespace", Diagnostics.Invalid_value_for_reactNamespace_0_is_not_a_valid_identifier, options.reactNamespace); + createOptionValueDiagnostic("reactNamespace", Diagnostics.Invalid_value_for_0_1_is_not_a_valid_identifier, options.reactNamespace); } if (options.jsxFragmentFactory) { if (!options.jsxFactory) { createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "jsxFragmentFactory", "jsxFactory"); } - if (!parseIsolatedEntityName(options.jsxFragmentFactory, languageVersion)) { - createOptionValueDiagnostic("jsxFragmentFactory", Diagnostics.Invalid_value_for_jsxFragmentFactory_0_is_not_a_valid_identifier_or_qualified_name, options.jsxFragmentFactory); + if (!isEntityNameText(options.jsxFragmentFactory, languageVersion)) { + createOptionValueDiagnostic("jsxFragmentFactory", Diagnostics.Invalid_value_for_0_1_is_not_a_valid_identifier_or_qualified_name, options.jsxFragmentFactory); } } @@ -3378,7 +3405,7 @@ namespace ts { } function createOptionValueDiagnostic(option1: string, message: DiagnosticMessage, arg0: string) { - createDiagnosticForOption(/*onKey*/ false, option1, /*option2*/ undefined, message, arg0); + createDiagnosticForOption(/*onKey*/ false, option1, /*option2*/ undefined, message, option1, arg0); } function createDiagnosticForReference(sourceFile: JsonSourceFile | undefined, index: number, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number) { @@ -3808,4 +3835,32 @@ namespace ts { } return res; } + + function isEntityNameText(text: string, languageVersion: ScriptTarget) { + if (hasTriviaAt(0)) return false; + + const entity = parseIsolatedEntityName(text, languageVersion); + if (!entity) return false; + + let pos = 0; + return visit(entity); + + function hasTriviaAt(pos: number) { + return skipTrivia(text, pos) !== pos; + } + + function visit(node: EntityName): boolean { + if (pos !== node.pos) return false; + if (node.kind === SyntaxKind.QualifiedName) { + if (!visit(node.left)) return false; + pos++; // . + if (hasTriviaAt(pos)) return false; + return visit(node.right); + } + else { + pos = node.end; + return !hasTriviaAt(pos); + } + } + } } diff --git a/src/compiler/transformers/declarations.ts b/src/compiler/transformers/declarations.ts index 477ccef9437..4ec7a4859f9 100644 --- a/src/compiler/transformers/declarations.ts +++ b/src/compiler/transformers/declarations.ts @@ -86,6 +86,8 @@ namespace ts { let emittedImports: readonly AnyImportSyntax[] | undefined; // must be declared in container so it can be `undefined` while transformer's first pass const resolver = context.getEmitResolver(); const options = context.getCompilerOptions(); + const umdExport = getSingleFileUmdExport(options); + const umdGlobal = getSingleFileUmdGlobalNamespace(options); const { noResolve, stripInternal } = options; return transformRoot; @@ -235,7 +237,7 @@ namespace ts { libs = new Map(); let hasNoDefaultLib = false; const bundle = factory.createBundle(map(node.sourceFiles, - sourceFile => { + (sourceFile, index) => { if (sourceFile.isDeclarationFile) return undefined!; // Omit declaration files from bundle results, too // TODO: GH#18217 hasNoDefaultLib = hasNoDefaultLib || sourceFile.hasNoDefaultLib; currentSourceFile = sourceFile; @@ -262,7 +264,19 @@ namespace ts { } needsDeclare = true; const updated = isSourceFileJS(sourceFile) ? factory.createNodeArray(transformDeclarationsForJS(sourceFile)) : visitNodes(sourceFile.statements, visitDeclarationStatements); - return factory.updateSourceFile(sourceFile, transformAndReplaceLatePaintedStatements(updated), /*isDeclarationFile*/ true, /*referencedFiles*/ [], /*typeReferences*/ [], /*hasNoDefaultLib*/ false, /*libReferences*/ []); + let statements = transformAndReplaceLatePaintedStatements(updated); + if (umdExport && index === node.sourceFiles.length - 1) { + const umdExportNamespace = parseIsolatedEntityName(umdExport, ScriptTarget.ESNext); + if (umdExportNamespace) { + const statementsArray = [...statements]; + statementsArray.push(factory.createExportAssignment(/*decorators*/ undefined, /*modifiers*/ undefined, /*isExportEquals*/ true, createExpressionFromEntityName(factory, umdExportNamespace))); + if (umdGlobal) { + statementsArray.push(factory.createNamespaceExportDeclaration(umdGlobal)); + } + statements = setTextRange(factory.createNodeArray(statementsArray), statements); + } + } + return factory.updateSourceFile(sourceFile, statements, /*isDeclarationFile*/ true, /*referencedFiles*/ [], /*typeReferences*/ [], /*hasNoDefaultLib*/ false, /*libReferences*/ []); } ), mapDefined(node.prepends, prepend => { if (prepend.kind === SyntaxKind.InputFiles) { diff --git a/src/compiler/transformers/module/module.ts b/src/compiler/transformers/module/module.ts index 584be850c65..2f358173ad0 100644 --- a/src/compiler/transformers/module/module.ts +++ b/src/compiler/transformers/module/module.ts @@ -41,13 +41,46 @@ namespace ts { const moduleInfoMap: ExternalModuleInfo[] = []; // The ExternalModuleInfo for each file. const deferredExports: (Statement[] | undefined)[] = []; // Exports to defer until an EndOfDeclarationMarker is found. + const umdExport = getSingleFileUmdExport(compilerOptions); + const umdGlobal = getSingleFileUmdGlobalNamespace(compilerOptions); + const umdGlobalAlways = !!umdGlobal && compilerOptions.umdGlobalAlways; let currentSourceFile: SourceFile; // The current file. let currentModuleInfo: ExternalModuleInfo; // The ExternalModuleInfo for the current file. let noSubstitution: boolean[]; // Set of nodes for which substitution rules should be ignored. let needUMDDynamicImportHelper: boolean; - return chainBundle(context, transformSourceFile); + return transformSourceFileOrBundle; + + function transformSourceFileOrBundle(node: SourceFile | Bundle) { + return node.kind === SyntaxKind.SourceFile ? transformSourceFile(node) : transformBundle(node); + } + + function transformBundle(node: Bundle) { + if (umdExport) { + const statements: Statement[] = map(node.sourceFiles, factory.createEmbeddedSourceFileStatement); + const exportEqualsStatement = factory.createExportAssignment( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*isExportEquals*/ true, + factory.createIdentifier(umdExport) + ); + statements.push(exportEqualsStatement); + const wrapper = factory.createSourceFile( + statements, + factory.createToken(SyntaxKind.EndOfFileToken), + NodeFlags.None + ); + wrapper.moduleName = umdGlobal; + wrapper.externalModuleIndicator = exportEqualsStatement; + wrapper.amdDependencies = []; + for (const sourceFile of node.sourceFiles) { + moveEmitHelpers(sourceFile, wrapper, helper => !helper.scoped); + } + return context.factory.createBundle([transformSourceFile(wrapper)], node.prepends); + } + return context.factory.createBundle(map(node.sourceFiles, transformSourceFile), node.prepends); + } /** * Transforms the module aspects of a SourceFile. @@ -62,6 +95,9 @@ namespace ts { return node; } + const savedCurrentSourceFile = currentSourceFile; + const savedCurrentModuleInfo = currentModuleInfo; + const savedNeedUMDDynamicImportHelper = needUMDDynamicImportHelper; currentSourceFile = node; currentModuleInfo = collectExternalModuleInfo(context, node, resolver, compilerOptions); moduleInfoMap[getOriginalNodeId(node)] = currentModuleInfo; @@ -69,13 +105,12 @@ namespace ts { // Perform the transformation. const transformModule = getTransformModuleDelegate(moduleKind); const updated = transformModule(node); - currentSourceFile = undefined!; - currentModuleInfo = undefined!; - needUMDDynamicImportHelper = false; + currentSourceFile = savedCurrentSourceFile; + currentModuleInfo = savedCurrentModuleInfo; + needUMDDynamicImportHelper = savedNeedUMDDynamicImportHelper; return updated; } - function shouldEmitUnderscoreUnderscoreESModule() { if (!currentModuleInfo.exportEquals && isExternalModule(currentSourceFile)) { return true; @@ -207,12 +242,89 @@ namespace ts { function transformUMDModule(node: SourceFile) { const { aliasedModuleNames, unaliasedModuleNames, importAliasNames } = collectAsynchronousDependencies(node, /*includeNonAmdDependencies*/ false); const moduleName = tryGetModuleNameFromFile(factory, node, host, compilerOptions); + + // Create an updated SourceFile: + // Without '--umdGlobal': + // + // (function (factory) { + // if (typeof module === "object" && typeof module.exports === "object") { + // var v = factory(require, exports); + // if (v !== undefined) module.exports = v; + // } + // else if (typeof define === 'function' && define.amd) { + // define(["require", "exports"], factory); + // } + // })(function ...) + // + // With '--outFile foo.js --umdExport foo --umdGlobal foo' (single-file output with a single namespace export): + // + // (function (root, factory) { + // if (typeof module === "object" && typeof module.exports === "object") { + // var v = factory(require, exports); + // if (v !== undefined) module.exports = v; + // } + // else if (typeof define === 'function' && define.amd) { + // define(["require", "exports"], factory); + // } + // else if (root) { + // root.foo = factory(); + // } + // })(typeof globalThis === "object" ? globalThis : typeof self === "object" ? self : this, function () { + // ... + // return foo; + // }) + + let rootParameter: ParameterDeclaration | undefined; + let rootExpression: Expression | undefined; + let rootStatement: IfStatement | undefined; + if (umdGlobal) { + rootParameter = factory.createParameterDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, "root"); + rootExpression = factory.createConditionalExpression( + factory.createTypeCheck(factory.createIdentifier("globalThis"), "object"), + /*questionToken*/ undefined, + factory.createIdentifier("globalThis"), + /*colonToken*/ undefined, + factory.createConditionalExpression( + factory.createTypeCheck(factory.createIdentifier("self"), "object"), + /*questionToken*/ undefined, + factory.createIdentifier("self"), + /*colonToken*/ undefined, + factory.createConditionalExpression( + factory.createTypeCheck(factory.createIdentifier("global"), "object"), + /*questionToken*/ undefined, + factory.createIdentifier("global"), + /*colonToken*/ undefined, + factory.createThis() + ) + ) + ); + rootStatement = factory.createIfStatement( + factory.createIdentifier("root"), + factory.createBlock([ + factory.createExpressionStatement( + factory.createAssignment( + factory.createPropertyAccessExpression( + factory.createIdentifier("root"), + factory.createIdentifier(umdGlobal) + ), + factory.createCallExpression( + factory.createIdentifier("factory"), + /*typeArguments*/ undefined, + [] + ) + ) + ) + ], true) + ); + } + + const factoryParameter = factory.createParameterDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, "factory"); const umdHeader = factory.createFunctionExpression( /*modifiers*/ undefined, /*asteriskToken*/ undefined, /*name*/ undefined, /*typeParameters*/ undefined, - [factory.createParameterDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, "factory")], + rootParameter ? [rootParameter, factoryParameter] : [factoryParameter], /*type*/ undefined, setTextRange( factory.createBlock( @@ -255,7 +367,20 @@ namespace ts { ) ), EmitFlags.SingleLine - ) + ), + ...(umdGlobal && umdGlobalAlways ? [ + factory.createIfStatement( + factory.createIdentifier("root"), + factory.createBlock([ + factory.createExpressionStatement( + factory.createAssignment( + factory.createPropertyAccessExpression(factory.createIdentifier("root"), umdGlobal), + factory.createPropertyAccessExpression(factory.createIdentifier("module"), "exports") + ) + ) + ], true) + ) + ] : []) ]), factory.createIfStatement( factory.createLogicalAnd( @@ -276,11 +401,48 @@ namespace ts { ...aliasedModuleNames, ...unaliasedModuleNames ]), - factory.createIdentifier("factory") + umdGlobal && umdGlobalAlways ? + factory.createFunctionExpression( + /*modifiers*/ undefined, + /*asteriskToken*/ undefined, + /*name*/ undefined, + /*typeParameters*/ undefined, + /*parameters*/ undefined, + /*type*/ undefined, + factory.createBlock([ + factory.createVariableStatement(/*modifiers*/ undefined, [ + factory.createVariableDeclaration("result", + /*exclamationToken*/ undefined, + /*type*/ undefined, + factory.createFunctionApplyCall( + factory.createIdentifier("factory"), + factory.createThis(), + factory.createIdentifier("arguments") + ) + ) + ]), + factory.createIfStatement( + factory.createIdentifier("root"), + factory.createBlock([ + factory.createExpressionStatement( + factory.createAssignment( + factory.createPropertyAccessExpression(factory.createIdentifier("root"), umdGlobal), + factory.createIdentifier("result") + ) + ) + ], true) + ), + factory.createReturnStatement( + factory.createIdentifier("result") + ) + ], true) + ) : + factory.createIdentifier("factory") ] ) ) - ]) + ]), + rootStatement ) ) ], @@ -290,17 +452,22 @@ namespace ts { ) ); - // Create an updated SourceFile: + // Add the module body function argument: // - // (function (factory) { - // if (typeof module === "object" && typeof module.exports === "object") { - // var v = factory(require, exports); - // if (v !== undefined) module.exports = v; - // } - // else if (typeof define === 'function' && define.amd) { - // define(["require", "exports"], factory); - // } - // })(function ...) + // function (require, exports) ... + const bodyFunction = factory.createFunctionExpression( + /*modifiers*/ undefined, + /*asteriskToken*/ undefined, + /*name*/ undefined, + /*typeParameters*/ undefined, + [ + factory.createParameterDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, "require"), + factory.createParameterDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, "exports"), + ...importAliasNames + ], + /*type*/ undefined, + transformAsynchronousModuleBody(node) + ); const updated = factory.updateSourceFile( node, @@ -310,24 +477,7 @@ namespace ts { factory.createCallExpression( umdHeader, /*typeArguments*/ undefined, - [ - // Add the module body function argument: - // - // function (require, exports) ... - factory.createFunctionExpression( - /*modifiers*/ undefined, - /*asteriskToken*/ undefined, - /*name*/ undefined, - /*typeParameters*/ undefined, - [ - factory.createParameterDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, "require"), - factory.createParameterDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, "exports"), - ...importAliasNames - ], - /*type*/ undefined, - transformAsynchronousModuleBody(node) - ) - ] + rootExpression ? [rootExpression, bodyFunction] : [bodyFunction] ) ) ]), @@ -521,6 +671,9 @@ namespace ts { case SyntaxKind.EndOfDeclarationMarker: return visitEndOfDeclarationMarker(node); + case SyntaxKind.EmbeddedSourceFileStatement: + return visitEmbeddedSourceFileStatement(node); + default: return visitEachChild(node, moduleExpressionElementVisitor, context); } @@ -1362,6 +1515,10 @@ namespace ts { return node; } + function visitEmbeddedSourceFileStatement(node: EmbeddedSourceFileStatement) { + return factory.updateEmbeddedSourceFileStatement(node, transformSourceFile(node.sourceFile)); + } + /** * Appends the exports of an ImportDeclaration to a statement list, returning the * statement list. diff --git a/src/compiler/types.ts b/src/compiler/types.ts index d9468bd5123..e4615367c62 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -396,6 +396,7 @@ namespace ts { // Transformation nodes NotEmittedStatement, + EmbeddedSourceFileStatement, PartiallyEmittedExpression, CommaListExpression, MergeDeclarationMarker, @@ -2536,6 +2537,15 @@ namespace ts { readonly kind: SyntaxKind.NotEmittedStatement; } + /** + * Embeds a source file as a statement + */ + /* @internal */ + export interface EmbeddedSourceFileStatement extends Statement { + readonly kind: SyntaxKind.EmbeddedSourceFileStatement; + readonly sourceFile: SourceFile; + } + /** * Marks the end of transformed declaration to properly emit exports. */ @@ -5756,6 +5766,9 @@ namespace ts { esModuleInterop?: boolean; /* @internal */ showConfig?: boolean; useDefineForClassFields?: boolean; + umdGlobal?: string; + umdGlobalAlways?: boolean; + umdExport?: string; [option: string]: CompilerOptionsValue | TsConfigSourceFile | undefined; } @@ -7096,6 +7109,8 @@ namespace ts { /* @internal */ updateSyntheticReferenceExpression(node: SyntheticReferenceExpression, expression: Expression, thisArg: Expression): SyntheticReferenceExpression; createCommaListExpression(elements: readonly Expression[]): CommaListExpression; updateCommaListExpression(node: CommaListExpression, elements: readonly Expression[]): CommaListExpression; + /* @internal */ createEmbeddedSourceFileStatement(sourceFile: SourceFile): EmbeddedSourceFileStatement; + /* @internal */ updateEmbeddedSourceFileStatement(node: EmbeddedSourceFileStatement, sourceFile: SourceFile): EmbeddedSourceFileStatement; createBundle(sourceFiles: readonly SourceFile[], prepends?: readonly (UnparsedSource | InputFiles)[]): Bundle; updateBundle(node: Bundle, sourceFiles: readonly SourceFile[], prepends?: readonly (UnparsedSource | InputFiles)[]): Bundle; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 33d38853ec2..1fbeed49452 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -5867,6 +5867,20 @@ namespace ts { } } + export function getSingleFileUmdGlobalNamespace(options: CompilerOptions) { + const moduleKind = getEmitModuleKind(options); + if (moduleKind === ModuleKind.UMD && outFile(options)) { + return options.umdGlobal; + } + } + + export function getSingleFileUmdExport(options: CompilerOptions) { + const moduleKind = getEmitModuleKind(options); + if (moduleKind === ModuleKind.UMD && outFile(options)) { + return options.umdExport; + } + } + export function unreachableCodeIsError(options: CompilerOptions): boolean { return options.allowUnreachableCode === false; } diff --git a/src/compiler/utilitiesPublic.ts b/src/compiler/utilitiesPublic.ts index e9d28e8fab5..9d4979f6935 100644 --- a/src/compiler/utilitiesPublic.ts +++ b/src/compiler/utilitiesPublic.ts @@ -1699,6 +1699,7 @@ namespace ts { || kind === SyntaxKind.WhileStatement || kind === SyntaxKind.WithStatement || kind === SyntaxKind.NotEmittedStatement + || kind === SyntaxKind.EmbeddedSourceFileStatement || kind === SyntaxKind.EndOfDeclarationMarker || kind === SyntaxKind.MergeDeclarationMarker; } diff --git a/src/compiler/visitorPublic.ts b/src/compiler/visitorPublic.ts index b2ec665ce89..e87de37c5f8 100644 --- a/src/compiler/visitorPublic.ts +++ b/src/compiler/visitorPublic.ts @@ -1079,6 +1079,10 @@ namespace ts { visitLexicalEnvironment((node).statements, visitor, context)); // Transformation nodes + case SyntaxKind.EmbeddedSourceFileStatement: + return factory.updateEmbeddedSourceFileStatement(node, + nodeVisitor((node).sourceFile, visitor, isSourceFile)); + case SyntaxKind.PartiallyEmittedExpression: return factory.updatePartiallyEmittedExpression(node, nodeVisitor((node).expression, visitor, isExpression));