8

在过去的 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

我将把这个问题留给未来的贡献......

4

3 回答 3

4

我有一个 grunt 插件可以管理你的 typescript 项目:https ://github.com/basarat/grunt-ts

我看到大约 250 个文件的编译时间为 6 秒。这是一个使用 grunt-ts 的视频教程:http ://www.youtube.com/watch?v=0-6vT7xgE4Y&hd=1

于 2013-10-30T11:16:41.413 回答
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 秒,但是大多数文件编译速度很快,因为它们靠近依赖关系层次结构的底部)。所以我的第一个想法是创建一个系统:

  1. 监视新的和修改的 *.ts 文件
  2. tsc foo.ts对修改后的文件执行 a
  3. 连接所有 *.js 文件,保留依赖关系的顺序

您可以通过比较ls -ltc --full-time $(find ts -type f -name '*.ts')每秒的结果或通过更高级的方法(如inotify. 第三步并不难,因为 tsc 将 ///reference 注释保留在 js 文件中,因此您可以搜索它们并执行简单的 O(n) 拓扑排序。

tsc -d但是,我认为我们可以通过使用选项来创建声明文件来改进第二步。在第二步中,tsc 不仅会创建 foo.js,还会调查和编译 foo.ts 的所有依赖项,这很浪费时间。OTOH,如果 foo.ts 只引用了 *.d.ts 文件(这些文件又没有依赖项,或者至少数量非常有限),重新编译 foo.ts 的过程可能会更快。

为了使其工作,您必须找到并替换所有 ///reference 以便它们指向 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 秒!)。

这个练习对我的主要好处是,我必须修复许多错误和缺少的依赖项才能使其工作:)

于 2015-07-02T10:12:43.590 回答
0

我也从 Makefile 开始tsc --out,但我现在使用requirejstsc --watch --module amd.

可能有更好的替代方案tsc --watch(它不是很快),但是 requirejs 具有您可以使用的好处import// <reference.../>除了 .d.ts 文件),并且 requirejs 还可以在以后捆绑和优化您的项目。

于 2013-10-30T11:09:27.963 回答