-
Notifications
You must be signed in to change notification settings - Fork 13k
Description
π Search Terms
module interface, interface for modules, module implements interface
β Viability Checklist
- This wouldn't be a breaking change in existing TypeScript/JavaScript code
- This wouldn't change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
- This isn't a request to add a new utility type: https://github.com/microsoft/TypeScript/wiki/No-New-Utility-Types
- This feature would agree with the rest of our Design Goals: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals
β Suggestion
Module Interface Proposal
I found some old suggestions to this feature, but I think none is clean like this I'm proposing.
The main idea is give the ability to create an interface to a module. Something like was proposed in #420, but different: my proposal is adding a way to define a module interface, and a way to use it, adding the module interface
and module implements
keywords.
π Motivating Example
// file user-module-interface.ts
export module interface UserModuleInterface {
async function getUser(id: string): Promise<User>
async function createUser(): Promise<void>
}
// ---------------
// file prisma-user.ts
import type { UserModuleInterface } from "./user-module-interface"
module implements UserModuleInterface
export async function getUser(id) {}
export async function createUser() {}
// ---------------
// another-file.ts
import { getUser, createUser } from "./prisma-user"
Explaining the example
First thing we have to do is creating a module interface
. It can be exported from a specific file as type
or used in the same file that functions will be implemented.
Everything inside a module interface
must be implemented when module implements
is used.
Then you can import (as a type
) this module interface
into another file and use module implements
to implement the interface. This can be a line module implements MyInterface
or something with a body, like:
// file prisma-user.ts
import type { UserModuleInterface } from "./user-module-interface"
module implements UserModuleInterface {
export async function getUser(id) {}
export async function createUser() {}
}
We can also implement more than one interface, like in classes:
// file article-module-interface.ts
export module interface ArticleModuleInterface {
function getArticles(): Promise<Article[]>
}
// ---------------
// file prisma-user.ts
import type { UserModuleInterface } from "./user-module-interface"
import type { ArticleModuleInterface } from "./artictle-module-interface"
module implements UserModuleInterface, ArticleModuleInterface
export async function getUser(id) {}
export async function createUser() {}
export async function getArticles() {}
Rules
The module that is using module implements
must implement all functions defined in module interface
. Otherwhise, TypeScript should throw an error.
Shouldn't be a problem to implement other functions that are not defined in a module interface
, as long as we implement at least every function defined in module interface
.
We can possibly make some functions optional (not required to this suggestion):
// file user-module-interface.ts
export module interface UserModuleInterface {
async function getUser(id: string): Promise<User>
// createUser is optional
async createUser?(): Promise<void>
}
// ---------------
// file prisma-user.ts
import type { UserModuleInterface } from "./user-module-interface"
module implements UserModuleInterface
export async function getUser(id) {}
// no errors, even if `createUser` is not implemented
π» Use Cases
In this proposal, we do not rely on runtime features. All types (module interface
and module implements
) can be safely removed in build time.
The functions defined inside a module that extends a module interface
can be auto-inferred.
And with this implementation, we have tree-shaking, because we can import only the functions we'll use.
To do something similar today, we have to create a class
or an object and export the whole object, even if we want to use only one function.