DEV Community

Marcin Wosinek for Poznaj dev

Posted on • Updated on • Originally published at poznaj.dev

Jak korzystać z natywnych modułów ES

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";
Enter fullscreen mode Exit fullscreen mode

log.js:

console.log("Some test info");
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

Image description

Konkretny przykład w akcji

Problemy

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

I. 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;
Enter fullscreen mode Exit fullscreen mode

data-2.js:

var data = sin dolor;
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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.

II. 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);
Enter fullscreen mode Exit fullscreen mode

data.js:

const data = some data;
Enter fullscreen mode Exit fullscreen mode

display-data.js:

document.html = data;
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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";
Enter fullscreen mode Exit fullscreen mode

display-data.js:

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

document.body.innerHTML = data;
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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";
Enter fullscreen mode Exit fullscreen mode

display-data.js:

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

document.body.innerHTML = data;
Enter fullscreen mode Exit fullscreen mode

log-data.js:

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

console.log(data);
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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
);
Enter fullscreen mode Exit fullscreen mode

data.js:

export const data = "lorem ipsum";
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

Image description

Przykład 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, 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.

Top comments (0)