-
Notifications
You must be signed in to change notification settings - Fork 0
Module structure
A big codebase needs to be split into multiple files. We achieve this by using modules.
Every file is a module, that provides some exports (functions, classes, constants, etc.) and can import other modules.
We use the export
keyword to export something from a module. We don't use the export default
keyword to be consistant as it cannot always be used without using a namespace
. We don't use namespace
to improve the readability of the code.
Exporting can look something like this:
export const PI = 3.14;
export function add(lhs: number, rhs: number): number {
return lhs + rhs;
}
export class Foo {
constructor(public bar: number) {}
}
Names that aren't prefixed with export
are not exported from the module. They are however still available in the module and can be used in the module.
We can also export directly from an import (this is referred to as reexporting in this documentation):
export { add } from "./add";
export { PI } from "./constants";
export { Foo } from "./Foo";
In this case, the add
function, the PI
constant and the Foo
class are imported from the ./add
, ./constants
and ./Foo
modules and then exported from the current module. We don't have access to theses names in the current module.
All public names from modules should be reexported by their index.ts
files. If we need to import something from a module, we should find the most outer index.ts
file, that reexports the name we need, and also doesn't cause a circular import. We should then import the name from this index.ts
file.
Importing is done using the import
keyword. We can import a name from a module like this:
import { add } from "./add";
If we want to import multiple names from a module, we can do it like this:
import { add, PI } from "./add";
If we have a name conflict, we can rename the imported name like this:
import { PI, add as addNumbers } from "./add";
import { add as addStrings } from "./addString";
If we only need a type from a module, we can import it using the import type
syntax. When possible, this is prefered over a non-type import, because it doesn't import the name at runtime. This results in better performance and allow for circular imports.
import type { Foo } from "./Foo";
This is useful, for example, when we want to use a type in a type annotation or JSDoc comment.
import type { Foo } from "./Foo";
/**
* This class is a wrapper around the @link{Foo} class.
*/
class Bar {
constructor(public foo: Foo) {}
}
All import type
imports are removed from the compiled code.
To be consistent our import
and export
statements should be sorted in this way:
-
import type
statements -
import
statements -
export from
statements - The rest of the code (including
export
statements)
All of these sections should be seperated by a newline.
Example:
import type { Foo } from "./Foo";
import type { Bar } from "./Bar";
import { add } from "./add";
import { PI } from "./constants";
export { sub } from "./sub";
export function mult(lhs: number, rhs: number): number {
return lhs * rhs;
}