Webpack

Načítání Three.js knihovny přes script tag, jak jsme to dělali v minulé části, má pár omezení. První omezení je, že nemáme k dispozici všechny třídy, které pro práci s Three.js můžeme potřebovat. Pokud bychom je potřebovali, tak bychom si pro ně museli najít soubory v zazipovaném souboru, který jsme si stáhli a stejně jako samotné Three.js si je na stránku přidat přes script tag, což není moc komfortní. Je to tak proto, aby výsledný soubor knihovny nebyl zbytečně moc velký, když některé její části nepotřebujeme. Další omezení je, že z bezpečnostích důvodů by nám prohlížeč nedovolil načíst třeba textury, když bychom si html soubor jen tak otevřeli v prohlížeči. Pro práci s Three.js potřebujeme na svém počítači spustit webový server a stránku otevírat přes něj. Z těchto důvodů je lepší si Three.js nainstalovat přes NPM a použít nějaký sestavovací nástroj jako je Webpack. To v této části uděláme.

Co je NPM

Pokud nevíte co je to NPM a ještě jste jej nikdy nepoužívali, tak bych vám chtěl jen krátce popsat co to je a jak si jej nainstalujete. NPM je zkratka pro Node Package Manager. Jedná se o takovou službu, která obsahuje obrovské množství balíčků (knihoven), které si můžeme nainstalovat zavoláním jednoho příkazu a začít používat. Balíčky si můžeme klidně vyhledávat na oficiálních stránkách NPM.

NPM se vám automaticky nainstaluje, když si nainstalujete NodeJS, což je prostředí pro psaní JavaScriptu na straně serveru. Umožňuje nám tedy programovat backend v JavaScriptu. NodeJS se zabývat nebudeme, stačí nám jen vědět, že jej potřebujeme nainstalovat aby se nám nainstalovalo NPM. NodeJS můžete stáhnout z oficiálních stránek. Pokud potřebujete s instalací pomoct, tak určitě na internetu najdete spoustu tutoriálů, ale není to nic složitého.

Co je Webpack

Webpack je nástroj, který slouží k sestavování webových aplikací. Je určen hlavně pro JavaScript, ale můžeme s jeho pomocí zpracovávat i jiné typy souborů. Funguje tak, že vezme různé soubory, zpracuje je a sloučí do menší skupiny souborů, které se použijí pro produkční užití.

Webpack je velmi konfigurovatelný a jeho nastavování nemusí být nic jednoduchého. Kvůli tomu si myslím, že může hodně lidí odradit od toho aby se jej naučili a používali. Když jsem se jej zkoušel naučit poprvé, tak jsem od toho nakonec upustil a webové aplikace jsem si stále sestavoval ručně. Podruhé už se mi ale podařilo mu o něco víc porozumět a zjistil jsem, že v podstatě ani nemusím dopodrobna vědět jak všechno funguje. Je to také hodně o tom, umět si vygooglovat jak něco nakonfigurovat. Pokud byste se o webpacku chtěli dozvědět více, tak o něm mám webové stránky, na kterých se nachází menší tutoriál. Klidně si můžete tento tutoriál přečíst a potom se sem vrátit. Díky tomu pro vás bude tato část o něco jednodušší.

Instalace Three.js přes NPM

K používání NPM bychom měli alespoň trochu umět pracovat s příkazovým řádkem. Používáme jej totiž přes příkazový řádek. Nemusíme toho umět moc, stačí nám umět přepnout se v příkazovém řádku do složky s naším projektem. Poté už můžeme psát jen příkazy týkající se NPM. Pro přepínání do složky se používá příkaz cd. Můžeme jej použít následujícím způsobem.

cd D:\moje-slozka

Tento příkaz funguje v operačním systému Windows. V jiných operačních systémech to může být třeba jinak. Já nejsem na příkazový řádek žádný expert, takže to si budete muset kdyžtak zjistit jinde.

