Skip to content

Proposal: Bundling TS modules for consumption and execution #4434

Closed
@weswigham

Description

@weswigham

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);
      });
   };
});

Metadata

Metadata

Assignees

No one assigned

    Labels

    Out of ScopeThis idea sits outside of the TypeScript language design constraintsSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions