Karakteristike ES6 modula
Sa novom verzijom JavaScript-a ES6 dolazi integrisana podrška za modularno programiranje.
- Podržava module koji se smeštaju u datoteke – jedan modul po datoteci
- Moduli su jedinstveni primerci, svaki modul se izvršava samo jednom (singleton model).
- ES6 moduli mogu da rade i sinhrono i asihrono.
- Podržava “cyclic dependencies”.
- Sintaksa je još kompaktnija od CommonJS
- ES moduli imaju statičku strukturu. Pošto je struktura modula nepromenjiva, često je dovoljno da se pregleda kod i shvati šta se gde importuje. Ovo nije slučaj kod dinamičke strukture, gde je često potrebno da se kod izvrši da bi se videlo šta se importuje. Eventualne greške mogu da se nadju i u vreme kompajliranja (sa modul bundler-om), jer se sve radnje vezane za import i export modula odredjuju u tom trenutku.
- Svi uvezeni elementi su nepromenjivi iz modula koji importuje. Svaka operacija dodeljivanja vrednosti uveženom elementu bi prouzrokovala grešku (TypeError).
- ES6 se ne pravi kopiju svojstva već deli vezu na to svojstvo. Ovo važi čak i za deljenje primitivnih svojstava (“broj” ili “string”). U narednom primeru vidimo da ukoliko matični modul promeni vrednost deljenog svojstva (promena je izvršena metodom iz matičnog modula!), modul koji je importovao to svojsto “vidi” tu promenu.
PRIMER
proracun.js
1234export let brojac = 1;export function povecaj() {brojac++;}main.js
Importujemo sve elemente koji su eksportovani iz modula “proracun.js”
1234import {brojac} from './proracun';console.log(proracun.brojac); // 1proracun.povecaj(); // Pozivanje metotde iz uvezenog modulaconsole.log(proracun.brojac); // 2Ako bi se sada koristila promenjiva “brojač” u nekom “trećem” modulu imala bi vrednost 2.
Export modula
Standardno eksportovanje
Standardno eksportovanje se postiže stavljanjem rezervisane reči ispred deklaracije promenjive (funkcije):
1 2 3 4 5 6 7 8 |
export var foo = 1; export var foo = function () {}; export var bar; export let foo = 2; export let bar; export const foo = 3; export function foo () {} export class foo {} |
Imenovano eksportovanje
Imenovano (named) eksportovanje je sintaksa kojom na kraju modula definišemo elemente koji su za eksport. Naziv je potekao iz činjenice da se za definisanje eksportovanih elemenata, koriste njihova imena.
1 2 3 4 |
export {}; export {foo}; export {foo, bar}; export {foo as bar}; |
Podrazumevano eksportovanje
Preporuka je da se pri eksportovanju nekog modula definiše deo modula koji se podrazumevano eksportuje. Podrazumevano eksportovanje se definiše sa rezevisanom reči default. U jednom modulu je dozvoljen samo jedan podrazumevani eksport. Eksportovani deo se u drugom modulu koristi pod imenom “default”.
1 2 3 4 5 6 7 8 9 |
export default 42; export default {}; export default []; export default (1 + 2); export default foo; export default function () {} export default class {} export default function foo () {} export default class foo {} |
Defaultni eksport može da se definiše i sa “imenovanom formom” uz dodatkak ključne reči “as”:
1 2 |
export {foo as default}; export {foo as default, bar}; |
NAPOMENA: nije dozvoljena ovakva sintaksa (klasičan primer nedoslednosti sintakse ES6):
1 2 3 |
export default var=42; export default let=42; export default const=42; |
Re-eksportovanje
Re-eksporting znači eksportovanje iz modula koji nije matični.
1 2 3 4 5 6 7 8 9 |
export * from "foo"; export {} from "src/other_module"; export {foo} from "src/other_module"; export {foo, bar} from "src/other_module"; export {foo as bar} from "src/other_module"; export {foo as default} from "src/other_module"; export {foo as default, bar} from "src/other_module"; export {default} from "src/other_module"; export {default as foo} from "src/other_module"; |
Preporuke
Ukoliko modul ima priličan broj “razbacanih” elemenata koji se eksportuju, preporuka je da se na vrhu modula jasno i pregledeno definiše API kao na sledećem primeru:
1 2 3 4 5 6 7 8 |
// calculator.js const api = { add, subtract, multiply, divide }; function add(a,b) {...} function subtract(a,b) {...} function m ultiply(a,b) {...} function divide(a,b) {...} function somePrivateHelper() {...} export default api; |
Import modula
Karakteristike
- Deklaracija promenjive ili funkcije se u fazi kompajliranja diže na početak domena (hoisting). Zbog toga pozivanje funkcije u narednom primeru neće izbacivati grešku:
12foo();import { foo } from "foo"; - Svi uvezeni elementi su nepromenjivi iz modulua koji importuje. Svaka operacija dodeljivanja vrednosti u okviru modula (koji importuje) bi prouzrokovala grešku (TypeError). Medjutim u slučaju objekta možemo ga promeniti indirektno:
PRIMER
lib.js
1export let obj = {};main.js
123import { obj } from './lib';obj = {}; // TypeErrorobj.prop = 123; // OK - Nije dozvoljeno korišćenje promenjivih u import izrazu. Moduli su statični pa import ne sme da zavisi od nečega što je dobijeno u vreme izvršenja koda (runtime). Tako da je sintaksa u sledećem primeru pogrešna i vraća grešku:
1import foo from 'some_module'+SUFFIX; // Vraća grešku
- Uslovno učitavanje modula samo sa ES6 sintaksom nije moguće. Razlog za pomenuto je to što “import” naredba mora biti uvek “top level”, što se ne dobija uvlačenjem unutar if petlje. Stoga za ovakav slučaj mora da se koristi programibilan loader – System.JS koji bi imao neku dodatnu skriptu:
123456if (Math.random()) {System.import('some_module').then(some_module => {// Use some_module})}
Imenovani uvoz modula (named import)
1 2 3 4 5 |
import {} from "foo"; import {bar} from "foo"; import {bar, baz} from "foo"; import {bar as baz} from "foo"; import {bar as baz, xyz} from "foo"; |
Uvoz default elemenata
Importovanje podrazumevanih vrednosti se vrši jednostavnim pozivanjem imena modula ili imenskim pozivanjem.
1 2 |
import foo from "foo"; import {default as foo} from "foo"; |
Uvoz celog modula (Import namespace object)
Globalni import koristimo ukoliko želimo da u jednoj naredbi učitamo sve eksportovane elemente jednog modula. Import namespace (imenskog prostora) zahteva sintaksu * as.
1 |
import * as bar from "foo" |
Nakon čega su dostupni svi elementi iz modula “foo” u modulu pod “novim” imenom “bar”:
1 2 |
bar.x; bar.baz(); |
Ako je u modulu koji se exportuje bilo definisanih podrazumevanih (default) vrednosti, nakon importa celog imenskog prostora, on se poziva sa “default”:
PRIMER
proracun.js
1 2 3 4 |
export default function povecaj() { brojac++; } export function bar(){} |
main.js
1 2 3 |
import * as proracun from './proracun'; proracun.bar(); proracun.default(); |
Putešestvije do produkcije
Pošto ES6 sintaksa nije još uvek dovoljno podržana u browser-ima, potrebno je pre stavljanja u produkciju izvršiti odredjene predradnje:
- Izbor transpilera (Babel Typescript…)
- Izbor formata u koji želimo da prebacimo tokom transpiliranja (AMD ili CommonJS).
- Transpiliranje koda
- Izbor menadžera zavisnosti “modul loader” (require.js ili SystemJS) ili “modul bundler” (Browswerify ili Webpack).
- Integracija “dependencies manager-a”
Iako je ovaj put prilično trnovit, smatra se da su ES6 moduli budućnost i da će tokom vremena istisnuti sve do sada korišćene formate, pa čak i CommonJS na serverskoj strani.