V této části si vytvoříme nový projekt, takže si pro něj někde vytvořte složku a v příkazovém řádku se do ní přepněte. Poté můžeme NPM začít používat a nainstalovat si pro náš projekt nějaké balíčky. Než to ale uděláme, tak si pro projekt musíme vytvořit soubor package.json. Soubor package.json nám bude sloužit k uchování, které balíčky je potřeba pro náš projekt nainstalovat a také v něm budeme mít napsaný skript na spuštění webpacku. Vytvoříme jej pomocí příkazu npm init, který se nás po jeho spuštění zeptá na pár otázek. Můžete to všechno odentrovat a nic tam nepsat.

npm init

Poté co se nám soubor package.json vytvoří, můžeme pro projekt nainstalovat nějaké balíčky. Nainstalujeme si Three.js. Provedeme to zavoláním následujícího příkazu.

npm install three --save

Po instalaci by vám ve složce projektu měla přibýt složka node_modules. Tato složka bude obsahovat všechny balíčky (knihovny), které pro projekt nainstalujeme. Možnost --save říká, že se má balíček připsat do souboru package.json. Pokud bychom tedy později složku node_modules smazali, tak můžeme jen napsat příkaz "npm install" bez žádných další parametrů a balíčky se nám znovu nainstalují. Výhoda je také ta, že kdybychom chtěli náš projekt třeba někomu poslat k otestování, tak mu složku node_modules, která může zabírat dost místa, ani nemusíme posílat. Poslali bychom mu jen soubor package.json a on by si balíčky nainstaloval pomocí příkazu "npm install".

Souborová struktura projektu

Než si do projektu přidáme Webpack a nakonfigurujeme si jej, tak si ujasníme jakou by měl náš projekt mít souborovou strukturu. Podle toho potom budeme Webpack konfigurovat. Uděláme to třeba tak, že budeme mít složku src, kde budeme psát veškerý náš kód a složku static, ve které budeme mít všechny ostatní soubory jako jsou textury, 3D modely a tak podobně. Ve složce src můžeme pro začátek vytvořit HTML soubor (index.html), CSS soubor (style.css) a JavaScript soubor (main.js). Do HTML souboru můžeme napsat základní HTML kostru a přidat tam canvas, kterému přiřadíme nějaké id. JavaScript a CSS soubor zde ale nepřipojujeme, to necháme na Webpacku.

<!DOCTYPE html>
<html lang="cs">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Three.js projekt</title>
</head>
<body>
    <canvas id="WebGLCanvas"></canvas>
</body>
</html>

Do JavaScript souboru zatím můžeme napsat třeba console.log, abychom viděli jestli jej nám Webpack k HTML souboru připojil až jej budeme konfigurovat.

console.log("připojeno");

To je pro zatím vše. Můžeme se pustit do konfigurace Webpacku.

Konfigurace Webpacku

Abychom mohli Webpack začít používat, tak si jej musíme nainstalovat. Uděláme to stejným způsobem jako při instalaci Three.js. V případě webpacku si ale potřebujeme nainstalovat dva balíčky. První má název "webpack" a ten představuje samotný Webpack. Druhý má název "webpack-cli" a ten představuje command line interface pro Webpack. Ten zajišťuje abychom mohli Webpack používat z příkazového řádku (nebo skriptu v package.json). Pokud chceme pomocí příkazu npm install nainstalovat více balíčků, tak je můžeme oddělit mezerou. A protože je Webpack nástroj, který potřebujeme k vývoji a ne pro produkční spuštění aplikace, tak použijeme namísto možnosti --save možnost --save-dev. Co se týče naší aplikace, tak je v podstatě jedno jestli použijeme --save nebo --save-dev, protože naši aplikaci pomocí Webpacku budeme jen sestavovat a nebudeme projekt posílat nikam na server, aby se tam automaticky nainstaloval. I tak ale používejte --save-dev, když instalujete něco co potřebujete jen pro vývoj.

npm install webpack webpack-cli --save-dev

Konfigurační soubor

