关于typescript:掌握这些typescript性能提升一大截

42次阅读

共计 11255 个字符,预计需要花费 29 分钟才能阅读完成。

typescript 性能(译)

有些简略的 Typescript 配置,能够让你取得更快的编译和编辑体验,这些办法越早把握越好。上面列举了除了最佳实际以外,还有一些用于考察迟缓的编译 / 编辑体验的罕用技术,以及一些作为最初伎俩来帮忙 TypeScript 团队考察问题的罕用办法。

编写易编译代码

优先应用接口而不是穿插类型

很多时候,简略对象类型的类型别名与接口的作用十分类似

interface Foo {prop: string}

type Bar = {prop: string}; 

然而,只有你须要定义两个及以上的类型,你就能够选用接口来扩大这些类型,或者在类型别名中对它们相交,这时差别就变得显著了。

因为接口定义的是繁多立体对象类型,能够检测属性是否抵触,解决这些抵触是十分必要的。另一方面,穿插类型只是递归的合并属性,有些状况下会产生 never。接口则体现的一贯很好,而穿插类型定义的类型别名不能显示在其余的穿插类型上。接口之间的类型关系也会被缓存,而不是整个穿插类型。最初值得注意的区别是,如果是穿插类型,会在查看“无效”/“展平”类型之前查看所有属性。

因而,倡议在创立穿插类型时应用带有接口 / 扩大的扩大类型

- type Foo = Bar & Baz & {

-     someProp: string;

- }

+ interface Foo extends Bar, Baz {

+     someProp: string;

+ } 
应用类型正文

增加类型正文,尤其是返回类型,能够节俭编译器的大量工作。这是因为命名类型比匿名类型更简洁(编译器更喜爱),这缩小了大量的读写申明文件的工夫。尽管类型推导是十分不便的,没有必要到处这么做。然而,如果您晓得了代码的慢速局部,可能会很有用

- import {otherFunc} from "other";

+ import {otherFunc, otherType} from "other";

