Poznajemy, jak działają moduły w JavaScript. Lekcja szybka, łatwa, przyjemna a zarazem konieczność, aby pójść dalej z materiałem.

Ok, tworzymy testowy plik html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script type="module" src="./mymodule.js"></script>
</body>
</html>

Type module oznacza klika rzeczy:

  • Wewnątrz tego pliku można stosować importy z innych plików JS
  • Ten plik jest deferred z automatu
  • Ten plik może posiadać top level awaity
  • Inne pliki, z których się importuje, muszą wyeksportować coś
  • Nieważne co eksportują, najlepiej, aby nie miały żadnego innego kodu (wywołań funkcji), bo przy każdym imporcie jest egzekucja kodu i nie mamy tu nawet if name == main (znanego z Pythona)
  • To co importowane jest hoistowane, więc jak importujemy to raczej mniej się przejmujemy w jakiej kolejności to jest poukładane w jakimś tam pliczku js

Dokumentacja MDN pokazuje, że można dodawać słówko kluczowe export do każdej funkcji jaką eksportujemy (plik inny niż module, jakiś inny pliczek, przypominam):

export const name = "square";

export function draw(ctx, length, x, y, color) {
  ctx.fillStyle = color;
  ctx.fillRect(x, y, length, length);

  return { length, x, y, color };
}

I teraz w pliczku z type module możemy zrobić to:

import { name, draw, reportArea, reportPerimeter } from "./modules/square.js";

A co z funkcjami, które nie mają słówka kluczowego export? Cóż, funkcje export z tego samego pliku mogą z nich korzystać, ale te importowane już nie mają do nich dostępu.

Czyli mamy taki pliczek:

function getName(){
    return "John Doe";
}

function getAge(){
    return 42;
}

export function getInfo(){
    return `Name: ${getName()} Age: ${getAge()};`
}

Robimy import w pliku z type module:

import { getInfo } from "./importme2.js";
console.log(getInfo());
//Name: John Doe Age: 42;
console.log(getName());
//mymodule.js:8 Uncaught ReferenceError: getName is not defined

I tak to działa. Może nam się wydawać, że to dlatego, że getName nie importujemy. To spróbujmy:

import { getInfo, getName } from "./importme2.js";
//SyntaxError: The requested module './importme2.js' does not provide an export named 'getName

Teraz NIC nam się nie wyświetla, poza tym errorem. Bo chcieliśmy importować coś, co eksportowane nie jest…

Możemy też próbować zaimportować coś z gwiazdką, ale w odróżnieniu od Pythona, JS nie pozwala na wildcarda używanego bez aliasu (i bardzo dobrze):

import * as imp1 from "./importme2.js";
console.log(imp1.getInfo());
//Name: John Doe Age: 42;
console.log(imp1.getName());
//mymodule.js:8 typeError: imp1.getName is not a function

Mimo wszystko, obowiązuje ta sama zasada – funkcje bez eksportu są dostępne tylko dla funkcji wewnątrz tego pliku. A te z eksportem można importować (w type module), zaś te z tego samego pliku, ale bez export, to taka enkapsulacja, takie prywatne metody (i zmienne, zmienne też temu podlegają), dostępne tylko w tym jednym pliku.

Druga zasada, która obowiązuje, to nie wołaj żadnych funkcji, bo na każdy import odpala się kod:

function getName(){
    return "John Doe";
}

function getAge(){
    return 42;
}

export function getInfo(){
    return `Name: ${getName()} Age: ${getAge()};`
}

console.log("You cant run getName and getAge");
console.log("You cant import getName and getAge");
console.log("You CAN import getInfo");
console.log("getInfo CAN use getName and getAge");
console.log("And btw, you will see this msg each time you import getInfo...");

Importować getName i getAge nie możemy (one są prywatne, dostępne tylko dla innych w tym samym pliku), importować getInfo możemy, każdy import odpala kod, w tym wypadku console logi, więc się nie wrzuca wywołań i poleceń do kodów, z których się importuje, tylko definicje.

Ok, zróbmy inny pliczek do importu:

function useReactive(){
    console.log("use reactive");
    return 42;
}

console.log("importme.js");


export default useReactive;

I teraz użyjmy:

import useReactive from "./importme.js";

let variable = useReactive();
console.log(variable);

Zobaczymy console loga a potem używamy normalnie tej funkcji. Defaulty możemy też importować, nie znając nazwy oryginalnej:

import {default as idkName } from "./importme.js";

let variable = idkName();
console.log(variable);

Export default może eksportować tylko jedną rzecz. Mamy też named exports, czyli zamiast słówka kluczowego export robimy coś takiego:

function getName(){
    return "John Doe";
}

function getAge(){
    return 42;
}

function getInfo(){
    return `Name: ${getName()} Age: ${getAge()};`
}

export {getInfo};

Co jeszcze warto wiedzieć? Cóż, moduły mogą mieć preloada. Tag link z rel=”modulepreloaded”.

Druga rzecz – ponoć w node można zrobić takie coś:

if (require.main === module) {
    console.log('called directly');
} else {
    console.log('required as a module');
}

Ma to być odpowiednik Pythonowego if name == __main__….