Nasza strona używa cookies. Korzystając ze strony, wyrażasz zgodę na używanie cookies, zgodnie z aktualnymi ustawieniami przeglądarki. Rozumiem

Rozgryzamy moduły ES w JavaScript

Dowiedz się, czym są moduły ES, w jaki sposób używać ich w JavaScript oraz jak mogą Ci pomóc w Twojej codziennej pracy.
Rozgryzamy moduły ES w JavaScript

Moduły są w praktycznie każdym języku programowania. Jest to sposób na wprowadzenie danej funkcji przy pomocy innego kodu. Są one tam, gdzie programiści tworzą kod z funkcjami, których można ponownie użyć w innym miejscu. Ich zalety to m.in. rozbicia kodu na kilka części. Warto tutaj jednak zaznaczyć, że modułów jako takich nie ma w JavaScript. 

Programiści wrócili więc do tagów <script> HTML-a, aby ładować pliki JS do swoich apek. Później pojawiły się inne formaty definicji modułów. 

  • CommonJS — module.exports i składnia require z Node.js
  • Asynchronous Module Definition (AMD)
  • Universal Module Definition (UMD)
  • Moduły ES


Zobaczmy najpierw, dlaczego w ogóle potrzebujemy modułów.


Po co nam moduły?

Kiedy zastanowimy się nad tym, jak działają programy, to tak naprawdę polega to wszystko na zarządzaniu zmiennymi — moduły przypisują do nich wartości, modyfikują je, łączą ze sobą zmienne itd. Ale kiedy liczba zmiennych rośnie wraz z Twoją aplikacją, to może być potem ciężko zarządzać nimi i Twoim kodem. Rozwiązaniem tego problemu było posiadanie zaledwie kilku zmiennych, które są w jakiś sposób istotne. JS osiągnęło to za pomocą zasięgu — ze względu na to, jak zasięg działa w JS, funkcje nie mogą otrzymać dostępu do zmiennych, które są zdefiniowane w innych funkcjach. 

Pomimo że pozwala to zmiennym na bycie niedostępnymi dla innych funkcji, to tworzy to następny problem — ciężko się nimi między funkcjami dzielić. Popularnym sposobem na radzenie sobie z tym problemem i dzielenie się zmiennymi poza zasięgiem było umieszczenie ich w globalnym zasięgu. To działało, ale pojawiły się jeszcze inne problemy.

Pierwszy problem: tagi skryptu powinny być w odpowiedniej kolejności, której nie można zmieniać. Jeśli ich kolejność zostanie zmieniona, to aplikacja wyrzuci błąd. Trochę utrudnia to sprawę, bo nigdy nie wiesz, co może się popsuć. Funkcje mają dostęp do wszystkiego w globalnym zasięgu, a więc nie wiadomo, które z nich zależą od którego skryptu. 

Kolejnym problemem był fakt, że kod, który jest częścią globalnego zasięgu, może zmodyfikować zmienną. A to sprawiłoby, że zarówno złośliwy, jak i normalny kod miałby dostęp do i możliwość modyfikowania zmiennych globalnych. 

No i potem wprowadzono pojęcie modułów, aby powyższe problemy rozwiązać.

Co moduły robią lepiej?

Moduły pozwalają na lepszą organizację i zarządzanie zmiennymi oraz funkcjami. Pozwalają na stworzenie paczki funkcji i zmiennych, które realizują wspólnie określone zadanie. Spowoduje to umieszczenie tych zmiennych w zasięgu modułu. Może on służyć do udostępniania zmiennych w danym module między funkcjami.


Ilustracja od Lin Clark


Pozwala to też na udostępnianie zmiennych dla innych modułów. Mogą one wyraźnie określić, która zmienna, klasa lub funkcja powinna być dostępna dla modułów zewnętrznych. Nazywa się to eksportem. Inne moduły mogą po wyeksportowaniu wyraźnie stwierdzić, że zależą od jakiejś zmiennej, klasy lub funkcji. Taka relacja jest dosyć wyraźna, więc będzie widać, które moduły się psują, jeśli któryś z nich zostanie usunięty. 

Gdy będziesz w stanie importować i eksportować zmienne oraz funkcje, to łatwiej będzie rozbijać kod na fragmenty, które mogą działać niezależnie. Korzystając z tych modułów, można zbudować aplikację, tak samo, jak buduje się przy pomocy klocków Lego.

