加入咱们一块儿学习,每天向上html
原文:github.com/microsoft/T…node
豆皮粉儿们,又又又见面了,今天这一期,由字节跳动数据平台的“StoneyAllen ”,给你们翻译一篇文章“typescript性能”。webpack
最近某公司员工在下班途中猝死,引起你们的关注。代码要认真写,可是也不要太过劳累哦,休息好才能工做好!git
下面就开始仔细阅读吧!github
翻译者:StoneyAllenweb
有些简单的Typescript配置,可让你得到更快的编译和编辑体验,这些方法越早掌握越好。下面列举了除了最佳实践之外,还有一些用于调查缓慢的编译/编辑体验的经常使用技术,以及一些做为最后手段来帮助TypeScript团队调查问题的经常使用方法。typescript
不少时候,简单对象类型的类型别名与接口的做用很是类似json
然而,只要你须要定义两个及以上的类型,你就能够选用接口来扩展这些类型,或者在类型别名中对它们相交,这时差别就变得明显了。gulp
因为接口定义的是单一平面对象类型,能够检测属性是否冲突,解决这些冲突是很是必要的。另外一方面,交叉类型只是递归的合并属性,有些状况下会产生never。接口则表现的一向很好,而交叉类型定义的类型别名不能显示在其余的交叉类型上。接口之间的类型关系也会被缓存,而不是整个交叉类型。最后值得注意的区别是,若是是交叉类型,会在检查“有效” /“展平”类型以前检查全部属性。缓存
所以,建议在建立交叉类型时使用带有接口/扩展的扩展类型。
添加类型注释,尤为是返回类型,能够节省编译器的大量工做。这是由于命名类型比匿名类型更简洁(编译器更喜欢),这减小了大量的读写声明文件的时间。虽然类型推导是很是方便的,没有必要处处这么作。可是,若是您知道了代码的慢速部分,可能会颇有用。
联合类型很是好用--它可让你表达一种类型的可能值范围。
可是他们也带来了必定开销。每次将参数传递给 printSchedule
时,须要比较联合类型里的每一个元素。对于一个由两个元素组成的联合类型来讲,这是微不足道的。可是,若是你的联合类型有不少元素,这将引发编译速度的问题。例如,从联合类型中淘汰多余的部分,元素须要成对的去比较,工做量是呈二次递增的。当大量联合类型交叉一块儿时发生这种检查,会在每一个联合类型上相交致使大量的类型,须要减小这种状况发生。避免这种状况的一种方法是使用子类型,而不是联合类型。
一个更现实的例子是,定义每种内置DOM元素的类型时。这种状况下,更优雅的方式是建立一个包含全部元素的 HtmlElement
基础类型,其中包括 DivElement
、 ImgElement
等。使用继承而不是建立一个无穷多的联合类型 DivElement|/*...*/|ImgElement|/*...*/
。
使用TypeScript构建内容较多的代码时,将代码库组织成几个独立的项目会颇有用。每一个项目都有本身的 tsconfig.json
,可能它会对其余项目有依赖性。这有益于避免在一次编译中导入太多文件,也使某些代码库布局策略更容易地放在一块儿。
有一些很是基本的方法将一个代码库分解成多个项目。举个例子,一个程序代码,一部分用做客户端,一部分用做服务端,另外一部分被其它两个共享
测试也能够分解到本身的项目中
一个常见的问题是 "一个项目应该有多大?"。这很像问 "一个函数应该有多大?"或 "一个类应该有多大?",在很大程度上,这归结于经验。人们熟悉的一种分割JS/TS代码的方法是使用文件夹。做为一种启发式的方法,若是它们关联性足够大,能够放在同一个文件夹中,那么它们就属于同一个项目。除此以外,要避免出现极大或极小规模的项目。若是一个项目比其余全部项目加起来都要大,那就是一个警告信号。一样,最好避免有几十个单文件项目,由于也会增长开销。
你能够在这里阅读更多关于项目参考资(www.typescriptlang.org/docs/handbo…
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
注意:若是没有排除列表,默认状况下nodemodules是被排除的;一旦添加了nodemodules,就必须明确地将node_modules添加到列表中。
下面是一个合理的 tsconfig.json
,用来演示这个操做
默认状况下, TypeScript
会自动包含每个在 node_modules
文件夹中找到的 @types
包,无论你是否导入它。这是为了在使用Node.js、Jasmine、Mocha、Chai等工具/包时,使某些东西 "可以工做",由于这些工具/包没有被导入--它们只是被加载到全局环境中
有时这种逻辑在编译和编辑场景下都会拖慢程序的构建时间,甚至会形成多个全局包的声明冲突的问题,形成相似于以下问题
在不须要全局包的状况下,修复方法很简单,只要在 tsconfig.json/jsconfig.json
中为 "type "选项指定一个空字段便可。
若是您仍然须要一些全局包,请将它们添加到类型字段中
--incremental
标志容许TypeScript将上次编译的状态保存到一个 .tsbuildinfo
文件中。这个文件用来计算上次运行后可能被从新检查/从新输出的最小文件集,就像TypeScript的 --watch
模式同样。
当对项目引用使用复合标志时,默认状况下会启用增量编译,但这样也能带来一样的速度提高。
默认状况下,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输出须要的语义信息可能不是本地文件。这是为了理解如何输出像 constenums
和 namespaces
这样的功能。可是须要检查其余文件来生成某个文件,这会使输出速度变慢。
对须要非本地信息的功能需求是比较少见的--常规枚举能够用来代替 const
枚举,模块能够用来代替命名空间。鉴于此,TypeScript提供了 isolatedModules
标志,以便在由非本地信息驱动的功能上报错。启用 isolatedModules
意味着你的代码库对于使用 TypeScriptAPIs
(如 transpileModule
)或替代编译器(如 Babel
)的工具是安全的。
举个例子,下面的代码在运行时没法正常使用独立的文件转换,由于 constenum
值被指望内联;幸运的是, isolatedModules
会在早期告诉咱们这一点
记住: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相关的插件),看看是否能解决性能和响应速度方面的问题。
某些编辑器也有本身的性能故障排除指南,因此能够考虑阅读一下。例如, VisualStudioCode
也有本身的性能问题介绍。
你能够用 --extendedDiagnostics
来运行TypeScript,以得到编译器花费时间的打印日志。
请注意,总时间不是前面全部时间的总和,由于有一些重叠,有些工做是没有衡量工具的。
对于大多数用户来讲,最相关的信息是:
考虑到这些投入,你可能会想问一些问题:
文件数/代码行数是否与您项目中的文件数大体一致?若是不符合,请尝试运行 --listFiles
程序时间或I/O读取时间是否至关高?请确保你的include/exclude配置正确
其余时间看起来不对劲吗?你可能想提出一个问题。你能够作如下事情来帮助诊断
若是打印时间较高,则使用 emitDeclarationOnly
运行
阅读关于报告编译器性能问题的说明
当运行 tsc 时,并不能明显地看到编译的内容设置,特别是考虑到 tsconfig.jsons
能够扩展其余配置文件。showConfig
能够解释 tsc 将为一个调用计算着什么。
运行 traceResolution
能够有助于解释,一个文件为何被包含在编译中。输出有点繁琐,因此你可能想把输出重定向到一个文件。
若是你发现了一个不该该存在的文件,你可能须要修改你的tsconfig.json中的include/exclude列表,或者,你可能须要调整其余设置,好比type、typeRoots或paths。
不少时候,用户在使用第三方构建工具(如Gulp、Rollup、Webpack等)时都会遇到性能缓慢的问题。运行tsc --extendedDiagnostics
,能够发现TypeScript和工具之间的差别,用以说明外部配置的错误或效率低下。
一些须要注意的问题:
tsc和集成了TypeScript的构建工具在构建时间上有很大的区别吗?
若是构建工具提供诊断,那么TypeScript的分辨率和构建工具的分辨率是否有区别?
构建工具是否有本身的配置,可能的缘由是什么?
构建工具是否有多是TypeScript集成的配置缘由?(例如ts-loader的选项?)
有时TypeScript的类型检查会受到计算密集的 .d.ts
文件的影响。这很罕见也极可能会发生。升级到一个较新的TypeScript版本(能够更有效率)或一个较新版本的@types包(可能已经恢复了一个回归)一般能够解决这个问题。
一旦你已经排除了故障,你可能想探索一些常见问题的修复方法。若是如下解决方案不起做用,可能值得提出问题。
如上所述,include/exclude选项能够在如下几个方面被滥用
若是你的项目已经进行了正确的优化配置,你可能须要提出一个问题。
最好的性能问题报告包含容易得到的和最小的问题复制品。换句话说,一个容易经过git克隆的代码库,只包含几个文件。它们不须要与构建工具的外部集成--它们能够经过调用tsc或调用TypeScript API的独立代码。不优先考虑那些须要复杂调用和设置的代码库。
咱们理解这一点却不容易实现--特别是,很难在代码库中隔离问题的源头,并且共享知识产权可能也是一个问题。在某些状况下,若是咱们认为问题影响较大,团队将愿意发送一份保密协议(NDA)。
不管是否能够复制,在提交问题时,按照这些方法,将有助于为您提供性能修复。
有时,你会在构建时间以及编辑场景中发现性能问题。在这种状况下,最好关注于TypeScript编译器。
首先,应该使用TypeScript的next版本,以确保你不会碰到那些已解决的问题。
一个编译器的问题可能包括
安装的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_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日志是最有用的。
打开你的命令菜单栏,而后选择
进入 "首选项 "打开您的全局设置。打开用户设置
入偏好设置,打开本地项目。打开工做区设置
设置选项 "typecript.tsserver.log":"verbose"
重启VS Code,重现问题
在VS Code中,运行TypeScript。打开TS服务器日志命令
这将打开tsserver.log文件
⚠警告:TSServer日志可能会包含你的工做空间的信息,包括文件路径和源代码。若是你对在GitHub上公开发布有任何顾虑,请告诉咱们,你能够私下分享细节。