Skip to content

[Proposal] External Module Type #13231

Closed
Closed

Description

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 written implements 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Labels

    In DiscussionNot yet reached consensusSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions