-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Proposal: extend legacy module import syntax to a fully dynamic module loader #2508
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
Comments
@rotemdan, just for your info: TypeScript attempts to operate under a set of design goals. In particular,
My preference is for the legacy import syntax to be discontinued after version 2.0. It's confusing to have to explain the semantics of both systems - especially as they are both so similar. |
@NoelAbrahams: as I've mentioned, I didn't post this necessarily as a concrete proposal (or with the actual belief it would be accepted), but as a way to encourage discussion on the importance of dynamic module loading and its usefulness and essential appropriateness for Javascript development. I also mentioned that CommonJS already supports loading libraries dynamically (and has a rather similar syntax to the TS one), so seen in this way, TS is not aligned with CommonJS. But reading this more literally I understand their goal is rather to align with ECMAScript, so when ECMAScript provides a way to load modules dynamically (and I guess they probably will, at some point?), they will support it, and will have to "polyfill" (meaning "emulate" its syntax) it on other module systems (I do realize now using the word "polyfill" was a pretty poor word selection, so I might change it). As for adding run-time functionality - I don't think it adds any - it simply allows to do what is already possible in CommonJS, and tries to be more closely compatible with it . Whether they will remove support to the old syntax? I don't know, that wasn't the issue here, I simply wanted to demonstrate how that might be done and the reasoning behind it. |
Maybe to second @NoelAbrahams. We got a lot of good out of the old external module syntax, and it's served us well. I suspect we'll continue to support it, but as a legacy feature. The ES6 module syntax is generally more robust and full-featured, as well as being standards-aligned, so we'll encourage people to prefer it. |
Will it allow loading modules dynamically? and if not, how does that make it more robust or full-featured? Are there any plans on how to approach that issue? |
One big example of 'more full-featured' is its ability to allow circular references between modules. |
As mentioned by @NoelAbrahams, supporting this feature would contradict with the TS design goals. more over modules are currently complex enough and do not need yet another variation. |
There is a dynamic import proposal in stage 3. As a proposal in stage 3 it does align with TypeScript's design goals. |
@trustedtomato that is covered by #12364 |
_Note: this is still a work in progress - I'm aware it isn't perfect or final by any means and should be seen mostly as a starting point to encourage discussion and re-consideration of the existing approach and solutions._
(based on ideas I expressed in #2357 (comment))
Summary
Since the new ES6 module syntax will be introduced soon, the older one - that's not actually bound to any standard, could be extended to a fully portable, dynamic and flexible module loader, that may even introduce "polyfills" as needed to achieve consistent behavior between different module systems and platforms.
Rationale
Javascript is a dynamic and network-oriented language. Documents, images and scripts can (and arguably should, in many cases) be loaded on the fly and as-needed, this leads to many performance advantages such as faster page loads, less pauses in execution, saving of network bandwidth and reductions in mobile battery use.
Loading modules may be expensive (for any of the above mentioned reasons, or additional ones), and should arguably be done only when necessary (this should be a standard and encouraged style in my opinion). Additionally, it is quite common to have cases where a module (or even the means to load it) is simply not available or compatible with the current platform (e.g. trying to load Node module in the browser) or there's a selection of modules with similar functionality (for example, different versions of the same module for mobile, desktop or server usage) which have to be loaded conditionally.
The current solution
The currently offered functionality is a relatively limited and "static" one, reminiscent of languages such as C# or Java where it is important to compile and sometimes "bundle" classes and modules together (or enable the generation of dependency graphs etc.). In these languages it would make sense to have all imports declared ahead-of-time and assign them global identifiers since in the vast majority of cases it is expected that the program would run in a relatively homogeneous and consistent platform.
As pretty much any JS developer would realize, this is certainly not the case for Javascript, where compatibility issues are abundant and JIT compilation, initialization and script download may be a lengthy and expensive process, especially with larger libraries and on mobile platforms. CommonJS has long featured a simple and fully dynamic system, meaning it can be used in any scope of the program and yield locally scoped identifiers. The current TypeScript support for it is quite limited, and in many cases not powerful enough to express the full range of flexibility and convenience it offers.
The proposed solution
(Note: this should only be seen as a starting point for consideration, as I'm not a TS designer or implementer and probably not aware of all the edge cases and complexity this would entail, especially regarding backwards compatibility. I'm aware this may be completely impossible to implement, if only because of very subtle issues, but I decided to suggest it anyway)
I propose to extend the current (or soon to be "legacy") syntax e.g.:
To allow the following additional features and capabilities:
M
is locally bound, and can be used as any variable can, including in closures etc.Additionally, it may also support new features from the new ES6 system, such as
default
and*
exports e.g.:And of course named exports, which would be a useful addition anyway, even today:
It would make sense to target the "non-specific" import statement e.g.:
to be equivalent to a
*
import, but that is open to discussion.Possible challenges
Although the vast majority of TS developers are probably not really aware of this, current semantics and behavior is "lazy", meaning it will only perform the actual loading of the module if it is used at least once at a value position. This may not be an expected or desirable behavior for many people and in some cases, especially if the act of loading the module has side-effects.
Personally I would prefer eager evaluation in this case, as it makes the program behave in a more predictable and literal way. Since the
import
keyword is mostly read as a verb and therequire
keyword closely parallels the CommonJSrequire()
function (that's effectively used as a command), it would heavily suggest immediate execution. I understand changing this behavior would be a significant breaking change - this is a serious limitation that has to be discussed (of course it would be possible to keep this behavior, but I don't see that as desirable, workarounds may be possible though, but I haven't really considered that deeply yet).The text was updated successfully, but these errors were encountered: