tsc性能优化Project References使用详解

目录
  • 什么是 Project References
  • 示例项目结构
  • 不使用 Project References 带来的问题
  • tsconfig.json 的 references 配置项
  • tsconfig.json 的 composite 配置项
  • 使用 Project References 改造示例项目
  • 全量构建
  • 增量构建
  • 对__test__测试代码的处理
  • 总结

什么是 Project References

在了解一个东西是什么的时候,直接看其官方定义是最直观的

TypeScript: Documentation中对于Project References的介绍如下:

Project references are a new feature in TypeScript 3.0 that allow you to structure your TypeScript programs into smaller pieces.

这是TypeScript 3.0新增的特性,这个特性有啥用呢?

将我们的项目分成多个小的片段,也就是允许我们将项目进行分包模块化

这样一来我们在执行tsc对项目进行构建的时候,无论是出于代码转译成js的目的还是说出于单纯的类型检查(将compilerOptions.noEmit置为true)的目的

都可以严格按照自己的需要对想要被tsc处理的那部分代码进行处理,而不是每次都对整个项目进行处理,这是我认为Project References的最大好处

就以一个前后端代码在同一个仓库里维护的项目为例,如果没有Project References,那么我执行tsc时,会对前后端模块都进行类型检查和转译

但实际上如果我只修改了前端部分的代码,理所应当让tsc只处理前端模块,后端模块的构建产物不需要进行重新构建,有了Project References,我们就能实现到这个需求,从而优化项目的构建或类型检查性能

相信大家对Project References有一个大概的认识了,接下来我们就开始实际动手体验一下Project References加深我们对它的理解吧!

示例项目结构

.
├── package.json
├── pnpm-lock.yaml
├── src
│   ├── __test__              // 单元测试
│   │   ├── client.test.ts    // 前端代码测试
│   │   ├── index.ts          // 简陋的单元测试 API
│   │   └── server.test.ts    // 后端代码测试
│   ├── client                // 前端模块
│   │   └── index.ts
│   ├── server                // 后端模块
│   │   └── index.ts
│   └── shared                // 共享模块 -- 包含通用的工具函数
│       └── index.ts
└── tsconfig.json             // TypeScript 配置

这是一个很常见的项目目录结构,有前端代码,有后端代码,也有通用工具函数代码以及前后端的单元测试代码

它们的依赖关系如下:

  • client 依赖 shared
  • server 依赖 shared
  • __test__ 依赖 client 和 server
  • shared 无依赖

不使用 Project References 带来的问题

现在整个项目只有一个tsconfig.json位于项目根目录下,其内容如下:

{
  "compilerOptions": {
    "target": "ES5",
    "module": "CommonJS",
    "strict": true,
    "outDir": "./dist"
  }
}

如果我们执行tsc,它会将各个模块的代码都打包到项目根目录的dist目录下

dist
├── __test__
│   ├── client.test.js
│   ├── index.js
│   └── server.test.js
├── client
│   └── index.js
├── server
│   └── index.js
└── shared
    └── index.js

这有一个很明显的问题,正如前面所说,当我们只修改一个模块,比如只修改了前端模块的代码,那么理应只需要再构建前端模块的产物即可,但是无论改动范围如何,都是会将整个项目都构建一次,这在项目规模变得越来越大的时候会带来极大的性能问题,构建时长会变得特别长

或许你会想着在每个模块里创建一个tsconfig.json,然后通过tsc -p指定每个模块的目录去单独对它们进行构建,没错,这是一种解决方案

但是这会带来下面两个问题:

  • 如果需要全量构建项目,你得需要运行三次tsc,对每个模块分别构建,而tsc的启动时间开销是比较大的,在这个小规模项目里甚至启动开销的时间比实际构建的时间更长,现在还只是运行三次tsc,如果项目模块很多,有几十上百个呢?那光是启动tsc几十上百次都已经会花一些时间了
  • tsc -w不能一次监控多个tsconfig.json,只能是对各个模块都启动一次tsc -w

Project References的出现,就是为了解决上述问题的

tsconfig.json 的 references 配置项

Project References就是tsconfig.json里的references配置项,其结构是一个包含若干对象的数组,对象的结构如下:

{
  "references": [{ "path": "path/to/referenced-project" }]
}

核心就是一个path属性,该属性指向被引用的项目模块路径,该路径下需要包含tsconfig.json,如果该模块不是用tsconfig.json命名的话,你也可以指定具体的文件名,比如:

{
  "references": [{ "path": "path/to/referenced-project/tsconfig.web.json" }]
}

当指定了references选项后,会发生如下改变:

  • 在主模块中导入被引用的模块时,会加载它的类型声明文件,也就是.d.ts后缀的文件
  • 使用tsc --buildtsc -b构建主模块时,会自动构建被引用的模块

这样一来能够带来三个好处:

  • 提升类型检查和构建的速度
  • 减少IDE的运行内存占用
  • 更容易对项目结构进行划分

tsconfig.json 的 composite 配置项

光是在主模块中指定references配置项还不够,还需要在被引用的项目对应的tsconfig.json中开启composite配置项

composite配置项又是干嘛的呢? -- 它可以帮助tsc快速确定如何寻找被引用项目的输出产物

当被引用的项目开启composite配置项后,会有如下改变和要求:

当未指定rootDir时,默认值不再是The longest common path of all non-declaration input files,而是包含了tsconfig.json的目录

Tips: 关于The longest common path of all non-declaration input files的意思可以到tsconfig.json 文章中关于 rootDir 的介绍中查阅

必须开启include或者files配置项将要参与构建的文件声明进来

必须开启declaration配置项(因为前面介绍references的时候说了,会加载被引入模块的类型声明文件,因此被引用模块自然得开启declaration配置项生成自己的类型声明文件供主模块加载)

使用 Project References 改造示例项目

根据目前我们对Project References的认识,现在可以开始改造一下我们的项目了,首先是根目录下的tsconfig.json配置,它起到一个类似于项目入口的作用,因此这里面只负责添加references声明项目中需要被构建的模块,以及通过exclude将不需要参与构建的模块排除(比如src/__test__中的测试代码)

/tsconfig.json

{
  "references": [
    { "path": "src/client" },
    { "path": "src/server" },
    { "path": "src/shared" }
  ],
  "exclude": ["**/__test__"]
}

然后是各个子模块的tsconfig.json配置,这里我们假设构建目标为es5的代码,所以对于clientserver以及shared来说是存在公共配置的,所以我们可以抽离出一个公共配置,然后在子模块中通过extends配置项公用一个配置

/tsconfig.base.json

{
  "compilerOptions": {
    "target": "ES5",
    "module": "CommonJS",
    "strict": true
  }
}

src/client/tsconfig.json

{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "outDir": "../../dist/client",
    "composite": true,
    "declaration": true
  },
  // 依赖哪个模块则引用哪个模块
  "references": [{ "path": "../shared" }]
}

src/server/tsconfig.json

{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "outDir": "../../dist/server",
    "composite": true,
    "declaration": true
  },
  // 依赖哪个模块则引用哪个模块
  "references": [{ "path": "../shared" }]
}

src/shared/tsconfig.json

{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "outDir": "../../dist/shared",
    "composite": true,
    "declaration": true
  }
}

全量构建

现在我们在项目根目录下运行tsc --build --verbose,就会根据references配置去寻找各个子模块,并对它们进行构建,可以理解为对项目的全量构建

--build 参数表示让tscbuild模式进行构建和类型检查,也就是会使用references配置项,如果不开启的话是不会使用references配置项的,这点可以从官方文档中得证:

--verbose 参数则是会将构建过程中的输出显示在控制台中,不开启该参数的话则不会显示输出(除非构建过程中报错)

运行后/dist目录结构如下

