Description
openedon Dec 30, 2016
Introduction
Currently, TypeScript allows to ensure typings on differents levels using type
, interface
or class
.
There are numbers of scenarios where we want to ensure typings on the module
level.
Scenario 1: Module Interface
Many libraries can be extended using plugins which are loaded as modules.
These libraries' maintainers could define an interface
which should be implemented by the module.
// mylib.d.ts
export interface IPlugin {
init(): void;
build(context: PluginContext): boolean | PromiseLike<boolean>;
dispose?: () => void;
}
//plugin.ts
import { IPlugin, PluginContext } from "mylib";
export implements IPlugin;
^^^ ERR: Module does not implement "IPlugin". Missing members: "init".
export function build(context: PluginContext) { return false; }
Scenario 2: Module proxy
Imagine a library which abstract RPC services. On the server part, you define services as modules and on the client part, you get Promised proxies of these services which you can use to remote call your service methods.
// myrpclib.d.ts
export type Deferred<T> = { [P in keyof T]: Promise<T[P]>; };
export function createClient<T>(url: string): Deferred<T>;
// services/entity.service.ts
export function getAll(): Promise<Entity[]> { /* ... */ }
export function getOne(id: string): Promise<Entity> { /* ... */ }
export function create(entity: Entity): Promise<Entity> { /* ... */ }
export function update(entity: Entity): Promise<Entity> { /* ... */ }
export function remote(id: string): Promise<void> { /* ... */ }
export function sync(id: string): Entity { /* ... */ }
// clients/client1.ts
import { createClient } from "myrpclib";
type RemoteService = typeof module("../services/entity.service");
const client = createClient<RemoteService>("http://host/entity.service");
Scenario 3: Inline module typing
By extending the preceding scenario, we could imagine that the library also provides a module loader plugin for its managed services.
// myrpclib.d.ts
declare module "rpc!*" {
const res: any;
export default res;
}
// clients/client2.ts
type RemoteService = typeof module("../services/entity.service");
import client from "rpc!host/entity.service" as Deferred<RemoteService>;
Scenario 4: Wildcart module typing
This time, we have a library that proxy services in a Web Worker
.
// myrpclib.d.ts
declare module "worker!*" {
const res: Deferred<typeof module("*")>;
export default res;
}
// clients/client3.ts
type RemoteService = typeof module("../services/entity.service");
import client from "worker!../services/entity.service";
client.getAll().then(/* ... */);
client.sync().then(/* ... */);
// client type is "Deferred<typeof module("../services/entity.service")>";
Language Features
Syntatic
What is the grammar of this feature?
-
export implements IPlugin
(could be writtenimplements IPlugin
) -
typeof module("module/path")
-
import myModule from "myModule" as Type
-
typeof module("*")
in wildcart module definition
Are there any implications for JavaScript back-compat? If so, are they sufficiently mitigated?
- No backward compatibility implications
Does this syntax interfere with ES6 or plausible ES7 changes?
- No conflicting keywords
Semantic
What is an error under the proposed feature? Show many examples of both errors and non-errors
//export implements...
// Same as an interface
//typeof module("module/path");
type Moduletype = typeof module("module/path"); // OK
type UnknownModuleType = typeof module("unknown/module");
^^^^ // COMPILER ERROR: "unknown/module" does not exists
const test: typeof module("module/path"); //OK
function test(): typeof module("module/path"); //OK
function load(name: string): typeof module(name); //OK
//import module as
import myModule from "myModule" as Type; //OK
import * as myModule from "myModule" as Type; //OK
import * as unknown from "unknown" as Type; //OK
import { someMember } from "myModule" as Type;
^^^^ // COMPILER ERROR: Only default and global import are allowed!
//typeof module("*")
declare module "proxy!*" {
const res: typeof module("*"); // OK
export default res;
}
type myType = typeof module("*");
^^^^ // COMPILER ERROR: module "*" does not exists
How does the feature impact subtype, supertype, identity, and assignability relationships?
- Moderate impact
- Modules should be treated as special Interfaces (or special Classes) so they could be used in same contexts and they can interact we others.
- An import could be typed
How does the feature interact with generics?
- Limited impact
- If a module will become a
type
(like an Interface is), it should interact with generics.
Emit
What are the effects of this feature on JavaScript emit? Be specific; show examples
- No effect
Does this emit correctly in the presence of variables of type ‘any’? Features cannot rely on runtime type information
- No emit difference
What are the impacts to declaration file (.d.ts) emit?
- Limited impact
- declaration file should contain
export implements...
informations
Does this feature play well with external modules?
- Exclusively for external modules
Compatibility
Is this a breaking change from the 1.0 compiler? Changes of this nature require strong justification
- New keywords are introduced but it is only typings utilities
Is this a breaking change from JavaScript behavior? TypeScript does not alter JavaScript expression semantics, so the answer here must be “no”
- No
Is this an incompatible implementation of a future JavaScript (i.e. ES6/ES7/later) feature?
- No
Other
Can the feature be implemented without negatively affecting compiler performance?
- Yes
What impact does it have on tooling scenarios, such as member completion and signature help in editors?
- Moderate impact
TODO
- Get validation from the community
- Get feedback from the community and improve spec
- Add more exemples if needed
- Implementation details