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') {

View file

@ -0,0 +1,32 @@
//// [es5-umd.ts]
class A
{
constructor ()
{
}
public B()
{
return 42;
}
}
//// [es5-umd.js]
var A = (function () {
function A() {
}
A.prototype.B = function () {
return 42;
};
return A;
})();
//# sourceMappingURL=es5-umd.js.map
//// [es5-umd.d.ts]
declare class A {
constructor();
B(): number;
}

View file

@ -0,0 +1,2 @@
//// [es5-umd.js.map]
{"version":3,"file":"es5-umd.js","sourceRoot":"","sources":["es5-umd.ts"],"names":["A","A.constructor","A.B"],"mappings":"AACA;IAEIA;IAGAC,CAACA;IAEMD,aAACA,GAARA;QAEIE,MAAMA,CAACA,EAAEA,CAACA;IACdA,CAACA;IACLF,QAACA;AAADA,CAACA,AAXD,IAWC"}

View file

@ -0,0 +1,115 @@
===================================================================
JsFile: es5-umd.js
mapUrl: es5-umd.js.map
sourceRoot:
sources: es5-umd.ts
===================================================================
-------------------------------------------------------------------
emittedFile:tests/cases/compiler/es5-umd.js
sourceFile:es5-umd.ts
-------------------------------------------------------------------
>>>var A = (function () {
1 >
2 >^^^^^^^^^^^^^^^^^^^->
1 >
>
1 >Emitted(1, 1) Source(2, 1) + SourceIndex(0)
---
>>> function A() {
1->^^^^
2 > ^^->
1->class A
>{
>
1->Emitted(2, 5) Source(4, 5) + SourceIndex(0) name (A)
---
>>> }
1->^^^^
2 > ^
3 > ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^->
1->constructor ()
> {
>
>
2 > }
1->Emitted(3, 5) Source(7, 5) + SourceIndex(0) name (A.constructor)
2 >Emitted(3, 6) Source(7, 6) + SourceIndex(0) name (A.constructor)
---
>>> A.prototype.B = function () {
1->^^^^
2 > ^^^^^^^^^^^^^
3 > ^^^
1->
>
> public
2 > B
3 >
1->Emitted(4, 5) Source(9, 12) + SourceIndex(0) name (A)
2 >Emitted(4, 18) Source(9, 13) + SourceIndex(0) name (A)
3 >Emitted(4, 21) Source(9, 5) + SourceIndex(0) name (A)
---
>>> return 42;
1 >^^^^^^^^
2 > ^^^^^^
3 > ^
4 > ^^
5 > ^
1 >public B()
> {
>
2 > return
3 >
4 > 42
5 > ;
1 >Emitted(5, 9) Source(11, 9) + SourceIndex(0) name (A.B)
2 >Emitted(5, 15) Source(11, 15) + SourceIndex(0) name (A.B)
3 >Emitted(5, 16) Source(11, 16) + SourceIndex(0) name (A.B)
4 >Emitted(5, 18) Source(11, 18) + SourceIndex(0) name (A.B)
5 >Emitted(5, 19) Source(11, 19) + SourceIndex(0) name (A.B)
---
>>> };
1 >^^^^
2 > ^
3 > ^^^^^^^^^->
1 >
>
2 > }
1 >Emitted(6, 5) Source(12, 5) + SourceIndex(0) name (A.B)
2 >Emitted(6, 6) Source(12, 6) + SourceIndex(0) name (A.B)
---
>>> return A;
1->^^^^
2 > ^^^^^^^^
1->
>
2 > }
1->Emitted(7, 5) Source(13, 1) + SourceIndex(0) name (A)
2 >Emitted(7, 13) Source(13, 2) + SourceIndex(0) name (A)
---
>>>})();
1 >
2 >^
3 >
4 > ^^^^
5 > ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^->
1 >
2 >}
3 >
4 > class A
> {
> constructor ()
> {
>
> }
>
> public B()
> {
> return 42;
> }
> }
1 >Emitted(8, 1) Source(13, 1) + SourceIndex(0) name (A)
2 >Emitted(8, 2) Source(13, 2) + SourceIndex(0) name (A)
3 >Emitted(8, 2) Source(2, 1) + SourceIndex(0)
4 >Emitted(8, 6) Source(13, 2) + SourceIndex(0)
---
>>>//# sourceMappingURL=es5-umd.js.map