dist
├── client
│   ├── index.d.ts
│   ├── index.js
│   └── tsconfig.tsbuildinfo
├── server
│   ├── index.d.ts
│   ├── index.js
│   └── tsconfig.tsbuildinfo
└── shared
    ├── index.d.ts
    ├── index.js
    └── tsconfig.tsbuildinfo

可以看到,所有子模块都被构建进来了,各模块的产物中有一个tsconfig.tsbuildinfo文件,这个文件起到一个类似缓存的作用,对于后续进行增量构建有着重要作用

前面看到的官方文档中对tsc --build的作用的介绍中的第二点Detect if they are up-to-date主要就是依靠这个缓存文件去识别的

开启--verbose参数后,可以看到控制台输出如下:

[4:50:26 PM] Projects in this build:
    * src/shared/tsconfig.json
    * src/client/tsconfig.json
    * src/server/tsconfig.json
    * tsconfig.json
[4:50:26 PM] Project 'src/shared/tsconfig.json' is out of date because output file 'dist/shared/tsconfig.tsbuildinfo' does not exist
[4:50:26 PM] Building project '/home/plasticine/demo/ts-reference-demo/src/shared/tsconfig.json'...
[4:50:28 PM] Project 'src/client/tsconfig.json' is out of date because output file 'dist/client/tsconfig.tsbuildinfo' does not exist
[4:50:28 PM] Building project '/home/plasticine/demo/ts-reference-demo/src/client/tsconfig.json'...
[4:50:28 PM] Project 'src/server/tsconfig.json' is out of date because output file 'dist/server/tsconfig.tsbuildinfo' does not exist
[4:50:28 PM] Building project '/home/plasticine/demo/ts-reference-demo/src/server/tsconfig.json'...
[4:50:29 PM] Project 'tsconfig.json' is out of date because output 'src/shared/index.js' is older than input 'src/client'
[4:50:29 PM] Building project '/home/plasticine/demo/ts-reference-demo/tsconfig.json'...
[4:50:29 PM] Updating unchanged output timestamps of project '/home/plasticine/demo/ts-reference-demo/tsconfig.json'...

xxx out of date xxx意思就是这个模块没被构建过,因此会开始对其进行构建

由于我们是首次构建,所以三个模块都是没被构建过的,所以三个模块都被检测为out of date

当我们再次运行tsc --build --verbose时,输出如下:

4:54:35 PM - Projects in this build:
    * src/shared/tsconfig.json
    * src/client/tsconfig.json
    * src/server/tsconfig.json
    * tsconfig.json
4:54:35 PM - Project 'src/shared/tsconfig.json' is up to date because newest input 'src/shared/index.ts' is older than output 'dist/shared/tsconfig.tsbuildinfo'
4:54:35 PM - Project 'src/client/tsconfig.json' is up to date because newest input 'src/client/index.ts' is older than output 'dist/client/tsconfig.tsbuildinfo'
4:54:35 PM - Project 'src/server/tsconfig.json' is up to date because newest input 'src/server/index.ts' is older than output 'dist/server/tsconfig.tsbuildinfo'
4:54:35 PM - Project 'tsconfig.json' is up to date because newest input 'dist/server/index.d.ts' is older than output 'src/client/index.js'

可以看到,所有模块都被检测为up to date,从而避免了重复构建

增量构建

如果现在我们修改了client模块的代码,再运行tsc --build --verbose会怎样呢?估计你也能猜到了,只有client模块会被构建,而其他模块则会跳过

4:56:44 PM - Projects in this build:
    * src/shared/tsconfig.json
    * src/client/tsconfig.json
    * src/server/tsconfig.json
    * tsconfig.json
