-
Notifications
You must be signed in to change notification settings - Fork 12.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Proposal: Bundling TS modules for consumption and execution #4434
Comments
What are the expected semantics of default exports? I believe we should error if we encounter multiple default exports when flattening into a bundle. |
A "default" export is literally an export named default. The keyword is just sugar for that (since default's a reserved word and all). It also has some special meaning with import statements... but that's less important here, given that the common point of conflict is when re-exporting things. Reexporting multiple |
What about other module systems, i.e. amd/umd/commonjs would we use System as well? is there a better way to avoid perf penality on exporting an reexporting? how about using amd style IIFE? Do you need a loader implementation? given that you know the right order, and dependency, the compiler can emit each module as an object, and pass the right objects to satisfy dependencies.. so something like: define("a", ["require", "exports"], function (require, exports) {
var _b = {};
var _c = {};
// c.ts
(function (require, exports) {
var Baz = (function () {
function Baz() {
}
return Baz;
})();
exports.Baz = Baz;
})(_c /* pass _c as exports*/);
// b.ts
(function(function (require, expots) {
var Bar = (function () {
function Bar() {
console.log('');
}
Bar.prototype.do = function () { throw new Error('Not implemented.'); };
return Bar;
})();
exports.Bar = Bar;
})(require, _b /* pass _b as exports*/);
// a.ts
(function (require, exports, b_1, c_1) {
function __export(m) {
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
}
__export(b_1);
__export(c_1);
})(require, exports /*a is the entry module, so pass in the actual exports here*/,_b, _c);
}); |
ES6 Modules can have circular dependencies, so we need a loader which can handle that (a la system). Simply ordering IIFEs isn't enough to retain ES6 semantics. If I'm not saying we should use system internally (we shouldn't) but our loader/rewriter (probably more of a rewriter with helper functions) is going to do a lot of the same things. Plus we don't need to support async operations. |
for that, you can do the same rewrite done today for loader, and execute all functions at the end. no need for a real loader though. |
I might be wrong, but circular dependencies and IIFE debates are what have made it so we have ES6 modules, but no ES6 loader... 😉 The "rewriter" will have to do some sort of introspection of the modules to resolve circular dependencies to make sure you get the order and instantiation of the modules correct. For reference, in AMD land, there is r.js, Dojo Builder and CramJS/RaveJS. Also most of the AMD builders allow bundling of other resources as well (like text and JSON). I don't know if that is a consideration for this "bundler". |
@mhegazy We may still need some semblance of a loader if we want to support having a loader within the library at runtime so it can dynamically include dependencies - but it was removed in draft rev 28 and moved to an external spec so I'm not sure how much we support it? We could have problems where users expect to be able to dynamically include their code with |
i am not sure i understand what you mean here.. can you elaborate. |
Something fairly common in node/commonjs scenarios is the These dynamic loads pose a problem for bunding modules internally. What if we bundle the modules, but then the consumer of those modules loads them lazily via |
but if i understand correctly, these all will be issues if we do provide a module loader implementation, if we do not, then these should just work. correct? |
No, if we do not then we'll have serious problems with people having modules in their bundle that they can't access in the way they'd expect to (in the above dynamic ways, anyway). It's why we'd need an internal loader/module lookup/whathaveyou. |
Yes, lots of people have challenges understanding how CommonJS modules actually gets resolved. There is a lot of "magic". Many developers forget that when you move from the server side, where you have access to a file system and what is trivial resolution "magic" becomes wholly impossible on the client side. It is one of the many things which make people not like AMD, but I argue a lot of the "magic" we suffer to make lives easier for devs disadvantages the consumers of our solutions and actually we should bear a bit more of the burden. I would be very cautious about introducing too much "magic" into module resolution. If there is to be any "magic" it is a configuration object which is utilised by the loader to map the final resolution. This of course can then be externalised our built in. Extending |
Just for clarification's sake, this is the essence of the troublesome dynamic case I'm considering: import * from "ui";
//Draw UI
// ...
instantiateLazyComponent("editor", "ComplexEditorElement").then(e => this.appendElement(e));
// ...
export class MyElement {}
export function instantiateLazyComponent(componentType: string, className: string): Promise<MyElement> {
return System.import(componentType+"_lazy.js").then(function(uiLazy: any) {
if (!uiLazy[className]) throw new Error("Class not found");
return new uiLazy[className]();
});
}
import {MyElement} from "ui"
export class ComplexEditorElement extends MyElement {}
import {MyElement} from "ui"
export class ComplexGraphElement extends MyElement {} This is (roughly, their injection framework's way more complex and they use script tags and global namespacing rather than modules) how lazy UI elements get loaded in the chrome devtools. Supporting bundling those lazy components into the bundle is troublesome - we can put the code into the bundle, but we need to teach the We also can't eliminate the possibility that either |
A reference for this feature: |
May I add my two cents (coming from #6419): Why is a call to I would think that export class MyElement {}
export function instantiateLazyComponent(componentType: string, className: string): Promise<MyElement>
{
import component from "componentType+"_lazy.js";
return component;
} Then It would be necessary for |
My original point was the regardless of what module system the user is compiling to, for completenesses' sake, we should provide an implementation of a canonical |
Webpack seems done this well. |
Fully agree. As @mhegazy and @weswigham (and I) are argumenting pro - I'm curious: is there a roadmap for getting this implemented or is there perhaps a reason for not implementing it? I'd like to add my personal point of view to:
TypeScript is not ES6. As long as there is no intention for having TypeScript sink into oblivion by becoming ES7 = TypeScript, I believe TypeScript should keep syntax peculiarities by its own. I strongly believe that two different TypeScript constructs performing the same action should not be introduced. I'd prefer the team would either decide for using the |
This scenario seems well-served by many good external bundlers today; we don't need to rewrite esbuild |
Relates to #3089, #2743, and #17.
Goals
Proposal
When all of
--bundle
,--module
and--out
are specified, the TS compiler should emit a single amalgamatedjs
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 asimport a = require
orexports =
. 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 notcommonjs
), 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
:b.ts
:c.ts
:Hypothetically, with
--out appLib.js
,--bundle a.ts
and--module system
arguments, we should get something likeappLib.js
:The text was updated successfully, but these errors were encountered: