Skip to content

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

Closed
rotemdan opened this issue Mar 26, 2015 · 8 comments
Closed
Labels
Declined The issue was declined as something which matches the TypeScript vision Out of Scope This idea sits outside of the TypeScript language design constraints Suggestion An idea for TypeScript

Comments

@rotemdan
Copy link

_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.:

import M = require("MyModule");

To allow the following additional features and capabilities:

  • Can be used in any scope of the program, including in functions, conditionals and loops.
  • Only loads the module on the first call, and caches for the following ones (standard behavior for CommonJS. May need to be simulated on others [note: this is very easy to implement]).
  • Identifier M is locally bound, and can be used as any variable can, including in closures etc.
  • Apply polyfills to load ES6 (or RequireJS?) modules dynamically, if or when that capability is added (personally it's hard for me to believe this capability wouldn't be [or is already?!] offered at some point, as this is a need that would quickly arise even on very simple cases).

Additionally, it may also support new features from the new ES6 system, such as default and * exports e.g.:

import DefaultExport = require("MyModule").default;
import NamespaceExport = require("MyModule").*;

And of course named exports, which would be a useful addition anyway, even today:

import NamedExport = require("MyModule").NamedExport;

It would make sense to target the "non-specific" import statement e.g.:

import MyModule = require("MyModule");

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 the require keyword closely parallels the CommonJS require() 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).

@NoelAbrahams
Copy link

@rotemdan, just for your info: TypeScript attempts to operate under a set of design goals.

In particular,

Align with current and future ECMAScript proposals

[Not] Provide additional runtime functionality or libraries.

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.

@rotemdan
Copy link
Author

@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.

@rotemdan rotemdan changed the title Proposal: extend legacy module import syntax to a fully dynamic module loader with polyfills Proposal: extend legacy module import syntax to a fully dynamic module loader Mar 26, 2015
@sophiajt
Copy link
Contributor

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.

@rotemdan
Copy link
Author

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?

@danquirk
Copy link
Member

One big example of 'more full-featured' is its ability to allow circular references between modules.

@mhegazy mhegazy added the Suggestion An idea for TypeScript label Apr 2, 2015
@mhegazy mhegazy added Declined The issue was declined as something which matches the TypeScript vision Out of Scope This idea sits outside of the TypeScript language design constraints labels Dec 9, 2015
@mhegazy
Copy link
Contributor

mhegazy commented Dec 9, 2015

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.

@trustedtomato
Copy link
Contributor

There is a dynamic import proposal in stage 3. As a proposal in stage 3 it does align with TypeScript's design goals.

@kitsonk
Copy link
Contributor

kitsonk commented Apr 19, 2017

@trustedtomato that is covered by #12364

@microsoft microsoft locked and limited conversation to collaborators Jun 18, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Declined The issue was declined as something which matches the TypeScript vision Out of Scope This idea sits outside of the TypeScript language design constraints Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

7 participants