4:56:44 PM - Project 'src/shared/tsconfig.json' is up to date because newest input 'src/shared/index.ts' is older than output 'dist/shared/tsconfig.tsbuildinfo'
4:56:44 PM - Project 'src/client/tsconfig.json' is out of date because output 'dist/client/tsconfig.tsbuildinfo' is older than input 'src/client/index.ts'
4:56:44 PM - Building project '/home/plasticine/demo/ts-reference-demo/src/client/tsconfig.json'...
4:56:45 PM - Project 'src/server/tsconfig.json' is up to date because newest input 'src/server/index.ts' is older than output 'dist/server/tsconfig.tsbuildinfo'
4:56:45 PM - Project 'tsconfig.json' is out of date because output file 'src/client/index.js' does not exist
4:56:45 PM - Building project '/home/plasticine/demo/ts-reference-demo/tsconfig.json'...
4:56:45 PM - Updating unchanged output timestamps of project '/home/plasticine/demo/ts-reference-demo/tsconfig.json'...

相信现在你能体会到Project References的好处了吧,能够很大程度上优化我们的构建速度!

不过实际开发中,tsc更多的是用来进行类型检查,至于compile的工作,则更多地是交给如Babelswcesbuild等工具去完成,这也是官方文档中有提到过的

这也是为什么你在vite创建的项目中能够看到默认的build命令配置为tsc && vite build,正是将类型检查的工作交给tsc,而构建工作则交给vite底层依赖的rollup去完成

对__test__测试代码的处理

我们的改造貌似已经完成了,但其实还忽略了一个src/__test__,它也可以被视为一个模块,它作为主模块,依赖了clientserver,因此也可以给它加上tsconfig.json配置,并且对于测试代码,我们一般不希望将它们构建成js,只希望tsc负责类型检查的工作,因此我们需要进行如下配置:

src/__test__/tsconfig.json

{
  "compilerOptions": {
    "noEmit": true
  },
  "references": [{ "path": "../client" }, { "path": "../server" }]
}

noEmit的作用刚刚在官方文档中也看到了,不会把产物文件输出,如果我们只需要类型检查能力的话很适合开启该配置项

现在我们如果需要对__test__中的代码进行类型检查的话,只需要执行:

# 忽略 references 配置项
tsc --project src/__test__
# 启用 references 配置项
tsc --build src/__test__

如果是使用--project参数的话,tsconfig.json中可以忽略references配置项,因为即便配置了也不会被使用,这在依赖产物未构建出来时能起作用

而如果使用--build参数,并且clientserver未构建出来时,会先构建它们,再对测试代码进行类型检查,可以根据个人需求场景来决定使用--project还是--build

总结

本篇文章介绍了Project References是什么,并通过一个简单的示例项目,并结合TypeScript Documentation官方文档边实战边解释

总的来说,其使用起来就是:

  • 主模块(tsc --build作用的模块视为主模块)中通过references配置项声明依赖的模块
  • 被引用模块中开启compositedeclaration配置项以支持被引用
  • 通过tsc --build 主模块才可以启用references配置项,这在官方文档中被称为Build Mode,如果直接tsc 主模块的话,是不会启用references配置项的,也就导致依然会对项目中的所有ts文件进行编译(如果没配置includefiles配置项的话)

希望通过本篇文章,能够让你对Project References有一个全面了解,也希望能够将其用在你的项目中,提升类型检查或构建(使用 tsc 进行构建的话)的速度,更多关于tsc性能Project References的资料请关注我们其它相关文章!

(0)

