TS Exploration - Small Insights on TS Modules
- typescript
JS officially supports a module system using import/export from ES2015. TS naturally shares this, with some distinct aspects noted below.
For information on modules in JS, refer to JS Exploration - require, import, and JS module system.
1. export
Like other objects in JS, types can be exported. This allows usage in other files.
// types.ts
export type Person = {
name: string;
age: number;
};
// index.ts
import { Person } from "./types";
Using *
allows importing the entire module, functioning as a copy of its contents. In the following example, types.ts
is imported and used under the name types
.
// index.ts
import * as types from "./types";
const person: types.Person = {
name: "witch",
age: 20,
};
This approach prevents collisions, allowing modules to have the same named interfaces or namespaces without merging.
1.1. type export
You can specify that the import/export target is a type using the type
keyword. This is known as type import/export.
// types.ts
type Person = {
name: string;
age: number;
};
export type { Person };
// index.ts
import type { Person } from "./types";
Typically, TS understands whether the values being imported/exported are types, making this declaration unnecessary. However, in some cases, it is beneficial, warranting its introduction, which will be discussed in a separate article.
1.2. export all
Using export *
allows all exports from a module to be used in another module.
export * as types from "./types";
This can be imported and used as follows.
import { types } from "./types";
2. Compatibility between cjs and esm
Initially, objects exported using CommonJS cannot be imported using ES2015 syntax. Nonetheless, TS offers several solutions for this.
2.1. CommonJS export
In CommonJS, the exports
object defines the values that can be exported within a file.
// commonJS
// Exporting multiple objects
exports.a = 1;
exports.b = 2;
exports.c = 3;
// Exporting a single object
const obj = {
a: 1,
b: 2,
c: 3
};
module.exports = obj;
The syntax similar to this in ES2015 is known as default export. However, they are not compatible, meaning you cannot export in cjs style and import in ES2015 style.
2.2. export = syntax
To address this, TS allows specifying a single object exported from a module using the export =
syntax. If a library's index.d.ts
contains export = ...
, that library adheres to the CommonJS module system, yet allows the module to be imported using ES2015 style with import
.
// types.ts
class Person {
name: string;
age: number;
}
export = Person;
To import such an exported module, a syntax combining import
and require
is used. Notably, since there is only one object exported from the ./types
file with export =
, it can be imported under any name.
import Person = require("./types");
The code utilizing this module will be compiled by the compiler into CommonJS, AMD, ES6 module syntax, etc. This module can be specified in the compile command using the --module
keyword.
tsc --module commonjs index.ts
2.3. esModuleInterop
However, using both import
and require
together feels awkward. Setting esModuleInterop
to true in tsconfig.json
allows natural usage of ES2015 module syntax when importing CommonJS modules.
{
"compilerOptions": {
"esModuleInterop": true
}
}
With this setting, code originally requiring both import
and require
can be simplified to:
import Person from "./types";
For principles regarding this, refer to Mixing ES Module and CommonJS Module Usage (esModuleInterop).
3. Script Files and Module Files
If there are no import
or export
keywords at the top level of the file, it is recognized as a script file, and the type definitions are accessible globally. Conversely, the presence of import
or export
defines it as a module file.
// Script file
interface Person {
name: string;
age: number;
}
In contrast, if export
is used in this way, it becomes a module file. It’s important to note that if export
is within a namespace or another non-top-level scope, it remains a script file.
// Module file
export interface Person {
name: string;
age: number;
}
Care must be taken when two types share the same name, one in a script file and the other in a module file. The content of the type may differ depending on whether it is used directly or imported.
For instance, if there is a type named Person
in a script file, it can be accessed without import. However, if a module file named person.ts
also exports a Person
type, the content may differ between direct use and import.
References
https://www.typescriptlang.org/ko/docs/handbook/modules.html
Jo Hyun-young - TypeScript Textbook