ES6 – modules

javascript

Do tej pory język JavaScript nie posiadał możliwości modułowego podejścia do architektury aplikacji. Mam oczywiście na myśli podejście natywne, bez używania zewnętrznych bibliotek. Wraz z implementacją ECMAScript2015 w przeglądarkach, zmieni się to na lepsze. Otrzymamy dzięki temu kolejne API, które pozwoli nam tworzyć aplikacje w bardziej ustandaryzowany sposób.


Zobacz całą serię: Let-s talk about ECMAScript 2015


Do tej pory musieliśmy się zadowalać dwoma standarami: CommonJS oraz AMD. Są to dwa najpopularniejsze sposoby w podejściu do modułowości w JavaScript, jednak zupełnie ze sobą niekompatybilne.

CommonJS – znane jest z ekosystemu Node.js. Dedykowane serwerom aplikacji, wspiera synchroniczne ładowanie modułów. Składnia oparta jest na niewielkim API, skupionym na słowach kluczowych export oraz require.

AMD – to podejście zaimplementowane zostało w bibliotece RequireJS. Dedykowane jest przeglądarkom, które wzbogacone zostają o asynchroniczne ładowanie modułów. Ceną – w porównaniu z CommonJS – jest jednak bardziej skomplikowane API.

Zadaniem ES6 jest połączenie obu znanych dzisiaj standardów oraz zapewnienie poprawnego działania zarówno po stronie serwerowej, jak i klienckiej. Otrzymujemy łatwe oraz spójne API do asynchronicznego i konfigurowalnego ładowania modułów w naszych aplikacjach.

Model asynchroniczny zakłada również, że kod nie zostaje wykonany póki wszystkie potrzebne moduły nie zostały załadowane oraz zinterpretowane.

named export

Jeden moduł posiadać może wiele nazwanych eksportów, gdzie każdy pozwala udostępniać na zewnątrz pojedyncze zmienne, obiekty, czy funkcje.

export function multiply (x, y) {
  return x * y;
};

Nie ma również problemu, by powyższą funkcję przypisać do zmiennej i eksportować na zewnątrz:

var multiply = function (x, y) {
  return x * y;
};

export { multiply };

Jedyne o czym musimy pamiętać, to nawiasy klamrowe, którymi musi być otoczone eksportowane wyrażenie.

Nic nie stoi również na przeszkodzie, by eksportować całe obiekty. Tutaj także musimy pamiętać o nawiasach klamrowych.

export hello = 'Hello World';
export function multiply (x, y) {
  return x * y;
};

// === OR ===

var hello = 'Hello World',
    multiply = function (x, y) {
      return x * y;
    };

export { hello, multiply };

import

Przejdźmy teraz do importu modułów. Wyobraźmy sobie, że mamy plik export.js, gdzie eksportowany jest obiekt z poprzedniego przykładu. Następnie tworzymy plik import.js, w którym importujemy owy obiekt. Posłużymy się tutaj składnią:

import { ... } from ...

Nawiasy klamrowe określają nam co chcemy zaimportować, a w kolejnej części wyrażenia podajemy skąd chcemy (z jakiego modułu) dokonać importu.

I tak, by zaimportować wyrażenie ‘hello’ z powyższego przykładu napiszemy:

import { hello } from './export';

W przypadku podawania nazwy modułu, możemy pominąć rozszerzenie .js. Podobnie jest w CommonJS oraz AMD.

Za zaimportowanie obu wartości posłuży nam poniższy kawałek kodu:

import { hello, multiply } from './export';

Każda z importowanych wartości może na dodatek posiadać swój alias:

import { multiply as pow2 } from './export';

Jeżeli zależy nam na imporcie całego modułu, wystarczy, że użyjemy wyrażenia typu wildcard (*):

import * as all from './export';

Musimy w tym wypadku skorzystać z aliasu, by móc się później odwoływać do metod w naszym module.

default export

Poza wartościami nazwanymi, eksportować możemy również wartość domyślną. Przydaje się to w wielu sytuacjach, ponieważ zwykle eksportujemy jeden model per moduł. Domyślny export jest więc najważniejszą wartością naszego modułu. Warte zapamiętania, biorąc pod uwagę zasadę SRP (Single responsibility principle).

Oczywiste jest również, że moduł posiadać może tylko jeden domyślny eksport. By stworzyć taki eksport w naszym module, musimy dodać słowo kluczowe default zaraz po export:

export default function (x, y) {
  return x * y;
};

Do importowania domyślnego wyrażenia nie musimy używać nawiasów klamrowych. Możemy ponad to nazwać (alias) importowane wyrażenie jak nam się podoba:

import multiply from './export';

// === OR ===

import pow2 from './export';

// === OR ===
...

Każdy moduł posiadać może zarówno eksporty nazwane, jak i jeden eksport domyślny:

// export.js
export hello = 'Hello World';
export default function (x, y) {
  return x * y;
};

// import.js
import pow2, { hello } from './export';

Domyślne wyrażenie możemy również zaimportować jak pozostałe eksporty nazwane. Importować w tym momencie będziemy wyrażenie default:

// export.js
export default function (x, y) {
  return x * y;
};

// import.js
import { default } from './export';

API modułów zakłada, że praca z nimi odbywa się z poziomu kodu i jest w pełni konfigurowalna. Specyfikacja przewiduje, że moduły zapewniają dynamiczne ładowanie oraz izolację od globalnej przestrzeni nazw.

Przy okazji modułów, warto wspomnieć o projekcie SystemJS. Jest to uniwersalna biblioteka do obsługi modułów ze specyfikacji ECMAScript2015 oraz CommonJS i AMD. Współpracuje także z transpilerami Traceur i Babel, zapewniając w ten sposób modułowość w aktualnych wersjach przeglądarek, które nie oferują wsparcia nowego standardu ES6.