我们一起来看下TypeScript编译过程涉及到的参数信息:
1)CLI 尝鲜
我们先看一下文件:/src/ch01/index.ts
/**
* 返回一个指定时长后resolve的Promise
* @param durationInMs 延迟时长,单位毫秒
*/
function delay(durationInMs: number) {
return new Promise(resolve => setTimeout(resolve, durationInMs))
}
/**
* 在指定毫秒后执行加法并返回结果
* @param num1 数字1
* @param num2 数字2
* @param delayInMs 延迟执行的时长,单位毫秒
*/
export async function addNumberAsync(num1: number, num2: number, delayInMs: number) {
await delay(delayInMs)
return num1 + num2
}
(async () => {
// 500ms后执行3+4,并打印结果
console.log(await addNumberAsync(3, 4, 500))
})()
这个文件中我们定义了两个函数:delay 和 addNumberAsync,再有个立即执行函数中对这两个函数进行了调用
第一个delay函数,返回的是一个Promise,它在指定时间之后就会resolve,就像是线程进行了指定时间的sleep,时间到之后就会触发回调;
第二个addNumberAsync函数则是接受3个数字,调用delay函数,在第三个参数指定时长后返回前两个参数之和; 这段代码使用到了一些高级的语法特性,并不是所有浏览器都能原生支持,我们将会用不同的方式进行编译以运行在不同场合
我们在 /src/ch01文件夹,运行 tsc 命令,运行后看到ch01文件夹里生成了一个新的文件index.js,这个文件比原来的复杂很多,多出了很多其他代码,其实这是基于ES3的语法规则,这是IE6能兼容的语法规则,我们一般是不需要去考虑IE6了,但这是tsc编译时的默认target值。也就是说默认情况下,编译出来的JavaScript基本上可以认定是可以运行在任何浏览器环境。
我们可以在compile时指定flag,告诉compiler需要支持的ES级别,比如ES2015:tsc --target es2015,再看一下生成出来的代码,发现很多用来兼容的代码都不见了,比如Promise已经不见了,因为es2015已经支持了Promise,但顶部的 __awaiter 定义说明还是需要兼容es2015所不支持async/await的补丁,我们再修改一下flag,运行 tsc --target es2017,发现生成的index.js中已经能找到async/await,生成前后的代码已经基本相同了,仔细看下发现差异是在两个函数的类型声明上,也就是要求参数必须是number类型,这些类型声明消失不见,这些类型信息并不是JavaScript的一部分。
下一步让我们尝试在node中运行生成的index.js,编译时并没指定是运行环境,运行 node ./src/index.js,然后我们得到了运行错误,说是 unexpected token export,大家都知道,node对模块的导出不能使用export,而要使用 module.exports,因为nodejs使用的是 commonjs module,导入需要使用require,导出使用module.exports。我们再修改一下flag,使用 tsc --target ES2017 --module commonjs,再看一下生成的index.js,发现现在导出使用的就是exports.addNumbers,我们再运行一下node ./src/index.js,运行顺利可以得到正确结果。
我们有很多不同的flag可以被使用,一个经常用到的flag是 --watch,这会让compiler一直监听源文件,在文件发生变化时自动进行增量编译,即时地输出文件。只是对变化的部分进行编译而不是整个项目都全部重新编译,所以编译速度很快。
2)初识tsconfig.json
tsc在指定多个参数时整个命令就会变得冗长,常规解决方式是增加一个配置文件,我们可以在项目根目录增加tsconfig.json,在配置compiler时候有两点需要考虑:
第一,是定义哪些文件是输入,我们可以指定一个文件列表,也就是 “files”: [];或者,以glob的格式指定哪些文件需要被包含进去,常见的glob像是 “include”: [“*.ts"],一个有效的glob是源码文件夹中的所有文件 “include": [“src”],我们要编译的是什么;
第二,怎么编译,也就是指定编译器的选项:compilerOptions 参数,为了得到我们刚看到的结果,我们需要添加几个配置,包括 module: commonjs, target: es2017,另外我们再设置一下输出路径,outDir: lib,这些编译参数的名称和值跟通过CLI命令传参是一致的;
修改tsconfig.json文件后,我们现在可以直接在ch01文件夹下运行tsc,就可以实现通过tsc命令行传参的效果,编译后的index.js文件输出到了lib文件夹。可以只是把src中的源代码提交到代码仓库,把lib文件夹进行打包发布,因为发布上线,提供的都是编译后的JavaScript文件。
一个问题是,源码中可以看到丰富的类型信息,可编译生成的JavaScript代码缺失这些类型信息,我们可能是在开发一个npm package,其他人可能基于编译后的代码去开发应用,类型信息对他们来说是非常必要的。这在tsconfig.json中提供了选项, declaration 为true,以及sourceMap为true,重新运行tsc,我们会看到lib文件夹中生成了两个新文件:
index.d.ts文件,文件名也是index,只是后缀变了,打开这个文件发现像是一个函数,只有声明但没有实现。这个函数的参数和返回值的类型都有定义,但没有任何关于函数实现的细节说明。这就是类型定义文件,用来作为同名JavaScript文件的附属类型说明信息。如果你使用的IDE对TypeScript能很好地支持,比如使用VS Code,IDE就会去读取与JavaScript文件相匹配的.d.ts文件。
/**
* 在指定毫秒后执行加法并返回结果
* @param num1 数字1
* @param num2 数字2
* @param delayInMs 延迟执行的时长,单位毫秒
*/
export declare function addNumberAsync(
num1: number, num2: number, delayInMs: number): Promise<number>;
另一个输出文件是sourcemap,这可以让我们在进行代码调试的时候,比如有个debugger,就会将调试中的断点映射到TypeScript的源代码上,调试过程你就会感觉你是在调试TypeScript代码,虽然事实上运行的并不是TypeScript代码——实际运行的是JavaScript代码。
此处给出另外一个配置文件的例子:
"compilerOptions": {
"module": "CommonJS", // 目标 module,如果运行在 nodeJs:CommonJs
"target": "ES2015", // 运行JavaScript的目标环境,视需要支持的环境兼容情况而定
"declaration": true, // 是否生成 .d.ts 文件
"sourceMap": true, // 是否生成 .js.map 文件
"jsx": "react", // TypeScript原生支持react,对于react开发人员非常友好
"strict": true, // 严格检查模式,相当于同时启用 --noImplicitAny, --noImplicitThis, --strictNullChecks 等多个选项
"noImplicitAny": true, // 是否允许未经声明的类型就当成 any 类型
"strictNullChecks": true, // 是否允许将 null 和 undefined 赋值给非 any 类型的变量
"allowJs": true // 是否允许JavaScript文件参与编译
}
上面列出了常用的一些编译选项,完整的选项可以参照:www.typescriptlang.org/docs/handbo…