W JavaScript było już dosyć dużo prób dodania takiej funkcji.


Istniejące podejścia do modułów


CommonJS

CommonJS używało się kiedyś w Node.js. Korzystając z Node’a, module.exports i require dostajemy out of the box. Niemniej jednak w przeciwieństwie do Node, przeglądarka nie obsługuje CommonJS. Co więcej, CommonJS ładuje moduły w sposób synchroniczny, więc nie jest to optymalne z punktu widzenia przeglądarki. 

Aby rozwiązać ten problem, można użyć takich narzędzi, jak Webpack lub Browserify.

//    filename: bar.js

//    dependencies
var $ = require('jquery');

//    methods
function myFunction(){};

//    exposed public method (single)
module.exports = myFunction;



Asynchronous Module Definition (AMD) 

Koncepcję tą stworzyła grupa programistów, którym nie podobał się kierunek, w jakim zmierzało CommonJS. AMD powstało w sumie jako odłam CommonJS we wczesnych fazach jego developmentu. Główną cechą charakterystyczną jest tutaj fakt, że AMD ładuje moduły w sposób asynchroniczny. Było to bardzo popularne w przeglądarkach, ponieważ czas wczytania aplikacji jest kluczowy w zapewnianiu pozytywnego UX. 

//    filename: bar.js
define(['jquery'], function ($) {
    //    methods
    function myFunction(){};

    //    exposed public methods
    return myFunction;
});



Universal Module Definition (UMD)

Wszyscy potrzebowaliśmy jakiegoś uniwersalnego podejścia, które sprawdziłoby się zarówno w świecie CommonJS i AMD. Okazało się niestety, że UMD nie było rozwiązaniem zbyt eleganckim i to pomimo tego, że obsługiwało zarówno AMD, jak i CommonJS oraz zmienne globalne.


Czym są Moduły ES

W JavaScript brakowało ujednoliconego i standardowego formatu definicji modułów. Nic więc dziwnego, że taki standard pojawił się w ES6. Statyczny charakter dyrektywy import i export umożliwia analizatorom statycznym tworzenie pełnego drzewa zależności bez uruchamiania kodu.

Rezultat ma ładną składnię i jest zgodny zarówno z synchronicznymi, jak i asynchronicznymi trybami pracy w przeglądarce. Moduły ES szybko stały się dostępne w przeglądarce. W Node.js było jednak nieco trudniej znaleźć rozwiązanie, które jest kompatybilne wstecznie i umożliwia stopniowe aktualizacje.

Natywne moduły ES w Node.js są już od dłuższego czasu dostępne za flagą experimental-modules. Oto przykłady.

JavaScript

//------ library.js ------
export const sqrt = Math.sqrt;
export function square(x) {
    return x * x;
}
export function diagonal(x, y) {
    return sqrt(square(x) + square(y));
}
//------ main.js ------
import {
    square,
    diagonal
} from 'library';
console.log(square(13)); // 169
console.log(diagonal(12, 5)); // 13
const app = document.getElementById("app");
app.innerHTML = "<h1>Demo App for ES Modules</h1>";
const input = document.getElementById("num");
input.addEventListener("change", displaySquare);

function displaySquare() {
    var sqrOutput = document.getElementById("sqr");
    sqrOutput.value = square(input.value);
}



HTML

<HTML>
    <head>
        <title>ES Modules Demo</title>
    </head>
    <body>
        <script type="module" src="./main.js" ></script>
        <div id="app"></div>
        <label>Input</label>
        <input id="num" type="number" placeholder="Enter number here"/>
       <br>
       <label>Output</label>
        <input id="sqr" type="number" disabled style="margin- top:20px"/>
    </body>
</HTML>


Jak widać w powyższym pliku HTML, musimy określić type="module" w tagu script, aby przeglądarka traktowała go jako moduł ECMAScript.


Kompatybilność wsteczna

Jeżeli chodzi o kompatybilność wsteczną, to możesz dodać nomodule w tagu script (gdzie kod JS to jeden spakowany plik). Przeglądarki obsługujące moduły ES będą wiedzieć, żeby to zignorować. Rozwiązanie to sprawdzi się nawet w najstarszych z nich. Ta odpowiedź ze StackOverflow bardzo dobrze to wyjaśnia.

