前言
了解编译器是深入了解一门语言的敲门砖,因此,撰写本文以记录学习typescript 3.1的编译配置内容。
模块解析
模块
从ECMAScript 2015开始,JavaScript引入了模块的概念。TypeScript也沿用这个概念。
-
ts模块的一些基本常识- 模块内容有自己的作用域,变量和函数不会存在于全局,外部也不可见
- 模块通过
export导出,通过import导入,以此来建立两个文件之间的关系 - 模块是通过模块加载器来导入其他模块的,因此,这里会涉及到模块加载器的知识,比如commonjs、requirejs、es modules
- 包含顶级
import或export的文件都会被当成模块,相反,如果不带有,则被视为全局可见(模块也可引用全局变量)。
-
模块导入导出方式:
// 1. 导出声明
// 可以把interface替换为class、function、const、let等
export interface Man {
name: string;
speak(words: string): string;
}
// ->对应导入方式
import { Man } from 'xxx';
// 2. 导出语句
function Run(name: string): void {
console.log(`${name} is runing`)
}
export { Run }; // import { Run } from 'xxx';
export { Run as RunMethod }; // import { RunMethod } from 'xxx';
// 3. 再导出。从一个文件导入,换个名称再导出
export { Run } from 'xxx'; // import { Run } from 'xxx';
export { Run as RunMethod} from 'xxx'; // import { RunMethod } from 'xxx';
export * from 'xxx'; // import methods from 'xxx';
// 4. 默认导出
export default 123; // import count from 'xxx';
export default {}; // import obj from 'xxx';
export default class Man {}; // import Man from 'xxx';
export default interface Run {}; // import Run from 'xxx';
// 5. 导入
import { Person } from 'xxx'; // 解构
import { Person as Man } from 'xxx'; // 解构别名
import * as xxx from 'xxx'; // 全量导入
import 'xxx.css'; // 副作用模块
// 6. 兼容性导出
// commonjs和amd环境都有一个`exports`变量,包含一个模块所有导出内容。
// ts提供了兼容导出的方法,支持`commonjs`和`amd`的`exports`
export = class Man {} // 只能使用这种方式导入 import Man = require('xxx')
-
编译目标 可以设定的编译目标有很多,支持平台有
- Node.js(Commonjs)
- Require.js(AMD)
- UMD
- SystemJs
- ECMAScript 2015 native modules(ES6)
ts编译器会根据不同的平台来设定生成代码的模型。 -
高级导入
ts编译器会检测是否每个模块都会在生成的JavaScript中用到。因此你如果一个模块只做了类型设定,在生成代码的时候是不会有文件引入的。 -
使用第三方库 比如我们用到了一个js写的库,那怎么把
ts用起来呢?就可以使用module关键字,并在一个.d.ts文件内用顶级export导出它,这样在ts类型系统内就可以看到这个类型。// 一个模块文件man.js的类型声明文件man.d.ts // 声明一个模块 declare module "man" { export interface Human { name: string; speak(words: string): string; } // 模块内存在的类型: Man接口 export interface Male extends Human {} // 模块内存在的类型: Man接口 export interface Female extends Human {} } // 接下来就可以在你的文件内导入这个Man了 // import something from 'module'; import { Male, Female } from 'man';// 通配符模块 这个在声明图片、json、css什么的时候用处比较大 declare module "*.css"; // 这样就可以在ts代码中引入任意css文件了,编译器不会报错 declare module "*.json";
模块导入策略
第一步
在写了一个import {a} from "module";语句时编译器首先会根据预定的策略来定位导入模块的文件。
-
Classic:AMD | System | ES2015属于这个模式-
相对导入的文件是相对于导入它的文件进行解析的
import a from './a'; // 搜索策略 // path/to/this/file/a.ts // path/to/this/file/a.d.ts -
非相对路径的会从包含导入文件的目录开始依次向上目录遍历
import a from 'a'; // 搜索策略 // root/sub/a.ts // root/sub/a.d.ts // root/a.ts // root/a.d.ts // /a.ts // /a.d.ts
-
-
Node: 除了上述的模式,都属于Node模式。本模式是模仿Node.js的模块解析模式,根据require的路径是相对路径还是非相对路径,解析行为不一样。// 相对路径 // /root/src/moduleA文件中 const b = require('./moduleB') // 搜索策略 // 1. root/src/moduleB.js是否存在 // 2. root/src/moduleB是否是一个目录 如果是目录,是否包含package.json。如果包含package.json则导向main模块 // 3. root/src/moduleB是否是一个目录,如果是目录,是否包含一个index.js文件,如果包含index.js,则作为模块 // 非相对路径 const c = require('moduleC') // 搜索策略 // 1. /root/src/node_modules/moduleC 这里的小策略和上面的相对路径一样,都是moduleC.js -> moduleC/package.json -> moduleC/index.js // 2. /root/node_modules/moduleC 同上 // 3. /node_modules/moduleC // 上述是Node.js的搜索路径,对typescript来说,就是变换了一下检测顺序 // 1. *.ts -> *.tsx -> *.d.ts -> package.json -> module/*.ts -> module/*.tsx -> module/*.d.ts // 2. 从导入它的文件的路径开始,逐级向上遍历: /root/src/node_modules/moduleB.ts -> /root/node_modules/ -> /node_modules/
第二步
如果第一步的策略没法找到对应模块,并且模块名是非相对的(如'moduleA'),编译器会尝试去定位一个外部模块声明(上一章有讲到)。
第三步
上述都时报了,编译器就会报错error TS2307: Cannot find module 'moduleA'
解析策略
相对导入与非相对导入
-
相对导入:
/|./|../开头的- 相对的是导入它的文件,并且不能解析为一个外部模块声明。
-
非相对导入: 'vue' | 'react' | '@angular/core'等类型
-
可以相对于
baseUrl解析baseUrl是AMD模块常用做法,要求运行时模块都在一个文件夹里面(构建到一起)- 所有非相对模块导入都会被当做相对于
baseUrl的 - 命令行中如果输入了
baseUrl参数,就使用。如果给定的是相对路径,就相对于当前路径进行计算。 tsconfig.json配置的baseUrl。如果给定的是相对路径,就相对于tsconfig.json进行计算。
-
可以根据路径映射来解析
- 路径配置为
compilerOptions.paths - 比如
{ "compilerOptions": { "paths": { "jquery": ["node_modules/jquery/dist/jquery"] // 相对于baseUrl来解析此路径 }, "*": [ // 匹配所有路径 "*", // 不发生改变 还是使用baseUrl/moduleName查找 "xxx/*" // 有xxx前缀的改为base/xxx/moduleName查找 ] } } - 路径配置为
-
可以被解析为外部模块
-
路径配置
compilerOptions.paths在上一节已经讲了,本小节讲一下compilerOptions.rootDirs。
{
"compilerOptions": {
"rootDirs": [ // 虚拟目录
"src/views",
"generated/templates/views" // 这两个实际目录被虚拟目录统一了,各自下面的文件就可以用import a from './xxx'来访问彼此的文件,因为他们都是在同一个虚拟目录下
]
}
}
官方文档中讲到了rootDirs的几个应用场景
-
两个目录下的文件会被构建时拷贝到同一个目录
-
特定的条件引入,比如国际化
{ "compilerOptions": { "rootDirs": [ "src/zh", "src/de", "src/#{locale}" // 允许你import messages from './#{locale}/messages',就会被解析为对应的国际化路径,比如./zh/messages ] } }
编译选项
由于编译器大多数情况下都是在解决模块问题,因此上面花了很多时间来记录ts的模块加载机制,下面正式贴出编译选项。由于所有的编译选项可以在官网找到,这里只列常用的一些
| 选项 | 类型 | 默认值 | 描述 | 注释 |
|---|---|---|---|---|
| target | string | "es3" | 指定ECMAScript目标版本 "ES3"(默认), "ES5", "ES6"/ "ES2015", "ES2016", "ES2017"或 "ESNext"。 |
表示编译器用什么版本来生成文件,一般都是生成"es5",来保证各个环境上都好用。也有的可以生成更高版本比如es6,再用其他工具(babel)来解析的。 |
| module | string | target==="es6"? "es6": "commonjs" | 生成代码哪个模块解析器系统的代码:"None", "CommonJS", "AMD", "System", "UMD", "ES6"或 "ES2015"。 |
"AMD"和 "System"因为编译后文件都在同一个目录,因此只有这两个目标模块系统可以和outFile一起使用。(上文有讲到) |
| allowJs | boolean | false | 允许编译js文件 | 比如你ts项目里面有的模块确实用的js,那就可以用这个设置为true |
| checkJs | boolean | false | 在.js中报告错误,和allowJs一起使用 |
|
| jsx | string | "Preserve" | 在.tsx文件内支持jsx |
有两个模式"React"或"Preserve" |
| baseUrl | string | 解析非相对模块时的基准目录 | 上文有讲到,比如配置为"."则表示以"tsconfig.json"所在目录为基准 | |
| paths | Object | 模块里面有讲到,再举个例子,比如你配置为{"@/*": "src/*"}那么你导入'@/components/vue'相当于导入'src/components/vue'。起到一个路径映射的作用,告诉编译器,找不到的路径可以通过这个路径试试 | ||
| outDir | string | 重定向输出目录 | 如果不配置就是在你的ts文件所在目录生成js文件,设置了比如'./dist'(这里的'.'表示tsconfig所在目录)就会把所有生成文件定向到dist里面 | |
| rootDir | string | 内部计算出来 | 用来控制输出目录结构 | 用来定义rootDir里面的文件目录格式 |
| moduleResolution | string | module === "AMD" or "System" or "ES6" ? "Classic" : "Node" | 决定如何处理模块。或者是"Node"对于Node.js/io.js,或者是"Classic"(默认) |
模块路径解决方案,前面几个小节已经讲到了。 |
| types | string[] | 要包含的类型声明文件列表 | ||
| typeRoots | stringp[] | 要包含的类型声明文件路径列表 | 支持*通配符,比如你项目用types/作为所有类型就可以配置["./src/**/*/types"] |
其他配置
tsconfig的典型配置中,最主要的就是"compilerOptions",另外还有些配置也需要了解
{
"compilerOptions": {},
"include": [], // 表明哪些文件被包含在内,
"exclude": [], // 表明哪些文件不被包含
}