2013-10-30 19 views
8

Tôi đã sử dụng Typecript trong 3 tháng qua để tạo các ứng dụng CRUD rất phức tạp. Sự an toàn thời gian biên dịch được cung cấp bởi Typecript đã cung cấp các tăng tốc đáng kể trong công việc của tôi - việc ghi lại các lỗi trong thời gian biên dịch là một Godsend, so với việc nhìn thấy chúng như là ngoại lệ và hành vi sai trái trong thời gian chạy.Tốc độ biên dịch các loại - cố gắng giải quyết nhưng bị kẹt khi hợp nhất

Có một điểm bắt.

Tôi phải xử lý hàng trăm bảng, vì vậy tôi đang sử dụng trình tạo mã tùy chỉnh được xây dựng bắt đầu từ lược đồ DB và tự động tạo nhiều tệp Typecript. Chừng nào lược đồ còn nhỏ, nó hoạt động hoàn hảo - nhưng đối với một lược đồ rất lớn chứa hàng trăm bảng, thời gian biên dịch của tsc đang trở thành vấn đề - Tôi thấy thời gian biên dịch là 15 phút cho một tập hợp 400 tệp. (cũng như lỗi biên dịch đáng sợ của "CALL_AND_RETRY_2 Phân bổ không thành công" - nghĩa là, hết các vấn đề bộ nhớ ...)

Cho đến nay, tôi đã sử dụng tsc trong một Makefile, gọi nó bằng "tsc --out ... "cú pháp, tạo ra một .js duy nhất từ ​​tất cả các tệp .ts của tôi. Do đó, tôi nghĩ rằng tôi có thể giải quyết vấn đề này bằng cách thực hiện việc xây dựng theo kiểu tăng dần: biên dịch từng bản. (Nghĩa là, chỉ truyền một tệp .ts tới tsc tại thời điểm đó) và cuối cùng, nối tất cả các tệp được tạo .js trong một cái duy nhất. Điều này thực sự xuất hiện để làm việc - chỉ có các tập tin thay đổi cần phải được biên dịch lại trong quá trình phát triển bình thường (và chỉ biên dịch ban đầu đi qua tất cả chúng, và do đó mất rất nhiều thời gian).

Nhưng nó bật ra rằng đây cũng vậy, có một vấn đề: để làm cho mỗi .ts "độc-lập-thể", tôi đã thêm tất cả các phụ thuộc có liên quan trên đỉnh - có nghĩa là, những câu nói như

/// <reference path=... 

... trên đầu mỗi tệp .ts.

Và hóa ra là do các tham chiếu này, các tệp .js được tạo ra chứa các phần giống nhau, được lặp lại trên nhiều phần ... Vì vậy, khi tôi nối các tệp .js, tôi nhận được nhiều định nghĩa cho cùng một hàm , và tệ hơn, các câu lệnh phạm vi toàn cầu (var global = new ...) lặp đi lặp lại!

Vì vậy, tôi cần một cách để bằng cách nào đó một cách thông minh "hợp nhất" các tập tin được tạo .js, để tránh nhìn thấy định nghĩa chức năng nhân rộng ...

Có một số cách để làm điều đó kết hợp một cách thông minh, tránh lặp lại? Hoặc có thể một số cách khác để đẩy nhanh quá trình biên dịch?

Bất kỳ đề xuất nào được hoan nghênh ... Tốc độ biên dịch tsc chậm hơn 30-100x so với các trình biên dịch thông thường - nó thực sự là điểm chặn ngay bây giờ.

UPDATE, 2 ngày sau

Basarat (xem câu trả lời của ông dưới đây) đã giúp tôi áp dụng giải pháp của mình trong dự án của tôi. Nó chỉ ra rằng mặc dù giải pháp của mình hoạt động hoàn hảo với các dự án có quy mô nhỏ và trung bình, với tôi, tôi đã gặp phải lỗi "FATAL ERROR: CALL_AND_RETRY_2 Phân bổ không thành công - xử lý hết bộ nhớ" - đó là lỗi tương tự khi tôi sử dụng "tsc --ngoài ...".