Dodamy coś takiego w naszym pliku HTML.

<script type="module" src="./main.js" >
</script>
<script nomodule src="./fallback.js" >
</script>



Uwaga:

Jeśli testujesz to lokalnie, to musisz też uruchomić powyższy kod na serwerze, ponieważ pojawią się problemy z CORS. Więcej informacji tutaj. Moduły są importowane z bezwzględnymi lub względnymi ścieżkamii i muszą się zaczynać jako “/”, “./”, albo “../”.


Importy dynamiczne

Najnowsza wersja ES2020 zawiera importy dynamiczne. Aby zaimportować moduł w sposób dynamiczny, słowo kluczowe import może zostać wywołane jako funkcja. Kiedy użyjemy import w taki sposób, to z powrotem otrzymamy obietnicę. 

import('/modules/library.js')
  .then((module) => {
    // Do something with the module.
  });
//or using await
let module = await import('/modules/library.js');


Kompatybilność przeglądarek z Modułami ES 

Użycie natywnych modułów w danej przeglądarce zależy od instrukcji import oraz export, które są kompatybilne z przeglądarkami w następujący sposób:


Źródło: MDN Docs



Źródło: MDN Docs


Czy warto wybrać moduły ES?

Moduły ES są dla przeglądarek nowym standardem. Mając asynchroniczne ładowanie modułów out of the box, możesz spodziewać się szybszego startu i lepszej wydajności. Pomimo że cały czas możemy użyć CommonJS z dodatkowymi wtyczkami w przeglądarce, to raczej zalecam migrację na moduły ES, ponieważ to podejście natywne. 

ES pozwala załadować indywidualne moduły, zamiast jednego spakowanego pliku. Redukuje to rozmiar danych, które ładujemy. Kompatybilność z natywnymi modułami jest o tyle ważna, że zadecyduje, czy użyć modułów ES, czy jednak poprzestać na tworzeniu jednego pliku JS, który zadziała w każdej przeglądarce.

Jednym z problemów jest tutaj fakt, że pojedynczy spakowany plik będzie się zwiększał razem z aplikacją, co wydłuży też czas startu oraz zmniejszy wydajność. Można tego uniknąć, używając dzielenia kodu, co jest funkcją dostępną w nowych bundlerach.

Istnieją jednak przypadki, w których lepiej użyć takiego narzędzia, jak Webpack, zamiast modułów ES. Przy rzeczach takich, jak CSS, obrazy, czcionki oraz pliki danych (np. XML, CSV) lepiej jest użyć Webpacka, ponieważ zapewnia on asset bundling. Co więcej, powinno się również wziąć pod uwagę obsługę HTTP2 w przeglądarkach. Gdy używasz natywnych modułów, Twoja przeglądarka załaduje je osobno. 

Jednak, zamiast wysyłać kilka żądań HTTP przy pomocy HTTP2, możemy obsłużyć kilka żądań naraz, używając jednego połączenia. 96.49% przeglądarek korzysta z HTTP2, według tego, co znajdziemy na CanIUse. Kiedy rozwijasz aplikację, która musi działać na jednej z niekompatybilnych przeglądarek, to użyj webpacka. 

Dzieje się tak, ponieważ Twoja aplikacja może wymagać wysłania kilku żądań HTTP, aby załadować każdy moduł z osobna (jeżeli będziesz pracować z ES). Rzeczy mają się inaczej w Node.js. Ponieważ funkcja ta jest nadal w fazie eksperymentalnej, lepiej jest zostać przy CommonJS. 

Jeśli chcesz wypróbować modułów ES, zajrzyj tutaj. Kod źródłowy do powyższych przykładów znajduje się tutaj.  Aplikacja demo znajduje się tutaj.


Mam nadzieję, że dobrze wyjaśniłem, czym są moduły ES i dlaczego ich potrzebujemy. Miłego kodowania!


Oryginał tekstu w języku angielskim możesz przeczytać tutaj.

Rozpocznij dyskusję

Lubisz dzielić się wiedzą i chcesz zostać autorem?

Podziel się wiedzą z 160 tysiącami naszych czytelników

Dowiedz się więcej