2013-10-30 7 views
8

Ho utilizzato Typescript negli ultimi 3 mesi per creare applicazioni CRUD molto complesse. La sicurezza in fase di compilazione offerta da Typescript ha fornito significativi aumenti di velocità nel mio lavoro: catturare errori in fase di compilazione è una manna dal cielo, rispetto a vederli manifestarsi come eccezioni e comportamenti scorretti in fase di esecuzione.Velocità di compilazione del dattiloscritto: tentativo di soluzione alternativa ma bloccato alla fusione

C'è un problema, però.

Ho a che fare con centinaia di tabelle, quindi sto utilizzando un generatore di codice personalizzato che parte dallo schema DB e genera automaticamente molti file Typescript. Finché lo schema è piccolo, questo funziona perfettamente - ma per uno schema molto grande contenente centinaia di tabelle, il tempo di compilazione di tsc sta diventando un problema - Sto vedendo tempi di compilazione di 15 minuti per un set di 400 file .. . (così come il terribile errore di compilazione di "CALL_AND_RETRY_2 allocazione fallita" - cioè, problemi di memoria ...)

Finora, ho usato tsc in un Makefile, invocandolo con il "tsc" --out ... "sintassi, che genera un singolo .js da tutti i miei file .ts. Ho quindi pensato che avrei potuto risolvere questo problema eseguendo la build in modo incrementale: compilando ogni .ts da solo (cioè passando un solo file .ts in tsc alla volta) e alla fine, concatenando tutti i generati .js in un singolo. Questo infatti sembrava funzionare - solo i file modificati devono essere ricompilati durante lo sviluppo normale (e solo la compilazione iniziale passa attraverso tutti loro, e quindi richiede molto tempo).

ma si è scoperto che anche questo, ha un problema: al fine di rendere ogni ts "standalone-compile-grado", ho dovuto aggiungere tutte le dipendenze rilevanti in cima - vale a dire, le linee come

/// <reference path=... 

... sopra ogni file .ts.

E a causa di questi riferimenti, i file .js generati contengono sezioni identiche, che vengono ripetute su molte di esse ... Quindi quando concatenando i file .js, ottengo più definizioni per le stesse funzioni e, peggio, ripetizioni di scope globali (var global = new ...) ripetute!

ho quindi bisogno di un modo per in qualche modo intelligente "fondere" i file generati .js, per evitare di vedere le definizioni di funzioni replicate ...

C'è qualche modo per farlo che si fondono in modo intelligente, evitando le ripetizioni? O forse un altro modo per accelerare la compilazione?

Qualsiasi suggerimento sia il benvenuto ... La velocità di compilazione di tsc è 30-100 volte più lenta dei normali compilatori - è davvero un punto di blocco ora.

UPDATE, 2 giorni dopo

Basarat (vedere la sua risposta qui sotto) mi ha aiutato a applicare la sua soluzione nel mio progetto. Si scopre che anche se la sua soluzione funziona perfettamente con progetti di piccole e medie dimensioni, con la mia ho avuto il temuto errore "FATAL ERROR: CALL_AND_RETRY_2 Allocation failed - process out of memory" - che è lo stesso errore che ottengo quando uso "tsc --out ... ".

Alla fine, la mia soluzione Makefile-based è l'unica cosa che ha funzionato - farlo in questo modo:

%.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 

