编译选项
compilerOptions
compilerOptions
(编译选项)是tsconfig.json
文件中的重要组成部分,通过配置compilerOptions
中的属性可以实现项目的定制化编译。当tsconfig.json
文件中不包含compilerOptions
对象时,编译器编译时会使用默认值。
本文将compilerOptions
中的属性划分为以下四类进行讲解:
Basic Options
incremental
incremental
增量编译,默认值为true
,作用是加快编译速度。
当该配置为 true
时,会保存 ts
最后一次编译的信息,信息保存在根目录下的 .tsbuildinfo
文件中。下一次编译时,编译器会根据 .tsbuildinfo
文件中的信息判断出编译的最小代价。
与incremental
相关的字段为tsBuildInfoFile
,通过该字段可以指定编译信息保存在自定义的文件。
{
"compilerOptions": {
"incremental": true, /* Enable incremental compilation */
"tsBuildInfoFile": "./buildcache/front-end" /* Specify file to store incremental compilation information */
}
}
target
target
编译后目标语言的版本,默认值为ES3
(不区分大小写)。除了设置为ES3
外,还可以设置为ES5
, ES2015
,......, ES2020
, or ESNEXT
。其中ESNEXT
总是指向最新的js
版本,随着版本更新的变化而变化。但是通常我们都将其设置为ES5
。
{
"compilerOptions": {
"target": "es5"
}
}
module
module
是指生成代码的模块标准,当target
的值为es6
时,module
的默认值为es6
,否则默认为commonjs
。其中es6
是js
模块化规范,commonjs
是node.js
的模块化规范。除了默认的这两个之外,它的值还可以是 'amd', 'umd'等。关于模块化的内容推荐学习 javaScript:模块机制。
下面我们通过一个ts
文件,列举了不同module
值编译后的结果:
ts
文件代码(ES6 规范):
const sum = (a: number, b: number) => a + b;
export default sum;
module: es6
const sum = (a, b) => a + b;
export default sum;
module: commonjs
'use strict';
Object.defineProperty(exports, '__esModule', {value: true});
const sum = (a, b) => a + b;
exports.default = sum;
module: umd
(function (factory) {
if (typeof module === 'object' && typeof module.exports === 'object') {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
} else if (typeof define === 'function' && define.amd) {
define(['require', 'exports'], factory);
}
})(function (require, exports) {
'use strict';
Object.defineProperty(exports, '__esModule', {value: true});
const sum = (a, b) => a + b;
exports.default = sum;
});
amd
不经常使用,但可以结合outFile
字段,将多个相互依赖的文件生成一个文件。
{
"compilerOptions": {
"module": "amd",
"outFile": "./app.js"
}
}
当执行tsc
命令时,会将本目录下的文件一起整合到./app.js
中。
lib
lib
是指TS
编译需要引用的库。当target
设置为较低版本JS
,却在代码中使用更高版本的API
时,代码编译会出错。
例如 index.ts 的内容为:
let myname: string | undefined = ['robbie', 'peter'].find(
item => item === 'robbie'
);
使用tsc index.js --target es5
编译时会编译失败。
error TS2339: Property 'find' does not exist on type 'string[]'.
此时如果使用tsc index.js --target es5 --lib es6
运行,代码将编译成功。
原因:虽然TS
是JS
的超集,但是仅仅是相对于JS
的语法来说。对于JS
各类型的API
需要引用类库(lib)来支持。所以当代码中使用了JS
较高版本的API
时,最好在lib
字段上添加相应版本。
建议配置:
{
"compilerOptions": {
"target": "es5",
"lib": ["dom","dom.iterable","esnext"],
}
}
outDir
outDir
表示编译后的输出目录。
"outDir": './dist'
当将outDir
设置为./dist
后,编译后的文件将输出到./dist
目录下。
rootDir
rootDir
通过设置输入文件的目录控制输出文件的目录结构,默认输入文件目录为当前目录。
当compilerOptions
的配置如下时:
{
"compilerOptions": {
"outDir": './dist',
"rootDir": './src'
}
}
编译后的目录结构:
|-- dist <========= 输出目录
|-- src
|-- ex1.js
|-- index.js
|-- src
|-- ex1.ts
|-- index.ts
由编译目录可以看到,index.ts
文件虽然不在src
下,但是依旧被编译了,但是执行会打印错误提示信息,建议将所有文件包含在rootDir
下。
error TS6059: File 'D:/Code/ts/index.ts' is not under 'rootDir' 'D:/Code/ts/src'. 'rootDir' is expected to contain all source files.
如果将index.ts
文件放入src
文件下再编译,文件目录如下:
|-- dist <========= 输出目录
|-- ex1.js
|-- index.js
|-- src
|-- ex1.ts
|-- index.ts
可以看出dist
文件下的目录发生了改变,文件中没有包含src
。
allowJs
allowJs
默认值为false
,当设置它的值为true
时,编译时允许编译 JS
文件。
allowJs: false
|-- dist
|-- ex1.js
|-- src
|-- ex1.ts
|-- ex2.js <= allowJs: false 时 js 文件不编译
allowJs: true
|-- dist
|-- ex1.js
|-- ex2.js <= allowJs: true 时 js 文件也编译了
|-- src
|-- ex1.ts
|-- ex2.js
将 js
工程升级为 ts
工程时,在将所有 js
结尾的文件改成 ts
结尾的同时要注意 ts
文件引用 js
文件的情形。此时引用的 js
文件也需要编译导出。
checkJs
checkJs
默认值为false
,当值为true
时允许在js
文件中进行类型检查和报错。但checkJs
需要配合allowJs
一起使用,当allowJs
为false
时,checkJs
即使为true
也不会起作用。
例如当没有配置outDir
且allowJs
的值为true
时,编译器会编译js
文件,编译后的 js
文件会覆盖源文件,此时如果checkJs
的值为true
时,编译时会报错。
{
"compilerOptions": {
// "outDir": './dist',
"allowJs": true,
"checkJs": true
}
}
jsx
当使用ts
开发时,通常都是以.ts
为后缀。但是当我们开发react
的时候,react
组件一般会以.tsx
作为文件的后缀,此时便需要对jsx
属性进行配置。
jsx
属性有三个值:
jsx 值 | 输入 | 输出 | 输出文件扩展名 |
---|---|---|---|
preserve | <div /> | <div /> | .jsx |
react | <div /> | React.createElement("div") | .js |
react-native | <div /> | <div /> | .js |
declaration
declaration
为true
时,编译后会为每个 ts
文件生成对应的声明文件(.d.ts
文件) 。
原目录:
|-- src
|-- index.ts
编译后目录:
|-- dist
|-- index.js
|-- index.d.ts <= 自动生成的类型声明文件
|-- src
|-- index.ts
声明文件默认会和编译后的js
文件保存在同一目录,但是我们可以在compilerOptions
中配置declarationDir
属性指定声明文件的输出目录。
当我们只想编译生成.d.ts
文件而不想编译生成js
文件时,可以在compilerOptions
中配置 emitDeclarationOnly
为true
。
在项目中使用工程引用时,必须在根 tsconfig.json
中配置declaration
。
sourceMap
sourceMap
为true
时,ts
编译时会生成对应的.js.map
文件。 .js.map
文件是一个信息文件,里面储存着位置信息,记录着编译文件前后的位置关系。当编译后的js
文件出错时,可以更方便的定位到ts
文件的错误位置。
原目录:
|-- src
|-- index.ts
编译后目录:
|-- dist
|-- index.js
|-- index.js.map <= 编译后生成对应的 *.js.map 文件
|-- src
|-- index.ts
declarationMap
与souceMap
类似,declarationMap
会为声明文件生成对应的.d.ts.map
文件。
removeComments
removeComments
的值为true
时,编译后的js
文件会删除源文件中的注释。
noEmit
noEmit
为 true
时,使用 tsc
编译后不会输出文件。
与之相关的属性有noEmitOnError
,noEmitOnError
为true
时,在编译发生错误时不会输出文件。
importHelpers
当ts
中使用类继承等操作时,编译后的js
文件需要用到一些公共方法(helper),每个文件中用到都重新定义一遍是很浪费且很占用空间的。所以当我们配置importHelpers
为true
时,编译后的文件将不再生成helper
函数。
例如当index.ts
文件内容如下时:
class A {}
class B extends A {}
export = B
编译后 helper 函数为:
var __extends =
(this && this.__extends) ||
(function () {
var extendStatics = function (d, b) {
extendStatics =
Object.setPrototypeOf ||
({__proto__: []} instanceof Array &&
function (d, b) {
d.__proto__ = b;
}) ||
function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
};
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() {
this.constructor = d;
}
d.prototype =
b === null
? Object.create(b)
: ((__.prototype = b.prototype), new __());
};
})();
配置importHelpers
为true
后,编译后的代码会引用tslib
包,tslib
包中包含__extends
方法。
var tslib_1 = require("tslib");
...
var B =(function (_super){
tslib_1.__extends(B,_super); <=== tslib_1中包含__extends方法
...
}(A))
downlevelIteration
当 target 为 es5 或 es3 时,downlevelIteration
为true
会降级实现遍历器。
例如index.ts
文件为:
let a: number[] = [1, 2, 3];
for (let item of a) {
console.log(item);
}
当不配置downlevelIteration
时,编译结果:
var a = [1, 2, 3];
for (var _i = 0, a_1 = a; _i < a_1.length; _i++) {
var item = a_1[_i];
console.log(item);
}
当downlevelIteration
为true
时,编译结果使用了helper
函数降级实现。
'use strict';
var __values =
(this && this.__values) ||
function (o) {
var s = typeof Symbol === 'function' && Symbol.iterator,
m = s && o[s],
i = 0;
if (m) return m.call(o);
if (o && typeof o.length === 'number')
return {
next: function () {
if (o && i >= o.length) o = void 0;
return {value: o && o[i++], done: !o};
},
};
throw new TypeError(
s ? 'Object is not iterable.' : 'Symbol.iterator is not defined.'
);
};
var e_1, _a;
var a = [1, 2, 3];
try {
for (
var a_1 = __values(a), a_1_1 = a_1.next();
!a_1_1.done;
a_1_1 = a_1.next()
) {
var item = a_1_1.value;
console.log(item);
}
} catch (e_1_1) {
e_1 = {error: e_1_1};
} finally {
try {
if (a_1_1 && !a_1_1.done && (_a = a_1.return)) _a.call(a_1);
} finally {
if (e_1) throw e_1.error;
}
}
isolatedModules
在typescript
中,默认情况下却将所有的 *.ts
文件作用域放到了一起。所以即使不同文件有同名变量也会报错。此时可以将isolatedModules
设为true
,这样就可以隔离各个文件的作用域。
|-- src
|-- index.ts
|-- exp1.ts
例如在src
目录下的index.ts
和exp1.ts
文件中都定义了一个变量test
,此时编译器会提示错误:Cannot redeclare block-scoped variable test
。
除了使用isolatedModules
字段外,还可以使用export { test }
或者export defalut test
,让ts
文件成为一个模块,这样也可以达到隔离作用域的效果。
Strict Type-Checking Options
与严格类型检查相关的配置项一共有以下八项:
// "strict": true, // 开启所有严格的类型检查
// "noImplicitAny": false, // 不允许隐式的 any 类型
// "strictNullChecks": false, // 不允许把 null、undefined 赋值给其他类型变量
// "strictFunctionTypes": false // 不允许函数参数双向协变
// "strictPropertyInitialization": false, // 类的实例属性必须初始化
// "strictBindCallApply": false, // 严格的 bind/call/apply 检查(参数类型相同)
// "noImplicitThis": false, // 不允许 this 有隐式的 any 类型,避免this指向全局
// "alwaysStrict": false, // 在代码中注入 "use strict";
strict
strict
的默认值为false
,当strict
配置为true
时,其它七项默认为开启状态,当配置为false
时,其它七项默认为关闭状态,也就是说其它七项是strict
的子集。
当strict
为false
时,可以根据自己的需求,通过设置其它几项自定义类型检查。
noImplicitAny
当noImplicitAny
的值为true
时,ts
文件中所有的函数参数都必须明确参数类型,否则会编译出错。
function echo(o) {
// <= 编译报错:Parameter 'o' implicitly has an 'any' type.
console.log(o);
}
strictNullChecks
当strictNullChecks
的值为true
时,ts
文件中不能将null
、undefined
赋值给其他类型变量。当赋值给其它类型时会编译出错。
strictFunctionTypes
当strictFunctionTypes
的值为true
时,ts
文件不允许函数参数双向协变。
strictBindCallApply
当strictBindCallApply
的值为true
时对 bind
、call
、apply
进行更加严格的类型检查,要求参数类型和个数必须保持一致。
例如当传递的参数少于函数参数,或参数类型不对时,编译时就会报错,如下例所示:
function foo(a: number, b: string): string {
return a + b;
}
let a = foo.call(undefined, 10); // <=== Expected 3 arguments, but got 2
let b = foo.call(undefined, 10, 2); // <=== Argument of type '2' is not assignable to parameter of type 'string'
strictPropertyInitialization
当strictPropertyInitialization
的值为true
时,类的实例属性必须初始化。即类中每个实例属性都有初始值,初始值可以在 constructor
中设置,也可以在声明时设置。
未赋值:
class C {
name: string; // <= Property 'name' has no initializer and is not definitely assigned in the constructor.
}
赋值:
class C {
name: string = 'dk'; // <=== 在声明变量时初始化值
age: number;
constructor(age: number) {
this.age = age; // <=== 在构造函数中赋值
}
}
noImplicitThis
当noImplicitThis
的值为true
时,不允许 this
有隐式的 any
类型,避免this
指向全局。
class Age {
age: number = 10;
getAge() {
return function () {
console.log(this.age);
};
}
}
let age = new Age().getAge();
age();
由上例可知this
并不指向Age
类,而是指向undefined
,此时如果noImplicitThis
的值为true
,将会编译出错。
alwaysStrict
alwaysStrict
为 true 时,编译后的代码会在文件开头加上"use strict"
。
Additional Checks
noUnusedLocals
noUnusedLocals
的值为true
时,ts
文件不允许出现只声明未使用的局部变量。
noUnusedParameters
noUnusedParameters
的值为true
时,函数中的参数必须在函数中被使用。
由下例可知,参数c
在函数参数中被声明,但是并未使用,编译出错
function sum(a: number, b: number, c: number) {
// <= 编译报错:'c' is declared but its value is never read.
return a + b;
}
noImplicitReturns
noImplicitReturns
的值为true
时,所有的分支必须有返回值。
function fn(a: number) {
if (a > 0) {
return false;
} else {
a = -a; // <= 编译报错:Not all code paths return a value.
}
}
由上例可知,else
分支没有返回值,此时只要加上return
语句便可编译通过。
noFallthroughCasesInSwitch
noFallthroughCasesInSwitch
的值为true
时,可以防止switch
语句贯穿(当没有break
的时候会一直向下执行)。当break
丢失时,会编译报错:Fallthrough case in switch。
Module Resolution Options
moduleResolution
moduleResolution
表示模块解析策略,它的默认值为node
,除了使用node
对模块进行解析外,还可以使用classic
(TypeScript pre-1.6)。
class
模块的解析规则:
node
模块的解析规则:
baseUrl
baseUrl
解析非相对导入模块的基地址,默认为当前目录。
paths
paths
是指相对于baseUrl
的路径映射,paths
依赖于baseUrl
而存在。
比如,当index.ts
想导入Jquery
的精简版本,而不是默认版本。可以设置path
为:
"baseUrl": "./",
"paths": {
"jquery": ["node_modules/jquery/dist/jquery.slim.min.js"]
}
目录结构:
|-- node_modules
|-- index.ts
rootDirs
rootDirs
功能是将多个目录放在一个虚拟目录下。
|-- dist
|--util.d.ts
|--util.js
|-- src
|-- index.ts
util
这个类库会一直在输出目录,不会再次被构建。而当src
文件下的index.js
引用util
时,需要使用以下方式:
import {util} from '../dist/util';
但当src
被构建进输出目录后,之前导入类库的路径就会出错。此时可以使用 rootDirs
将dist
和src
放在同一个虚拟目录,如下所示:
"rootDirs": ['src', 'dist']
此时util.*
和index.ts
可以看作在同一目录下,所以将引用改为:
import {util} from './util';
typeRoots
typeRoots
指的是声明文件的目录,默认node_modules/@types
。
当我们使用npm
安装了node
包后,发现引用fs
模块依旧出错,这是因为 typescript
不认识 nodejs
原生api
,需要安装对应的声明文件 @types/node
。与node
相似,当我们安装包后,引用依旧出错,可以看一下是否安装了它的声明文件。
types
types
为声明文件包,当我们将包名放到types
属性中时,只会从types
的属性值中进行查找。
例如配置为:
{
"compilerOptions": {
"types" : ["node", "lodash"]
}
}
编译时只会找 ./node_modules/@types/node
, ./node_modules/@types/lodash
两个包的声明文件,其它包的声明文件不会查找。
esModuleInterop
esModuleInterop
的默认值为false
,当esModuleInterop
的值为true
时,允许模块使用export = 模块名
导出,由import XX from "模块名"
导入。
例如在react
项目中,我们项目中会使用import React from 'react';
,因此我们需要将esModuleInterop
的值设为true
。否则,只能使用import * as React from 'react'
来引入react
allowUmdGlobalAccess
allowUmdGlobalAccess
的值为true
时,允许以全局变量的形式访问 UMD
模块