Po instalaci můžeme začít s konfigurací Webpacku. K tomu potřebujeme konfigurační soubor. Vytvořte jej v kořenové složce projektu a pojmenujte jej třeba jako webpack.config.js. Konfigurační soubor musí vracet objekt, ve kterém Webpack konfigurujeme. Objekt exportujeme jako CommonJS module pomocí module.exports. Pokud používáte NodeJS, tak jste se s tím už pravděpodobně setkali. Pokud ale ne, tak je to stejné jako "export default" v ES6 modulech v klasickém JavaScriptu. A pokud jste nikdy nepoužívali moduly, tak nevadí, prostě to berte tak, že v konfiguračním souboru musíme k module.exports přiřadit objekt s konfigurací aby jej Webpack mohl použít.

module.exports = {
    // v tomto objektu Webpack konfigurujeme
}

Webpack začíná náš kód zpracovávat z nějakého vstupního bodu, který mu určíme. Nastavíme jej pomocí vlastnosti entry, kde můžeme předat cestu k našemu JavaScript souboru. Pro nás je to soubor main.js ve složce src.

module.exports = {
    entry: "./src/main.js"
}

Kromě vstupního bodu musíme také nastavit, kde má Webpack umístit výsledný kód. To nastavíme pomocí vlastnosti output. Této vlastnosti předáváme objekt s vlastností path, kde nastavíme absolutní cestu ke složce a můžeme specifikovat i pár dalších nastavení. Mi ještě nastavíme, že se má obsah složky před umístěním výsledných souborů promazat. Protože musí být cesta ke složce absolutní, tak pro její vytvoření použijeme metodu resolve vestavěného NodeJS modulu jménem path. Jako parametr jí předáme cestu ke složce ve které se nachází konfigurační soubor (__dirname) a název složky do které chceme umístit výsledný kód. Tato složka ani nemusí existovat, automaticky se vytvoří. Výslednou složku pojmenujeme třeba jako "dist", což se používá dost často.

const path = require("path");

module.exports = {
    entry: "./src/main.js",
    output: {
        path: path.resolve(__dirname, "dist"),
        clean: true
    }
}

Nyní si můžeme zkusit Webpack spustit a v kořenové složce projektu by nám měla přibýt složka dist se zpracovaným JavaScript kódem. Ten bude úplně stejný jako ten nezpracovaný, protože tam zatím máme jen jeden řádek s console.log. Ke spuštění Webpacku si ale v souboru package.json musíme napsat skript. Skripty píšeme v části scripts a jeden testovací by tam už měl být přichystán. Ten můžete smazat a nahradit jej za ten, který ukazuje následující ukázka.

{
    "name": "threejs-startovni-kod",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "scripts": {
        "build": "webpack --config webpack.config.js --mode production"
    },
    "author": "",
    "license": "ISC",
    "dependencies": {
        "three": "^0.143.0"
    },
    "devDependencies": {
        "webpack": "^5.74.0",
        "webpack-cli": "^4.10.0"
    }
}

Skript, který předchozí ukázka ukazuje, spouští Webpack s konfiguračním souborem webpack.config.js v módu pro produkci. Skripty spouštíme pomocí příkazu "npm run název-skriptu". Náš skript tedy spustíme tímto způsobem:

npm run build

Po spuštění skriptu by se nám v kořenové složce projektu měla vytvořit složka dist s výsledným JavaScript souborem.

Zpracování HTML souboru

Chceme samozřejmě aby se nám do dist složky umístil také HTML soubor. Webpack ale v základu rozumí jen JavaScriptu (a JSON souborům). Proto si na to budeme muset nainstalovat plugin a říct Webpacku aby jej použil. Použijeme plugin jménem html-webpack-plugin. Můžeme jej nainstalovat jako jakýkoliv jiný NPM balíček.

npm install html-webpack-plugin --save-dev

Po instalaci můžeme plugin použít. Pluginy nastavujeme tak, že je přidáváme do pole v části plugins. Plugin můžeme klidně použít více než jednou a proto vytváříme jeho instanci. V konstruktoru specifikujeme pomocí vlastnosti template cestu k HTML souboru, který se má zpracovat. Co se jak nastavuje si vždycky najdete v dokumentaci daného pluginu.

const HtmlWebpackPlugin = require("html-webpack-plugin");
const path = require("path");

module.exports = {
    entry: "./src/main.js",
    output: {
        path: path.resolve(__dirname, "dist"),
        clean: true
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: "./src/index.html"
        })
    ]
}