...che fa due cose: usa i checksum MD5 per capire quando effettivamente eseguire una compilation, e fa la compilazione in modo "standalone" (cioè senza l'opzione "--out" di tsc).

Nella regola obiettivo reale, ho usato per unire i file generati Js ... ma questo mi ha lasciato senza lavorare file .map (per il debug) - così ora generato include diretto nel 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 

lascerò la questione aperta per i contributi futuri ...

risposta

0

ho anche iniziato con un Makefile con tsc --out, ma ora sto usando requirejs con tsc --watch --module amd.

v'è probabilmente una migliore alternativa a tsc --watch (non è molto veloce), ma requirejs ha i vantaggi che si possono utilizzare al posto di import// <reference.../> (ad eccezione dei file .d.ts), e requirejs può anche successivamente raggruppare e ottimizzare il tuo progetto.

4

Ho un plugin di grugnito in grado di gestire il vostro progetto dattiloscritto: https://github.com/basarat/grunt-ts

vedo i tempi di compilazione di 6 secondi per circa 250 file. Ecco un video tutorial con grunt-ts in uso: http://www.youtube.com/watch?v=0-6vT7xgE4Y&hd=1

+2

Questo mi sembra una buona opzione –

+1

Basarat mi ha aiutato a applicare la sua soluzione nel mio progetto - ma si scopre che anche se funziona perfettamente con progetti di piccole/medie dimensioni, ho ricevuto il solito "ERRORE FATALE: CALL_AND_RETRY_2 Assegnazione fallita - processo a memoria insufficiente" quando l'ho provato con il mio progetto. Alla fine, la mia soluzione basata su Makefile è l'unica cosa che ha funzionato: correggerò la domanda con la mia soluzione e la lascerò aperta per i contributi futuri ... – ttsiodras

+0

@ttsiodras puoi provare l'ultima versione? TS 1.0 + è stato aggiunto un nuovo predefinito di compilazione veloce – basarat

2

Ho raggiunto 300 + * .ts file nel mio progetto, e sono bloccato con il compilatore 0.9.1.1 (le versioni più recenti utilizzano la versione incompatibile di TypeScript e I ' Ho dovuto eseguire un enorme refactoring per rendere il compilatore felice) con tempi di compilazione ~ 25 sec. Io uso tsc app.ts --out app.js.

Ottengo tempi simili per tsc app.ts cioè, quando non si utilizza --out app.js, ma invece di generare un sacco di piccoli file js.

Tuttavia, se compilo un singolo file che non ha troppe dipendenze ottengo volte ~ 5 sec (ho eseguito tsc per ogni file * .ts separatamente per misurare questo, ci sono stati diversi valori anomali che hanno richiesto più di 10 secondi, ma la maggior parte dei file viene compilata rapidamente poiché sono vicini alla parte inferiore della gerarchia delle dipendenze). Quindi la mia prima idea sarebbe quella di creare un sistema che:

  1. orologi per nuova e modificata * .TS file
  2. esegue un tsc foo.ts sul file modificato
  3. concatena tutti i file * .js preservare l'ordine delle dipendenze

Si potrebbe implementare primo passo confrontando risultato di ls -ltc --full-time $(find ts -type f -name '*.ts') ogni secondo, o da qualcosa di più avanzato come inotify. Il terzo passaggio non è difficile visto che tsc conserva le annotazioni /// di riferimento nei file js, quindi è possibile cercarli ed eseguire un ordinamento topologico O (n) semplice.

Tuttavia, penso che potremmo migliorare il secondo passo utilizzando l'opzione tsc -d per creare file di dichiarazione. Nel secondo step il tsc creerà non solo foo.js, ma esaminerà anche e compilerà tutte le dipendenze di foo.ts, che è una perdita di tempo. OTOH se foo.ts avesse solo fatto riferimento a file * .d.ts (che a loro volta non avrebbero dipendenze, o almeno un numero molto limitato di essi) il processo di ricompilazione di foo.ts potrebbe essere più veloce.

Per far sì che funzioni, è necessario trovare e sostituire tutti /// reference in modo che puntino a bar.d.ts, non a bar.ts.

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

dovrebbe apportare le modifiche necessarie.

È inoltre necessario generare tutti i file * .d.ts per la prima volta. È un po 'come un problema di gallina e uova, dato che hai bisogno di questi file per eseguire qualsiasi compilazione. La buona notizia è che questo dovrebbe funzionare se si compila i file nell'ordine topologico dei riferimenti.

Quindi è necessario creare un elenco di file topologicamente ordinato. Esiste un programma tsort che esegue questa attività, purché si disponga di un elenco di bordi. posso trovare tutte le dipendenze con il seguente grep

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

L'unico problema è che l'output contiene i riferimenti alla lettera, per esempio:

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

che significa che dobbiamo risolvere i percorsi relativi ad alcune forma canonica. Un altro dettaglio è che effettivamente dipendiamo da * .ts non i * .d.ts ai fini dell'ordinamento. Questo script parse.sh semplice si occupa di questo:

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

Mettere tutto insieme con tsort genera un elenco di file nell'ordine corretto:

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

Ora, questo probabilmente fallire, semplicemente perché se il progetto non è mai stato compilato in questo modo prima probabilmente non ha definito le dipendenze abbastanza precisamente da evitare problemi. Inoltre, se si utilizzano alcune variabili globali (come var myApp;) in tutta l'app (myApp.doSomething()), potrebbe essere necessario dichiararlo in un file * .d.ts e farne riferimento. Ora, si potrebbe pensare che ciò creerebbe una dipendenza circolare (l'app richiede il modulo x, mentre il modulo x richiede l'app), ma ricorda che ora dipendiamo solo dai file * .d.ts. Quindi non c'è circolarità ora (questo è in qualche modo simile al modo in cui funziona in C o C++, dove uno dipende solo dai file di intestazione).

Una volta che tutti i riferimenti mancanti sono stati corretti e compilati tutti i file * .d.ts e * .ts. Puoi iniziare a guardare le modifiche e ricompilare solo i file modificati. Ma attenzione, se cambi qualcosa nel file foo.ts potresti anche ricompilare i file che richiedono foo.ts. Non perché sia ​​necessario aggiornarli - in realtà non dovrebbero assolutamente cambiare durante la ricompilazione. Piuttosto, questo è necessario per la convalida: tutti gli utenti di foo.d.ts dovrebbero verificare se la nuova interfaccia di foo è compatibile con il modo in cui viene utilizzata da loro! Pertanto, è possibile ricompilare tutti gli utenti di foo.d.ts in caso di modifiche di foo.d.ts. Questo potrebbe essere molto più difficile e dispendioso in termini di tempo, ma dovrebbe accadere raramente (solo se cambi la forma di foo). Un'altra opzione sarebbe semplicemente ricostruire tutto (nell'ordine topologico) in questi casi.

Sono al centro dell'attuazione di questo approccio, quindi aggiornerò la mia risposta una volta che ho finito (o fallisco).

UPDATE Così, sono riuscito a implementare tutto questo, utilizzando tsort e GNU make, per rendere la mia vita più facile con risoluzione delle dipendenze. Il problema è che in realtà era più lento dell'originale tsc --out app.js app.ts. La ragione di questo è che c'è una grande overhead per 0.9.1.1 compilatore per eseguire un singolo compilation - anche per un file di semplice come

class A{ 
} 

time tsc test.ts rendimenti superiori 3 secondi. Ora, se si deve ricompilare un singolo file, questo va bene. Ma una volta capito che è necessario ricompilare tutti i file che dipendono da esso (principalmente per eseguire il controllo dei tipi) e quindi i file che dipendono da essi ecc., È necessario eseguire qualcosa come da 5 a 10 di tali compilation. Quindi, anche se ogni passo di compilazione è molto veloce (3 sec < < 25 sec) l'esperienza complessiva di è peggiore (~ 50 sec!).

Il vantaggio principale di questo esercizio per me, è stato che ho dovuto risolvere molti bug e dipendenze mancanti per farla funzionare :)

Problemi correlati