Cuối cùng, giải pháp Makefile của tôi dựa trên là điều duy nhất mà làm việc - làm việc đó như thế này:

%.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 [email protected] "$<".md5 ;          \ 
        exit 1 ;            \ 
      } ;               \ 
      md5sum "$<" > "$<".md5 ;          \ 
    fi 

...có hai điều: nó sử dụng MD5 checksums để tìm ra khi nào thực sự thực hiện một trình biên dịch, và nó thực hiện việc biên dịch theo cách "độc lập" (tức là không có tùy chọn "--out" của tsc).

Trong quy tắc đích thực tế, tôi đã sử dụng để hợp nhất các tệp .js được tạo ... nhưng điều này đã để lại cho tôi mà không cần làm việc .map tệp (để gỡ lỗi) - vì vậy bây giờ tôi tạo trực tiếp bao gồm trong index.html:

${WEBFOLDER}/index.html:  $(patsubst %.ts,%.js,${CONTROLLERS_SOURCES}) ${WEBFOLDER}/index.html.template 
    cat ${WEBFOLDER}/index.html.template > [email protected] || 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>" >> [email protected] ;    \ 
    done || exit 1 
    cat RevExp/templates/index.html.parallel.footer >> [email protected] || exit 1 
    cp $(patsubst %.ts,%.js,${CONTROLLERS_SOURCES}) ${WEBFOLDER}/js/ || exit 1 

tôi sẽ rời khỏi câu hỏi mở cho những đóng góp trong tương lai ...

Trả lời

0

tôi cũng bắt đầu với một Makefile với tsc --out, nhưng bây giờ tôi đang sử dụng requirejs với tsc --watch --module amd. Có thể có một giải pháp thay thế tốt hơn cho tsc --watch (nó không phải là rất nhanh), nhưng requirejs có những lợi ích mà bạn có thể sử dụng import thay vì // <reference.../> (ngoại trừ các tệp .d.ts) và requirejs sau này có thể đóng gói và tối ưu hóa dự án của bạn.

4

Tôi có một plugin grunt có thể quản lý dự án nguyên cảo của bạn: https://github.com/basarat/grunt-ts

tôi nhìn thấy thời gian 6 giây biên dịch với giá khoảng 250 tập tin. Đây là video hướng dẫn sử dụng grunt-ts: http://www.youtube.com/watch?v=0-6vT7xgE4Y&hd=1

+2

Điều này có vẻ như là một lựa chọn tốt –

+1

Basarat giúp tôi áp dụng giải pháp của mình trong dự án của tôi - nhưng nó chỉ ra rằng mặc dù nó hoạt động hoàn hảo với các dự án có kích thước nhỏ/trung bình, tôi đã nhận được thông báo "FATAL ERROR: CALL_AND_RETRY_2 Allocation failed" - quá trình hết bộ nhớ "khi tôi thử nó với dự án của mình. Cuối cùng, giải pháp dựa trên Makefile của tôi là thứ duy nhất hoạt động - tôi sẽ sửa đổi câu hỏi với giải pháp của tôi, và để nó mở cho những đóng góp trong tương lai ... – ttsiodras

+0

@ttsiodras bạn có thể thử phiên bản mới nhất không? TS 1.0 + mặc định biên dịch nhanh mới đã được thêm – basarat

2

Tôi đã đạt đến hơn 300 tệp * .ts trong dự án của mình và tôi bị kẹt với trình biên dịch 0.9.1.1 (phiên bản mới hơn sử dụng phiên bản TypeScript và I không tương thích) d đã phải thực hiện một phép tái cấu trúc lớn để làm cho trình biên dịch hài lòng) với thời gian biên dịch ~ 25 giây. Tôi sử dụng tsc app.ts --out app.js.