Pokud si teď Webpack spustíte příkazem "npm run build", tak by se vám ve složce dist měl již vytvořit i HTML soubor. Tento soubor bude navíc i minifikovaný, což je lepší pro produkční užití. Když si jej otevřete v prohlížeči, měli byste v konzoli vidět, že se vám vypsalo "připojeno". Webpack tedy na stránku automaticky připojil vstupní JavaScript soubor.

Zpracování CSS stylů

Teď už nám zbývá zpracovat jen CSS styly a zkopírovat do dist složky složku static. Začneme s CSS styly. Kromě JS modulů můžeme pro Webpack v JavaScriptu importovat i jiné soubory jako jsou CSS styly, obrázky a podobně. Když na ně Webpack narazí, tak je také zpracuje. Naimportujeme si tedy do našeho JavaScript souboru i CSS soubor.

import './style.css';
console.log("připojeno");

Pokud teď Webpack spustíme, tak se nám aplikace sestaví protože v našem CSS souboru ještě nemáme žádné styly. Pokud by tam ale nějaké byly, tak by nám to vyhodilo chybu. Klidně si to můžeme zkusit a napsat si třeba styly na změnění pozadí body elementu.

body {
    background-color: #EBF1F2;
}

Teď už se nám aplikace nesestaví. Webpack v základu rozumí jen JavaScriptu (a JSON souborům). Pokud chceme přidat podporu pro jiné typy souborů, tak musíme nainstalovat loadery (někdy pluginy). Pro zpracování CSS stylů použijeme css-loader a style-loader, takže si je nainstalujeme.

npm install css-loader style-loader --save-dev

Po jejich instalaci je můžeme přidat do našeho konfiguračního souboru. Loadery přidáváme do pole, které nastavujeme jako hodnotu vlastnosti rules v objektu, který nastavujeme jako hodnotu pro vlastnost module. Lépe je to vidět v ukázce níže. Každá položka v poli rules je objekt, který má nakonfigurováno, které loadery se spustí pro jaký typ souboru.

const HtmlWebpackPlugin = require("html-webpack-plugin");
const path = require("path");

module.exports = {
    entry: "./src/main.js",
    output: {
        path: path.resolve(__dirname, "dist"),
        clean: true
    },
    module: {
        rules: [
            {
                test: /\.css$/i, // všechny soubory s koncovkou .css
                use: ["style-loader", "css-loader"]
            },
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: "./src/index.html"
        })
    ]
}

Teď už by se nám měla aplikace opět úspěšně sestavit. Co vás ale může překvapit je to, že se v dist složce nevygeneroval žádný CSS soubor. Je to proto, že používáme css-loader s less-loaderem. Css-loader bere CSS styly a kompiluje je do JavaScriptu. Style-loader poté tyto styly v JavaScriptu vezme a aplikuje je na stránku. Děláme to proto, že je to pro vývoj rychlejší než vytvářet samostatný CSS soubor. Pro produkci to ale potom samozřejmě změníme až budeme mít jeden konfigurační soubor pro produkci a jeden pro vývoj. Když teď stránku otevřete, barva pozadí by měla být lehce šedá.

Zkopírování složky

Poslední věc kterou musíme udělat, je zkopírovat složku static do složky dist. Na to využijeme plugin jménem copy-webpack-plugin. Takže si jej nainstalujeme.

npm install copy-webpack-plugin --save-dev

Po instalaci jej můžeme použít. Použijeme jej podobně jako html-webpack-plugin, akorát u něj nastavíme jiné možnosti. Možnost from říká co odkud kopírujeme, to říká kam to kopírujeme a možnost noErrorOnMissing říká, že se nemá vyhodit error když je složka prázdná.

const CopyPlugin = require("copy-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const path = require("path");

module.exports = {
    entry: "./src/main.js",
    output: {
        path: path.resolve(__dirname, "dist"),
        clean: true
    },
    module: {
        rules: [
            {
                test: /\.css$/i, // všechny soubory s koncovkou .css
                use: ["style-loader", "css-loader"]
            },
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: "./src/index.html"
        }),
        new CopyPlugin({
            patterns: [
                {
                    from: path.resolve(__dirname, "static").replace(/\\/g, "/"),
                    to: path.resolve(__dirname, "dist", "static"),
                    noErrorOnMissing: true
                }
            ]
        })
    ]
}

