Description
Relates to #3089, #2743, and #17.
Goals
- Bundle js emit for TS projects while preserving internal modular structure (complete with ES6 semantics and despite being concatenated into a single file), but not exposing it publicly and wrapping the chosen entrypoint with a loader for the desired external module system.
Proposal
When all of --bundle
, --module
and --out
are specified, the TS compiler should emit a single amalgamated js
file as output. --bundle
is a new compiler flag, indicating that the modules used should be concatenated into the output file and loaded with a microloader at runtime. The semantics of this microloader should preserve ES6 module semantics, but may not support TS-specific ES6 module semantics, such as import a = require
or exports =
. This is a major point of discussion, as to if these TS-specific syntax extensions are worth continuing to support with new language features. Additionally, this should wrap the result file with --module
-specific declaration wrappers, if need be.
Supporting ES6 semantics is some effort - some rewriting/moving and declaration hoisting needs to be done to support circular references between internal modules.
Most of the effort involved here involves the design of the microloader and emit for said microloader - since this is compile time, and we don't want to register any new external dependencies at runtime (with this loader), I expect we can make some optimizations compared to, say, using an internal copy of systemjs.
Additionally, we need to decide if we want to make further optimizations based on the chosen --module
- if the system chosen supports asynchronous module resolution (so not commonjs
), then we can potentially use a more lazy, async dependency resolver internally, too.
So, to give an example of the emit (let's say we use a system-like thing internally and are emitting a system module):
Given:
a.ts
:
export * from './b';
export * from './c';
b.ts
:
export interface Foo {}
export class Bar {
constructor() {
console.log('');
}
do(): Foo { throw new Error('Not implemented.'); }
}
c.ts
:
export class Baz {}
export interface Foo {}
Hypothetically, with --out appLib.js
, --bundle a.ts
and --module system
arguments, we should get something like
appLib.js
:
System.register([], function (exports_1) {
var __system = /* loader code */;
//a.ts
__system.register('./a', ['./b', './c'], function(exports_1) {
function exportStar_1(m) {
var exports = {};
for(var n in m) {
if (n !== "default") exports[n] = m[n];
}
exports_1(exports);
}
return {
setters:[
function (_b_1) {
exportStar_1(_b_1);
},
function (_c_1) {
exportStar_1(_c_1);
}],
execute: function() {
}
}
});
//b.ts
__system.register('./b', [], function(exports_1) {
var Bar;
return {
setters:[],
execute: function() {
Bar = (function () {
function Bar() {
console.log('');
}
Bar.prototype.do = function () { throw new Error('Not implemented.'); };
return Bar;
})();
exports_1("Bar", Bar);
}
}
});
//c.ts
__system.register('./c', [], function(exports_1) {
var Baz;
return {
setters:[],
execute: function() {
Baz = (function () {
function Baz() {
}
return Baz;
})();
exports_1("Baz", Baz);
}
}
});
function exportStar_1(m) {
var exports = {};
for(var n in m) {
if (n !== "default") exports[n] = m[n];
}
exports_1(exports);
}
return {
setters: [],
execute: function() {
__system.import('./a').then(function (_pub_1) {
exportStar_1(_pub_1);
});
};
});