View file

@ -0,0 +1,17 @@
=== tests/cases/compiler/es5-umd.ts ===
class A
>A : Symbol(A, Decl(es5-umd.ts, 0, 0))
{
constructor ()
{
}
public B()
>B : Symbol(B, Decl(es5-umd.ts, 6, 5))
{
return 42;
}
}

View file

@ -0,0 +1,18 @@
=== tests/cases/compiler/es5-umd.ts ===
class A
>A : A
{
constructor ()
{
}
public B()
>B : () => number
{
return 42;
>42 : number
}
}

View file

@ -0,0 +1,11 @@
tests/cases/compiler/umdDependencyComment2.ts(3,21): error TS2307: Cannot find external module 'm2'.
==== tests/cases/compiler/umdDependencyComment2.ts (1 errors) ====
///<amd-dependency path='bar'/>
import m1 = require("m2")
~~~~
!!! error TS2307: Cannot find external module 'm2'.
m1.f();

View file

@ -0,0 +1,20 @@
//// [umdDependencyComment2.ts]
///<amd-dependency path='bar'/>
import m1 = require("m2")
m1.f();
//// [umdDependencyComment2.js]
///<amd-dependency path='bar'/>
(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);
}
})(["require", "exports", "bar", "m2"], function (require, exports) {
var m1 = require("m2");
m1.f();
});

View file

@ -0,0 +1,11 @@
tests/cases/compiler/umdDependencyCommentName1.ts(3,21): error TS2307: Cannot find external module 'm2'.
==== tests/cases/compiler/umdDependencyCommentName1.ts (1 errors) ====
///<amd-dependency path='bar' name='b'/>
import m1 = require("m2")
~~~~
!!! error TS2307: Cannot find external module 'm2'.
m1.f();

View file

@ -0,0 +1,20 @@
//// [umdDependencyCommentName1.ts]
///<amd-dependency path='bar' name='b'/>
import m1 = require("m2")
m1.f();
//// [umdDependencyCommentName1.js]
///<amd-dependency path='bar' name='b'/>
(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);
}
})(["require", "exports", "bar", "m2"], function (require, exports, b) {
var m1 = require("m2");
m1.f();
});

View file

@ -0,0 +1,13 @@
tests/cases/compiler/umdDependencyCommentName2.ts(5,21): error TS2307: Cannot find external module 'm2'.
==== tests/cases/compiler/umdDependencyCommentName2.ts (1 errors) ====
///<amd-dependency path='bar' name='b'/>
///<amd-dependency path='foo'/>
///<amd-dependency path='goo' name='c'/>
import m1 = require("m2")
~~~~
!!! error TS2307: Cannot find external module 'm2'.
m1.f();

View file

@ -0,0 +1,24 @@
//// [umdDependencyCommentName2.ts]
///<amd-dependency path='bar' name='b'/>
///<amd-dependency path='foo'/>
///<amd-dependency path='goo' name='c'/>
import m1 = require("m2")
m1.f();
//// [umdDependencyCommentName2.js]
///<amd-dependency path='bar' name='b'/>
///<amd-dependency path='foo'/>
///<amd-dependency path='goo' name='c'/>
(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);
}
})(["require", "exports", "bar", "goo", "foo", "m2"], function (require, exports, b, c) {
var m1 = require("m2");
m1.f();
});

View file

@ -0,0 +1,17 @@
// @target: ES5
// @sourcemap: false
// @declaration: false
// @module: umd
class A
{
constructor ()
{
}
public B()
{
return 42;
}
}

View file

@ -0,0 +1,5 @@
//@module: umd
///<amd-dependency path='bar'/>
import m1 = require("m2")
m1.f();

View file

@ -0,0 +1,5 @@
//@module: umd
///<amd-dependency path='bar' name='b'/>
import m1 = require("m2")
m1.f();

View file

@ -0,0 +1,7 @@
//@module: umd
///<amd-dependency path='bar' name='b'/>
///<amd-dependency path='foo'/>
///<amd-dependency path='goo' name='c'/>
import m1 = require("m2")
m1.f();