TypeScript 模块

118 阅读2分钟

简介

TypeScript 模块除了支持所有 ES 模块的语法,特别之处在于允许输出和输入类型。

import type 语句

import 在一条语句中,可以同时输入类型和正常接口。

import { type A, a } from './a';
import type { A } from './a';

import type 语句也可以输入默认类型。

import type DefaultType from 'moduleA';

import type 在一个名称空间下,输入所有类型的写法如下。

import type * as TypeNS from 'moduleA';

同样的,export 语句也有两种方法,表示输出的是类型。

type A = 'a';
type B = 'b';

// 方法一
export {type A, type B};

// 方法二
export type {A, B};

importsNotUsedAsValues 编译设置

(1)remove:这是默认值,自动删除输入类型的 import 语句。

(2)preserve:保留输入类型的 import 语句。

(3)error:保留输入类型的 import 语句(与preserve相同),但是必须写成import type的形式,否则报错。

import = 语句

TypeScript 使用import =语句输入 CommonJS 模块。

import fs = require('fs');
const code = fs.readFileSync('hello.ts', 'utf8');

使用import * as [接口名] from "模块文件"输入 CommonJS 模块。

import * as fs from 'fs';
// 等同于
import fs = require('fs');

export = 语句

TypeScript 使用export =语句,输出 CommonJS 模块的对象,等同于 CommonJS 的module.exports对象。

let obj = { foo: 123 };

export = obj;

export =语句输出的对象,只能使用import =语句加载。

import obj = require('./a');

console.log(obj.foo); // 123

模块定位

模块定位(module resolution)指的是确定 import 语句和 export 语句里面的模块文件位置。 模块定位有两种方法,一种称为 Classic 方法,另一种称为 Node 方法。可以使用编译参数moduleResolution,指定使用哪一种方法。

没有指定定位方法时,就看原始脚本采用什么模块格式。如果模块格式是 CommonJS(即编译时指定--module commonjs),那么模块定位采用 Node 方法,否则采用 Classic 方法(模块格式为 es2015、 esnext、amd, system, umd 等等)。

相对模块,非相对模块 # **

加载模块时,目标模块分为相对模块(relative import)和非相对模块两种(non-relative import)。

相对模块指的是路径以/./../开头的模块。下面 import 语句加载的模块,都是相对模块。

  • import Entry from "./components/Entry";
  • import { DefaultHeaders } from "../constants/http";
  • import "/mod";

非相对模块指的是不带有路径信息的模块。下面 import 语句加载的模块,都是非相对模块。

  • import * as $ from "jquery";
  • import { Component } from "@angular/core";

Classic 方法

Classic 方法以当前脚本的路径作为“基准路径”,计算相对模块的位置。比如,脚本a.ts里面有一行代码import { b } from "./b",那么 TypeScript 就会在a.ts所在的目录,查找b.tsb.d.ts

至于非相对模块,也是以当前脚本的路径作为起点,一层层查找上级目录。比如,脚本a.ts里面有一行代码import { b } from "b",那么就会查找b.tsb.d.ts

Node 方法

Node 方法就是模拟 Node.js 的模块加载方法。

相对模块依然是以当前脚本的路径作为“基准路径”。比如,脚本文件a.ts里面有一行代码let x = require("./b");,TypeScript 按照以下顺序查找。

  1. 当前目录是否包含b.tsb.tsxb.d.ts
  2. 当前目录是否有子目录b,该子目录是否存在文件package.json,该文件的types字段是否指定了入口文件,如果是的就加载该文件。
  3. 当前目录的子目录b是否包含index.tsindex.tsxindex.d.ts

非相对模块则是以当前脚本的路径作为起点,逐级向上层目录查找是否存在子目录node_modules。比如,脚本文件a.js有一行let x = require("b");,TypeScript 按照以下顺序进行查找。

  1. 当前目录的子目录node_modules是否包含b.tsb.tsxb.d.ts
  2. 当前目录的子目录node_modules,是否存在文件package.json,该文件的types字段是否指定了入口文件,如果是的就加载该文件。
  3. 当前目录的子目录node_modules里面,是否包含子目录@types,在该目录中查找文件b.d.ts
  4. 当前目录的子目录node_modules里面,是否包含子目录b,在该目录中查找index.tsindex.tsxindex.d.ts
  5. 进入上一层目录,重复上面4步,直到找到为止。

路径映射

TypeScript 允许开发者在tsconfig.json文件里面,手动指定脚本模块的路径。

(1)baseUrl

baseUrl字段可以手动指定脚本模块的基准目录。

{
  "compilerOptions": {
    "baseUrl": "."
  }
}

上面示例中,baseUrl是一个点,表示基准目录就是tsconfig.json所在的目录。

(2)paths

paths字段指定非相对路径的模块与实际脚本的映射。

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "jquery": ["node_modules/jquery/dist/jquery"]
    }
  }
}

上面示例中,加载模块jquery时,实际加载的脚本是node_modules/jquery/dist/jquery,它的位置要根据baseUrl字段计算得到。

注意,上例的jquery属性的值是一个数组,可以指定多个路径。如果第一个脚本路径不存在,那么就加载第二个路径,以此类推。

(3)rootDirs

rootDirs字段指定模块定位时必须查找的其他目录。

{
  "compilerOptions": {
    "rootDirs": ["src/zh", "src/de", "src/#{locale}"]
  }
}

上面示例中,rootDirs指定了模块定位时,需要查找的不同的国际化目录。

tsc 的--traceResolution参数

由于模块定位的过程很复杂,tsc 命令有一个--traceResolution参数,能够在编译时在命令行显示模块定位的每一步。

$ tsc --traceResolution

上面示例中,traceResolution会输出模块定位的判断过程。

tsc 的--noResolve参数

tsc 命令的--noResolve参数,表示模块定位时,只考虑在命令行传入的模块。

举例来说,app.ts包含如下两行代码。

import * as A from "moduleA";
import * as B from "moduleB";

使用下面的命令进行编译。

$ tsc app.ts moduleA.ts --noResolve

上面命令使用--noResolve参数,因此可以定位到moduleA.ts,因为它从命令行传入了;无法定位到moduleB,因为它没有传入,因此会报错。