W poprzednim wpisie pokazałem jak w łatwy sposób dodawać do projektu TypeScriptowe moduły. Na koniec wspomniałem jednak, że istnieją inne, czasem lepsze, podejścia do tego tematu.
Dziś pokażę sposób na zarządzanie modułami, który uważam za najciekawszy. Nie dość, że korzysta on z nowoczesnej składni EcmaScript6, to sprawdza się naprawdę dobrze niezależnie od specyfiki projektu.
Niestety na tę chwilę przeglądarka sama nie poradzi sobie z modułami zarządzanymi na modłę ES6. Potrzebuję do tego dodatkowej pomocy. Na szczęście, rozwiązanie tego problemu jest banalnie proste. Istnieją tak zwane Module Loadery, które zajmują się dodawaniem modułów do projektu w taki sposób, że przeglądarka dobrze sobie z nimi radzi. W tym przypadku skorzystamy z Module Loader’a System.js.
Jest kilka sposobów na dodanie System.js do projektu. Najprostszym jest korzystanie z CDNu i to rozwiązanie użyję dziś. Można też ręcznie pobrać kod z ich githuba lub zainstalować System przez NPMa. To już zależy od was i od sytuacji projektowej w jakiej jesteście.
Niezależnie od tego jak zdobędziecie kod loadera, pierwszym krokiem jest dodanie go do dokumentu HTML. Tak wygląda przykładowy dokument, którego używam w dzisiejszych przykładach. Uwaga, nie jest specjalnie rozbudowany:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> </head> <body> </body> <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.40/system-csp-production.js"></script> <script> System.defaultJSExtensions = true; System.import('main'); </script> </html> |
Tak jak pisałem, nic tu nie ma. Oprócz dwóch tagów script, które są dla nas jak najbardziej interesujące. Pierwszy zawiera link do kodu module loadera. Drugi jest ciekawszy, to tu definiuję gdzie loader ma szukać modułów. Pierwsza linijka kodu jest konieczna, jeśli dobrze rozumiem, chodzi o poprawne odczytywanie systemu plików. Nie ma co się zagłębiać. Druga linijka jest znacznie ważniejsza. Wywołuje metodę import obiektu System, która jako parametr otrzymuję nazwę podstawowego modułu, czyli ścieżkę do głównego pliku projektu. Jak widać nie podaję tu rozszerzenia pliku.
Do dokumentu HTML nie dodajemy i nie będziemy dodawać już nic więcej, przez cały czas rozwoju projektu. Prawda, że wygodne? 🙂 Wszystkie zależności definiowane będą w kodzie.
Zanim przejdę dalej, muszę jeszcze przedstawić aktualną konfigurację w pliku tsconfig.json:
1 2 3 4 5 6 7 8 |
{ "compilerOptions": { "target": "es5", "module": "system" }, "compileOnSave": true, "buildOnSave": true } |
Teraz dwa pola wewnątrz opcji kompilatora mają znaczenie. Pierwsze oznacza na jaki standard ma być kompilowany nasz kod a drugi z jakiego rodzaju modułów ma korzystać. Ponieważ korzystam z loadera system, właśnie taką wartość wpisuję w drugiej opcji.
Warto zauważyć, że tym razem nie kompiluję wszystkiego do jednego pliku. Oznacza to, że każdy plik TS będzie miał swój JSowy odpowiednik. I z tym system.js radzi sobie bez problemu.
Czas wracać do przykładowego kodu. Oprócz głównego pliku projektowego main.ts mam też dogs.ts czyli dodatkowy moduł, z którego będzie korzystać mój program. Oto jego zawartość:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
export class Dog { name:string; constructor(name){ this.name = name; } action() { console.log(this.name + " the Dog is here!"); } } export interface dogTalk { bark():void; growl():void; } |
Tak jak pisałem w poprzednim poście. Każdy plik jest modułem, więc nie muszę otaczać tego kodu dodatkową parą nawiasów. Mogę od razu dodawać elementy do modułu. Jeśli chcę aby któryś z elementów dostępny był poza modułem muszę dodać do niego operator export.
W tym wypadku mam dwa elementy: klasę oraz interfejs. Oba są eksportowane na zewnątrz.
Skoro to wszystko jest już gotowe czas przejść do głównego pliku programu main.ts, w którym wykorzystam moduł dogs. Oto kod:
1 2 3 4 5 6 |
import * as dogModule from './dogs'; let newTalk:dogModule.dogTalk; let maja:dogModule.Dog = new dogModule.Dog("maja") maja.action(); |
Tak jak pisałem na początku, do importowania modułów korzystam ze składni ES6. TypeScript rozumie też składnie commonjs, ale skoro mogę już używać standardów przyszłości, to czemu tego nie robić 🙂 .
Cała magia dzieje się w pierwszej linijce powyższego kodu. To w niej dodaję do programu moduł dogs.
Najpierw wpisuję operator import a następnie gwiazdkę. Oznacza to, że chcę zaimportować cały moduł. W takim wypadku muszę podać alias pod jakim chcę mieć ten moduł dostępny. Zajmuje się tym operator as. Na koniec zostało podanie lokacji w której znajduje się mój moduł. Tą częścią zajmuje się operator from. Warto zauważyć, że alias modułu podajemy w formie literała a ścieżkę do niego jako łańcuch znaków. Dodatkowo w ścieżce nie trzeba podawać rozszerzenia pliku.
I gotowe, mam dostępny cały moduł dogs wewnątrz projektu. Mogę odwoływać się do jego elementów poprzez alias, który wybrałem czyli dogModule. Po odpaleniu kodu w przeglądarce wszystko działa jak trzeba.
Jest super, a może być jeszcze lepiej. Operator import pozwala nam na trochę więcej. Nie zawsze będziemy chcieli korzystać ze wszystkich elementów modułu. Często jest wręcz przeciwnie, potrzebne są tylko niektóre jego elementy. Oto jak można to osiągnąć:
1 2 3 4 |
import {Dog} from './dogs'; let maja:Dog = new Dog("maja") maja.action(); |
Zamiast * as przekazałem do import parę nawiasów klamrowych. Wewnątrz mogę wymienić tylko te elementy z modułu, które potrzebuje. Będą one dostępne pod taką samą nazwą bezpośrednio w projekcie. Nie muszę się nawet odwoływać do nich przez żaden alias. Oczywiście czasem możemy chcieć podać takowy importowanemu elementowi. I to da się zrobić:
1 2 3 4 5 6 |
import {Dog, dogTalk as iDog} from './dogs'; let newTalk:iDog; let maja:Dog = new Dog("maja") maja.action(); |
Tym razem oprócz klasy Dog importuje też interfejs dogTalk, któremu automatycznie przypisuje alias iDog. Aby zaimportować więcej elementów wystarczy wymienić je po przecinku.
To wszystko jeśli chodzi o magię modułów. Nawet jeśli na początku wszystko wydaje się zagmatwane, warto jest włożyć trochę trudu w pojęcie tych mechanizmów, na pewno się na tym skorzysta. Ja skorzystam na pewno.
Tymczasem, jeżeli chcesz być na bieżąco z postami na blogu zachęcam do polubienia mojej strony na facebooku. Zawsze zamieszczam tam informacje o wszystkich nowościach. Jest to też dobre miejsce na kontakt ze mną. Na wszystkie pytania zawsze odpowiem :).