Tôi nhận được thời gian tương tự cho tsc app.ts tức là khi không sử dụng --out app.js mà thay vào đó tạo nhiều tệp js nhỏ.

Tuy nhiên, nếu tôi biên dịch một tệp không có quá nhiều phụ thuộc, tôi nhận được thời gian ~ 5 giây (tôi đã thực hiện tsc cho mỗi tệp * .ts một cách riêng biệt để đo lường điều này, có một số ngoại lệ mất nhiều hơn 10 giây, nhưng hầu hết các tập tin biên dịch nhanh chóng khi chúng ở gần đáy của các phần phụ thuộc). Vì vậy, ý tưởng đầu tiên của tôi sẽ là để tạo ra một hệ thống đó:

  1. đồng hồ cho mới và sửa đổi * .ts file
  2. thực hiện một tsc foo.ts vào file sửa đổi
  3. concatenates tất cả * .js file giữ gìn trật tự phụ thuộc

Bạn có thể thực hiện bước đầu tiên bằng cách so sánh kết quả của ls -ltc --full-time $(find ts -type f -name '*.ts') mỗi giây, hoặc bằng một cái gì đó nâng cao hơn như inotify. Bước thứ ba không khó như tsc bảo tồn /// chú thích tham chiếu trong các tệp js, vì vậy bạn có thể tìm kiếm chúng và thực hiện một kiểu cấu trúc liên kết O (n) đơn giản.

Tuy nhiên, tôi nghĩ chúng tôi có thể cải thiện bước thứ hai bằng cách sử dụng tùy chọn tsc -d để tạo tệp khai báo. Trong bước thứ hai, tsc sẽ tạo ra không chỉ foo.js, mà còn sẽ điều tra và biên dịch tất cả các phụ thuộc của foo.ts, đó là một sự lãng phí thời gian. OTOH nếu foo.ts chỉ tham chiếu các tệp * .d.ts (do đó sẽ không có phụ thuộc hoặc ít nhất số lượng hạn chế), quá trình biên dịch lại các foo.ts có thể nhanh hơn.

Để làm việc này, bạn phải tìm và thay thế tất cả /// tham chiếu để chúng trỏ tới bar.d.ts, chứ không phải bar.ts.

find -name '*.ts' | 
xargs sed -i -r 's/(\/\/\/<reference path=".*([^d]|[^\.]d)).ts"\/>/\1.d.ts"\/>/g' 

cần thực hiện thay đổi cần thiết.

Bạn cũng cần tạo tất cả các tệp * .d.ts lần đầu tiên. Nó giống như một vấn đề về gà và trứng, vì bạn cần những tệp này để thực hiện bất kỳ trình biên dịch nào. Tin tốt là điều này sẽ hoạt động nếu bạn biên dịch tệp theo thứ tự tham chiếu tô pô.

Vì vậy, chúng tôi cần tạo danh sách tệp được sắp xếp topo. Có một chương trình tsort thực hiện tác vụ này, miễn là bạn có danh sách các cạnh. tôi có thể tìm thấy tất cả các phụ thuộc với grep sau

grep --include=*.ts --exclude=*.d.ts --exclude-dir=.svn -o '///<reference path.*"/>' -R . 

Vấn đề duy nhất là đầu ra chứa các tài liệu tham khảo nguyên văn, ví dụ:

./entities/school_classes.ts:///<reference path="../common/app_backbone.d.ts"/> 

có nghĩa là chúng ta phải giải quyết đường dẫn tương đối với một số hình thức kinh điển. Một chi tiết nữa là chúng tôi thực sự phụ thuộc vào * .ts không phải là * .d.ts cho mục đích sắp xếp. kịch bản parse.sh đơn giản này sẽ chăm sóc của rằng:

#!/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 

Đưa nó tất cả cùng với tsort tạo ra một danh sách các file theo đúng thứ tự:

grep --include=*.ts --exclude=*.d.ts --exclude-dir=.svn -o '///<reference path.*"/>' -R . | 
./parse.sh | 
tsort | 
xargs -n1 tsc -d 

