Indice dei contenuti
Durante un lavoro di revisione del codice sorgente di un libro di programmazione, ho recentemente avuto alcune difficoltà nel compilare la parte TypeScript di una applicazione basata su ASP.NET Core e Angular2: il problema era dovuto al fatto che il compilatore TypeScript integrato in Visual Studio 2015 non voleva assolutamente saperne di compilare il codice client, lamentando la mancanza di una serie di classi ECMAScript6 nonostante la presenza di una libreria shim apposita (l'ormai irrinunciabile core-js). Nello specifico, gli errori riscontrati erano i seguenti:
1 2 3 4 5 |
TS2304: Cannot find name 'Map'. TS2304: Cannot find name 'Set'. TS2304: Cannot find name 'Promise'. TS2304: Cannot find name 'MapConstructor'. TS2304: Cannot find name 'SetConstructor'. |
La cosa mi ha colto di sorpresa perché è cominciata subito dopo l'aggiornamento dei sorgenti Angular2 da beta-17 a RC1. Ci ho messo un bel pò a capire a cosa fosse dovuto il problema, anche per via della mancanza di documentazione a riguardo. Per questo motivo, nella speranza di essere d'aiuto a quanti come me si trovano di fronte a questa problematica, ho deciso di scrivere questo articolo.
Il Problema
Per farla breve, il problema è legato all'utilizzo di librerie TypeScript che utilizzano funzionalitàECMAScript6 (ES6) senza la presenza dei type definition file adeguati che rendano il compilatore TypeScript in grado di gestirle. E' proprio il caso di Angular2 RC1, ma anche di molte altre librerie recenti.
A questo punto è legittimo chiedersi: "come possiamo fare per rendere il compilatore TypeScript in grado di gestire le funzionalità ES6?" Ebbene, ci sono due importanti passaggi che possiamo (anzi dobbiamo) svolgere:
- Configurare il compilatore TypeScript integrato in Visual Studio in modo che generi codice JavaScript ECMAScript6 oppure, se vogliamo generare codice ECMAScript5, utilizzare un compatibility shim adeguato. Se non sapete cos'è uno shim, consiglio di recuperare l'informazione leggendo questo articolo sul blog ufficiale Microsof TechNet. In poche parole, uno shim è una interfaccia software che è in grado di accettare un determinato set di comandi (nel nostro caso ES6) e fa in modo di gestirli in totale trasparenza tramite un altro set di comandi (nel nostro caso ES5), restituendo il medesimo output atteso. In altre parole, un layer di (retro)compatibilità che svolge per ECMAScript5 quello che un polyfill fa per un browser di vecchia generazione. La libreria migliore per svolgere questo ruolo è ormai da alcuni mesi la già citata core-js, che fornisce un supporto pressoché totale per i tipi nativi ECMAScript6.
- Assicurarci che il compilatore Typescript possa accedere ai Type Definition File delle librerie utilizzate, incluso l'eventuale compatibility shim. Con il termine Type Definition File (o Typings) si intendono quei file che descrivono i tipi utilizzati dal codice che il transpiler TypeScript integrato in Visual Studio dovrà compilare seguendo l'algoritmo di risoluzione modulare di Node.js (o altra strategia di risoluzione utilizzata): per maggiori informazioni su questo aspetto specifico, consiglio di leggere questo illuminante paragrafo contenuto all'interno della documentazione ufficiale TypeScript. E' importante considerare che il compilatore TypeScript fa già del suo meglio per recuperare i Type Definition File all'interno delle varie librerie utilizzate, guardando all'interno dei file package.json e/o index.d.ts presenti nella cartella principale di ogni libreria. Nella maggior parte dei casi questi file sono presenti, risolvendo di fatto qualsiasi problema di definizione: non è così per le librerie ancora in via di sviluppo, come appunto Angular2, e per quelle non appositamente pensate per essere utilizzate con TypeScript, come appunto core-js: in questi casi è necessario provvedere manualmente, altrimenti si incorrerà inevitabilmente negli errori di cui sopra.
Il mio problema era dovuto al fatto che, pur avendo svolto correttamente quanto descritto dal punto 1, non avevo considerato la necessità di fare anche quanto indicato dal punto 2. Nello specifico, avevo impostato il compilatore TypeScript per produrre codice ECMAScript5, cosa di per sé buona e giusta visto che mi interessava supportare anche browser di vecchia generazione: per questo motivo avevo anche incluso la libreria core-js tra i pacchetti NPM da scaricare, così da dotare il mio codice di tutti gli shim del caso. Quello che mancava erano i Typings di core-js, necessari per "informare" il compilatore TypeScript di Visual Studio che tutti i tipi ECMAScript6 utilizzati dalla nuova versione RC1 di Angular2 erano correttamente gestiti da una delle librerie presenti nel progetto (core-js) e quindi, banalmente, "potevano essere presi per buoni".
La Soluzione
Il problema, come detto, si può risolvere sostanzialmente in due modi: configurare il compilatore TypeScript per generare codice in ECMAScript6 oppure installare i Type Definition File della libreria core-js.
Metodo 1: Compilare in ES6
E' il workaround più semplice da implementare: è sufficiente aprire il file tsconfig.json presente nella root del nostro progetto e modificare il valore della chiave target da es5 a es6 . Questo è un esempio di file tsconfig.json modificato in tal senso:
1 2 3 4 5 6 7 8 9 10 11 12 |
{ "compilerOptions": { "target": "es6", "module": "system", "moduleResolution": "node", ... }, "exclude": [ "node_modules", ... ] } |
Ovviamente, questa scelta non è priva di inconvenienti: in questo modo non sarà possibile utilizzare strumenti, pacchetti NPM o librerie JS che non supportino ES6, come ad esempio la maggior parte dei tool di minifying/uglifying, primo tra tutti UglifyJS. Se non avete in programma di utilizzarli siete a posto, altrimenti converrà utilizzare il metodo successivo.
Metodo 2: Installare Typings e i Type Definition File di core-js
Il vantaggio di questo metodo è che vi consente di mantenere il target di generazione del codice JavaScript risultante in ECMAScript5, con indubbi vantaggi a livello di compatibilità. Prima di andare avanti, assicuratevi che il vostro file tsconfig.json sia grossomodo compatibile con queste impostazioni:
1 2 3 4 5 6 7 8 9 10 11 12 |
{ "compilerOptions": { "target": "es5", "module": "system", "moduleResolution": "node", ... }, "exclude": [ "node_modules", ... ] } |
Aprite il file package.json (utilizzato da Visual Studio per gestire i pacchetti NPM) e aggiungete, se non già presente, il pacchetto typings all'interno della chiave dependencies oppure devDependencies . Fate lo stesso con la chiave scripts , a cui dovrete aggiungere una sotto-chiave postinstall avente il seguente valore: typings install dt~core-js --global . Al termine del vostro intervento, il file dovrebbe avere una struttura analoga alla seguente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
{ "version": "1.0.0", "name": "yourproject", "private": true, "dependencies": { ... "typings": "^1.3.2", ... }, "devDependencies": { ... }, "scripts": { "postinstall": "typings install dt~core-js --global" } } |
In questo modo stiamo dicendo a Visual Studio di scaricare una versione aggiornata dello strumento typings e di lanciarlo per scaricare i typings aggiornati di core-js a seguito di ogni aggiornamento dei pacchetti NPM. La prima esecuzione dello script avverrà nel momento in cui faremo click su Save: basterà attendere pochi istanti e una nuova cartella typings verrà aggiunta nella root della nostra applicazione, contenente i type definition file di core-js necessari per compilare opportunamente il codice ES6.
Per il momento è tutto: felice transpiling!