我在过去3个月里一直在使用Typescript来创建非常复杂的CRUD应用程序。 Typescript提供的编译时安全性为我的工作提供了显着的加速 - 在编译时捕获错误是一个天赐之物,而不是看到它们在运行时表现为异常和不当行为。
但是有一个问题。
我必须处理数百个表,所以我使用的是从DB模式开始的自定义代码生成器,并自动生成大量的Typescript文件。只要架构很小,这就完美了 - 但对于包含数百个表的非常大的模式,tsc的编译时间正在成为一个问题 - 我看到一组400个文件的编译时间为15分钟...(以及“CALL_AND_RETRY_2分配失败”的可怕编译错误 - 即内存不足问题......)
到目前为止,我一直在Makefile中使用tsc,使用“tsc --out ...”语法调用它,从我的所有.ts文件生成一个.js。因此,我认为我可以通过以增量方式进行构建来解决这个问题:自己编译每个.ts(即,在时间只将一个.ts文件传递给tsc)并最终连接所有生成的.js在一个单一的。这确实有效 - 只有在正常开发期间才需要重新编译已更改的文件(并且只有初始编译通过所有这些文件,因此需要花费很多时间)。
但事实证明,这也有一个问题:为了使每个.ts“独立可编译”,我必须在顶部添加所有相关的依赖项 - 也就是说,
这样的行/// <reference path=...
...在每个.ts文件的顶部。
事实证明,由于这些引用,生成的.js文件包含相同的部分,这些部分在其中许多部分重复...所以当我连接.js文件时,我得到了相同函数的多个定义,更糟糕的是,全局范围语句(var global = new ...)重复了!
因此,我需要一种方法以某种方式智能地“合并”生成的.js文件,以避免看到复制的函数定义......
有没有办法以聪明的方式进行合并,避免重复?或者可能还有其他一些加速编译的方法?
任何建议最受欢迎... tsc编译速度比普通编译器慢30-100倍 - 现在它确实是一个阻塞点。
更新,2天后
Basarat(见下面的答案)帮助我在我的项目中应用他的解决方案。事实证明,尽管他的解决方案与小型和平均大小的项目完美配合,但我得到了可怕的“致命错误:CALL_AND_RETRY_2分配失败 - 处理内存不足”错误 - 这与我使用“tsc时遇到的错误相同 - 出......“。
最后,我的基于Makefile的解决方案是唯一有效的方法 - 按照这样做:
%.js: %.ts
@UPTODATE=0 ; \
if [ -f "$<".md5 ] ; then \
md5sum -c "$<".md5 >/dev/null 2>&1 && { \
UPTODATE=1 ; \
} ; \
fi ; \
if [ $$UPTODATE -eq 0 ] ; then \
echo Compiling $< ; \
tsc --sourcemap --sourceRoot /RevExp/src/ --target ES5 $< || { \
rm $@ "$<".md5 ; \
exit 1 ; \
} ; \
md5sum "$<" > "$<".md5 ; \
fi
...它做两件事:它使用MD5校验和来确定何时实际执行编译,并以“独立”方式进行编译(即没有tsc的“--out”选项)。
在实际的目标规则中,我曾经合并了生成的.js文件......但是这让我没有工作.map文件(用于调试) - 所以我现在在index.html生成直接包含:
${WEBFOLDER}/index.html: $(patsubst %.ts,%.js,${CONTROLLERS_SOURCES}) ${WEBFOLDER}/index.html.template
cat ${WEBFOLDER}/index.html.template > $@ || exit 1
REV=$$(cat revision) ; \
for i in $(patsubst %.ts,%.js,${CONTROLLERS_SOURCES}) ; do \
BASE=$$(basename $$i) ; \
echo " <script type='text/javascript' src='js/$${BASE}?rev=$$REV'></script>" >> $@ ; \
done || exit 1
cat RevExp/templates/index.html.parallel.footer >> $@ || exit 1
cp $(patsubst %.ts,%.js,${CONTROLLERS_SOURCES}) ${WEBFOLDER}/js/ || exit 1
我将把这个问题留待未来的贡献......
答案 0 :(得分:4)
我有一个可以管理你的打字稿项目的咕噜插件:https://github.com/basarat/grunt-ts
我看到大约250个文件的编译时间为6秒。这是一个使用grunt-ts的视频教程:http://www.youtube.com/watch?v=0-6vT7xgE4Y&hd=1
答案 1 :(得分:2)
我的项目中已经达到了300多个* .ts文件,而且我遇到了0.9.1.1编译器(较新版本使用了TypeScript的不兼容版本,我不得不执行大量重构以使编译器满意)编译时间约为25秒。
我使用tsc app.ts --out app.js
。
我得到tsc app.ts
的类似时间,即不使用--out app.js
而是生成大量小js文件。
然而,如果我编译一个没有太多依赖关系的单个文件,我得到的时间约为5秒(我已经分别为每个* .ts文件执行tsc来测量这个,有几个异常值需要更多超过10秒,但大多数文件编译得很快,因为它们接近依赖关系层次的底部)。 所以我的第一个想法是创建一个系统:
tsc foo.ts
您可以通过比较ls -ltc --full-time $(find ts -type f -name '*.ts')
每秒的结果或更高级的inotify
来实现第一步。
第3步并不难,因为tsc保留了js文件中的///引用注释,因此您可以搜索它们并执行简单的O(n)拓扑排序。
但是,我认为我们可以通过使用tsc -d
选项来创建声明文件来改进第二步。在第二步中,tsc不仅会创建foo.js,还会调查和编译foo.ts的所有依赖项,这是浪费时间。
OTOH如果foo.ts只引用了* .d.ts文件(反过来它们没有依赖关系,或者至少数量非常有限),重新编译foo.ts的过程可能会更快。
为了使其工作,您必须找到并替换所有///引用,以便它们指向bar.d.ts,而不是bar.ts。
find -name '*.ts' |
xargs sed -i -r 's/(\/\/\/<reference path=".*([^d]|[^\.]d)).ts"\/>/\1.d.ts"\/>/g'
应该进行必要的改变。
您还需要首次生成所有* .d.ts文件。 它有点像鸡和蛋的问题,因为你需要这些文件来执行任何编译。 好消息是,如果按引用的拓扑顺序编译文件,这应该有效。
所以我们需要构建一个拓扑排序的文件列表。如果您有边缘列表,则有tsort
程序执行此任务。
我可以使用以下grep
grep --include=*.ts --exclude=*.d.ts --exclude-dir=.svn -o '///<reference path.*"/>' -R .
唯一的问题是输出包含逐字引用,例如:
./entities/school_classes.ts:///<reference path="../common/app_backbone.d.ts"/>
这意味着我们必须解析某些规范形式的相对路径。还有一个细节是,我们实际上依赖* .ts而不是* .d.ts来进行排序。 这个简单的parse.sh脚本负责:
#!/bin/bash
here=`pwd`
while read line
do
a=${line/:*/}
t=${line/\"\/>/}
b=${t/*\"/}
c=$(cd `dirname $a`;cd `dirname $b`;pwd);d=$(cd `dirname $a`;basename $b);
B="$c/$d"
B=${B/$here/.}
B=${B/.d.ts/.ts}
echo "$a $B"
done
将所有内容与tsort放在一起以正确的顺序生成文件列表:
grep --include=*.ts --exclude=*.d.ts --exclude-dir=.svn -o '///<reference path.*"/>' -R . |
./parse.sh |
tsort |
xargs -n1 tsc -d
现在,这可能会失败,因为如果项目从未以这种方式编译,那么它可能没有足够精确地定义依赖关系来避免问题。此外,如果您在整个应用程序(var myApp;
)中使用某些全局变量(如myApp.doSomething()
),则可能需要在* .d.ts文件中声明它并引用它。现在,您可能认为这会创建一个循环依赖(app需要模块x,而模块x需要app),但请记住我们现在只依赖于* .d.ts文件。所以现在没有循环(这有点类似于它在C或C ++中的工作方式,其中一个只依赖于头文件)。
修复所有缺失的引用并编译所有* .d.ts和* .ts文件。您可以开始观察更改并仅重新编译已更改的文件。
但要注意,如果您更改文件foo.ts
中的某些内容,您可能还会重新编译需要foo.ts的文件。不是因为有必要更新它们 - 实际上它们在重新编译期间根本不应该改变。相反,这需要验证 - foo.d.ts的所有用户都应检查foo的新接口是否与它们使用的方式兼容!
因此,您可能希望在foo.d.ts
更改时重新编译foo.d.ts
的所有用户。这可能会更加棘手和耗时,但很少发生(只有当你改变foo的形状时)。
另一种选择是在这种情况下简单地重建所有内容(按拓扑顺序)。
我正在实施这种方法,所以我会在完成(或失败)后更新我的答案。
<强>更新强>
所以,我设法使用tsort和gnu make来实现所有这些,通过依赖解析使我的生活变得更轻松。
问题是它实际上比原始tsc --out app.js app.ts
慢。
这背后的原因是0.9.1.1编译器执行单个编译会有很大的开销 - 即使对于像
class A{
}
time tsc test.ts
收益超过3秒。
现在,如果你必须重新编译一个文件,这很好。
但是一旦你意识到你必须重新编译依赖于它的所有文件(主要是执行类型检查),然后重新编译依赖它们的文件等,你需要执行5到10次这样的编译。
因此,即使每个编译步骤非常快(3秒<25秒),总体而言
经验更糟糕(约50秒!)。
本练习对我来说的主要好处是,我必须修复许多错误并缺少依赖项才能使其工作:)
答案 2 :(得分:0)
我也开始使用tsc --out
的Makefile,但我现在使用requirejs tsc --watch --module amd
。
tsc --watch
可能有更好的选择(它不是很快),但requirejs有一些好处,你可以使用import
代替// <reference.../>
(.d.ts除外)文件)和requirejs以后也可以捆绑和优化你的项目。