Skip to main content

编译选项

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。其中es6js模块化规范,commonjsnode.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运行,代码将编译成功。

note

原因:虽然TSJS的超集,但是仅仅是相对于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
note

js 工程升级为 ts 工程时,在将所有 js 结尾的文件改成 ts 结尾的同时要注意 ts 文件引用 js 文件的情形。此时引用的 js 文件也需要编译导出。

checkJs

checkJs默认值为false,当值为true时允许在js文件中进行类型检查和报错。但checkJs需要配合allowJs一起使用,当allowJsfalse时,checkJs即使为true也不会起作用。

例如当没有配置outDirallowJs的值为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

declarationtrue时,编译后会为每个 ts 文件生成对应的声明文件(.d.ts 文件) 。

原目录:

|-- src
|-- index.ts

编译后目录:

|-- dist
|-- index.js
|-- index.d.ts <= 自动生成的类型声明文件
|-- src
|-- index.ts

声明文件默认会和编译后的js文件保存在同一目录,但是我们可以在compilerOptions中配置declarationDir属性指定声明文件的输出目录。

当我们只想编译生成.d.ts文件而不想编译生成js文件时,可以在compilerOptions中配置 emitDeclarationOnlytrue

caution

在项目中使用工程引用时,必须在根 tsconfig.json 中配置declaration

sourceMap

sourceMaptrue时,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

noEmittrue时,使用 tsc 编译后不会输出文件。

与之相关的属性有noEmitOnErrornoEmitOnErrortrue时,在编译发生错误时不会输出文件。

importHelpers

ts中使用类继承等操作时,编译后的js文件需要用到一些公共方法(helper),每个文件中用到都重新定义一遍是很浪费且很占用空间的。所以当我们配置importHelperstrue时,编译后的文件将不再生成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 __());
};
})();

配置importHelperstrue后,编译后的代码会引用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 时,downlevelIterationtrue会降级实现遍历器。

例如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);
}

downlevelIterationtrue时,编译结果使用了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.tsexp1.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的子集。

strictfalse时,可以根据自己的需求,通过设置其它几项自定义类型检查。

noImplicitAny

noImplicitAny的值为true时,ts文件中所有的函数参数都必须明确参数类型,否则会编译出错。

function echo(o) {
// <= 编译报错:Parameter 'o' implicitly has an 'any' type.
console.log(o);
}

strictNullChecks

strictNullChecks的值为true时,ts文件中不能将nullundefined 赋值给其他类型变量。当赋值给其它类型时会编译出错。

strictFunctionTypes

strictFunctionTypes的值为true时,ts文件不允许函数参数双向协变。

strictBindCallApply

strictBindCallApply的值为true时对 bindcallapply 进行更加严格的类型检查,要求参数类型和个数必须保持一致。

例如当传递的参数少于函数参数,或参数类型不对时,编译时就会报错,如下例所示:

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模块的解析规则:

D3twOQ

node模块的解析规则:

D3twOQ

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被构建进输出目录后,之前导入类库的路径就会出错。此时可以使用 rootDirsdistsrc放在同一个虚拟目录,如下所示:

"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 模块

参考目录

  1. 配置 tsconfig.json:编译选项,by 梁宵
  2. tsconfig.json 详解一,by dkvirus
  3. TypeScript Deep Dive,by Basarat
  4. TypeScript official docs: Compiler Options