Jak můžete v předchozí ukázce vidět, tak jsem u možnosti from za metodu resolve zřetězil ještě metodu replace. To jsem si zkopíroval z dokumentace aby mi to fungovalo na Windowsu. Pokud máte jiný operační systém, tak to smažte pokud vám to nefunguje. Pokud Webpack spustíte, tak by se vám aplikace měla sestavit, ale ve složce static nic nemáme, takže se do dist složky nezkopíruje. Pokud chcete, tak tam jen tak můžete zkusit vytvořit nějaký soubor.

Konfigurace pro vývoj

Při vývoji aplikace bychom asi nechtěli při každé změně psát "npm run build" a čekat až se aplikace sestaví. Proto si vytvoříme jeden konfigurační soubor pro vývoj a jeden pro produkci. Nebudeme ale do každého souboru psát celou konfiguraci od začátku. Společnou část konfigurace necháme v jednom souboru, který potom sloučíme s konfigurací pro produkci a pro vývoj.

Začneme s tím, že si vytvoříme soubory webpack.prod.js a webpack.dev.js. Soubor webpack.prod.js bude obsahovat konfiguraci specifickou pro produkci a soubor webpack.dev.js bude obsahovat konfiguraci specifickou pro vývoj. Soubor s naší stávající konfigurací přejmenujeme na webpack.common.js, protože bude obsahovat společnou část konfigurace pro produkci i pro vývoj. Kompilovat CSS styly do JavaScriptu budeme chtít jen při vývoji, takže tuto část konfigurace přesuneme do souboru webpack.dev.js.

module.exports = {
    module: {
        rules: [
            {
                test: /\.css$/i,
                use: ["style-loader", "css-loader"]
            },
        ]
    }
}

V souboru webpack.common.js zůstane jen kód, který ukazuje následující ukázka.

const CopyPlugin = require("copy-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const path = require("path");

module.exports = {
    entry: "./src/main.js",
    output: {
        path: path.resolve(__dirname, "dist"),
        clean: true
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: "./src/index.html"
        }),
        new CopyPlugin({
            patterns: [
                {
                    from: path.resolve(__dirname, "static").replace(/\\/g, "/"),
                    to: path.resolve(__dirname, "dist", "static"),
                    noErrorOnMissing: true
                }
            ]
        })
    ]
}

Abychom mohli sloučit dva konfigurační objekty do jednoho, tak k tomu potřebujeme nástroj jménem webpack-merge. Takže si jej pomocí NPM nainstalujeme.

npm install webpack-merge --save-dev

Po instalaci jej můžeme použít. Použijeme jej zatím jen v souboru webpack.dev.js, protože v souboru webpack.prod.js zatím nic nemáme. Balíček webpack-merge nám poskytuje funkci merge, které předáme konfigurační objekty a ona je sloučí dohromady.

const { merge } = require("webpack-merge");
const commonConfig = require("./webpack.common");

module.exports = merge(commonConfig, {
    module: {
        rules: [
            {
                test: /\.css$/i,
                use: ["style-loader", "css-loader"]
            },
        ]
    }
});

Abychom při vývoji nemuseli po každé změně psát "npm run build", tak si nainstalujeme Dev Server, který se o to postará za nás. NPM balíček, který potřebujeme nainstalovat je webpack-dev-server.

npm install webpack-dev-server --save-dev

Dev Server budeme chtít používat při vývoji, takže si jej nakonfigurujeme v souboru webpack.dev.js. Uděláme to pomocí vlastnosti devServer, kde určíme kterou složku chceme servírovat a na jakém portu.

const { merge } = require("webpack-merge");
const commonConfig = require("./webpack.common");

module.exports = merge(commonConfig, {
    module: {
        rules: [
            {
                test: /\.css$/i,
                use: ["style-loader", "css-loader"]
            },
        ]
    },
    devServer: {
        static: "./dist",
        port: 3000
    }
});

