En simpel JavaScript app
example/ index.html app.js randomGenerator.js
index.html kalder en main funktion efter at hele dom’en er loaded:
$(function() { main(); });
function main() { var appComponent = document.getElementById('app'); setInterval(function() { appComponent.innerHTML = generateRandomId(); }, 1000); }
Til at beregne et random token benytter vi funktionen generateRandomId fra filen randomGenerator.js
function generateRandomId() { return '#' + Math.random().toString(36).substr(2, 7) }
Al koden til denne artikel finder du her. Når du har hentet koden ned kan du åbne index.html i en browser for at se applikationen live.
Benyt TypeScript
Vi vil nu sætte app’en op til at bruge TypeScript. I example-ts har vi kopieret vores app filer fra example/ ind i følgende struktur:
example-ts/ dist/ index.html src/ app.js randomGenerator.js
npm install -g typescript
Nu skal vi have vores projekt initialiseret til at benytte TypeScript. Det er kun nødvendigt at gøre dette én gang. Vi initialiserer vores projekt via kommandoen mens vi står i ./example biblioteket:
tsc --init
Vi retter indholdet af tsconfig.json til:
{ "compilerOptions": { "outDir": "dist" }, "files": [ "src/app.js", "src/randomGenerator.js" ] }
“files” angiver hvilke filer der skal kompileres og outDir angiver i hvilken folder de kompilerede filer skal placeres.
Vi er nu klar til den første kompilering.
Kører vi kommandoen `<code> tsc</code> kommer følgende fejl:
error TS6054: File 'example-ts/src/app.js' has unsupported extension. The only supported extensions are '.ts', '.tsx', '.d.ts'.
I tsconfig.json
"compilerOptions": { "allowJs": true, "outDir": "dist"
“allowJs”: true tillader Typescript compileren at kompile JavaScript filer. Kompilerer vi igen kan vi se at kompileren kører uden fejl og at der er kommet en app.js og en randomGenerator.js fil i dist folderen. De er næsten identisk med dem i src folderen på nær lidt formatering. Åbner vi vi vores index.html fra dist folderen i en browser ser vi at app stadig virker som før.
Refactor til TypeScript
Vi er nu klar til at refactore dele af koden til TypeScript. Med “allowJs”: true kan vi nu selv vælge hvilke dele af kodebasen vi ønsker at konvertere til TypeScript. Vi vælger i dette tilfælde at refaktorere randomGenerator.js filen til TypeScript. Først omdøber vi filen til randomGenerator.ts og retter tsconfig.json
"files": [ "src/app.js", "src/randomGenerator.ts" ]
var token: string = '#'; const base: number = 36; function generateRamdomId() { return token + Math.random().toString(base).substr(2, 7) }
Vi har her valgt at tildele typer til token og base. base er ydermere erklæret som et konstant. Bemærk hvordan den kompilerede dist/randomGenerator.js ser ud. const compileres til var. Det er fordi default target for compilering er es3. Konfiguerer vi “target”=”es2015 hvor der findes konstanter giver kompileringen i stedet:
const token = "#";
Denne evne til at kompilerer til flere forskellige versioner af JavaScript kaldes for transpiling.
Et endnu bedre eksempel på dette er hvis vi introducerer klasser i randomGenerator.ts
var token:String = "#"; const base: number = 36; class TokenGenerator { base: number; token: String; constructor(base: number, token: String) { this.base = base; this.token = token; } generateToken() :String { return token + Math.random().toString(base).substr(2, 7); } } var generator: TokenGenerator = new TokenGenerator(base,token); function generateRandomId() { return generator.generateToken();
Med “target”: “es2015 kompileres det til klasse konstruktion som es2105 har:
class TokenGenerator { constructor(base, token) { this.base = base; this.token = token; } generateToken() { return token + Math.random().toString(base).substr(2, 7); } }
med “target”: “es5” falder vi tilbage til et traditionelt JavaScript IIFE (Immedeatly-Invoked Function Experession) pattern for at skabe et object med klasse lignende egenskaber.
var TokenGenerator = (function () { function TokenGenerator(base, token) { this.base = base; this.token = token; } TokenGenerator.prototype.generateToken = function () { return token + Math.random().toString(base). substr(2, 7); }; return TokenGenerator; }());
Strict null check
Selvom vi har valgt, at kun refaktorere randomGenerator.js, kan vi lade TypeScript kompileren checke JavaScript koden i app.js. Ved at konfigurere tsconfig.json med
"checkJs": true,
vil kompileren også rapportere fejl i JavaScript filer. Et eksempel er med kompiler setting
"strictNullChecks": true,
src/app.js(5,9): error TS2531: Object is possibly 'null'.
Ser vi i koden, kan document.getElementById(‘app’) ganske rigtigt returnere null, hvis der ikke findes et element med id app i dom’en. Det tager vores kode ikke højde for. I det tilfælde vil appComponent.innerHTML = generateRandomId() i linie 5 give exception. Vi kan dog ændre koden så vi undgår problemet.
if (appComponent) { appComponent.innerHTML = generateRandomId(); }
Sourcemap debug med TS code
En kritik af at have et ikke JavaScript sprog og et kompileringstrin før man får eksekverbar kode er, at den kode man skriver ikke er det der køre i browseren. Det kan f.eks. gøre debugging besværlig. Dette kan man dog minimere vha. source maps. Sætter vi
"sourceMap": true
i tsconfig.json genererere TypeScript kompileren ekstra filer til debug.
F.eks. får vi en randomGenerator.js.map
Denne fil giver et map, mellem den oprindelige TypeScript fil randomGenerator.ts og den transpileret JavaScript fil. Med dette map, kan vi sætte breakpoints i TypeScript filen, samt lave inspektion af værdier.
Refactoring og IntelliSense
Med TypeScript har editoren nu mulighed for at give udvikleren meget mere støtte.
Inline hjælp til en funktions eller klasses API. Navigering til en definition af en klasse eller fil. Og markering af fejl, der vil lede til kompileringsfejl.
Udover editor understøttelsen til IntelliSense giver TypeScript også muligheden for at refaktore koden meget mere effektivt end klassisk JavaScript. Re-navngivning af klasse og variabel navne, virker på tværs af filer, sikrer en højere kode kvalitet, som ellers kun kunne fanges i test.
Typings
Som i måske har lagt mærke til, så benytter vi jQuery i vores index.html. Vi kunne kode vores app.js om til at benytte jQuery istedet for.
IntelliSense viser kun ‘any’ i VS Code
Her er der dog ikke IntelliSense, da jQuery ikke er baseret på TypeScript. any er typescript’s default besked når den ikke kan udlede noget om funktionen. Så hvordan får vi IntelliSense fra eksisterende JavaScript baserede biblioteker?
Det gør vi via TypeScript definition files. Det er filer der beskriver parametertype, returtyper osv. på JavaScript bibliotker, som var de skrevet i TypeScript.
Via @types projektet kan vi installere definitionsfiler på en meget stor del af populære JavaScript librairies. (Op mod 2000)
Her installere vi jQuerys typings
npm install --save @types/jquery
Introducerer vi nu noget jquery kode i vores app.js´
function main() { var appComponent = $('#app'); setInterval(function () { appComponent.text(generateRandomId()); }, 1000); }
IntelliSense virker nu i VS Code
Konklusion
Som man kan se ved ovenstående eksempler, så er det relativt nemt at komme i gang med TypeScript, også i eksisterende JavaScript projekter. TypeScript gør udviklingen mere effektiv, vha. velfungerende IntelliSence, refactoring, samt ved at fange fejl på kompiletidspunktet, i stedet for i test. Vi har kun skrabet overfladen med hensyn til TypeScript funktionalitet og muligheder. Kom og hør meget mere på vores en-dags TypeScript Foundation kursus hvor vi vil gå i dybden med TypeScript.
Medforfatter: Flemming Bregnvig
Flemming er en engageret og initiativrig arkitektprofil med ekspertviden indenfor TypeScript & JavaScript. Flemming har specialiseret sig indenfor frontend og integration. Flemming har over 19 års erfaring som udvikler, hvoraf de seneste 10 år er som arkitekt og lead-udvikler. Flemming har indgående kendskab til finans og pensionsbranchen.