poznaj.dev

poznaj.dev

Jak korzystać z natywnych modułów ES

Jak korzystać z natywnych modułów ES

Marcin Wosinek's photo
Marcin Wosinek
·Jan 9, 2022·

5 min read

W niniejszym artykule prezentuję przykłady modułów ECMAScript (ES) – co można z nimi zrobić i jakie są ich ograniczenia. Moduły ES są obsługiwane przez wszystkie przeglądarki wydane po maju 2018 roku, więc możesz założyć, że można z nich bezpiecznie korzystać w większości przypadków.

Image description

źródło

Kodowanie bez modułów ES

Przed powstaniem modułów ES wszystkie JS trzeba było importować globalnie. Każdy plik miał dostęp do wcześniej zdefiniowanych zmiennych i mógł zostawić informację dla kodu wykonywanego później. Kolejność importowania miała znaczenie, zwłaszcza że elementy zaimportowane później mogły zmienić te, które zostały zaimportowane wcześniej. Starsze importy wyglądały mniej więcej tak:

display-data.js:

document.body.innerHTML = "lorem ipsum";

log.js:

console.log("Some test info");

index.html:

<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <title>No modules</title>
    <link rel="shortcut icon" href="#" />
  </head>

  <body>
    <script src="./display-data.js"></script>
    <script src="./log.js"></script>
  </body>
</html>

Image description

Konkretny przykład w akcji.

Problemy

Podejście to jest problematyczne głównie z dwóch powodów:

  1. Zanieczyszcza zakresu globalnego przez kod. Jeśli kilka plików określa tę samą wartość, będą one ze sobą kolidować i będą się na siebie wzajemnie nadpisywać: powodzenia w szukaniu i naprawianiu powstałych w związku z tym bugów. Przykład: data-1.js:

    var data = “lorem ipsum”;
    

    data-2.js:

    var data = “sin dolor”;
    

    index.html:

    <html>
    <head>
     <meta http-equiv="content-type" content="text/html; charset=utf-8" />
     <title>Name collision</title>
     <link rel="shortcut icon" href="#" />
    </head>
    
    <body>
     <script src="./data-1.js"></script>
     <script src="./data-2.js"></script>
     <script>
       document.body.innerHTML = data;
     </script>
    </body>
    </html>
    

    Przykład. Obchodzenie tego problemu polegało najczęściej na wykorzystywaniu natychmiastowo wywoływanego wyrażenia funkcyjnego. Pozwalało to na izolację bloków kodu i chroniło przed zanieczyszczeniem zakresu globalnego, ale jednocześnie sprawiało, że kod stawał się bardziej zagmatwany.

  2. Wszelkimi zależnościami trzeba zarządzać ręcznie. Jeśli jeden plik zależy od drugiego, pliki te trzeba zaimportować w odpowiedniej kolejności. Na przykład: log-data.js:

    console.log(data);
    

    data.js:

    const data = ‘some data’;
    

    display-data.js:

    document.html = data;
    

    index.html:

    <html>
    <head>
     <meta http-equiv="content-type" content="text/html; charset=utf-8" />
     <title>File order</title>
     <link rel="shortcut icon" href="#" />
    </head>
    
    <body>
     <script src="./log-data.js"></script>
     <script src="./data.js"></script>
     <script src="./display-data.js"></script>
    </body>
    </html>
    

    Jak widać tutaj, część wyświetlająca dane działa, jak należy, a część z logowaniem danych – nie.

Moduły ES w akcji

Co się zmieni, gdy to samo zrobimy z modułami ES? Przede wszystkim zależności definiowane są na poziomie kodu. Jeśli więc chcesz, żeby plik wykorzystywał wartości z innego pliku, definiujesz to bezpośrednio w tym pliku. Podejście to wiele zmienia, zwłaszcza w zakresie czytania kodu: aby poznać cały kontekst stosowany przez konkretny plik, wystarczy go otworzyć i przeczytać.

Jak zatem stosować moduły ES?