Ke spuštění Dev Serveru si v package.json musíme napsat skript. Ukazuje jej následující ukázka.

"dev": "webpack serve --config webpack.prod.js --mode development --open"

Připsáním textu "serve" za "webpack" zajistíme aby se nám dev server spustil. Možnost --open říká, že se nám má v prohlížeči naše aplikace automaticky spustit. Skript spustíme příkazem "npm run dev" a stránka by se nám automaticky měla otevřít v prohlížeči. Pokud by se z nějakého důvodu neotevřela, tak bude k dispozici na této adrese: http://localhost:3000/.

Teď už nám jen zbývá vytvořit konfiguraci pro produkci. Tam namísto kompilování CSS stylů do JavaScriptu vytvoříme samostatný CSS soubor. Použijeme k tomu plugin jménem mini-css-extract-plugin. Nainstalujeme jej následujícím příkazem.

npm install mini-css-extract-plugin --save-dev

Po instalaci můžeme plugin použít v souboru webpack.prod.js. Jak jej použít ukazuje následující ukázka. Zároveň tam i slučujeme konfigurační objekt s konfiguračním objektem v souboru webpack.common.js.

const { merge } = require("webpack-merge");
const commonConfig = require("./webpack.common");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = merge(commonConfig, {
    module: {
        rules: [
            {
                test: /\.css$/i,
                use: [MiniCssExtractPlugin.loader ,"css-loader"]
            }
        ]
    },
    plugins: [
        new MiniCssExtractPlugin()
    ]
});

S konfigurací pro produkci jsme hotovi. Mohli bychom sice přidat ještě pár dalších věcí a zminifikovat třeba CSS soubor, ale tím už se zabývat nebudeme. Jak jsem psal, pokud se chcete o Webpacku dozvědět více, tak můžeme navštívit mé webové stránky o Webpacku, na kterých se nachází menší tutoriál. Teď můžeme změnit název konfiguračního souboru v našem build skriptu a zkusit aplikaci sestavit.

"build": "webpack --config webpack.prod.js --mode production",

Po zavolání příkazu "npm run build" by se nám aplikace měla sestavit. Teď máme již vše připraveno, abychom mohli začít pracovat s Three.js.

Importování Three.js

Když máme Three.js nainstalované přes NPM, tak si jej k jeho používání musíme naimportovat do našeho JavaScript souboru jako jakýkoliv jiný JavaScript module. U nainstalovaných modulů přes NPM nemusíme specifikovat cestu, stačí předat název modulu a ten se automaticky najde ve složce node_modules.

import './style.css';
import * as THREE from 'three';

// teď máme objekt THREE k dispozici a můžeme začít Three.js používat

Pokud si chcete vyzkoušet jestli to opravdu funguje, tak si můžete do svého JavaScript souboru zkopírovat následující kód, který na canvas vyrenderuje kostku stejně jako v minulé části.

import './style.css';
import * as THREE from 'three';

const scene = new THREE.Scene();

const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({
    color: 0x78E8FA
});
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);

const sizes = {
    width: 700,
    height: 400
};

const canvas = document.getElementById("WebGLCanvas");

const camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height);
camera.position.z = 3;
camera.position.x = 1;
camera.position.y = 1;
scene.add(camera);

const renderer = new THREE.WebGLRenderer({
    canvas: canvas
});
renderer.setSize(sizes.width, sizes.height);

renderer.render(scene, camera);

Startovní kód

Projekt, který jsme si v této části vytvořili budeme používat i v ostatních částech tutoriálu abychom si vždy nemuseli vše znovu pracně konfigurovat. Připravil jsem vám zde tedy tlačítko pro stažení startovního kódu. Po jeho stažení si vždy budete muset nainstalovat balíčky, které jsou specifikované v souboru package.json pomocí příkazu "npm install". Poté můžete spustit Dev Server pomocí příkazu "npm run dev" a začít s Three.js pracovat.

To je pro tuto část vše. Teď jsme již připraveni abychom v příštích částech mohli Three.js začít zkoumat do větší hloubky.