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__….