Repository navigation
[Design] Syntax to represent module namespace object in dynamic import #14844
Description
Activity
- addedDiscussionIssues which may not have code impactIssues which may not have code impact
on Mar 24, 2017 Why not just like?
var p = import<typeof MyModule1 | typeof MyModule2>(somecondition ? "./MyModule1" : "./MyModule2");
Reacted by Michał Lytek and Omri Luzoncevek two things with that syntax: 1) it won't be clear that the output is a
Promise of typeof Module1 or typeof Module2ortypeof Module1 or typeof Module22) during design meeting we decided to go with using casting syntax and allow contextual type because as you can see the type arguments syntax get pretty ugly withimport<Promise<moduleof MyModule1 | moduleof MyModule2>>I will update the proposal with what we discuss during design meeting
So looking at the proposed snippets above:
import * as MyModule1 from "./MyModule1"; import * as MyModule12 from "./MyModule2"; var p = import(somecondition ? "./MyModule1" : "./MyModule2") as Promise<typeof MyModule1 | typeof MyModule2>;
var p = import(somecondition ? "./MyModule1" : "./MyModule2") as Promise<moduleof MyModule1 | moduleof MyModule2>;
I don't see how
typeofandmoduleofdiffer... Moreover, in the second snippet, where does theMyModule1identifier come from? Is it just imported (like byimport * as MyModule1 from "./MyModule1";) or is there some entirely distinct mechanicism I'm not picking up on?My hope is that this is some huge typo and that the
moduleof './MyModule1'syntax that Mohamed Hegazy (@mhegazy) suggested in #14495 is what you meant.Reacted by Felix BeckerPatrick Lienau (@rozzzly) it is a typo for this one.... Copy and paste mistake here 😭 I have updated the original post.
Reacted by Patrick LienauReacted by Patrick LienauUpdate
- The top candidate for syntax for describing shape of module is
module(...) - nuance semantic : should the syntax bring in type of module as well as namespace?
// 0.ts export interface foo {} export class C {} // 1.ts var p: module("./0");
-
is
module(...)bring both type of module and namespace so you can dop.fooas well asp.C? -
or is
module(...)only bring namespace side and therefore to get type of module one will have to dovar anotherP: typeof module("./0");
If we go with (2.) then for dynamic import, to express the shape of import module will be
var d = import(blah) as Promise<typeof module("blah")>; // very verbose
Another syntax that comes up for using with dynamic import is
var d = import<"blah">(blah); var d = import(<"blah">blah);
In both casting syntax, type argument must be string literal.,
Conclusion
- Possible use short hand to specify shape of dynamic import separately like
var d = import<"blah">(blah); var d = import(<"blah">blah);
- May be being able to bring just type of module should be treat in other issues so we can discuss syntax and semantic in details. So we won't have to worry about how such syntax plays with dynamic import (e.g. it may be a long verbose syntax like
var d = import(blah) as Promise<typeof module("blah")>;)
- The top candidate for syntax for describing shape of module is
This is a subset of this larger issue #13231
I definitely like the type operator for looking up modules, since it would probably be nice if
importcould just be a function(like) in the appropriate lib.d.ts typed likedeclare function import<T extends string>(path: T): Promise<typeof module(T)>;
where
module(T)behaves like the type indexing operator with respect to unions of string literal types, but looks up the symbol associated with the module that string type indicates, rather than indexing off a specific type (and behaved like it has an indexer to an implicitanyto catch cases where a string literal could not be inferred). I would almost like to use square brackets formodulelike a map, except that the string literal needs to be treated as a path and resolved from the containing file (probably? configuration dependent?), and that transformation makes it more like a function than a straight map. The new syntax also simultaneously allows one to typerequireand any other module-loader-specific lookup in a similar way toimport, which would be excellent.Oh, and this is unrelated but oddly unsatisfying: The import spec as currently written allows something like this:
export default (x) => import(x);
But not this:
export default import;
since it's not really a function.
This seems.... bad. Unnecessarily confusing. If the engine can record where the call-site for theImportCallis, I don't (as not a JS-engine-author) see why it couldn't just addImportKeywordto the list of expressions and make its reference (a la a getter, but in the global scope) return an annotated importing function keyed to the file the reference was in. But that's not about the TS syntax for looking up modules.Unfortunately we can not do the general type operator; given the way the compiler is architected today all file-system interactions happen at the very beginning when we are collecting files to compile; whereas resolving types happens at a later stage where new files are not expected to be added, nor are file-system operations expected. so it has to be
module("literal")and notmodule(T).wouldn't the
typeof module("foo")syntax conflict with the potential syntax for arbitrary typeof expressions (#6606)?e.g.
declare function foo(a: boolean): string; type Q = typeof foo(true); // Q is string
What is the final syntax?
A module instance is required to avoid repeating the same
import('module')and deal withawait/Promiseevery time a module reference is needed. Even though the module loaded only once, it require the calling function to beasyncor wrap the bottom one with.then().I came across where there is a conditional import to only add hardware specific module, load the module to get an initialization side effect and doing some cleanup before exit. Currently I keep a variable of required function within the module instead of the module itself.
In my opinion
The top candidate for syntax for describing shape of module is module(...)
module(...)is awesome.
nuance semantic : should the syntax bring in type of module as well as namespace?
- I vote for the
module(...)not to bring in type but just namespace to make it similar withimport * as a from "a"becausemodule(...)may refer to default property.
var p: typeof module("a"); p = import<typeof module("a")>(a); type moduleA = typeof module("a");
- I like implicit type parameter above (without the
Promise) instead of casting because it is well documented in IntelliSense instead of casting fromany. Any concern to have something likefunction import<T>(module: string): Promise<T>?
I prefer to avoid
typeof Modulebecause it requireimport * as Module from "Module", the static import may produce side effect.This seems like something that this feature request of mine could solve quite simply...
TL;DR: it'd reify types as pseudo-properties (transparent to the runtime) and allow them to be passed around and defined like so. Support for this would fall out fairly naturally.
However, it might potentially be a little cumbersome due to the later binding of types.
- addedSuggestionAn idea for TypeScriptAn idea for TypeScriptand removedDiscussionIssues which may not have code impactIssues which may not have code impact
on Mar 9, 2018 Discussed this in #22445, and conclusion is to go with
import(<StringLiteral>)Reacted by Claudia Meadows, Denis Malinochkin, Jonas Ahrens, CallMeLaNN, Nacho Caballero and superamadeusfelixfbecker commented
on Mar 20, 2018 ContributorMore actionsSorry if I'm missing something, but what is wrong with this, which is already possible?
import * as _foo from './foo' let foo: typeof _foo
Also want to point to this piece from the DT "common mistakes" readme why
import()shouldn't get a type parameter that I strongly agree with:getMeAT<T>(): T:
If a type parameter does not appear in the types of any parameters, you don't really have a generic function, you just have a disguised type assertion.
Prefer to use a real type assertion, e.g.getMeAT() as number.
Example where a type parameter is acceptable:function id<T>(value: T): T;.
Example where it is not acceptable:function parseJson<T>(json: string): T;.
Exception:new Map<string, number>()is OK.Felix Becker (@felixfbecker) The string isn't a type parameter.
import(...)in JS is a call-like syntactic expression much likesuper(...). For similar reasons, you can't dolist.map(import)orlist.map(super).felixfbecker commented
on Mar 21, 2018 ContributorMore actions@isiahmeadows I am aware - like
importis call-like, this proposal definitely looks type-parameter-like, so the argument still applies:var p = import<Promise<moduleof "./MyModule1" | moduleof "./MyModule2">>(somecondition ? "./MyModule1" : "./MyModule2");
Felix Becker (@felixfbecker) why would you not just cast there?
felixfbecker commented
on Mar 21, 2018 ContributorMore actionsWesley Wigham (@weswigham) that's my point
- addedFixedA PR has been merged for this issueA PR has been merged for this issue
on Apr 2, 2018 This seems to not work with
export default classfoo.js
export default class Foo { constructor() { this.name = 'bar'; } }Fails:
Property 'name' does not exist on type 'typeof import("/test/foo")'./** @typedef {typeof import('./foo')} Foo */ /** * @param {Foo} foo * @return {string} */ function getName(foo) { return foo.name; }If I change foo.js to use
export class Foo {, then /** zouying (@typedef) {import('./foo').Foo} Foo */ works fine.EDIT: Nevermind. I have to use
/** @typedef {typeof import('./foo').default} Foo */Reacted by Wesley Wigham- locked and limited conversation to collaborators
on Jul 31, 2018
Another half of dynamic import #14774 is to enable users to be able to describe the shape of the module namespace object return by dynamic import. This is needed because
TypeScript currently can only resolve module if the specifier is a string literal (recall syntax of dynamic import
import(specifier)) andPromise<any>will be returned...Therefore we need a way for users to be able to specify the module shape in type argument of the Promise and escape noImplicitAny (Note if dynamic import is used as expression statement. we won't issue noImplicitAny)When we emit declaration file, we need a syntax to indicate that this is a
Promise of some module. Currently compiler will give an error as reference external module is not the same as the module of containing file (or lack of one)🚲 🏠 There are two ares which need to be discuss:
Proposes for (1) - bring in the module (This assume that we will use casting syntax but it doesn't have)
moduleof....when collecting module reference we will also collect frommoduleofas wellProposes for (2) - indicate the return type
Just use casting
allow all of the above.
Design meeting update #14853
In addition to be able to simply refer to shape of module namespace object, it is also desirable to use as a
QualifiedName(e.gvar x: <some syntax to indicate that we refer to module object>.namespace.interface) and to have syntax that can easily allow users to be able to do so is crucial.Candidates we are currently considered:
I will want to discuss some performance/incremental parsing related to this area and see if we think it will be desirable to consider top-level type import like
import type * as ns from "./a.js".