Bây giờ, điều này có lẽ sẽ thất bại, đơn giản bởi vì nếu dự án không bao giờ được biên dịch theo cách này trước khi nó có thể không có các phụ thuộc được định nghĩa chính xác, đủ để tránh các vấn đề. Ngoài ra, nếu bạn sử dụng một số biến toàn cầu (như var myApp;) trong suốt ứng dụng của mình (myApp.doSomething()), bạn có thể cần phải khai báo nó trong tệp * .d.ts và tham chiếu nó. Bây giờ, bạn có thể nghĩ rằng điều này sẽ tạo ra một phụ thuộc vòng tròn (ứng dụng yêu cầu mô-đun x, trong khi mô-đun x yêu cầu ứng dụng), nhưng nhớ lại rằng bây giờ chúng ta chỉ phụ thuộc vào các tệp * .d.ts. Vì vậy, không có thông tư nào bây giờ (điều này có phần tương tự như cách nó hoạt động trong C hoặc C++, nơi mà nó chỉ phụ thuộc vào các tệp tiêu đề).

Khi bạn có tất cả các tham chiếu bị thiếu cố định và tất cả các tệp * .d.ts và * .ts được biên dịch. Bạn có thể bắt đầu xem các thay đổi và chỉ biên dịch lại các tệp đã thay đổi. Nhưng hãy cẩn thận, nếu bạn thay đổi một cái gì đó trong tập tin foo.ts bạn cũng có thể biên dịch lại các tệp yêu cầu foo.ts. Không phải vì đó là cần thiết để cập nhật chúng - thực sự họ không nên thay đổi ở tất cả trong quá trình biên dịch lại. Thay vào đó, điều này là cần thiết để xác thực - tất cả người dùng của foo.d.ts nên kiểm tra xem giao diện mới của foo có tương thích với giao diện của nó không! Vì vậy, bạn có thể muốn biên dịch lại tất cả người dùng của foo.d.ts bất cứ khi nào foo.d.ts thay đổi. Điều này có thể phức tạp hơn nhiều và tốn thời gian, nhưng hiếm khi xảy ra (chỉ khi bạn thay đổi hình dạng của foo). Một tùy chọn khác sẽ đơn giản là xây dựng lại mọi thứ (theo thứ tự cấu trúc liên kết) trong các trường hợp như vậy.

Tôi đang ở giữa triển khai phương pháp này, vì vậy tôi sẽ cập nhật câu trả lời của mình sau khi hoàn thành (hoặc không thành công).

CẬP NHẬT Vì vậy, tôi đã quản lý để thực hiện tất cả điều này, sử dụng tsort và làm gnu, để làm cho cuộc sống của tôi dễ dàng với độ phân giải phụ thuộc. Vấn đề là nó thực sự chậm hơn so với tsc --out app.js app.ts gốc. Lý do đằng sau này là có một chi phí lớn cho 0.9.1.1 biên dịch để thực hiện một bộ sưu tập duy nhất - ngay cả đối với một tập tin đơn giản như

class A{ 
} 

time tsc test.ts sản lượng trên 3 giây. Bây giờ, nếu bạn phải biên dịch lại một tệp, điều này là tốt. Nhưng một khi bạn nhận ra rằng bạn phải biên dịch lại tất cả các tệp phụ thuộc vào nó (chủ yếu để thực hiện kiểm tra kiểu) và sau đó các tệp phụ thuộc vào chúng vv, bạn cần thực hiện một cái gì đó như từ 5 đến 10 bản dịch như vậy. Vì vậy, mặc dù mỗi bước biên dịch rất nhanh (3 giây < < 25 giây) tổng thể trải nghiệm tồi tệ hơn (~ 50 giây!).

Lợi ích chính của bài tập này đối với tôi, là tôi đã phải sửa chữa nhiều lỗi và phụ thuộc thiếu để làm cho nó hoạt động :)

Các vấn đề liên quan