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)) and Promise<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)
import * as MyModule1 from "./MyModule1"
import * as MyModule12 from "./MyModule2"
var p = import(somecondition ? "./MyModule1" : "./MyModule2") as Promise<typeof MyModule1 | typeof MyModule2>;
moduleof....when collecting module reference we will also collect from moduleof as wellvar p = import(somecondition ? "./MyModule1" : "./MyModule2") as Promise<moduleof "./MyModule1" | moduleof "./MyModule2">;
Proposes for (2) - indicate the return type
var p = import<Promise<moduleof "./MyModule1" | moduleof "./MyModule2">>(somecondition ? "./MyModule1" : "./MyModule2");
var p: <Promise<moduleof "./MyModule1" | moduleof "./MyModule2">> = import(somecondition ? "./MyModule1" : "./MyModule2");
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.g var 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:
declare var m: module("./foo").namespace.Interface // this one seems to be the most popular
declare var m: (moduleof "./module").namespace.Interface;
declare var m: (importof "./module").namespace.Interface;
declare var m: (module "./module").namespace.Interface;
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".
Why not just like?
var p = import<typeof MyModule1 | typeof MyModule2>(somecondition ? "./MyModule1" : "./MyModule2");
@cevek two things with that syntax: 1) it won't be clear that the output is a Promise of typeof Module1 or typeof Module2 or typeof Module1 or typeof Module2 2) 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 with import<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 typeof and moduleof differ... Moreover, in the second snippet, where does the MyModule1 identifier come from? Is it just imported (like by import * 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 @mhegazy suggested in #14495 is what you meant.
@rozzzly it is a typo for this one.... Copy and paste mistake here 馃槶 I have updated the original post.
Update
module(...)// 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 do p.foo as well as p.C ?
or is module(...) only bring namespace side and therefore to get type of module one will have to do var 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
var d = import<"blah">(blah);
var d = import(<"blah">blah);
var d = import(blah) as Promise<typeof module("blah")>;)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 import could just be a function(like) in the appropriate lib.d.ts typed like
declare 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 implicit any to catch cases where a string literal could not be inferred). I would almost like to use square brackets for module like 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 type require and any other module-loader-specific lookup in a similar way to import, 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 the ImportCall is, I don't (as not a JS-engine-author) see why it couldn't just add ImportKeyword to 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 not module(T).
wouldn't the typeof module("foo") syntax conflict with the potential syntax for arbitrary typeof expressions (https://github.com/microsoft/typescript/issues/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 with await/Promise every time a module reference is needed. Even though the module loaded only once, it require the calling function to be async or 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?
module(...) not to bring in type but just namespace to make it similar with import * as a from "a" because module(...) may refer to default property.var p: typeof module("a");
p = import<typeof module("a")>(a);
type moduleA = typeof module("a");
Promise) instead of casting because it is well documented in IntelliSense instead of casting from any. Any concern to have something like function import<T>(module: string): Promise<T>?I prefer to avoid typeof Module because it require import * 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.
Discussed this in #22445, and conclusion is to go with import(<StringLiteral>)
Sorry 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.
@felixfbecker The string isn't a type parameter. import(...) in JS is a call-like syntactic expression much like super(...). For similar reasons, you can't do list.map(import) or list.map(super).
@isiahmeadows I am aware - like import is 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");
@felixfbecker why would you not just cast there?
@weswigham that's my point
This seems to not work with export default class
foo.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 /** @typedef {import('./foo').Foo} Foo */ works fine.
EDIT: Nevermind. I have to use /** @typedef {typeof import('./foo').default} Foo */
Most helpful comment
Discussed this in #22445, and conclusion is to go with
import(<StringLiteral>)