Add support for UMD-like module export format

The new module format enables global-less universal modules,
compatible with both AMD and CJS module loaders.

Fixes #2036.
This commit is contained in:
Colin Snover
2015-04-03 06:05:58 +00:00
parent 8a8d175f79
commit 378b5ffd1a
21 changed files with 378 additions and 25 deletions

View File

@@ -50,11 +50,12 @@ module ts {
shortName: "m",
type: {
"commonjs": ModuleKind.CommonJS,
"amd": ModuleKind.AMD
"amd": ModuleKind.AMD,
"umd": ModuleKind.UMD
},
description: Diagnostics.Specify_module_code_generation_Colon_commonjs_or_amd,
description: Diagnostics.Specify_module_code_generation_Colon_commonjs_amd_or_umd,
paramType: Diagnostics.KIND,
error: Diagnostics.Argument_for_module_option_must_be_commonjs_or_amd
error: Diagnostics.Argument_for_module_option_must_be_commonjs_amd_or_umd
},
{
name: "noEmit",

View File

@@ -463,7 +463,7 @@ module ts {
Do_not_emit_comments_to_output: { code: 6009, category: DiagnosticCategory.Message, key: "Do not emit comments to output." },
Do_not_emit_outputs: { code: 6010, category: DiagnosticCategory.Message, key: "Do not emit outputs." },
Specify_ECMAScript_target_version_Colon_ES3_default_ES5_or_ES6_experimental: { code: 6015, category: DiagnosticCategory.Message, key: "Specify ECMAScript target version: 'ES3' (default), 'ES5', or 'ES6' (experimental)" },
Specify_module_code_generation_Colon_commonjs_or_amd: { code: 6016, category: DiagnosticCategory.Message, key: "Specify module code generation: 'commonjs' or 'amd'" },
Specify_module_code_generation_Colon_commonjs_amd_or_umd: { code: 6016, category: DiagnosticCategory.Message, key: "Specify module code generation: 'commonjs', 'amd', or 'umd'" },
Print_this_message: { code: 6017, category: DiagnosticCategory.Message, key: "Print this message." },
Print_the_compiler_s_version: { code: 6019, category: DiagnosticCategory.Message, key: "Print the compiler's version." },
Compile_the_project_in_the_given_directory: { code: 6020, category: DiagnosticCategory.Message, key: "Compile the project in the given directory." },
@@ -484,7 +484,7 @@ module ts {
Generates_corresponding_map_file: { code: 6043, category: DiagnosticCategory.Message, key: "Generates corresponding '.map' file." },
Compiler_option_0_expects_an_argument: { code: 6044, category: DiagnosticCategory.Error, key: "Compiler option '{0}' expects an argument." },
Unterminated_quoted_string_in_response_file_0: { code: 6045, category: DiagnosticCategory.Error, key: "Unterminated quoted string in response file '{0}'." },
Argument_for_module_option_must_be_commonjs_or_amd: { code: 6046, category: DiagnosticCategory.Error, key: "Argument for '--module' option must be 'commonjs' or 'amd'." },
Argument_for_module_option_must_be_commonjs_amd_or_umd: { code: 6046, category: DiagnosticCategory.Error, key: "Argument for '--module' option must be 'commonjs', 'amd', or 'umd'." },
Argument_for_target_option_must_be_es3_es5_or_es6: { code: 6047, category: DiagnosticCategory.Error, key: "Argument for '--target' option must be 'es3', 'es5', or 'es6'." },
Locale_must_be_of_the_form_language_or_language_territory_For_example_0_or_1: { code: 6048, category: DiagnosticCategory.Error, key: "Locale must be of the form <language> or <language>-<territory>. For example '{0}' or '{1}'." },
Unsupported_locale_0: { code: 6049, category: DiagnosticCategory.Error, key: "Unsupported locale '{0}'." },

View File

@@ -1840,7 +1840,7 @@
"category": "Message",
"code": 6015
},
"Specify module code generation: 'commonjs' or 'amd'": {
"Specify module code generation: 'commonjs', 'amd', or 'umd'": {
"category": "Message",
"code": 6016
},
@@ -1924,7 +1924,7 @@
"category": "Error",
"code": 6045
},
"Argument for '--module' option must be 'commonjs' or 'amd'.": {
"Argument for '--module' option must be 'commonjs', 'amd', or 'umd'.": {
"category": "Error",
"code": 6046
},

View File

@@ -4645,27 +4645,25 @@ var __param = this.__param || function(index, decorator) { return function (targ
}
}
function emitAMDModule(node: SourceFile, startIndex: number) {
collectExternalModuleInfo(node);
function emitAMDDependencies(node: SourceFile, includeNonAmdDependencies: boolean) {
// An AMD define function has the following shape:
// define(id?, dependencies?, factory);
//
// This has the shape of
// define(name, ["module1", "module2"], function (module1Alias) {
// The location of the alias in the parameter list in the factory function needs to
// The location of the alias in the parameter list in the factory function needs to
// match the position of the module name in the dependency list.
//
// To ensure this is true in cases of modules with no aliases, e.g.:
// `import "module"` or `<amd-dependency path= "a.css" />`
// To ensure this is true in cases of modules with no aliases, e.g.:
// `import "module"` or `<amd-dependency path= "a.css" />`
// we need to add modules without alias names to the end of the dependencies list
let aliasedModuleNames: string[] = []; // names of modules with corresponding parameter in the
let aliasedModuleNames: string[] = []; // names of modules with corresponding parameter in the
// factory function.
let unaliasedModuleNames: string[] = []; // names of modules with no corresponding parameters in
// factory function.
let importAliasNames: string[] = []; // names of the parameters in the factory function; these
// paramters need to match the indexes of the corresponding
let importAliasNames: string[] = []; // names of the parameters in the factory function; these
// parameters need to match the indexes of the corresponding
// module names in aliasedModuleNames.
// Fill in amd-dependency tags
@@ -4687,7 +4685,7 @@ var __param = this.__param || function(index, decorator) { return function (targ
externalModuleName = getLiteralText(<LiteralExpression>moduleName);
}
// Find the name of the module alais, if there is one
// Find the name of the module alias, if there is one
let importAliasName: string;
let namespaceDeclaration = getNamespaceDeclarationNode(importNode);
if (namespaceDeclaration && !isDefaultImport(importNode)) {
@@ -4697,7 +4695,7 @@ var __param = this.__param || function(index, decorator) { return function (targ
importAliasName = getGeneratedNameForNode(<ImportDeclaration | ExportDeclaration>importNode);
}
if (importAliasName) {
if (includeNonAmdDependencies && importAliasName) {
aliasedModuleNames.push(externalModuleName);
importAliasNames.push(importAliasName);
}
@@ -4705,12 +4703,7 @@ var __param = this.__param || function(index, decorator) { return function (targ
unaliasedModuleNames.push(externalModuleName);
}
}
writeLine();
write("define(");
if (node.amdModuleName) {
write("\"" + node.amdModuleName + "\", ");
}
write("[\"require\", \"exports\"");
if (aliasedModuleNames.length) {
write(", ");
@@ -4725,6 +4718,17 @@ var __param = this.__param || function(index, decorator) { return function (targ
write(", ");
write(importAliasNames.join(", "));
}
}
function emitAMDModule(node: SourceFile, startIndex: number) {
collectExternalModuleInfo(node);
writeLine();
write("define(");
if (node.amdModuleName) {
write("\"" + node.amdModuleName + "\", ");
}
emitAMDDependencies(node, /*includeNonAmdDependencies*/ true);
write(") {");
increaseIndent();
emitExportStarHelper();
@@ -4746,6 +4750,31 @@ var __param = this.__param || function(index, decorator) { return function (targ
emitExportEquals(/*emitAsReturn*/ false);
}
function emitUMDModule(node: SourceFile, startIndex: number) {
collectExternalModuleInfo(node);
// Module is detected first to support Browserify users that load into a browser with an AMD loader
writeLines(`(function (deps, 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(deps, factory);
}
})(`);
emitAMDDependencies(node, false);
write(") {");
increaseIndent();
emitExportStarHelper();
emitCaptureThisForNodeIfNecessary(node);
emitLinesStartingAt(node.statements, startIndex);
emitTempDeclarations(/*newLine*/ true);
emitExportEquals(/*emitAsReturn*/ true);
decreaseIndent();
writeLine();
write("});");
}
function emitES6Module(node: SourceFile, startIndex: number) {
externalImports = undefined;
exportSpecifiers = undefined;
@@ -4830,6 +4859,9 @@ var __param = this.__param || function(index, decorator) { return function (targ
else if (compilerOptions.module === ModuleKind.AMD) {
emitAMDModule(node, startIndex);
}
else if (compilerOptions.module === ModuleKind.UMD) {
emitUMDModule(node, startIndex);
}
else {
emitCommonJSModule(node, startIndex);
}

View File

@@ -1671,6 +1671,7 @@ module ts {
None = 0,
CommonJS = 1,
AMD = 2,
UMD = 3,
}
export interface LineAndCharacter {

View File

@@ -957,6 +957,8 @@ module Harness {
if (typeof setting.value === 'string') {
if (setting.value.toLowerCase() === 'amd') {
options.module = ts.ModuleKind.AMD;
} else if (setting.value.toLowerCase() === 'umd') {
options.module = ts.ModuleKind.UMD;
} else if (setting.value.toLowerCase() === 'commonjs') {
options.module = ts.ModuleKind.CommonJS;
} else if (setting.value.toLowerCase() === 'unspecified') {