Skip to content

Commit 378b5ff

Browse files
committed
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.
1 parent 8a8d175 commit 378b5ff

21 files changed

+378
-25
lines changed

Diff for: src/compiler/commandLineParser.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,12 @@ module ts {
5050
shortName: "m",
5151
type: {
5252
"commonjs": ModuleKind.CommonJS,
53-
"amd": ModuleKind.AMD
53+
"amd": ModuleKind.AMD,
54+
"umd": ModuleKind.UMD
5455
},
55-
description: Diagnostics.Specify_module_code_generation_Colon_commonjs_or_amd,
56+
description: Diagnostics.Specify_module_code_generation_Colon_commonjs_amd_or_umd,
5657
paramType: Diagnostics.KIND,
57-
error: Diagnostics.Argument_for_module_option_must_be_commonjs_or_amd
58+
error: Diagnostics.Argument_for_module_option_must_be_commonjs_amd_or_umd
5859
},
5960
{
6061
name: "noEmit",

Diff for: src/compiler/diagnosticInformationMap.generated.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -463,7 +463,7 @@ module ts {
463463
Do_not_emit_comments_to_output: { code: 6009, category: DiagnosticCategory.Message, key: "Do not emit comments to output." },
464464
Do_not_emit_outputs: { code: 6010, category: DiagnosticCategory.Message, key: "Do not emit outputs." },
465465
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)" },
466-
Specify_module_code_generation_Colon_commonjs_or_amd: { code: 6016, category: DiagnosticCategory.Message, key: "Specify module code generation: 'commonjs' or 'amd'" },
466+
Specify_module_code_generation_Colon_commonjs_amd_or_umd: { code: 6016, category: DiagnosticCategory.Message, key: "Specify module code generation: 'commonjs', 'amd', or 'umd'" },
467467
Print_this_message: { code: 6017, category: DiagnosticCategory.Message, key: "Print this message." },
468468
Print_the_compiler_s_version: { code: 6019, category: DiagnosticCategory.Message, key: "Print the compiler's version." },
469469
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 {
484484
Generates_corresponding_map_file: { code: 6043, category: DiagnosticCategory.Message, key: "Generates corresponding '.map' file." },
485485
Compiler_option_0_expects_an_argument: { code: 6044, category: DiagnosticCategory.Error, key: "Compiler option '{0}' expects an argument." },
486486
Unterminated_quoted_string_in_response_file_0: { code: 6045, category: DiagnosticCategory.Error, key: "Unterminated quoted string in response file '{0}'." },
487-
Argument_for_module_option_must_be_commonjs_or_amd: { code: 6046, category: DiagnosticCategory.Error, key: "Argument for '--module' option must be 'commonjs' or 'amd'." },
487+
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'." },
488488
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'." },
489489
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}'." },
490490
Unsupported_locale_0: { code: 6049, category: DiagnosticCategory.Error, key: "Unsupported locale '{0}'." },

Diff for: src/compiler/diagnosticMessages.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1840,7 +1840,7 @@
18401840
"category": "Message",
18411841
"code": 6015
18421842
},
1843-
"Specify module code generation: 'commonjs' or 'amd'": {
1843+
"Specify module code generation: 'commonjs', 'amd', or 'umd'": {
18441844
"category": "Message",
18451845
"code": 6016
18461846
},
@@ -1924,7 +1924,7 @@
19241924
"category": "Error",
19251925
"code": 6045
19261926
},
1927-
"Argument for '--module' option must be 'commonjs' or 'amd'.": {
1927+
"Argument for '--module' option must be 'commonjs', 'amd', or 'umd'.": {
19281928
"category": "Error",
19291929
"code": 6046
19301930
},

Diff for: src/compiler/emitter.ts

+50-18
Original file line numberDiff line numberDiff line change
@@ -4645,27 +4645,25 @@ var __param = this.__param || function(index, decorator) { return function (targ
46454645
}
46464646
}
46474647

4648-
function emitAMDModule(node: SourceFile, startIndex: number) {
4649-
collectExternalModuleInfo(node);
4650-
4648+
function emitAMDDependencies(node: SourceFile, includeNonAmdDependencies: boolean) {
46514649
// An AMD define function has the following shape:
46524650
// define(id?, dependencies?, factory);
46534651
//
46544652
// This has the shape of
46554653
// define(name, ["module1", "module2"], function (module1Alias) {
4656-
// The location of the alias in the parameter list in the factory function needs to
4654+
// The location of the alias in the parameter list in the factory function needs to
46574655
// match the position of the module name in the dependency list.
46584656
//
4659-
// To ensure this is true in cases of modules with no aliases, e.g.:
4660-
// `import "module"` or `<amd-dependency path= "a.css" />`
4657+
// To ensure this is true in cases of modules with no aliases, e.g.:
4658+
// `import "module"` or `<amd-dependency path= "a.css" />`
46614659
// we need to add modules without alias names to the end of the dependencies list
4662-
4663-
let aliasedModuleNames: string[] = []; // names of modules with corresponding parameter in the
4660+
4661+
let aliasedModuleNames: string[] = []; // names of modules with corresponding parameter in the
46644662
// factory function.
46654663
let unaliasedModuleNames: string[] = []; // names of modules with no corresponding parameters in
46664664
// factory function.
4667-
let importAliasNames: string[] = []; // names of the parameters in the factory function; these
4668-
// paramters need to match the indexes of the corresponding
4665+
let importAliasNames: string[] = []; // names of the parameters in the factory function; these
4666+
// parameters need to match the indexes of the corresponding
46694667
// module names in aliasedModuleNames.
46704668

46714669
// Fill in amd-dependency tags
@@ -4687,7 +4685,7 @@ var __param = this.__param || function(index, decorator) { return function (targ
46874685
externalModuleName = getLiteralText(<LiteralExpression>moduleName);
46884686
}
46894687

4690-
// Find the name of the module alais, if there is one
4688+
// Find the name of the module alias, if there is one
46914689
let importAliasName: string;
46924690
let namespaceDeclaration = getNamespaceDeclarationNode(importNode);
46934691
if (namespaceDeclaration && !isDefaultImport(importNode)) {
@@ -4697,20 +4695,15 @@ var __param = this.__param || function(index, decorator) { return function (targ
46974695
importAliasName = getGeneratedNameForNode(<ImportDeclaration | ExportDeclaration>importNode);
46984696
}
46994697

4700-
if (importAliasName) {
4698+
if (includeNonAmdDependencies && importAliasName) {
47014699
aliasedModuleNames.push(externalModuleName);
47024700
importAliasNames.push(importAliasName);
47034701
}
47044702
else {
47054703
unaliasedModuleNames.push(externalModuleName);
47064704
}
47074705
}
4708-
4709-
writeLine();
4710-
write("define(");
4711-
if (node.amdModuleName) {
4712-
write("\"" + node.amdModuleName + "\", ");
4713-
}
4706+
47144707
write("[\"require\", \"exports\"");
47154708
if (aliasedModuleNames.length) {
47164709
write(", ");
@@ -4725,6 +4718,17 @@ var __param = this.__param || function(index, decorator) { return function (targ
47254718
write(", ");
47264719
write(importAliasNames.join(", "));
47274720
}
4721+
}
4722+
4723+
function emitAMDModule(node: SourceFile, startIndex: number) {
4724+
collectExternalModuleInfo(node);
4725+
4726+
writeLine();
4727+
write("define(");
4728+
if (node.amdModuleName) {
4729+
write("\"" + node.amdModuleName + "\", ");
4730+
}
4731+
emitAMDDependencies(node, /*includeNonAmdDependencies*/ true);
47284732
write(") {");
47294733
increaseIndent();
47304734
emitExportStarHelper();
@@ -4746,6 +4750,31 @@ var __param = this.__param || function(index, decorator) { return function (targ
47464750
emitExportEquals(/*emitAsReturn*/ false);
47474751
}
47484752

4753+
function emitUMDModule(node: SourceFile, startIndex: number) {
4754+
collectExternalModuleInfo(node);
4755+
4756+
// Module is detected first to support Browserify users that load into a browser with an AMD loader
4757+
writeLines(`(function (deps, factory) {
4758+
if (typeof module === 'object' && typeof module.exports === 'object') {
4759+
var v = factory(require, exports); if (v !== undefined) module.exports = v;
4760+
}
4761+
else if (typeof define === 'function' && define.amd) {
4762+
define(deps, factory);
4763+
}
4764+
})(`);
4765+
emitAMDDependencies(node, false);
4766+
write(") {");
4767+
increaseIndent();
4768+
emitExportStarHelper();
4769+
emitCaptureThisForNodeIfNecessary(node);
4770+
emitLinesStartingAt(node.statements, startIndex);
4771+
emitTempDeclarations(/*newLine*/ true);
4772+
emitExportEquals(/*emitAsReturn*/ true);
4773+
decreaseIndent();
4774+
writeLine();
4775+
write("});");
4776+
}
4777+
47494778
function emitES6Module(node: SourceFile, startIndex: number) {
47504779
externalImports = undefined;
47514780
exportSpecifiers = undefined;
@@ -4830,6 +4859,9 @@ var __param = this.__param || function(index, decorator) { return function (targ
48304859
else if (compilerOptions.module === ModuleKind.AMD) {
48314860
emitAMDModule(node, startIndex);
48324861
}
4862+
else if (compilerOptions.module === ModuleKind.UMD) {
4863+
emitUMDModule(node, startIndex);
4864+
}
48334865
else {
48344866
emitCommonJSModule(node, startIndex);
48354867
}

Diff for: src/compiler/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1671,6 +1671,7 @@ module ts {
16711671
None = 0,
16721672
CommonJS = 1,
16731673
AMD = 2,
1674+
UMD = 3,
16741675
}
16751676

16761677
export interface LineAndCharacter {

Diff for: src/harness/harness.ts

+2
Original file line numberDiff line numberDiff line change
@@ -957,6 +957,8 @@ module Harness {
957957
if (typeof setting.value === 'string') {
958958
if (setting.value.toLowerCase() === 'amd') {
959959
options.module = ts.ModuleKind.AMD;
960+
} else if (setting.value.toLowerCase() === 'umd') {
961+
options.module = ts.ModuleKind.UMD;
960962
} else if (setting.value.toLowerCase() === 'commonjs') {
961963
options.module = ts.ModuleKind.CommonJS;
962964
} else if (setting.value.toLowerCase() === 'unspecified') {

Diff for: tests/baselines/reference/es5-umd.js

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//// [es5-umd.ts]
2+
3+
class A
4+
{
5+
constructor ()
6+
{
7+
8+
}
9+
10+
public B()
11+
{
12+
return 42;
13+
}
14+
}
15+
16+
17+
//// [es5-umd.js]
18+
var A = (function () {
19+
function A() {
20+
}
21+
A.prototype.B = function () {
22+
return 42;
23+
};
24+
return A;
25+
})();
26+
//# sourceMappingURL=es5-umd.js.map
27+
28+
//// [es5-umd.d.ts]
29+
declare class A {
30+
constructor();
31+
B(): number;
32+
}

Diff for: tests/baselines/reference/es5-umd.js.map

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: tests/baselines/reference/es5-umd.sourcemap.txt

+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
===================================================================
2+
JsFile: es5-umd.js
3+
mapUrl: es5-umd.js.map
4+
sourceRoot:
5+
sources: es5-umd.ts
6+
===================================================================
7+
-------------------------------------------------------------------
8+
emittedFile:tests/cases/compiler/es5-umd.js
9+
sourceFile:es5-umd.ts
10+
-------------------------------------------------------------------
11+
>>>var A = (function () {
12+
1 >
13+
2 >^^^^^^^^^^^^^^^^^^^->
14+
1 >
15+
>
16+
1 >Emitted(1, 1) Source(2, 1) + SourceIndex(0)
17+
---
18+
>>> function A() {
19+
1->^^^^
20+
2 > ^^->
21+
1->class A
22+
>{
23+
>
24+
1->Emitted(2, 5) Source(4, 5) + SourceIndex(0) name (A)
25+
---
26+
>>> }
27+
1->^^^^
28+
2 > ^
29+
3 > ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^->
30+
1->constructor ()
31+
> {
32+
>
33+
>
34+
2 > }
35+
1->Emitted(3, 5) Source(7, 5) + SourceIndex(0) name (A.constructor)
36+
2 >Emitted(3, 6) Source(7, 6) + SourceIndex(0) name (A.constructor)
37+
---
38+
>>> A.prototype.B = function () {
39+
1->^^^^
40+
2 > ^^^^^^^^^^^^^
41+
3 > ^^^
42+
1->
43+
>
44+
> public
45+
2 > B
46+
3 >
47+
1->Emitted(4, 5) Source(9, 12) + SourceIndex(0) name (A)
48+
2 >Emitted(4, 18) Source(9, 13) + SourceIndex(0) name (A)
49+
3 >Emitted(4, 21) Source(9, 5) + SourceIndex(0) name (A)
50+
---
51+
>>> return 42;
52+
1 >^^^^^^^^
53+
2 > ^^^^^^
54+
3 > ^
55+
4 > ^^
56+
5 > ^
57+
1 >public B()
58+
> {
59+
>
60+
2 > return
61+
3 >
62+
4 > 42
63+
5 > ;
64+
1 >Emitted(5, 9) Source(11, 9) + SourceIndex(0) name (A.B)
65+
2 >Emitted(5, 15) Source(11, 15) + SourceIndex(0) name (A.B)
66+
3 >Emitted(5, 16) Source(11, 16) + SourceIndex(0) name (A.B)
67+
4 >Emitted(5, 18) Source(11, 18) + SourceIndex(0) name (A.B)
68+
5 >Emitted(5, 19) Source(11, 19) + SourceIndex(0) name (A.B)
69+
---
70+
>>> };
71+
1 >^^^^
72+
2 > ^
73+
3 > ^^^^^^^^^->
74+
1 >
75+
>
76+
2 > }
77+
1 >Emitted(6, 5) Source(12, 5) + SourceIndex(0) name (A.B)
78+
2 >Emitted(6, 6) Source(12, 6) + SourceIndex(0) name (A.B)
79+
---
80+
>>> return A;
81+
1->^^^^
82+
2 > ^^^^^^^^
83+
1->
84+
>
85+
2 > }
86+
1->Emitted(7, 5) Source(13, 1) + SourceIndex(0) name (A)
87+
2 >Emitted(7, 13) Source(13, 2) + SourceIndex(0) name (A)
88+
---
89+
>>>})();
90+
1 >
91+
2 >^
92+
3 >
93+
4 > ^^^^
94+
5 > ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^->
95+
1 >
96+
2 >}
97+
3 >
98+
4 > class A
99+
> {
100+
> constructor ()
101+
> {
102+
>
103+
> }
104+
>
105+
> public B()
106+
> {
107+
> return 42;
108+
> }
109+
> }
110+
1 >Emitted(8, 1) Source(13, 1) + SourceIndex(0) name (A)
111+
2 >Emitted(8, 2) Source(13, 2) + SourceIndex(0) name (A)
112+
3 >Emitted(8, 2) Source(2, 1) + SourceIndex(0)
113+
4 >Emitted(8, 6) Source(13, 2) + SourceIndex(0)
114+
---
115+
>>>//# sourceMappingURL=es5-umd.js.map

Diff for: tests/baselines/reference/es5-umd.symbols

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
=== tests/cases/compiler/es5-umd.ts ===
2+
3+
class A
4+
>A : Symbol(A, Decl(es5-umd.ts, 0, 0))
5+
{
6+
constructor ()
7+
{
8+
9+
}
10+
11+
public B()
12+
>B : Symbol(B, Decl(es5-umd.ts, 6, 5))
13+
{
14+
return 42;
15+
}
16+
}
17+

Diff for: tests/baselines/reference/es5-umd.types

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
=== tests/cases/compiler/es5-umd.ts ===
2+
3+
class A
4+
>A : A
5+
{
6+
constructor ()
7+
{
8+
9+
}
10+
11+
public B()
12+
>B : () => number
13+
{
14+
return 42;
15+
>42 : number
16+
}
17+
}
18+

0 commit comments

Comments
 (0)