Moduly v JavaScripte

Časť 3: UMD a natívne moduly

Milan Herda, 01/2017, úpravy 09/2022

UMD

Problém

Som vývojár populárnej javaScriptovej knižnice

Problém

Som vývojár populárnej javaScriptovej knižnice

a natívne moduly ešte nie sú "in"

Moji používatelia pracujú

  • niektorí v globálnom namespace
  • niektorí používajú CJS moduly
  • ďalší používajú AMD moduly

Akým spôsobom budem poskytovať knižnicu?

  • je lepšie ju mať ako globálnu premennú
  • AMD modul?
  • CJS modul?
  • tri samostatné verzie a nech si používateľ stiahne tú svoju?

Odpoveď

Všetky verzie v jednom súbore

UMD

Universal Module Definition

UMD

Modul bez závislostí

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        define([], factory);
    } else if (typeof module === 'object' && module.exports) {
        module.exports = factory();
    } else {
        root.ourModuleName = factory();
    }
}(typeof self !== 'undefined' ? self : this, function () {
    // ...
    return { /* ... */ };
}));

UMD

Modul so závislosťou na knižnici foo

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        define(['foo'], factory);
    } else if (typeof module === 'object' && module.exports) {
        module.exports = factory(require('foo'));
    } else {
        root.ourModuleName = factory(root.foo);
    }
}(typeof self !== 'undefined' ? self : this, function (foo) {
    // ...
    return { /* ... */ };
}));

Natívne moduly

ESM

Natívne moduly

  • prišli v roku 2015 so špecifikáciou JavaScriptu ES2015 (aka ES6)
  • preto skratka ESM (EcmaScript Modules)
  • trvalo roky, kým sa stali automaticky podporované v rôznych prostrediach (stále nie sme v ideálnom stave)

Natívne moduly

import
  • Príkaz, pomocou ktorého importujeme veci z iného modulu
  • Môže sa použiť iba v top-level úrovni (tj. nesmie byť v blokoch) a musí predchádzať iným príkazom

import abc from './utils/module1';
import { foo, bar } from './utils/module2';

foo(abc);

bar();

Natívne moduly

export
  • týmto príkazom určujeme, ktoré časti modulu budú dostupné pre vonkajší svet
  • vieme ho použiť viackrát v jednom module

const abc = 'abc';

export abc;
export const bar = 'bar';

const foo = 'foo';

export default foo;

Natívne moduly

Vytvorenie projektu

yarn create vite js-modules-3 --template=vanilla

# alebo

npm create vite js-modules-3 --template=vanilla

Natívne moduly

Spustenie projektu

yarn dev

# alebo

npx run dev

Úloha

  • vytvorte súbor src/theme/colors.js
  • súbor bude obsahovať konštanty definujúce farby pre našu aplikáciu. Farby sú PRIMARY, SECONDARY, ERROR, WARNING, INFO
  • každú farbu exportujte samostatne
  • zmažte doterajší obsah main.js
  • v main.js naimportuje primárnu farbu a použite ju ako farbu pozadia pre nový div element

Riešenie

src/theme/colors.js

export const PRIMARY = '#000080';
export const SECONDARY = '#008000';
export const WARNING = 'orange';
export const ERROR = '#800000';
export const INFO = '#2266cc';

Riešenie

main.js

import { PRIMARY } from './src/theme/colors.js';

const colorInfo = (name, color) => {
    const divStyle = `
        width: 300px;
        height: 100px;
        background-color: ${color};
    `;
  
    const titleStyle = `
        background-color: rgba(255, 255, 255, 0.5);
    `;

    return `
        <div style="${divStyle}">
            <h3 style="${titleStyle}">${name}</h3>
        </div>
    `;
};

document.querySelector('#app').innerHTML = colorInfo('PRIMARY', PRIMARY);

Export

Named exports

Pomenované exporty

Named exports

  • každá vec sa z modulu exportuje pod nejakým svojím menom
  • pod týmto menom je potom možný aj jej import
// colors.js
export const PRIMARY = '#0000cc'

// main.js
import { PRIMARY } from './src/theme/colors.js';

Pomenované exporty

Majú viacero "foriem"

export const PRIMARY = '#0000cc';
export const SECONDARY = '#00cc00';
export const WARNING = 'orange';
export const ERROR = '#cc0000';
export const INFO = '#2266cc';

Export spojený priamo s deklaráciou

const PRIMARY = '#0000cc';
const SECONDARY = '#00cc00';
const WARNING = 'orange';
const ERROR = '#cc0000';
const INFO = '#2266cc';

export {
    PRIMARY,
    SECONDARY,
    WARNING,
    ERROR,
    INFO,
};

Export predtým nadeklarovaných mien

Tzv. export zoznamu

const PRIMARY = '#0000cc';
const SECONDARY = '#00cc00';
const WARNING = 'orange';
const ERROR = '#cc0000';
const INFO = '#2266cc';

