TypeScript 生产环境构建与运行全指南
在开发 TypeScript 应用时,将其在生产环境中顺利构建和运行是至关重要的。本文将详细介绍如何在浏览器和服务器环境中完成这一目标,涵盖了编译版本选择、库支持、源映射、模块化、错误监控等多个方面。
1. 谨慎使用 Polyfill
如果你计划在浏览器中运行应用程序,要注意避免因包含不必要的 polyfill 而使 JavaScript 包体积过大。因为目标平台可能已经支持了部分你正在填充的特性。建议使用 Polyfill.io 这样的服务,仅加载用户浏览器所需的 polyfill。
添加 polyfill 后,需要在
tsconfig.json
的
lib
字段中告知 TSC 你的环境支持这些特性。例如,如果你填充了所有 ES2015 特性以及 ES2016 的
Array.prototype.includes
,可以使用以下配置:
{
"compilerOptions": {
"lib": ["es2015", "es2016.array.includes"]
}
}
如果在浏览器中运行代码,还需要启用 DOM 类型声明:
{
"compilerOptions": {
"lib": ["es2015", "es2016.array.include", "dom"]
}
}
要获取支持的库的完整列表,可以运行
tsc --help
。
2. 启用源映射
源映射是将转译后的代码与生成它的源代码关联起来的一种方式。大多数开发工具(如 Chrome DevTools)、错误报告和日志框架以及构建工具都支持源映射。在构建过程中使用源映射可以更轻松地调试生成的 JavaScript 代码。
一般来说,在开发环境中使用源映射是个好主意,并且在浏览器和服务器环境的生产环境中也可以提供源映射。但需要注意的是,如果你依赖代码的模糊性来保证一定的安全性,那么在生产环境的浏览器中不要提供源映射。
3. 项目引用
随着应用程序的增长,TSC 进行类型检查和编译代码的时间会越来越长。为了解决这个问题,TSC 提供了项目引用功能,它可以显著加快编译时间,尤其是增量编译时间。对于包含数百个文件或更多文件的项目,项目引用是必不可少的。
使用项目引用的步骤如下:
1.
拆分项目
:将 TypeScript 项目拆分为多个子项目。每个子项目是一个包含
tsconfig.json
和 TypeScript 代码的文件夹。尽量将经常一起更新的代码放在同一个文件夹中。
2.
配置子项目的
tsconfig.json
:在每个子项目文件夹中创建一个
tsconfig.json
,至少包含以下内容:
{
"compilerOptions": {
"composite": true,
"declaration": true,
"declarationMap": true,
"rootDir": "."
},
"include": [
"./**/*.ts"
],
"references": [
{
"path": "../myReferencedProject",
"prepend": true
}
]
}
- `composite`:告诉 TSC 这个文件夹是一个更大的 TypeScript 项目的子项目。
- `declaration`:告诉 TSC 为该项目生成 `.d.ts` 声明文件。
- `declarationMap`:告诉 TSC 为生成的类型声明构建源映射。
- `references`:子项目依赖的其他子项目的数组。
- `prepend`:将引用的子项目生成的 JavaScript 和源映射与当前子项目生成的合并。
- `rootDir`:明确指定该子项目应相对于根项目进行编译。
-
创建根
tsconfig.json:引用那些尚未被其他子项目引用的子项目:
{
"files": [],
"references": [
{"path": "./myProject"},
{"path": "./mySecondProject"}
]
}
-
编译项目
:使用
--build标志告诉 TSC 考虑项目引用:
tsc --build # 或简写为 tsc -b
使用项目引用时需要注意以下几点:
- 克隆或重新获取项目后,使用
tsc -b
重新构建整个项目,以重新生成任何缺失或过时的
.d.ts
文件。或者,将生成的
.d.ts
文件提交到版本控制中。
- 不要在项目引用中使用
noEmitOnError: false
,TSC 会始终将该选项硬编码为
true
。
- 手动确保一个子项目不会被多个其他子项目重复前置。否则,该子项目会在编译输出中出现多次。
4. 使用
extends
减少
tsconfig.json
的样板代码
为了让所有子项目共享相同的编译器选项,可以在根目录创建一个 “基础” 的
tsconfig.json
,子项目的
tsconfig.json
可以继承它:
{
"compilerOptions": {
"composite": true,
"declaration": true,
"declarationMap": true,
"lib": ["es2015", "es2016.array.include"],
"rootDir": ".",
"sourceMap": true,
"strict": true,
"target": "es5"
}
}
然后,更新子项目的
tsconfig.json
以继承这个基础配置:
{
"extends": "../tsconfig.base",
"include": [
"./**/*.ts"
],
"references": [
{
"path": "../myReferencedProject",
"prepend": true
}
]
}
5. 错误监控
TypeScript 会在编译时警告你代码中的错误,但你还需要一种方法来了解用户在运行时遇到的异常,以便在编译时尝试预防这些异常(或者至少修复导致运行时错误的 bug)。可以使用 Sentry 或 Bugsnag 等错误监控工具来报告和整理运行时异常。
6. 在服务器上运行 TypeScript
要在 NodeJS 环境中运行 TypeScript 代码,只需将代码编译为 ES2015 JavaScript(如果针对的是旧版 NodeJS 版本,则编译为 ES5),并将
tsconfig.json
的
module
标志设置为
commonjs
:
{
"compilerOptions": {
"target": "es2015",
"module": "commonjs"
}
}
这样,ES2015 的
import
和
export
调用将分别编译为
require
和
module.exports
,代码可以在 NodeJS 上直接运行,无需进一步打包。
如果你使用了源映射,需要将源映射提供给 NodeJS 进程。可以从 NPM 安装
source-map-support
包,并按照其设置说明进行操作。大多数进程监控、日志记录和错误报告工具(如 PM2、Winston 和 Sentry)都内置了对源映射的支持。
7. 在浏览器中运行 TypeScript
在浏览器中编译和运行 TypeScript 比在服务器上要复杂一些。
首先,选择要编译的模块系统:
- 如果要发布一个供他人使用的库(例如在 NPM 上),建议使用
umd
格式以最大化与各种模块打包工具的兼容性。
- 如果只是自己使用代码而不发布到 NPM,编译格式取决于你使用的模块打包工具。例如,Webpack 和 Rollup 与 ES2015 模块配合使用效果最佳,而 Browserify 需要 CommonJS 模块。具体配置如下:
- 如果你使用 SystemJS 模块加载器,将
module
设置为
systemjs
。
- 如果你使用支持 ES2015 的模块打包工具(如 Webpack 或 Rollup),将
module
设置为
es2015
或更高版本。
- 如果你使用支持 ES2015 的模块打包工具且代码使用了动态导入,将
module
设置为
esnext
。
- 如果你正在构建一个供其他项目使用的库,并且在
tsc
之后不进行其他构建步骤,将
module
设置为
umd
以最大化与不同加载器的兼容性。
- 如果你使用 CommonJS 打包工具(如 Browserify),将
module
设置为
commonjs
。
- 如果你计划使用 RequireJS 或其他 AMD 模块加载器,将
module
设置为
amd
。
- 如果你希望顶级导出在
window
对象上全局可用,将
module
设置为
none
。但如果你的代码处于模块模式,TSC 会尝试将其编译为
commonjs
。
接下来,配置构建管道将所有 TypeScript 代码编译为一个 JavaScript 文件(通常称为 “包”)或一组 JavaScript 文件。虽然 TSC 可以使用
outFile
标志为小项目完成此任务,但该标志仅限于生成 SystemJS 和 AMD 包。因此,建议使用更强大的打包工具,如 Webpack、Browserify、Babel、Gulp 或 Grunt,并使用相应的 TypeScript 插件:
- Webpack:
ts-loader
- Browserify:
tsify
- Babel:
@babel/preset-typescript
- Gulp:
gulp-typescript
- Grunt:
grunt-ts
为了优化 JavaScript 包的加载速度,可以遵循以下建议:
- 保持代码模块化,避免代码中的隐式依赖。
- 使用动态导入来懒加载初始页面加载不需要的代码。
- 利用打包工具的自动代码分割功能。
- 制定测量页面加载时间的策略。
- 保持生产构建与开发构建尽可能相似。
最后,在将 TypeScript 代码部署到浏览器时,需要有一个策略来填充缺失的浏览器特性。可以是一组标准的 polyfill,也可以根据用户浏览器支持的特性动态加载。
8. 发布 TypeScript 代码到 NPM
将 TypeScript 代码编译为可供其他 TypeScript 和 JavaScript 项目使用的格式很简单。编译为 JavaScript 供外部使用时,需要注意以下最佳实践:
- 生成源映射,以便调试自己的代码。
- 编译为 ES5,以便他人可以轻松构建和运行你的代码。
- 注意编译的模块格式(如 UMD、CommonJS、ES2015 等)。
- 生成类型声明,以便其他 TypeScript 用户可以使用你的代码的类型。
具体步骤如下:
1. 编译 TypeScript 代码为 JavaScript 并生成相应的类型声明。配置
tsconfig.json
以最大化与流行的 JavaScript 环境和构建系统的兼容性:
{
"compilerOptions": {
"declaration": true,
"module": "umd",
"sourceMaps": true,
"target": "es5"
}
}
-
在
.npmignore中排除 TypeScript 源代码,避免包体积过大;在.gitignore中排除生成的工件,避免污染 Git 仓库:
# .npmignore
*.ts # 忽略 .ts 文件
!*.d.ts # 允许 .d.ts 文件
# .gitignore
*.d.ts # 忽略 .d.ts 文件
*.js # 忽略 .js 文件
如果你遵循推荐的项目布局,将源文件放在
src/
中,生成的文件放在
dist/
中,
.ignore
文件会更简单:
# .npmignore
src/ # 忽略源文件
# .gitignore
dist/ # 忽略生成的文件
-
在项目的
package.json中添加types字段,指示它包含类型声明,并添加一个脚本在发布前构建包:
{
"name": "my-awesome-typescript-project",
"version": "1.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"prepublishOnly": "tsc -d"
}
}
现在,当你使用
npm publish
将包发布到 NPM 时,NPM 会自动将你的 TypeScript 代码编译为可供 TypeScript 和 JavaScript 用户使用的格式。
9. 三斜杠指令
TypeScript 有一个鲜为人知、很少使用且大多过时的特性,即三斜杠指令。这些指令是特殊格式的 TypeScript 注释,用于向 TSC 提供指令。这里介绍两种常见的三斜杠指令:
types
指令
当从模块导入内容时,根据导入的内容,TypeScript 在将代码编译为 JavaScript 时并不总是需要生成
import
或
require
调用。如果导入的内容仅在类型位置使用,TypeScript 不会为该导入生成 JavaScript 代码,这称为导入省略。
但如果导入整个模块用于副作用,编译时会生成 JavaScript 代码。例如:
// global.ts
type MyGlobal = number
// app.ts
import './global'
编译后:
// app.js
import './global'
如果你发现自己编写了这样的导入语句,可以先确保该导入确实需要使用副作用,或者尝试重写代码以更明确地导入值或类型。如果这些方法都不适用,并且你想继续使用全模块导入但避免生成 JavaScript 导入或
require
调用,可以使用
types
三斜杠指令:
/// <reference types="./global" />
amd-module
指令
当将 TypeScript 代码编译为 AMD 模块格式时,TypeScript 默认会生成匿名 AMD 模块。可以使用
amd-module
三斜杠指令为生成的模块命名。例如:
/// <amd-module name="LogService" />
export let LogService = {
log() {
// ...
}
}
编译后:
/// <amd-module name='LogService' />
define('LogService', ['require', 'exports'], function(require, exports) {
exports.__esModule = true
exports.LogService = {
log() {
// ...
}
}
})
在编译为 AMD 模块时,使用
amd-module
指令可以使代码更容易打包和调试。如果可能的话,也可以切换到更现代的模块格式,如 ES2015 模块。
综上所述,通过合理使用上述技术和方法,可以在生产环境中高效地构建、运行和发布 TypeScript 应用程序,同时提高开发效率和代码质量。
TypeScript 生产环境构建与运行全指南
10. 总结
本文全面涵盖了在生产环境中构建和运行 TypeScript 应用程序所需的关键知识,无论是在浏览器还是服务器环境。下面通过表格总结关键要点:
| 要点 | 详细说明 |
| — | — |
| Polyfill 使用 | 避免不必要的 polyfill 增加包体积,使用 Polyfill.io 按需加载,在
tsconfig.json
的
lib
字段声明支持特性 |
| 源映射 | 关联转译代码与源代码,开发和生产环境可用,但注意安全问题 |
| 项目引用 | 拆分项目,配置子项目和根项目的
tsconfig.json
,使用
--build
编译,注意相关使用限制 |
|
extends
使用 | 创建基础
tsconfig.json
,子项目继承以减少样板代码 |
| 错误监控 | 使用 Sentry 或 Bugsnag 等工具监控运行时异常 |
| 服务器运行 | 编译为 ES2015 或 ES5,设置
module
为
commonjs
,使用
source-map-support
处理源映射 |
| 浏览器运行 | 选择合适的模块系统,使用强大的打包工具和对应插件,优化包加载速度,填充缺失浏览器特性 |
| 发布到 NPM | 编译为 ES5,生成源映射和类型声明,配置
.npmignore
、
.gitignore
和
package.json
|
| 三斜杠指令 |
types
指令避免全模块导入生成 JavaScript 代码,
amd-module
指令为 AMD 模块命名 |
以下是整个 TypeScript 项目构建和运行的流程图:
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px
A([开始]):::startend --> B{选择运行环境}:::decision
B -->|服务器| C(编译为 ES2015/ES5, module: commonjs):::process
B -->|浏览器| D(选择模块系统):::process
C --> E(使用 source-map-support 处理源映射):::process
D --> E1{是否发布库}:::decision
E1 -->|是| F(使用 umd 格式):::process
E1 -->|否| G(根据打包工具选择格式):::process
F --> H(使用打包工具编译):::process
G --> H
H --> I(优化包加载速度):::process
I --> J(填充缺失浏览器特性):::process
C --> K(错误监控):::process
J --> K
K --> L(项目模块化: 项目引用):::process
L --> M(配置 tsconfig.json: extends 减少样板):::process
M --> N(发布到 NPM: 编译、配置文件):::process
N --> O([结束]):::startend
E --> K
通过遵循这些步骤和建议,开发者可以确保 TypeScript 应用程序在生产环境中高效、稳定地运行,同时提高开发效率和代码的可维护性。在实际应用中,根据项目的具体需求和规模,灵活运用这些技术,不断优化项目构建和运行流程。例如,对于小型项目,可能不需要使用项目引用;而对于大型项目,项目引用和合理的模块化则是提高编译效率的关键。同时,持续关注 TypeScript 和相关工具的更新,及时采用新的特性和优化方法,将有助于提升项目的整体质量和性能。
希望本文能为开发者在 TypeScript 项目的生产环境部署中提供有价值的参考,帮助大家顺利完成项目的构建、运行和发布任务。
超级会员免费看

3188

被折叠的 条评论
为什么被折叠?



