Podstawy TypeScript #1- pierwsze kroki i konfiguracja (mój tsconfig.json)
TypeScript od absolutnego początku!
Nie ma w tym tajemnicy, że zakochałam się na zabój w TypeScriptcie i mam nadzieje, że uda mi się przekonać Was do spróbowania. Dzisiaj rozwiązemy pierwszy problem zniechacający początkujących do TypeScripta, czyli ustawienie konfiguracji.
Mam dla was dwa rozwiązania: po prostu skopiujcie cudzą konfigurację (w ostateczności moją) albo zapraszam do czytania całości posta gdzie staram sie prostym językiem wytłumaczyć mój tsconfig.json
Zaczynając ten tutorial powinieneś, drogi czytelniku, znać podstawy programowania w JavaScript i obiektowego.
Pierwsze kroki
TypeScript przetwarza się do JavaScriptu, więc musimy mieć możliwość do wykonania tej czynności.
- Zainstaluj stabilną wersją NodeJS z oficjalnej strony lub innego bezpiecznego źródła. Dzięki temu zyskasz środowisko uruchomieniowe wraz z popularnym managerem paczek npm.
- Zainstaluj globalnie TypeScripta z managera paczek:
npm install -g typescript
W tym tutorialu będziemy korzystać z edytora Visual Code, bo ma bardzo duże wsparcie dla TypeScripta. Ba! Jest w nim napisany :) Ale jeśli chcesz to możesz wybrać jakikolwiek inny :)
Stwórzmy sobie folder i otwórzmy go w VC.
W tym folderze utwórzmy dwa pliki: html oraz ts.
Plik html to będzie standardowa strona, która będzie zawierała plik Javscript.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="hi.js" async></script>
<title>Event interface</title>
</head>
<body>
<div>
<button id="hi">
Hi!
</button>
</div>
</body>
</html>
Teraz jakiś prosty skrypt (napisany w JavaScript) z rozszerzeniem TypeScript (*.ts).
// hi.ts
window.onload = function() {
let button = document.getElementById('hi');
alert('wczytany!');
};
Teraz w konsoli (można otworzyć ją w VC, gdzie od razu przeniesie nas do folderu (Ctrl/Command + ~) wpisujemy:
tsc hi.ts
Po zakończeniu powinien nam się wygenerować plik hi.js i osiągnęliśmy pierwszy sukces na naszej drodze do zostania mistrzem TypeScript'a.
Oczywiście nikt tego w ten sposób nie robi, ale fajnie znać podstawy :)
Tsconfig.json - konfiguracja
Czekajcie! Ale skąd polecenie tsc wiedziało co i gdzie ma wygenerować? Dlaczego przeszedł taki zwykły JavaScript?
Podstawową odpowiedzią jest plik konfiguracyjny (którego nota bene zabrakło w moim tutorialu z Node.js) TypeScript'a. Jeszcze nie stworzyliśmy takiego pliku, dlatego są brane wartości domyślne np. generowanie plików w obecnym folderze :)
Jednak projekty są bardzo skompilowane, a Typescript służy do pisania przeróżnych aplikacji, więc musi istnieć miejsce w którym możemy sobie wszystko zdefiniować.
W celu podania konfiguracji musimy stworzyć plik o nazwie tsconfig.json. Na przykład:
{
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/",
"moduleResolution": "node",
"lib": ["es7", "es2015", "dom"],
"target": "es5"
},
"include": ["./src/**/*", "public"],
"exclude": ["node_modules", "**/*.spec.ts"]
}
W tym przykładnie najciekawszy jest obiekt compilerOptions
. Podajemy kolejno domyślną ścieżkę aplikacji, folder w którym ma się pojawić plik wynikowy (hi.js na przykład), jaki typ importów został użyty, biblioteki które się przydadzą oraz w jakiej wersji powinien być wynikowy JavaScript.
Nie przejmujcie się, jeśli coś wydaje się zbyt skomplikowane, zaraz wszystko wyjaśnię, a zwykle wystarczy skopiować plik do swojego projektu.
Mój tsconfig.json
Ten wpis będzie służył za punkt odniesienia do moich innych TypeScriptowych postów, bo często pytacie o konfigurację. Zwykle jest taka sama lub bardzo podobna, więc nie ma sensu zamieszczać jej co wpis :)
baseUrl
To ścieżka bazowa dla całej konfiguracji.
outDir
To ścieżka w której umieszczone będę wynikowe pliki.
moduleResolution
To właściwość od której zależy jak TypeScript zarządza modularnością (rozpoznaje moduły). Tu sprawa jest trochę grubsza i pewnie można by zrobić o tym osobny wpis. Jedyne co musicie jednak wiedzieć to, że tsc przyjmuje tu dwie stałe: node
oraz classic
. Typ klasyczny odchodzi do lamusa, a więcej możecie o tym poczytać tutaj.
noImplicitAny
W TypeScripcie można też deklarować typ poprzez przypisanie wartości. Na przykład:
(1) const x = 'napis';
(2) const x: string = 'napis2';
Domyślnie oba są poprawne, ale osobiście wolę, żeby w każdym moim projekcie był wymuszany sposób drugi.
removeComments
Najoczywistsza z opcji: w czasie kompilacji do JavaScript usuwa komentarze.
preserveConstEnums
Przy kompilacji transpiler zamienia poszczególne wartości stałych+ enumów na proste typy. Możemy chcieć zachować enum jako obiekt. Brzmi skomplikowanie? Oto przykład:
const enum Pojazdy {
Samochod = -1,
}
Domyślną wartością dla preserveConstEnums
jest false, więc po kompilacji ten kod... zniknie. Zostaną przypisane jedynie wartości w miejscach gdzie się go używa. Jest to spora optymalizacja kodu, ale tracimy grunt pod nogami w Javascriptcie.
Jak go zachować? Usunąć const
lub ustawić preserveConstEnums
na true, żeby kompilował się normalnie. Znaczy jak na TypeScripta :)
var Pojazdy;
(function (Pojazdy) {
Pojazdy[Pojazdy["Samochod"] = -1] = "Samochod";
})(Pojazdy||(Pojazdy={}));
sourceMap
Generuje mapkę do zewnętrznego pliku. Ułatwia kodowanie, nie zapycha skryptu wynikowego. Nie wiesz co to source-mapka? Zajrzyj tutaj.
module
Skoro już w moduleResolution wiemy jak TypeScript rozwiązuje moduły w plikach .ts, to jeszcze musimy zdefiniować jaki typ pojawi się w plikach wynikowych.
target
Javascript, w sumie EMCAScript, ma wiele wersji i tutaj możemy zdefiniować w jakiej wersji będzie plik wynikowy.
allowJs
Wartość, która określi czy kompilator ma dopuszczać przetwarzanie JavaScriptu, bowiem nic nie stoi na przeszkodzie, żeby część aplikacji była w JavaScriptcie, a część w TypeScriptcie. Jest to bardzo wygodne, gdy chcesz skorzystać z zewnętrznych bibliotek albo powoli przenosić aplikację na TypeScripta.
allowSyntheticDefaultImports
Nie wszystkie biblioteki, szczególnie te napisane w JavaScripcie, mają domyślne exporty. Można umożliwić pobieranie globalnych jako modułu poprzez:
import * as myModule from 'myLibrary';
jeśli powyższa właściwość będzie ustawiona na true.
esModuleInterop
Przydatny do babela, dzięki niemu z powodzeniem można kompilować różne wersje i typy importów. Ustawia allowSyntheticDefaultImports na true, więc w moim pliku konfiguracyjnym jest pewna niekonsekwencja: dwa razy deklaruje to samo. Jest powodowana lenistwem, gdyż używam jednego tsconfiga do wielu aplikacji.
emitDecoratorMetadata i experimentalDecorators
Umożliwia korzystanie z dekoratorów. Nie będę się rozpisywać, bowiem jestem w trakcie przygotowania specjalnego wpisu o dekoratorach.
lib
Tablica dodatkowych (niemniej jednak podstawowych) bibliotek. Tutaj możemy dodać obsługę DOM (do pisania front-endu), funkcji oraz metod znanych z ES6 czy ES7 czy pojedyńcze rozszerzenia znane z nowszych wersji TypeScripta.
paths
To obiekt w którym możemy sobie zdefiniować ścieżki, które będzie rozpoznawał Typescript. Umożliwia to skracanie importów, pisanie wzorców ścieżek.
"paths": {
"Components/*": [
"src/components/*"
],
"Entity/*": [
"src/entity/*"
],
"*": ["types/*"]
}
include/exclude
Można wyłączyć niektóre ścieżki i pliki z kompilacji. Najczęściej include dotyczy jakiegoś katalogu src (lub kilku), exclude zewnętrznych bibliotek (także folderu node_modules
), testów itd.
Mój tsconfig.json
Pisanie tego wpisu uświadomiło mnie jak bardzo naśmieciłam w swoim tsconfigu :) Obiecuję poprawę w przyszłości, ale na razie śmiało, bierzcie jeden z nich:
Mój podstawowy:
{
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/",
"moduleResolution": "node",
"noImplicitAny": true,
"removeComments": true,
"preserveConstEnums": true,
"sourceMap": true,
"module": "commonjs",
"target": "es6",
"allowJs": true,
"esModuleInterop": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"lib": ["es7", "es2015", "dom"],
"paths": {
"Components/*": ["src/components/*"],
"Entity/*": ["src/models/*"],
"*": ["types/*"]
}
},
"include": ["./src/**/*", "public"],
"exclude": ["node_modules", "**/*.spec.ts"]
}
A teraz przykładowy frontowy:
{
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/",
"moduleResolution": "node",
"noImplicitAny": true,
"removeComments": true,
"preserveConstEnums": true,
"sourceMap": true,
"module": "commonjs",
"target": "es5",
"allowJs": true,
"esModuleInterop": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"lib": [
"es7",
"es2015",
"dom"
],
"paths": {
"Components/*": [
"src/components/*"
],
"Entity/*": [
"src/entity/*"
],
"*": ["types/*"]
}
},
"include": [
"./src/**/*"
],
"exclude": [
"node_modules",
"**/*.spec.ts"
]
}
W webpacku również korzystam z tych konfiguracji poprzez plugin:
// webpack.config.js
// ...
plugins: [new TsconfigPathsPlugin({ configFile: "./frontend.tsconfig.json" })],
// ...
Podsumowanie
Mam nadzieje, że ta seria pozwoli Wam się zakochać w TypeScriptcie, bo pisanie w nim to sama przyjemność, a konfiguracja nie jest taka straszna. Może i ci którzy trochę znają TypeScripta też znajdą coś dla siebie :)
W najbliższym czasie będę umieszczała trochę bardziej zaawansowanych wpisów o TypeScriptcie, ale zawsze chętnie wracam do podstaw, żeby utrwalić wiedzę.
Podobało się Wam? Popełniłam gdzieś błąd? Jakieś niedopowiedzenie? Coś powinnam zmienić w konfiguracji?
Jak zwykle zapraszam do dyskusji i pozdrawiam :)