data.js:

export const data = "lorem ipsum";

display-data.js:

import { data } from "./data.js";

document.body.innerHTML = data;

index.html:

<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <title>Simple modules</title>
    <link rel="shortcut icon" href="#" />
  </head>

  <body>
    <script type="module" src="./display-data.js"></script>
  </body>
</html>

Główne zmiany wprowadzone w kodzie:

  1. dodanie type=”module” do importu <script> w pliku HTML;
  2. zastosowanie eksportowych i importowych słów kluczowych w plikach JS w celu zdefiniowania i załadowania modułów.

Image description

Działający przykład.

Import tego samego pliku przez kilka innych plików

Opisywany przykład można urozmaicić poprzez zaimportowanie tych samych plików dwukrotnie. Jako że każdy z plików musi być niezależny od tego drugiego, import zostanie dodany dwa razy – osobno dla każdego pliku. Przeglądarki zarządzają importem prawidłowo i ładują dany plik tylko raz.

data.js:

export const data = "lorem ipsum";

display-data.js:

import { data } from "./data.js";

document.body.innerHTML = data;

log-data.js:

import { data } from "./data.js";

console.log(data);

index.html:

<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <title>Shared import</title>
    <link rel="shortcut icon" href="#" />
  </head>

  <body>
    <script type="module" src="./display-data.js"></script>
    <script type="module" src="./log-data.js"></script>
  </body>
</html>

Image description

Przykład

Leniwe ładowanie aka lazy load

Lazy load opóźnia ładowanie aplikacji do chwili, w której kod zaczyna być potrzebny. Ta technika optymalizacji jest bardziej skomplikowana niż ładowanie wszystkiego od razu, ale pozwala na większą kontrolę nad tym, co i kiedy się ładuje. W poniższym przykładzie ładuję i wyświetlam dane z około półsekundowym opóźnieniem:

display-data.js:

setTimeout(
  () =>
    import("./data.js").then(({ data }) => {
      document.body.innerHTML = data;
    }),
  500
);

data.js:

export const data = "lorem ipsum";

index.html:

<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <title>Lazy load</title>
    <link rel="shortcut icon" href="#" />
  </head>

  <body>
    <script type="module" src="./display-data.js"></script>
  </body>
</html>

Image description

[Przykład lazy load] (how-to-js.github.io/es-modules/lazy-load)

Czy moduł ES obejmuje wszystko, czego potrzeba w nowoczesnym JS?

Chociaż natywne moduły ES znacznie poprawiają wcześniejsze modele wprowadzania nowych elementów, brakuje im kilku cech kluczowych w nowoczesnym procesie programowania w JavaScript. Aktualnie nie można wykonać następujących działań:

  1. Importu typów innych niż JS. Obecnie przygotowywane są inne pliki [JSON] (tc39.es/proposal-json-modules), ale minie jeszcze trochę czasu, zanim będą one obsługiwane przez przeglądarki.
  2. Importu zewnętrznych bibliotek tak jak w Node.js. Można by skopiować pliki podczas kompilacji i zaimportować je z lokalizacji w node_modules, ale proces ten wydaje się dużo bardziej skomplikowany niż zwyczajne import “library”.
  3. Transpilacji. Wiele nowoczesnych kodów JS pisze się w różnych językach – na przykład w TypeScript. Nawet czysty JS wymaga transpilacji do obsługi starszych przeglądarek lub wykorzystywania najnowszych funkcji języków.

Z tych względów większość projektów wykorzystuje narzędzia typu JS bundler, czyli swego rodzaju kompilatorów, które przygotowują build na poszczególne wdrożenia. Jeśli zaciekawił Cię temat bundlerów, daj znać w komentarzu i sprawdź poniższe linki.

Linki

Podsumowanie

W niniejszym artykule omówiłem istotne przypadki użycia modułów ES. Kolejnym krokiem będzie skonfigurowanie JS bundlera tak, aby obejść ograniczenia modułów natywnych.

 
Share this