- export function func() {+ export function func(): otherType {return otherFunc();

  } 
优先应用根底类型而不是联结类型

联结类型十分好用 – 它能够让你表白一种类型的可能值范畴

interface WeekdaySchedule {

    day: "Monday" | "Tuesday" | "Wednesday" | "Thursday" | "Friday";

    wake: Time;

    startWork: Time;

    endWork: Time;

    sleep: Time;

}

interface WeekendSchedule {

    day: "Saturday" | "Sunday";

    wake: Time;

    familyMeal: Time;

    sleep: Time;

}

declare function printSchedule(schedule: WeekdaySchedule | WeekendSchedule); 

然而他们也带来了肯定开销。每次将参数传递给 printSchedule 时,须要比拟联结类型里的每个元素。对于一个由两个元素组成的联结类型来说,这是微不足道的。然而,如果你的联结类型有很多元素,这将引起编译速度的问题。例如,从联结类型中淘汰多余的局部,元素须要成对的去比拟,工作量是呈二次递增的。当大量联结类型穿插一起时产生这种查看,会在每个联结类型上相交导致大量的类型,须要缩小这种状况产生。防止这种状况的一种办法是应用子类型,而不是联结类型。

interface Schedule {

    day: "Monday" | "Tuesday" | "Wednesday" | "Thursday" | "Friday" | "Saturday" | "Sunday";

    wake: Time;

    sleep: Time;

}

interface WeekdaySchedule extends Schedule {

    day: "Monday" | "Tuesday" | "Wednesday" | "Thursday" | "Friday";

    startWork: Time;

    endWork: Time;

}

interface WeekendSchedule extends Schedule {

    day: "Saturday" | "Sunday";

    familyMeal: Time;

}

declare function printSchedule(schedule: Schedule); 

一个更事实的例子是,定义每种内置 DOM 元素的类型时。这种状况下,更优雅的形式是创立一个蕴含所有元素的 HtmlElement 根底类型,其中包含 DivElement、ImgElement 等。应用继承而不是创立一个无穷多的联结类型 DivElement | // | ImgElement | //。

应用我的项目援用

应用 TypeScript 构建内容较多的代码时,将代码库组织成几个独立的我的项目会很有用。每个我的项目都有本人的 tsconfig.json,可能它会对其余我的项目有依赖性。这有益于防止在一次编译中导入太多文件,也使某些代码库布局策略更容易地放在一起。

有一些十分根本的办法将一个代码库分解成多个我的项目。举个例子,一个程序代码,一部分用作客户端,一部分用作服务端,另一部分被其它两个共享

测试也能够合成到本人的我的项目中

一个常见的问题是 “ 一个我的项目应该有多大?”。这很像问 “ 一个函数应该有多大?” 或 “ 一个类应该有多大?”,在很大水平上,这归纳于教训。人们相熟的一种宰割 JS/TS 代码的办法是应用文件夹。作为一种启发式的办法,如果它们关联性足够大,能够放在同一个文件夹中,那么它们就属于同一个我的项目。除此之外,要避免出现极大或极小规模的我的项目。如果一个我的项目比其余所有我的项目加起来都要大,那就是一个正告信号。同样,最好防止有几十个单文件我的项目,因为也会减少开销。

你能够在这里浏览更多对于我的项目参考资料

配置 tsconfig.json 或 jsconfig.json

TypeScript 和 JavaScript 用户能够用 tsconfig.json 文件任意配置编译形式。JavaScript 用户也能够应用 jsconfig.json 文件配置本人的编辑体验。

指定文件

你应该始终确保你的配置文件没有蕴含太多文件

在 tsconfig.json 中,有两种形式能够指定我的项目中的文件

  • files 列表
  • include、exclude 列表

两者的次要区别是,files 冀望失去一个源文件的文件门路列表,而 include/exclude 应用通配符模式对文件进行匹配

尽管指定文件能够让 TypeScript 间接疾速地加载文件,但如果你的我的项目中有很多文件,而不只是几个顶层的入口,那就会很麻烦。此外,很容易遗记增加新文件到 tsconfig.json 中,这意味着你可能最终会失去奇怪的编辑器行为,这些新文件被谬误地剖析,这些都很辣手。

include/exclude 有助于防止指定这些文件,但代价是:必须通过 include 蕴含的目录来发现文件。当运行大量的文件夹时,这可能会减慢编译速度。此外,有时编译会蕴含很多不必要的.d.ts 文件和测试文件,这会减少编译工夫和内存开销。最初,尽管 exclude 有一些正当的默认值,但某些配置比方 mono-repos,意味着像 node_modules 这样的 “ 重 “ 文件夹依然能够最终被蕴含。

对于最佳做法,咱们倡议如下:

  • 在您的我的项目中只指定输出文件夹(即您想将其源代码蕴含在编译 / 剖析中的文件夹)
  • 不要把其余我的项目的源文件混在同一个文件夹里
  • 如果把测试和其余源文件放在同一个文件夹里,请给它们取一个不同的名字,这样就能够很容易地把它们排除在外
  • 防止在源目录中呈现大的构建工件和依赖文件夹,如 node_modules

留神:如果没有排除列表,默认状况下 node_modules 是被排除的;一旦增加了 node_modules,就必须明确地将 node_modules 增加到列表中。

上面是一个正当的 tsconfig.json,用来演示这个操作

{

    "compilerOptions": {// ...},

    "include": ["src"],

    "exclude": ["**/node_modules", "**/.*/"],

} 
管制蕴含的 @types

默认状况下,TypeScript 会主动蕴含每一个在 node_modules 文件夹中找到的 @types 包,不论你是否导入它。这是为了在应用 Node.js、Jasmine、Mocha、Chai 等工具 / 包时,使某些货色 “ 可能工作 ”,因为这些工具 / 包没有被导入 – 它们只是被加载到全局环境中

有时这种逻辑在编译和编辑场景下都会拖慢程序的构建工夫,甚至会造成多个全局包的申明抵触的问题,造成相似于如下问题

Duplicate identifier 'IteratorResult'.

Duplicate identifier 'it'.

Duplicate identifier 'define'.

Duplicate identifier 'require'. 

在不须要全局包的状况下,修复办法很简略,只有在 tsconfig.json/jsconfig.json 中为 “type “ 选项指定一个空字段即可。

// src/tsconfig.json

{

    "compilerOptions": {

        // ...

        // Don't automatically include anything.

        // Only include `@types` packages that we need to import.

        "types" : []},

    "files": ["foo.ts"]

} 

如果您依然须要一些全局包,请将它们增加到类型字段中

// tests/tsconfig.json

{

   "compilerOptions": {

       // ...

       // Only include `@types/node` and `@types/mocha`.

       "types" : ["node", "mocha"]

   },

   "files": ["foo.test.ts"]

} 
增量我的项目输入

–incremental 标记容许 TypeScript 将上次编译的状态保留到一个 .tsbuildinfo 文件中。这个文件用来计算上次运行后可能被从新查看 / 从新输入的最小文件集,就像 TypeScript 的 –watch 模式一样。

当对我的项目援用应用复合标记时,默认状况下会启用增量编译,但这样也能带来同样的速度晋升。

跳过 .d.ts 查看

默认状况下,TypeScript 会对一个我的项目中的所有.d.ts 文件进行全面查看,以发现问题或不统一的中央;然而,这查看通常是不必要的。大多数时候,.d.ts 文件都是已知如何工作的 – 类型之间互相扩大的形式曾经被验证过一次,重要的申明还是会被查看。

TypeScript 提供了一个选项,应用 skipDefaultLibCheck 标记来跳过.d.ts 文件的类型查看(例如 lib.d.ts)

另外,你也能够启用 skipLibCheck 标记来跳过编译中的所有 .d.ts 文件

这两个选项通常会暗藏.d.ts 文件中的谬误配置和抵触,所以只倡议在疾速构建场景中应用它们。

应用更快的差别查看

狗的列表是动物的列表吗?也就是说,List<Dog> 是否能够调配给 List<Animals>?寻找答案的间接办法是一一成员进行类型构造比拟。可怜的是,这可能带来低廉的性能开销。然而,如果咱们对 List<T> 有足够的理解,咱们能够将这个可调配性查看简化为确定 Dog,是否能够调配给 Animal(即不思考 List<T> 的每个成员)。特地是,当咱们须要晓得类型参数 T 的差异。编译器只有在启用 strictFunctionTypes 标记的状况下,能力充分利用这种潜在的减速劣势(否则,它就会应用较慢的,但更宽松的构造查看)。因而,咱们倡议应用 –strictFunctionTypes 来构建(默认在 –strict 下启用)

配置其余构建工具

TypeScript 编译常常与其余构建工具一起执行 – 特地是在编写可能波及捆绑程序的 Web 应用程序时。尽管咱们只能对一些构建工具提出倡议,但现实状况下,这些技术能够被遍及。

确保除了浏览本节外,你还浏览了对于你所抉择的构建工具的性能 – 例如:

  • ts-loader 的 Faster Builds 局部
  • awesome-typescript-loader 的性能问题局部
并行类型查看

类型查看通常须要从其余文件中获取信息,与转换 / 输入代码等其余步骤相比,类型查看可能绝对低廉。因为类型查看可能会破费更多的工夫,它可能会影响到外部的开发循环 – 换句话说,你可能会经验更长的编辑 / 编译 / 运行周期,这可能会令你头疼。

出于这个起因,一些构建工具能够在一个独自的过程中运行类型查看,而不会阻塞输入。尽管这意味着在 TypeScript 构建而产生错误报告之前曾经有有效的代码运行,通常会先在编辑器中看到谬误,而不会被长时间地阻止运行工作代码

一个理论的例子是 Webpack 的 fork-ts-checker-webpack-plugin 插件,或者 awesome-typescript-loader 有时也会这样做。

隔离文件输入

默认状况下,TypeScript 输入须要的语义信息可能不是本地文件。这是为了了解如何输入像 const enums 和 namespaces 这样的性能。然而须要查看其余文件来生成某个文件,这会使输入速度变慢。

对须要非本地信息的性能需要是比拟少见的 – 惯例枚举能够用来代替 const 枚举,模块能够用来代替命名空间。鉴于此,TypeScript 提供了 isolatedModules 标记,以便在由非本地信息驱动的性能上报错。启用 isolatedModules 意味着你的代码库对于应用 TypeScript APIs(如 transpileModule)或代替编译器(如 Babel)的工具是平安的。

举个例子,上面的代码在运行时无奈失常应用独立的文件转换,因为 const enum 值被冀望内联;侥幸的是,isolatedModules 会在晚期通知咱们这一点

// ./src/fileA.ts

export declare const enum E {

    A = 0,

    B = 1,

}

// ./src/fileB.ts

import {E} from "./fileA";

console.log(E.A);//          ~

// error: Cannot access ambient const enums when the '--isolatedModules' flag is provided. 

记住:isolatedModules 不会主动让代码生成速度更快 – 它只是通知你,你行将应用一个可能不被反对的性能。你要的是独立模块在不同的构建工具和 API 中的输入

能够通过应用以下工具来影响独立文件的输入

  • ts-loader 提供了一个 transpileOnly 标记,通过应用 transpileModule 来执行独立文件输入
  • awesome-typescript-loader 提供了一个 transpileOnly 标记,通过应用 transpileModule 来执行独立文件输入
  • TypeScript 能够间接应用 transpileModule API
  • awesome-typescript-loader 提供了 useBabel 标记
  • babel-loader 以独自的形式编译文件(但不提供类型查看)
  • gulp-typescript 启用 isolatedModules 时,能够实现独立文件输入
  • rollup-plugin-typescript 只执行独立文件编译
  • ts-jest 能够应用(isolatedModules 标记设为 true)isolatedModules 为 true
  • ts-node 能够检测 tsconfig.json 的 “ts-node “ 字段中的 “transpileOnly “ 选项,也有一个 –transpile-only 标记。

考察问题

有肯定的办法能够失去可能出问题的提醒

禁用编辑器插件

编辑器的体验受到插件的影响。尝试禁用插件(尤其是 JavaScript/TypeScript 相干的插件),看看是否能解决性能和响应速度方面的问题。

某些编辑器也有本人的性能故障排除指南,所以能够思考浏览一下。例如,Visual Studio Code 也有本人的性能问题介绍。

诊断扩大

你能够用 –extendedDiagnostics 来运行 TypeScript,以取得编译器破费工夫的打印日志。

Files:                         6

Lines:                     24906

Nodes:                    112200

Identifiers:               41097

Symbols:                   27972

Types:                      8298

Memory used:              77984K

Assignability cache size:  33123

Identity cache size:           2

Subtype cache size:            0

I/O Read time:             0.01s

Parse time:                0.44s

Program time:              0.45s

Bind time:                 0.21s

Check time:                1.07s

transformTime time:        0.01s

commentTime time:          0.00s

I/O Write time:            0.00s

printTime time:            0.01s

Emit time:                 0.01s

Total time:                1.75s 

请留神,总工夫不是后面所有工夫的总和,因为有一些重叠,有些工作是没有掂量工具的。

对于大多数用户来说,最相干的信息是:

Field Meaning
Files the number of files that the program is including (use --listFiles to see what they are).
I/O Read time time spent reading from the file system – this includes traversing include‘d folders.
Parse time time spent scanning and parsing the program
Program time combined time spent performing reading from the file system, scanning and parsing the program, and other calculation of the program graph. These steps are intermingled and combined here because files need to be resolved and loaded once they’re included via imports and exports.
Bind time Time spent building up various semantic information that is local to a single file.
Check time Time spent type-checking the program.
transformTime time Time spent rewriting TypeScript ASTs (trees that represent source files) into forms that work in older runtimes.
commentTime Time spent calculating comments in output files.
I/O Write time Time spent writing/updating files on disk.
printTime Time spent calculating the string representation of an output file and emitting it to disk.

思考到这些投入,你可能会想问一些问题:

  • 文件数 / 代码行数是否与您我的项目中的文件数大抵统一?如果不合乎,请尝试运行 –listFiles
  • 程序工夫或 I / O 读取工夫是否相当高?请确保你的 include/exclude 配置正确
  • 其余工夫看起来不对劲吗?你可能想提出一个问题。你能够做以下事件来帮忙诊断

    • 如果打印工夫较高,则应用 emitDeclarationOnly 运行
    • 浏览对于报告编译器性能问题的阐明
显示配置

当运行 tsc 时,并不能显著地看到编译的内容设置,特地是思考到 tsconfig.jsons 能够扩大其余配置文件。showConfig 能够解释 tsc 将为一个调用计算着什么。

tsc --showConfig

# or to select a specific config file...

tsc --showConfig -p tsconfig.json 
追踪分辨率

运行 traceResolution 能够有助于解释,一个文件为什么被蕴含在编译中。输入有点繁琐,所以你可能想把输入重定向到一个文件。

tsc --traceResolution > resolution.txt 

如果你发现了一个不应该存在的文件,你可能须要批改你的 tsconfig.json 中的 include/exclude 列表,或者,你可能须要调整其余设置,比方 type、typeRoots 或 paths

独立运行 tsc

很多时候,用户在应用第三方构建工具(如 Gulp、Rollup、Webpack 等)时都会遇到性能迟缓的问题。运行 tsc –extendedDiagnostics,能够发现 TypeScript 和工具之间的差别,用以阐明内部配置的谬误或效率低下。

一些须要留神的问题:

  • tsc 和集成了 TypeScript 的构建工具在构建工夫上有很大的区别吗?
  • 如果构建工具提供诊断,那么 TypeScript 的分辨率和构建工具的分辨率是否有区别?
  • 构建工具是否有本人的配置,可能的起因是什么?
  • 构建工具是否有可能是 TypeScript 集成的配置起因?(例如 ts-loader 的选项?)
降级依赖性

有时 TypeScript 的类型查看会受到计算密集的.d.ts 文件的影响。这很常见也很可能会产生。降级到一个较新的 TypeScript 版本 (能够更有效率) 或一个较新版本的 @types 包 (可能曾经复原了一个回归) 通常能够解决这个问题。

常见的问题

一旦你曾经排除了故障,你可能想摸索一些常见问题的修复办法。如果以下解决方案不起作用,可能值得提出问题。

include 和 exclude 配置不当

如上所述,include/exclude 选项能够在以下几个方面被滥用

Problem Cause Fix
node_modules was accidentally included from deeper folder exclude was not set “exclude”: [“/node_modules”, “/.*/”]
node_modules was accidentally included from deeper folder “exclude”: [“node_modules”] “exclude”: [“/node_modules”, “/.*/”]
Hidden dot files (e.g. .git) were accidentally included “exclude”: [“**/node_modules”] “exclude”: [“/node_modules”, “/.*/”]
Unexpected files are being included. include was not set “include”: [“src”]

提出问题

如果你的我的项目曾经进行了正确的优化配置,你可能须要提出一个问题。

最好的性能问题报告蕴含容易取得的和最小的问题复制品。换句话说,一个容易通过 git 克隆的代码库,只蕴含几个文件。它们不须要与构建工具的内部集成 – 它们能够通过调用 tsc 或调用 TypeScript API 的独立代码。不优先思考那些须要简单调用和设置的代码库。

咱们了解这一点却不容易实现 – 特地是,很难在代码库中隔离问题的源头,而且共享知识产权可能也是一个问题。在某些状况下,如果咱们认为问题影响较大,团队将违心发送一份窃密协定(NDA)。

无论是否能够复制,在提交问题时,依照这些办法,将有助于为您提供性能修复。

报告编译器性能问题

有时,你会在构建工夫以及编辑场景中发现性能问题。在这种状况下,最好关注于 TypeScript 编译器。

首先,应该应用 TypeScript 的 next 版本,以确保你不会碰到那些已解决的问题。

npm install --save-dev typescript@next

# or

yarn add typescript@next --dev 

一个编译器的问题可能包含

  • 装置的 TypeScript 版本(例如:npx tsc -v 或 yarn tsc -v)
  • TypeScript 运行的 Node 版本(例如:node -v)
  • 应用 extendedDiagnostics 运行的输入(tsc –extendedDiagnostics -p tsconfig.json)
  • 现实的状况是,一个我的项目可能展现所遇到的问题
  • 分析编译器的输入日志(isolate-.log 和.cpuprofile 文件)
分析编译器

通过应用 –trace-ic 标记与 –generateCpuProfile 标记,来让 TypeScript 运行 Node.js v10+,这对团队提供诊断后果来说是很重要的:

node --trace-ic ./node_modules/typescript/lib/tsc.js --generateCpuProfile profile.cpuprofile -p tsconfig.json 

这里的 ./node_modules/typescript/lib/tsc.js 能够用来替换你的 TypeScript 编译器的装置版本,而 tsconfig.json 能够是任何 TypeScript 配置文件。profile.cpuprofile 是你抉择的输入文件。

这将产生两个文件:

  • –trace-ic 将输入到 isolate--*.log 的文件中(例如 isolate-00000176DB2DF130-17676-v8.log)
  • –generateCpuProfile 将以您抉择的名称输入到一个文件中。在下面的例子中,它将是一个名为 profile.cpuprofile 的文件

正告:这些文件可能蕴含你的工作空间的信息,包含文件门路和源代码。这两个文件都能够作为纯文本浏览,您能够在将它们提交为 GitHub 问题之前批改它们。(例如,革除可能裸露外部专用信息的文件门路)。

然而,如果你对在 GitHub 上公开公布这些有任何顾虑,请通知咱们,能够私下分享细节。

报告编辑绩效问题

编辑性能常常受到很多货色的影响,TypeScript 团队惟一能管制的是 JavaScript/TypeScript 语言服务的性能,以及该语言服务和某些编辑器(即 Visual Studio、Visual Studio Code、Visual Studio for Mac 和 Sublime Text)之间的集成。确保所有第三方插件在编辑器中被敞开,以确定是否有 TypeScript 自身的问题。

编辑性能问题稍有波及,但同样的想法也实用于:可被克隆的最小重现代码库是现实的,尽管在某些状况下,团队将可能签订 NDA 来考察和隔离问题。

包含 tsc–extendedDiagnostics 的输入是很好的上下文,但取一个 TSServer 日志是最有用的。

收集 TSServer 日志
在 Visual Studio 代码中收集 TSServer 日志
  1. 关上你的命令调色板,而后抉择

    1. 进入 “ 首选项 “ 关上您的全局设置。关上用户设置
    2. 入偏好设置,关上本地我的项目。关上工作区设置
  2. 设置选项 “typecript.tsserver.log”:”verbose”
  3. 重启 VS Code,重现问题
  4. 在 VS Code 中,运行 TypeScript。关上 TS 服务器日志命令
  5. 这将关上 tsserver.log 文件

⚠正告:TSServer 日志可能会蕴含你的工作空间的信息,包含文件门路和源代码。如果你对在 GitHub 上公开公布有任何顾虑,请通知咱们,你能够私下分享细节。

正文完
 0