export {
    PRIMARY,
    SECONDARY,
};

export {
    WARNING,
    ERROR,
    INFO,
};

Môžeme exportovať viac zoznamov z jedného modulu

const PRIMARY = '#0000cc';
const SECONDARY = '#00cc00';
const WARNING = 'orange';
const ERROR = '#cc0000';
const INFO = '#2266cc';

export {
    PRIMARY as mainColor,
    SECONDARY,
};

export {
    WARNING as itsFine,
    ERROR as itEscalatedQuickly,
    INFO,
};

V zozname môžeme robiť premenovávanie

Default export

Default export

  • z každého modulu môže byť jedna vec určená ako tzv. defaultný export
  • importujúci modul si ju vloží pod takým menom, akým chce

  • pri importe sa nepoužívajú { }

const PRIMARY = '#0000cc';
// export const PRIMARY = '#0000cc';

export default PRIMARY;
import mainColor from './src/theme/colors.js';

Default export

  • keďže exportovaná vec nemusí mať názov, tak ňou môže byť hocijaký výraz
export default 1 + 1;
import result from './subor.js';

Default export

  • export zoznamu vieme "zneužiť" na urobenie default exportu
export {
    foo,
    bar as default,
    baz,
};
import result from './subor.js';

Import

Import pomenových exportov

Import pomenovaných exportov

  • importované veci sa uvádzajú vo vnútri { }
  • vymenujeme len tie, ktoré potrebujeme
import { PRIMARY, SECONDARY, ERROR } from './src/theme/colors.js';

Import pomenovaných exportov

  • ak modul potrebuje, môže počas importu urobiť premenovanie
import {
    PRIMARY as mainColor,
    SECONDARY,
    ERROR
} from './src/theme/colors.js';

Import pomenovaných exportov

  • pomocou * vieme naimportovať všetko z modulu do jedného objektu
  • naimportuje sa aj prípadny default export pod kľúčom default
import * as colors from './src/theme/colors.js';

console.log(colors.PRIMARY);
console.log(colors.default);

Import default exportu

Import default exportu

  • default export vieme naimportovať mimo { }
  • názov premennej, do ktorej sa default export vloží, je zálezitosťou importujúceho modulu
import bar from './foo.js';

Import default exportu

  • v jednom import príkaze vieme kombinovať default export aj pomenovaný
import mainColor, { PRIMARY, SECONDARY } from './foo.js';

Import default exportu

  • default export vieme naimportovať aj použitím kľúča default v zozname pomenovaných importov
  • musíme ho ale premenovať
import { PRIMARY, default as mainColor } from './foo.js';

Import pre potreby exportu

Import pre potreby exportu

export * from "./subor.js";
export * as foo from "./subor";
export { PRIMARY, SECONDARY } from "./src/theme/colors.js";
export { 
    PRIMARY as primaryColor,
    SECONDARY as secondaryColor
} from "./src/theme/colors.js";
export { default, PRIMARY, SECONDARY } from "./src/theme/colors.js";

Občas potrebujeme naimportovať veci z viacerých modulov len preto, aby sme ich ďalej exportovali.

  • napr. v agregovaných súboroch pre viacero modulov (redux, zložené komponenty...)

Import pre potreby exportu

export { 
    default,
    PRIMARY,
    SECONDARY as secondaryColor,
} from "./src/theme/colors.js";

Jediný spôsob, ako reexportovať jedným príkazom default aj pomenový export, je pomocou zoznamu

Ak default nebude premenovaný, stáva sa default exportom pre aktuálny modul

Na čo si dať pozor

  • importovaný identifikátor má živé prepojenie so svojím modulom
  • do importovaného identifikátora nevieme priradiť novú hodnotu:
    • pre klientský kód sa správa rovnako ako const premenná
    • pre zdrojový modul však ako let

Zhrnutie

Moduly robíme, pretože

  • JS kódu je veľa
  • potrebujeme časti s jasnou zodpovednosťou
  • modul je samostatný a tak znovupoužiteľný
  • nechceme prasiť globálny namespace a zvyšovať tak riziko konfliktu názvov

Naučili sme sa:

  • platnosť premenných v JS
  • čo je to closure
  • čo je to IIFE
  • ako vyzerá module pattern
  • 3 rôzne implementácie modulov:
    • CommonJS
    • AMD
    • UMD
    • ESM
  • že technológia nemusí byť najlepšia, aby sa presadila. Stačí, že je dosť dobrá a k dispozícii

Vyskúšali sme si:

  • npm a yarn
  • webpack
  • RequireJS a r.js
  • vite

Videli sme:

  • príklady, ako používať rôzne modulové systémy na webe a serveri
  • ako by mohla vyzerať základná adresárová štruktúra JS projektu
  • ako sa moduly riešili kedysi a dnes

Ďakujem za pozornosť