相关推荐

  • vue3 typescript封装axios过程示例

    目录 1.目录层级 2.request层 2.1请求主体 2.2拦截器 2.3 封装请求方法 3.api层 3.1细分功能模块 3.2api层主体 4.service层 5.将api层请求挂载到全局中 6.后端接口 6.1 express搭建本地服务 6.2路由层封装 6.3读写操作 7.总结 1.目录层级 src目录下分为5个文件夹.这里做简单处理,其中axios请求主要体现在api层,request层,config层和sevices层 2.request层 这里是封装请求层,主要是使用axi

  • TypeScript前端上传文件到MinIO示例详解

    目录 什么是MinIO? 本地Docker部署测试服务器 上传的API TypeScript实现 1. XMLHttpRequest 2. Fetch API 3. Axios 从后端获取临时上传链接 上传文件 踩过的坑 1. presignedPutObject方式上传提交的方法必须得是PUT 2. 直接发送File即可 3. 使用Axios上传的时候,需要自己把Content-Type填写成为file.type 示例代码 什么是MinIO? MinIO 是一款高性能.分布式的对象存储系统.

  • TypeScript的类型指令单行注释详解

    目录 正文 @ts-ignore 和 @ts-expect-error @ts-check 和 @ts-nocheck 正文 单行注释应该在项目里用的很少吧, 我没见过在项目中使用过, 但是了解一下又不吃亏! 那么一起来看看吧!这里开启了TypeScript提示器. 这里谈谈我对它的理解,也可以看看林不渡的TypeScript小册 一般单行注释是以@ts-开头 @ts-ignore 和 @ts-expect-error @ts-ignore 和 @ts-expect-error 仅仅对紧随其后的

  • Project Reference优化TypeScript编译性能示例

    目录 引言 Project Reference 总结 引言 TypeScript 给 JavaScript 添加了一套类型系统,可以在编译期间检查出类型错误,这增加了代码的健壮性,但也多了一个编译的过程. ts 编译速度与项目规模有关,如果项目比较大,代码很多,那就需要编译很长一段时间. 有没有什么办法可以提升 tsc 编译的性能呢? 还真有,TypeScript 3.0 的时候实现了 Project Reference 的特性,就是用于优化编译和类型检查的性能的. 那 Project Refe

  • Typescript tipe freshness 更严格对象字面量检查

    目录 引言 严格检查存在缺点 具体规则而言 引言 Typescript 是结构化的类型系统,那么对于对象来说,如果 A 具有 B 的所有属性,并且属性对应的类型相匹配,那么 A 就能赋值给 B type A = { name:string; age:number; } type B = { name:string } declare let a:A declare let b: B b = a 但是其中有一个例外的情况,会强制 a.b 具有完全相同的结构,被称为 freshness,也被称为更严

  • tsc性能优化Project References使用详解

    目录 什么是 Project References 示例项目结构 不使用 Project References 带来的问题 tsconfig.json 的 references 配置项 tsconfig.json 的 composite 配置项 使用 Project References 改造示例项目 全量构建 增量构建 对__test__测试代码的处理 总结 什么是 Project References 在了解一个东西是什么的时候,直接看其官方定义是最直观的 TypeScript: Docum

  • Android性能优化大图治理示例详解

    目录 引言 1 自定义大图View 1.1 准备工作 1.2 图片宽高适配 1.3 BitmapRegionDecoder 2 大图View的手势事件处理 2.1 GestureDetector 2.2 双击放大效果处理 2.3 手指放大效果处理 引言 在实际的Android项目开发中,图片是必不可少的元素,几乎所有的界面都是由图片构成的:像列表页.查看大图页等,都是需要展示图片,而且这两者是有共同点的,列表展示的Item数量多,如果全部加载进来势必会造成OOM,因此列表页通常采用分页加载,加上

  • Android性能优化死锁监控知识点详解

    目录 前言 死锁检测 线程Block状态 获取当前线程所请求的锁 通过锁获取当前持有的线程 线程启动 nativePeer 与 native Thread tid 与java Thread tid dlsym与调用 系统限制 死锁检测所有代码 总结 前言 “死锁”,这个从接触程序开发的时候就会经常听到的词,它其实也可以被称为一种“艺术”,即互斥资源访问循环的艺术,在Android中,如果主线程产生死锁,那么通常会以ANR结束app的生命周期,如果是两个子线程的死锁,那么就会白白浪费cpu的调度资

  • React性能优化的实现方法详解

    目录 前言 遍历视图key使用 React.memo缓存组件 React.useCallback让函数保持相同的引用 避免使用内联对象 使用React.useMemo缓存计算结果或者组件 使用React.Fragment片段 组件懒加载 通过 CSS 加载和卸载组件 变与不变的地方做分离 总结 前言 想要写出高质量的代码,仅仅靠框架底层帮我们的优化还远远不够,在编写的过程中,需要我们自己去使用提高的 api,或者根据它底层的原理去做一些优化,以及规范. 相比于 Vue ,React 不会再框架源

  • java接口性能从20s优化到500ms示例详解

    目录 前言 1. 案发现场 2. 现状 3. 第一次优化 4. 第二次优化 5. 第三次优化 5.1 前端做分页 5.2 分批调用接口 前言 接口性能问题,对于从事后端开发的同学来说,是一个绕不开的话题.想要优化一个接口的性能,需要从多个方面着手. 其实,我之前也写过一篇接口性能优化相关的文章<java接口性能优化小技巧>,发表之后在全网广受好评,感兴趣的小伙们可以仔细看看. 本文将会接着接口性能优化这个话题,从实战的角度出发,聊聊我是如何优化一个慢查询接口的. 上周我优化了一下线上的批量评分

  • Vue.js3.2的vnode部分优化升级使用示例详解

    目录 背景 什么是 vnode 普通元素 vnode 组件 vnode vnode 的优势 如何创建 vnode 创建 vnode 过程的优化 总结 背景 上一篇文章,分析了 Vue.js 3.2 关于响应式部分的优化,此外,在这次优化升级中,还有一个运行时的优化: ~200% faster creation of plain element VNodes 即针对普通元素类型 vnode 的创建,提升了约 200% 的性能.这也是一个非常伟大的优化,是 Vue 的官方核心开发者 HcySunYa

  • Doris Join 优化原理文档详解

    目录 Doris Join 优化原理 Doris Shuffle 方式 四种 Shuffle 方式对比 Runtime Filter Join 优化 Runtime Filter 类型 Join Reorder Doris Join 调优方法 调优案例实战 案例一 案例二 案例三 Doris Join 调优建议 Doris Join 优化原理 Doris 支持两种物理算子,一类是 Hash Join,另一类是 Nest Loop Join. Hash Join:在右表上根据等值 Join 列建立

  • vue实现的多页面项目如何优化打包的步骤详解

    遇到的问题 在多页面框架打包的过程中会,随着业务的增加页面越来越多,使用的三方包也会越来越多,但并不是所有页面都会使用到三方插件,使用webpack打包会让所有的三方包打包到一起,会导致vendor.js(三方包打包后的文件)越来越大,即使没使用过三方插件的页面也会引入,页面加载会越来越慢. 如何优化 使用cdn引入,这种就每次新建一个页面的时候手动通过cdn的方式引入,但是并不是特别好的方式,还需要手动加入. 就是使用splitChunks分割三方包,将三方包单独打包出来,根据页面的依赖情况自

  • Java Stopwatch类,性能与时间计时器案例详解

    在研究性能的时候,完全可以使用Stopwatch计时器计算一项技术的效率.但是有时想知道某想技术的性能的时候,又常常想不起可以运用Stopwatch这个东西,太可悲了. 属性: Elapsed 获取当前实例测量得出的总运行时间. ElapsedMilliseconds  获取当前实例测量得出的总运行时间(以毫秒为单位). ElapsedTicks  获取当前实例测量得出的总运行时间(用计时器计时周期表示). IsRunning   获取一个指示 Stopwatch 计时器是否在运行的值. 方法

  • sql语句优化的一般步骤详解

    前言 本文主要给大家分享了关于sql语句优化的一般步骤,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 一.通过 show status 命令了解各种 sql 的执行频率 mysql 客户端连接成功后,通过 show [session|global] status 命令可以提供服务器状态信息,也可以在操作系统上使用 mysqladmin extend-status 命令获取这些消息. show status 命令中间可以加入选项 session(默认) 或 global: se

随机推荐