Integrace Webpacku

V tomto článku si ukážeme, jak můžeme při tvorbě servletových webových aplikací používat Webpack. Jedná se o nástroj, který slouží k sestavování webových aplikací. Může například vzít různé soubory, zpracovat je a sloučit do menší skupiny souborů, které jsou lepší pro produkční užití. Také díky němu můžeme používat technologie jako jsou CSS preprocesory nebo třeba TypeScript.

Nebudu tu podrobně vysvětlovat jak Webpack funguje a jak se konfiguruje. Cílem je vám jen ukázat, jak jej můžete použít ve svých servletových webových aplikacích. Pokud byste se o něm chtěli dozvědět více, tak můžete navštívit třeba tento web, který jsem o Webpacku vytvořil.

Výhody použití Webpacku

Použití Webpacku přináší spoustu výhod. Některé jsem vypsal do následujícího seznamu:

  • Můžeme pro frontend používat moderní technologie, jako je TypeScript, různé CSS preprocesory, a tak podobně.
  • Tím že můžeme použít CSS preprocesory a různé nadstavby JavaScriptu jako je TypeScript, tak může být náš kód více čistší a lépe strukturovaný.
  • Nemusíme ručně skládat například SVG sprity (Webpack to udělá automaticky za nás).
  • Můžeme optimalizovat výsledné soubory (minifikovat je, atp.).
  • Můžeme převést novější JavaScript kód do staršího, aby mu rozumněli i starší prohlížeče.
  • Můžeme automaticky prefixnout novější CSS vlastnosti, které zatím některé prohlížeče podporují jen se speciálním prefixem.

Ukázka integrace Webpacku do webové aplikace

Abychom si ukázali, jak Webpack v servletové webové aplikaci můžeme použít, tak si budeme muset nějakou ukázkovou aplikaci vytvořit. Bude se jednat o web, který bude obsahovat jen dvě JSP stránky. Jedna bude představovat úvodní stránku a na druhé se bude nacházet jednoduchá kalkulačka. Postupně tuto aplikaci naprogramujeme, jak budeme Webpack konfigurovat.

Založení projektů

Naše ukázková aplikace bude rozdělena do dvou samostatných projektů. Jeden bude obsahovat serverový Java a JSP kód, a druhý kód pro frontend (takže CSS, JavaScript, ale také třeba obrázky, atp.). Takže by se dalo říct, že jeden projekt budeme mít pro backend a druhý pro frontend.

Oba tyto projekty budeme mít umístěné v nějaké kořenové složce. Takže začneme tím, že si ji vytvoříme, a uvnitř ní si založíme složky, které můžeme pojmenovat třeba jako "backend" a "frontend". Složka backend bude obsahovat náš backend kód a složka frontend bude obsahovat náš frontend kód.

Uvnitř složky backend si můžeme založit nový Maven projekt a v souboru pom.xml si nadefinovat závislost pro servlety, jak ukazuje následující ukázka. Po otevření levého panelu si můžete také prohlédnout adresářovou strukturu.

  • backend
    • src
      • main
        • java
        • resources
        • webapp
      • test
        • java
        • resources
    • target
  • frontend
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>example-app</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>example-app</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>17</java.version>
        <maven.compiler.source>${java.version}</maven.compiler.source>
        <maven.compiler.target>${java.version}</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</project>

Uvnitř složky frontend si nebudeme vytvářet Maven projekt, ale namísto něj použijeme NPM. Maven se totiž používá primárně pro Java projekty. NPM je podobný nástroj jako Maven (také slouží pro správu balíčků a závislostí), ale narozdíl od něj se zase používá hlavně pro JavaScript projekty. Instaluje se společně s Node.js, což je prostředí pro běh JavaScriptu i na serveru. Pokud tedy NPM ještě nemáte nainstalované, tak si jej můžete nainstalovat tím, že si nainstalujete Node.js.

Pokud máme NPM nainstalované, tak se můžeme v příkazovém řádku přepnout do složky frontend, a spustit příkaz "npm init -y". Tím se nám ve složce vygeneruje soubor package.json. Jedná se o něco podobného jako pom.xml.

npm init -y

V následující ukázce si můžete prohlédnout obsah vygenerovaného souboru.

  • frontend
{
    "name": "frontend",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
    },
    "keywords": [],
    "author": "",
    "license": "ISC"
}

Vytvoření serverové části aplikace

Projekty máme vytvořené a můžeme tedy začít s programováním aplikace. Začneme backendem a až poté se pustíme do instalace a konfigurace Webpacku. Jak jsem již psal, tak naše aplikace bude obsahovat dvě stránky. Jednu úvodní a druhou s kalkulačkou. Začneme s úvodní stránkou. Vytvoříme si pro ni servlet, který můžeme pojmenovat jako "HomeServlet". Jeho kód ukazuje následující ukázka. Je úplně jednoduchý, akorát vykresluje JSP stránku.

  • backend
import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class HomeServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        req.getRequestDispatcher("/WEB-INF/jsp/HomePage.jsp").forward(req, res);
    }
}

JSP stránku, kterou servlet vykresluje, vytvoříme ve složce webapp/WEB-INF/jsp. Kód pro ni ukazuje následující ukázka. Na stránce se nachází akorát nadpis, úvodní text a odkaz na stránku s kalkulačkou.

  • backend/src/main/webapp/WEB-INF/jsp
  • backend/src/main/webapp/WEB-INF/jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Ukázková aplikace</title>
</head>
<body>
    <div class="page">
        <h1 class="heading-primary u-text-center u-mb-4">Vítejte</h1>
        <p class="paragraph u-text-center u-mb-4">Na tomto webu se nachází kalkulačka, ke které se dostanete kliknutím na následující tlačítko.</p>
        <div class="u-text-center">
            <a href="./kalkulacka" class="button-primary">Otevřít kalkulačku</a>
        </div>
    </div>
</body>
</html>

Teď ve složce WEB-INF vytvoříme soubor web.xml a servlet namapujeme na "/home".

  • backend/src/main/webapp/WEB-INF
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://Java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" 
id="WebApp_ID" version="3.0">
    <servlet>
        <servlet-name>HomeServlet</servlet-name>
        <servlet-class>HomeServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>HomeServlet</servlet-name>
        <url-pattern>/home</url-pattern>
    </servlet-mapping>
</web-app>

Po nasazení aplikace na server a spuštění serveru byste po navštívení http://localhost:8080/example-app/home měli vidět stránku, kterou ukazuje následující obrázek.

Domovská stránka ukázkové aplikace

Teď vytvoříme ještě servlet a JSP stránku pro stránku s kalkulačkou, a tím budeme mít serverovou část prozatím hotovou. Servlet můžeme nazvat jako "CalculatorServlet". Kód pro něj ukazuje následující ukázka. Stejně jako servlet pro domovskou stránku, vykresluje jen JSP stránku.

  • backend
import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class CalculatorServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        req.getRequestDispatcher("/WEB-INF/jsp/CalculatorPage.jsp").forward(req, res);
    }
}

Následující ukázka ukazuje kód JSP stránky. Na stránce se nachází dva inputy a text zobrazující výsledek. Později to nastylujeme pomocí CSS stylů a zprovozníme pomocí JavaScriptu.

  • backend/src/main/webapp/WEB-INF/jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Ukázková aplikace</title>
</head>
<body>
    <div class="page">
        <h1 class="heading-primary u-text-center u-mb-2">Kalkulačka</h1>
        <div class="calculator">
            <input id="Input1" type="number" class="calculator__input">
            <span class="calculator__sign">+</span>
            <input id="Input2" type="number" class="calculator__input">
            <span class="calculator__sign">=</span>
            <span id="ResultContainer" class="calculator__result">0</span>
        </div>
    </div>
</body>
</html>

Servlet pro stránku s kalkulačkou namapujeme v souboru web.xml na "/kalkulacka", jak ukazuje následující ukázka.

  • backend/src/main/webapp/WEB-INF
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://Java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" 
id="WebApp_ID" version="3.0">
    <servlet>
        <servlet-name>HomeServlet</servlet-name>
        <servlet-class>HomeServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>HomeServlet</servlet-name>
        <url-pattern>/home</url-pattern>
    </servlet-mapping>
    
    <servlet>
        <servlet-name>CalculatorServlet</servlet-name>
        <servlet-class>CalculatorServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>CalculatorServlet</servlet-name>
        <url-pattern>/kalkulacka</url-pattern>
    </servlet-mapping>
</web-app>

Pokud teď restartujete server a na hlavní stránce aplikace kliknete na odkaz "Otevřít kalkulačku", tak se vám zobrazí stránka, kterou ukazuje následující obrázek.

Stránka s kalkulačkou ukázkové aplikace

Instalace Webpacku

Serverovou část aplikace máme prozatím hotovou. Teď se tedy pustíme do konfigurace Webpacku a tvorby kódu pro frontend.

První věc, kterou uděláme, je nainstalování všech potřebných balíčků, které budeme při konfiguraci Webpacku potřebovat. Je jich celkem dost. Vypsal jsem je do následujícího seznamu:

  • webpack - Základní balíček pro Webpack.
  • webpack-cli - Command line interface pro Webpack.
  • webpack-merge - Umožňuje sloučit více konfigurací Webpacku do jedné.
  • webpack-dev-server - Umožní nám spustit Webpack Dev Server pro vývoj.
  • webpack-remove-empty-scripts - Plugin, který odstraňuje prázdné skripty z výsledné složky.
  • css-loader - Budeme zpracovávat CSS kód, takže potřebujeme CSS loader.
  • less - Použijeme CSS preprocesor jménem LESS, takže si jej budeme muset nainstalovat.
  • less-loader - Budeme zpracovávat LESS kód, takže pro něj potřebujeme speciální loader.
  • mini-css-extract-plugin - CSS kód budeme chtít mít v samostatném souboru, takže na to potřebujeme plugin.
  • postcss - Budeme chtít automaticky prefixnout novější CSS vlastnosti, takže k tomu budeme potřebovat nástroj jménem PostCSS.
  • postcss-loader - Abychom mohli PostCSS použít ve Webpacku, potřebujeme k tomu loader.
  • postcss-preset-env - Plugin pro PostCSS, který nám automaticky prefixne novější CSS vlastnosti. Více si o něm můžete přečíst zde.
  • css-minimizer-webpack-plugin - Pomocí tohoto pluginu minifikujeme výsledný CSS soubor.
  • typescript - Namísto klasického JavaScriptu budeme používat TypeScript, takže si jej budeme muset nainstalovat.
  • ts-loader - Budeme zpracovávat TypeScript kód, takže na to potřebujeme speciální loader.
  • copy-webpack-plugin - Budeme mít složku, která bude uchovávat statické věci, jako jsou třeba obrázky. K jejímu nakopírování do složky s výslednými soubory použijeme tento plugin.
  • svg-sprite-loader - Budeme chtít, aby nám Webpack automaticky sestavil SVG sprite. K tomu použijeme speciální loader.
  • svgo-loader - Pro zpracování SVG souborů budeme také potřebovat SVGO loader. Pomocí něj si můžeme zpracovávané soubory trochu upravovat (například odstraňovat některé atributy).

V příkazovém řádku se přepněte do složky frontend a spusťte příkaz, který ukazuje následující ukázka. Tím se nainstalují všechny balíčky, které jsou vypsané výše.

npm install --save-dev webpack webpack-cli webpack-merge webpack-dev-server webpack-remove-empty-scripts css-loader less less-loader mini-css-extract-plugin postcss postcss-loader postcss-preset-env css-minimizer-webpack-plugin typescript ts-loader copy-webpack-plugin svg-sprite-loader svgo-loader

Po instalaci by se vám měla vytvořit složka node_modules, do které se balíčky nainstalovaly. Tady v ukázkách na webu bude tato složka vždy prázdná, ale u vás v ní samozřejmě něco bude. Také by se vám měl automaticky aktualizovat soubor package.json, kde by vám měla přibýt sekce "devDependencies" s nadefinovanými závislostmi, jak ukazuje následující ukázka (je to podobné jak závislosti v pom.xml). Pokud byste někdy složku node_modules smazali, tak stačí napsat příkaz "npm install" a balíčky se vám podle souboru package.json nainstalují.

  • frontend/node_modules
  • frontend
{
    "name": "frontend",
    "version": "1.0.0",
    "main": "index.js",
    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "description": "",
    "devDependencies": {
        "copy-webpack-plugin": "^12.0.2",
        "css-loader": "^7.1.2",
        "css-minimizer-webpack-plugin": "^7.0.0",
        "less": "^4.2.0",
        "less-loader": "^12.2.0",
        "mini-css-extract-plugin": "^2.9.0",
        "postcss": "^8.4.38",
        "postcss-loader": "^8.1.1",
        "postcss-preset-env": "^9.5.14",
        "svg-sprite-loader": "^6.0.11",
        "svgo-loader": "^4.0.0",
        "ts-loader": "^9.5.1",
        "typescript": "^5.4.5",
        "webpack": "^5.91.0",
        "webpack-cli": "^5.1.4",
        "webpack-dev-server": "^5.0.4",
        "webpack-merge": "^5.10.0",
        "webpack-remove-empty-scripts": "^1.0.4"
    }
}

Vytvoření CSS stylů

Teď když máme všechny potřebné balíčky nainstalované, můžeme se pustit do tvorby kódu. Začneme s CSS styly. Budeme používat preprocesor LESS, takže CSS kód můžeme psát klidně do více souborů, které potom sloučíme do jednoho. Vytvoříme si na ně složku less, do které je budeme umísťovat. Kód budeme rozdělovat do souborů a složek podle 7-1 vzoru. Jedná se o systém organizace CSS kódu, který definuje následující složky:

  • abstracts - Obsahuje věci, které nejsou obsaženy ve vygenerovaném CSS souboru (např. proměnné).
  • base - Obsahuje základní styly, jako je třeba nějaký reset CSS stylů, nastavení REM jednotky nebo třeba typografii.
  • components - Obsahuje různé komponenty (např. tlačítko).
  • layout - Obsahuje komponenty pro tvorbu layoutu.
  • pages - Obsahu styly, které jsou specifické pro různé stránky.
  • vendors - Obsahuje CSS styly třetích stran.
  • themes - Styly pro různé theme (mohli bychom například vytvořit pro náš web světlý a tmavý styl).

Více si o 7-1 vzoru můžete přečíst třeba na tomto webu, který jsem kdysi dávno vytvořil. Dále budeme pro tvorbu CSS stylů používat BEM (Block Element Modifier) metodiku, o které si také můžete krátce přečíst na tom samém webu na této stránce. Pokud pro organizaci kódu použijete 7-1 vzor a pro psaní CSS stylů BEM metodiku, tak svůj CSS kód posunete úplně na jinou úroveň a bude se snadněji číst.

Tento článek je hlavně o integraci Webpacku do servletových aplikací, takže tu nebudu dál vysvětlovat další věci ohledně tvorby CSS kódu pomocí preprocesoru LESS a přejdu rovnou ke kódu. Kód, který ukazuje následující ukázka, vložte do souboru variables.less, který vytvořte ve složce less/abstracts. Definuje proměnné pro barvy, které budeme v našich stylech používat.

  • frontend/less/abstracts
  • frontend/less/abstracts
@colors: {
    primary: #E54C47;
    primary-light: #FF6E69;
    primary-dark: #992420;

    black: #000;
    grey-80: #1E1C1C;
    grey-60: #6A6666;
    grey-40: #BDB4B3;
    grey-20: #F0E9E9;
    white: #FFF;
}

Dále vytvořte soubor base.less, který umístěte do složky less/base. Kód pro něj ukazuje následující ukázka. Jedná se o základní reset CSS stylů a nastavení velikosti pro rem jednotku (je lepší používat rem jednotku namísto px, protože tím potom uživateli umožníme, změnit si v prohlížeči velikost fontu).

  • frontend/less/base
  • frontend/less/base
*, *::before, *::after {
    margin: 0;
    padding: 0;
    box-sizing: inherit;
}

html {
    font-size: 62.5%; // 1rem = 10px
}

body {
    box-sizing: border-box;
    overflow-x: hidden;
    padding: 3.2rem;
}

Dále ve složce base vytvořte soubor typography.less a vložte do něj kód z následující ukázky. Jedná se o styly pro typografii.

  • frontend/less/base
body {
    font-family: Arial, Helvetica, sans-serif;
}

.heading-primary {
    font-size: 4rem;
    font-weight: 700;
    line-height: 1;
    text-transform: uppercase;

    color: @colors[grey-80];
}

.paragraph {
    font-size: 1.6rem;
    line-height: 2.4rem;

    color: @colors[grey-60];
}

Nakonec ve složce base vytvořte soubor utilities.less a opět do něj vložte kód z následující ukázky. Jedná se o různé utility třídy (například pro nastavení spodního marginu).

  • frontend/less/base
.u-mb-1 { margin-bottom: .4rem !important; }
.u-mb-2 { margin-bottom: .8rem !important; }
.u-mb-3 { margin-bottom: 1.2rem !important; }
.u-mb-4 { margin-bottom: 1.6rem !important; }
.u-mb-5 { margin-bottom: 2rem !important; }
.u-mb-6 { margin-bottom: 2.4rem !important; }
.u-mb-7 { margin-bottom: 2.8rem !important; }
.u-mb-8 { margin-bottom: 3.2rem !important; }

.u-text-center { text-align: center !important; }

Teď vytvoříme složku layout a v ní soubor page.less. Bude definovat CSS třídu, která bude představovat takový container pro stránku. Kód pro něj ukazuje následující ukázka.

  • frontend/less/layout
  • frontend/less/layout
.page {
    margin: 0 auto;
    max-width: 80rem;

    background-color: @colors[grey-20];
    box-shadow: 0 .4rem 1rem rgba(@colors[black], .2);

    padding: 3.2rem;
}

Teď si vytvoříme složku components a v ní pár komponent. Začneme s primárním tlačítkem. Vytvořte v ní soubor button-primary.less a vložte do něj kód z následující ukázky.

  • frontend/less/components
  • frontend/less/components
.button-primary {
    &:link, &:visited {
        display: inline-block;

        font-size: 1.6rem;
        line-height: 1.6rem;
        text-decoration: none;

        background-color: @colors[primary];
        color: @colors[white];

        padding: 1.2rem 1.6rem;
    }
}

Dále si přidáme styly pro kalkulačku. Vytvořte ve složce components soubor calculator.less a zkopírujte do něj kód z následující ukázky.

  • frontend/less/components
.calculator {
    display: flex;
    align-items: center;
    gap: .4rem;

    font-size: 2rem;

    &__input {
        font-family: inherit;
        font-size: 1.6rem;
        line-height: 2rem;

        background-color: @colors[white];
        color: @colors[grey-80];
        border: .2rem solid @colors[grey-60];

        width: 8rem;
        padding: .4rem .8rem;
    }

    &__sign {
        color: @colors[grey-60];
    }

    &__result {
        font-weight: 700;

        color: @colors[primary];
    }
}

Na závěr si vytvoříme ve složce less soubor main.less a všechny ostatní vytvořené LESS soubory do něj naimportujeme. Bude se jednat o hlavní soubor, který bude Webpack zpracovávat.

  • frontend/less
@import "./abstracts/variables.less";

@import "./base/base.less";
@import "./base/typography.less";
@import "./base/utilities.less";

@import "./layout/page.less";

@import "./components/button-primary.less";
@import "./components/calculator.less";

Vytvoření konfiguračních souborů pro Webpack a zpracování CSS stylů

Nyní náš LESS kód pomocí Webpacku zpracujeme a vytvoříme z něj jeden CSS soubor, který budeme moci přidat na naše webové stránky. Tím se dostáváme k tomu, že si budeme muset vytvořit konfigurační soubor pro webpack. Vlastně si vytvoříme tři konfigurační soubory. Chceme totiž jednu konfiguraci pro vývoj a jednu pro zabalení kódu pro produkci. Budeme mít jeden konfigurační pro vývoj, jeden pro produkci a jeden, který bude obsahovat společnou část konfigurace pro vývoj i pro produkci. Následující seznam popisuje, které soubory si ve složce frontend vytvoříme:

  • webpack.common.js - Společná část konfigurace pro vývoj i pro produkci.
  • webpack.dev.js - Konfigurace pro vývoj.
  • webpack.prod.js - Konfigurace pro produkci.

Začneme se soubor webpack.common.js, který bude obsahovat společnou konfiguraci pro vývoj i produkci. Můžete si jej tedy vytvořit a zkopírovat do něj konfiguraci z následující ukázky. Konfigurace definuje jako vstupní bod soubor main.less, který Webpack zpracuje. Jako pluginy přidává MiniCssExtractPlugin, pomocí kterého se vytáhne CSS kód do samostatného souboru, který se umístí do složky css, a plugin RemoveEmptyScriptsPlugin, který zajistí, že se ve výstupní složce nebude nacházet prázdný skript (jako vstupní soubor máme totiž nastavený less soubor, takže by se vygeneroval prázdný skript). Jako výstupní složka je nastavena složka assets, která se vytvoří ve složce backend/src/main/webapp. Možnost clean nastavuje, že se má obsah této složky vždy nejdříve smazat, než se v ní vytvoří nový obsah.

  • frontend
const path = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const RemoveEmptyScriptsPlugin = require('webpack-remove-empty-scripts');

module.exports = {
    entry: {
        style: "./less/main.less"
    },
    output: {
        clean: true,
        path: path.resolve(__dirname, "..", "backend", "src", "main", "webapp", "assets")
    },
    plugins: [
        new MiniCssExtractPlugin({
            filename: "css/[name].css"
        }),
        new RemoveEmptyScriptsPlugin()
    ]
}

Teď si vytvoříme konfigurační soubor pro produkci, který pojmenujeme jako webpack.prod.js. Vytvořte jej tedy a vložte do něj konfiguraci, která se nachází v následující ukázce.

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

module.exports = merge(commonConfig, {
    module: {
        rules: [
            {
                test: /\.less$/,
                use: [
                    MiniCssExtractPlugin.loader,
                    {
                        loader: "css-loader",
                        options: {
                            url: false
                        }
                    },
                    {
                        loader: "postcss-loader",
                        options: {
                            postcssOptions: {
                                plugins: [
                                    ["postcss-preset-env", {}]
                                ]
                            }
                        }
                    },
                    "less-loader"
                ]
            }
        ]
    },
    optimization: {
        minimizer: [
            `...`,
            new CssMinimizerPlugin()
        ]
    }
});

Jak si můžete všimnout, tak pro spojení dvou konfigurací do jedné se používá funkce merge. V konfiguraci se nastavují loadery pro LESS soubory a minimizer CSSMinizerPlugin, který minifikuje výsledný CSS soubor. Co dělají jednotlivé loadery popisuje následující seznam:

  1. less-loader - Převádí LESS na CSS kód.
  2. postcss-loader - Provádí na CSS kódu úpravy, jako je třeba automatické prefixnutí novějších CSS vlastností.
  3. css-loader - Přidává CSS kód do JavaScriptu.
  4. MiniCssExtractPlugin.loader - Vytahuje z JavaScriptu CSS kód a umisťuje jej do samostatného souboru.

U css-loaderu je důležité, aby byla možnost "url" nastavena na false. Tím se zajistí, že se nebudou zpracovávat url (například pro obrázky pozadí), ale nechají se tak jak jsou.

Nyní si vytvoříme konfigurační soubor pro vývoj. Pojmenujte jej jako webpack.dev.js a vložte do něj kód z následující ukázky.

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

module.exports = merge(commonConfig, {
    module: {
        rules: [
            {
                test: /\.less$/,
                use: [
                    MiniCssExtractPlugin.loader,
                    {
                        loader: "css-loader",
                        options: {
                            url: false
                        }
                    },
                    "less-loader"
                ]
            }
        ]
    },
    devtool: 'inline-source-map',
    devServer: {
        static: "../backend/src/main/webapp/assets",
        port: 3000
    }
});

Loadery pro vývoj jsou podobné, jako ty pro produkci. Rozdíl je ale v tom, že se nemusí provádět prefixování a minifikování CSS kódu a zpracování je tak rychlejší. V konfiguraci se také nastavuje DEV server, který budeme při vývoji používat. Vlastnost port nastavuje port, na kterém bude server běžet, a vlastnost static složku pro statické assety (ta by možná ani nemusela být nastavená, ale nechal jsem ji tam).

Sestavení CSS kódu pro produkci

Webpack máme nakonfigurovaný a můžeme si tedy zkusit náš CSS kód sestavit. Zatím to uděláme pro produkci. Spuštění DEV serveru pro vývoj si vyzkoušíme až později.

V souboru package.json můžete smazat výchozí skript a vložit místo něj ten, který ukazuje následující ukázka. Tento příkaz spouští Webpack s konfiguračním souborem webpack.prod.js v módu pro produkci.

  • frontend
{
    "name": "frontend",
    "version": "1.0.0",
    "main": "index.js",
    "scripts": {
        "build": "webpack --config webpack.prod.js --mode production"
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "description": "",
    "devDependencies": {
        "copy-webpack-plugin": "^12.0.2",
        "css-loader": "^7.1.2",
        "css-minimizer-webpack-plugin": "^7.0.0",
        "less": "^4.2.0",
        "less-loader": "^12.2.0",
        "mini-css-extract-plugin": "^2.9.0",
        "postcss": "^8.4.38",
        "postcss-loader": "^8.1.1",
        "postcss-preset-env": "^9.5.14",
        "svg-sprite-loader": "^6.0.11",
        "svgo-loader": "^4.0.0",
        "ts-loader": "^9.5.1",
        "typescript": "^5.4.5",
        "webpack": "^5.91.0",
        "webpack-cli": "^5.1.4",
        "webpack-dev-server": "^5.0.4",
        "webpack-merge": "^5.10.0",
        "webpack-remove-empty-scripts": "^1.0.4"
    }
}

Název skriptu, který jsme si do souboru package.json přidali, je "build". Můžete jej tedy spustit pomocí příkazu "npm run build". Jakmile to uděláte, tak by se vám za chvíli měla ve složce "backend/src/main/webapp" vytvořit složka "assets" a v ní podsložka "css" se souborem "style.css".

npm run build

V následující ukázce si můžete prohlédnout obsah výsledného CSS souboru. Jak vidíte, kód je minifikovaný a nachází se jen na jednom řádku.

  • backend/src/main/webapp/assets/css
  • backend/src/main/webapp/assets/css
*,:after,:before{box-sizing:inherit;margin:0;padding:0}html{font-size:62.5%}body{box-sizing:border-box;font-family:Arial,Helvetica,sans-serif;overflow-x:hidden;padding:3.2rem}.heading-primary{color:#1e1c1c;font-size:4rem;font-weight:700;line-height:1;text-transform:uppercase}.paragraph{color:#6a6666;font-size:1.6rem;line-height:2.4rem}.u-mb-1{margin-bottom:.4rem!important}.u-mb-2{margin-bottom:.8rem!important}.u-mb-3{margin-bottom:1.2rem!important}.u-mb-4{margin-bottom:1.6rem!important}.u-mb-5{margin-bottom:2rem!important}.u-mb-6{margin-bottom:2.4rem!important}.u-mb-7{margin-bottom:2.8rem!important}.u-mb-8{margin-bottom:3.2rem!important}.u-text-center{text-align:center!important}.page{background-color:#f0e9e9;box-shadow:0 .4rem 1rem rgba(0,0,0,.2);margin:0 auto;max-width:80rem;padding:3.2rem}.button-primary:link,.button-primary:visited{background-color:#e54c47;color:#fff;display:inline-block;font-size:1.6rem;line-height:1.6rem;padding:1.2rem 1.6rem;-webkit-text-decoration:none;text-decoration:none}.calculator{align-items:center;display:flex;font-size:2rem;gap:.4rem}.calculator__input{background-color:#fff;border:.2rem solid #6a6666;color:#1e1c1c;font-family:inherit;font-size:1.6rem;line-height:2rem;padding:.4rem .8rem;width:8rem}.calculator__sign{color:#6a6666}.calculator__result{color:#e54c47;font-weight:700}

Klidně si teď můžeme vygenerovaný CSS soubor připojit do našich JSP stránek, jak ukazují následující ukázky.

  • backend/src/main/webapp/WEB-INF/jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Ukázková aplikace</title>
    
    <link rel="stylesheet" href="./assets/css/style.css" />
</head>
<body>
    <div class="page">
        <h1 class="heading-primary u-text-center u-mb-4">Vítejte</h1>
        <p class="paragraph u-text-center u-mb-4">Na tomto webu se nachází kalkulačka, ke které se dostanete kliknutím na následující tlačítko.</p>
        <div class="u-text-center">
            <a href="./kalkulacka" class="button-primary">Otevřít kalkulačku</a>
        </div>
    </div>
</body>
</html>
  • backend/src/main/webapp/WEB-INF/jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Ukázková aplikace</title>
    
    <link rel="stylesheet" href="./assets/css/style.css" />
</head>
<body>
    <div class="page">
        <h1 class="heading-primary u-text-center u-mb-4">Vítejte</h1>
        <p class="paragraph u-text-center u-mb-4">Na tomto webu se nachází kalkulačka, ke které se dostanete kliknutím na následující tlačítko.</p>
        <div class="u-text-center">
            <a href="./kalkulacka" class="button-primary">Otevřít kalkulačku</a>
        </div>
    </div>
</body>
</html>

Následující obrázky ukazují, jak nyní vypadají naše stránky s aplikovanými CSS styly.

Nastylovaná domovská stránka ukázkové aplikace Nastylovaná stránka s kalkulačkou ukázkové aplikace

Přidání TypeScript kódu

Abychom zprovoznili naši webovou kalkulačku, tak si budeme muset napsat nějaký JavaScript kód. Využijeme toho, že používáme Webpack, a použijeme nadstavbu JavaScriptu jménem TypeScript. Jedná se o programovací jazyk, který JavaScript staví na úroveň jazyků, jako je třeba Java. Přidává mu typovou bezpečnost, zapouzdření, a tak podobně, a tím z něj dělá mnohem sofistikovanější programovací jazyk.

První co můžeme udělat, je vytvoření konfiguračního souboru pro TypeScript jménem tsconfig.json, v kořenové složce frontend. Obsah pro něj ukazuje následující ukázka. Nastavuje se tam například do jaké verze JavaScriptu se má TypeScript kompilovat, jestli má být zapnuta striktní typová kontrola, a tak podobně.

  • frontend
{
    "include": ["ts"],
    "compilerOptions": {
        "outDir": "../backend/src/main/webapp/assets",
        "sourceMap": true,
        "module": "ES6",
        "target": "ES5",
        "allowJs": true,
        "moduleResolution": "node",
        "strict": true
    }
}

Teď můžeme založit složku ts, ve které budeme vytvářet TypeScript soubory, a pustit se do programování kalkulačky. Kalkulačku bude představovat třída Calculator, kterou ukazuje následující ukázka. Tento kód můžete vložit do souboru Calculator.ts.

  • frontend/ts
  • frontend/ts
class Calculator {
    private resultElement : HTMLElement;
    private inputElement1 : HTMLInputElement;
    private inputElement2 : HTMLInputElement;

    constructor(inputElement1 : HTMLInputElement, inputElement2 : HTMLInputElement, resultElement : HTMLElement) {
        this.inputElement1 = inputElement1;
        this.inputElement2 = inputElement2;
        this.resultElement = resultElement;

        inputElement1.addEventListener("input", () => this.onInputChange());
        inputElement2.addEventListener("input", () => this.onInputChange());
    }

    private onInputChange() {
        const number1 = Number.parseFloat(this.inputElement1.value) || 0;
        const number2 = Number.parseFloat(this.inputElement2.value) || 0;

        const result = number1 + number2;

        this.resultElement.innerText = result.toString();
    }
}

export default Calculator;

Třídu Calculator z předchozí ukázky bude používat soubor main.ts, který můžete také vytvořit ve složce ts. Kód pro něj ukazuje následující ukázka. Jedná se o hlavní soubor, který bude Webpack zpracovávat.

  • frontend/ts
import Calculator from "./Calculator";

const input1 = document.getElementById("Input1") as HTMLInputElement;
const input2 = document.getElementById("Input2") as HTMLInputElement;
const resultContainer = document.getElementById("ResultContainer");

if (input1 && input2 && resultContainer) {
    new Calculator(input1, input2, resultContainer);
}

Kód máme hotový. Teď nám stačí již jen nakonfigurovat Webpack, aby náš TypeScript kód zpracoval a vytvořil z něj jeden výsledný JavaScript soubor. Následující ukázka ukazuje upravený konfigurační soubor webpack.common.js.

  • frontend
const path = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const RemoveEmptyScriptsPlugin = require('webpack-remove-empty-scripts');

module.exports = {
    entry: {
        style: "./less/main.less",
        calculator: {
            import: './ts/main.ts',
            filename: "js/calculator.js"
        }
    },
    output: {
        clean: true,
        path: path.resolve(__dirname, "..", "backend", "src", "main", "webapp", "assets")
    },
    module: {
        rules: [
            {
                test: /(\.ts|\.d.ts)$/,
                use: 'ts-loader',
                exclude: /node_modules/,
            }
        ]
    },
    resolve: {
        extensions: ['.ts', '.js']
    },
    plugins: [
        new MiniCssExtractPlugin({
            filename: "css/[name].css"
        }),
        new RemoveEmptyScriptsPlugin()
    ]
}

V konfiguraci nastavujeme jako vstupní soubor ts/main.ts, pro který také nastavujeme, že se má vytvořit složka js a v ní soubor calculator.js. Pro TypeScript soubory jsme si také nadefinovali loader, který se postará o jejich zpracování.

Pokud nyní znovu spustíte příkaz "npm run build", tak se vám ve složce assets vytvoří složka js a v ní soubor calculator.js. V následující ukázka si můžete prohlédnout jeho obsah.

  • backend/src/main/webapp/assets/js
  • backend/src/main/webapp/assets/js
(()=>{"use strict";const t=function(){function t(t,n,e){var u=this;this.inputElement1=t,this.inputElement2=n,this.resultElement=e,t.addEventListener("input",(function(){return u.onInputChange()})),n.addEventListener("input",(function(){return u.onInputChange()}))}return t.prototype.onInputChange=function(){var t=(Number.parseFloat(this.inputElement1.value)||0)+(Number.parseFloat(this.inputElement2.value)||0);this.resultElement.innerText=t.toString()},t}();var n=document.getElementById("Input1"),e=document.getElementById("Input2"),u=document.getElementById("ResultContainer");n&&e&&u&&new t(n,e,u)})();

Vygenerovaný JavaScript soubor si můžeme napojit na stránce s kalkulačkou, jak ukazuje následující ukázka. Tím by nám měla začít fungovat.

  • backend/src/main/webapp/WEB-INF/jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Ukázková aplikace</title>
    
    <link rel="stylesheet" href="./assets/css/style.css" />
</head>
<body>
    <div class="page">
        <h1 class="heading-primary u-text-center u-mb-2">Kalkulačka</h1>
        <div class="calculator">
            <input id="Input1" type="number" class="calculator__input">
            <span class="calculator__sign">+</span>
            <input id="Input2" type="number" class="calculator__input">
            <span class="calculator__sign">=</span>
            <span id="ResultContainer" class="calculator__result">0</span>
        </div>
    </div>
    <script src="./assets/js/calculator.js"></script>
</body>
</html>

Pokud teď zkusíte do kalkulačky zadat nějaká čísla, měl by se vám automaticky zobrazit jejich součet.

Fungující kalkulačka

Kopírování složky se statickými assety

Na webových stránkách často používáme různé statické assety, jakou jsou třeba obrázky. Mohli bychom je sice vkládat přímo do složky webapp, ale myslím že bude lepší, když budou pohromadě spolu s ostatními věcmi, které se týkají frontendu. Proto si ve složce frontend založíme složku, do které statické assety budeme ukládat, a nakonfigurujeme Webpack, aby tuto složku kopíroval.

Abychom v naší webové aplikaci nějaké statické assety používaly, tak si alespoň v CSS kódu nastavíme obrázek pozadí. Následující ukázka ukazuje upravený soubor base.less. Obrázek pozadí je nastaven na obrázek background.jpg, nacházející se ve složce static/img. Vytvořte tedy ve složce frontend složku static, podsložku img a do ní vložce soubor background.jpg. Ten si můžete stáhnout z ukázky po otevření levého panelu.

  • frontend/less/base
  • frontend/static/img
  • frontend/static/img
*, *::before, *::after {
    margin: 0;
    padding: 0;
    box-sizing: inherit;
}

html {
    font-size: 62.5%; // 1rem = 10px
}

body {
    box-sizing: border-box;
    overflow-x: hidden;
    padding: 3.2rem;

    background-image: linear-gradient(rgba(@colors[black], .4), rgba(@colors[black], .4)), url(../../static/img/background.jpg);
    background-size: cover;
}
background.jpg

Teď můžeme do konfigurace Webpacku přidat plugin na kopírování složky static, jak ukazuje následující ukázka.

  • frontend
const path = require("path");
const CopyPlugin = require("copy-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const RemoveEmptyScriptsPlugin = require('webpack-remove-empty-scripts');

module.exports = {
    entry: {
        style: "./less/main.less",
        calculator: {
            import: './ts/main.ts',
            filename: "js/calculator.js"
        }
    },
    output: {
        clean: true,
        path: path.resolve(__dirname, "..", "backend", "src", "main", "webapp", "assets")
    },
    module: {
        rules: [
            {
                test: /(\.ts|\.d.ts)$/,
                use: 'ts-loader',
                exclude: /node_modules/,
            }
        ]
    },
    resolve: {
        extensions: ['.ts', '.js']
    },
    plugins: [
        new MiniCssExtractPlugin({
            filename: "css/[name].css"
        }),
        new CopyPlugin({
            patterns: [
                {
                    from: path.resolve(__dirname, "static").replace(/\\/g, "/"),
                    to: path.resolve(__dirname, "..", "backend", "src", "main", "webapp", "assets", "static"),
                    noErrorOnMissing: true
                }
            ]
        }),
        new RemoveEmptyScriptsPlugin()
    ]
}

Pokud nyní spustíte příkaz "npm run build", tak by se vám složka static ze složky frontend měla zkopírovat do složky backend/src/main/webapp/assets. Když si poté v prohlížeči webovou aplikaci otevřete, tak by se měl obrázek pozadí aplikovat, jak ukazuje následující obrázek.

Domovská stránka s obrázkem pozadí

Generování SVG spritu

Poslední věcí, kterou si ve Webpacku nakonfigurujeme, je generování SVG spritu pro ikony ze samostatných SVG souborů. Nejprve si upravíme domovskou stránku, na které si nějaké ikony zobrazíme. Následující ukázka ukazuje upravený soubor HomePage.jsp.

  • backend/src/main/webapp/WEB-INF/jsp
  • backend/src/main/webapp/assets/static/img
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Ukázková aplikace</title>
    
    <link rel="stylesheet" href="./assets/css/style.css" />
</head>
<body>
    <div class="page">
        <h1 class="heading-primary u-text-center u-mb-4">Vítejte</h1>
        <div class="icons-container u-mb-4">
            <svg>
                <use xlink:href="./assets/icon-sprite.svg#happy"></use>
            </svg>
            <svg>
                <use xlink:href="./assets/icon-sprite.svg#calculator"></use>
            </svg>
            <svg>
                <use xlink:href="./assets/icon-sprite.svg#cool"></use>
            </svg>
        </div>
        <p class="paragraph u-text-center u-mb-4">Na tomto webu se nachází kalkulačka, ke které se dostanete kliknutím na následující tlačítko.</p>
        <div class="u-text-center">
            <a href="./kalkulacka" class="button-primary">Otevřít kalkulačku</a>
        </div>
    </div>
</body>
</html>
background.jpg

Dále si také přidáme soubor "icons-container.less" do složky frontend/less/components, pomocí kterého ikony na domovské stránce nastylujeme. Následující ukázka ukazuje jeho kód.

  • frontend/less/components
.icons-container {
    display: flex;
    justify-content: center;
    gap: 1.6rem;

    > svg {
        width: 3.2rem;
        height: 3.2rem;
        fill: @colors[primary];
    }
}

Nový LESS soubor musíme také naimportovat v souboru main.less.

  • frontend/less
@import "./abstracts/variables.less";

@import "./base/base.less";
@import "./base/typography.less";
@import "./base/utilities.less";

@import "./layout/page.less";

@import "./components/button-primary.less";
@import "./components/calculator.less";
@import "./components/icons-container.less";

Na SVG ikony budeme mít ve složce frontend vyhrazenou složku, kterou můžeme pojmenovat třeba jako "icons". Budou se v ní nacházet tři SVG ikony, které zobrazujeme na hlavní stránce, a JavaScript soubor "main.js", do kterého se budou tyto ikony importovat. Tento JavaScript soubor bude Webpack zpracovávat a vytvářet podle něj výsledný SVG sprite. V následující ukázce se můžete podívat, jak se SVG ikony do JavaScriptu importují a po otevření levého panelu ukázky si můžete importované ikony stáhnout.

  • frontend/icons
  • frontend/icons
import "./calculator.svg";
import "./cool.svg";
import "./happy.svg";
calculator.svg
cool.svg
happy.svg

Teď si v souboru webpack.common.js můžeme nadefinovat nový vstupní bod a nastavit loadery pro svg soubory, jak ukazuje následující ukázka. Pomocí svgo-loaderu odstraňujeme z SVG ikon, které mají jako výplň/tah nastavenou černou barvu, atributy fill nebo stroke, aby se to dalo přepsat v CSS kódu. Barevné ikony necháváme tak, jak jsou. Pomocí svg-sprite-loaderu nastavujeme, aby se vytvořil SVG sprite, který se bude jmenovat jako "icon-sprite.svg".

  • frontend
const path = require("path");
const CopyPlugin = require("copy-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const RemoveEmptyScriptsPlugin = require('webpack-remove-empty-scripts');
const SpriteLoaderPlugin = require('svg-sprite-loader/plugin');

module.exports = {
    entry: {
        style: "./less/main.less",
        icons: './icons/main.js',
        calculator: {
            import: './ts/main.ts',
            filename: "js/calculator.js"
        }
    },
    output: {
        clean: true,
        path: path.resolve(__dirname, "..", "backend", "src", "main", "webapp", "assets")
    },
    module: {
        rules: [
            {
                test: /(\.ts|\.d.ts)$/,
                use: 'ts-loader',
                exclude: /node_modules/,
            },
            {
                test: /\.svg$/,
                use: [
                    {
                        loader: 'svg-sprite-loader',
                        options: {
                            extract: true,
                            spriteFilename: "icon-sprite.svg"
                        }
                    },
                    {
                        loader: 'svgo-loader',
                        options: {
                            plugins: [
                                {
                                    name: 'removeAttrs',
                                    params: {
                                        attrs: ['*:fill:(none|black)', '*:stroke:(none|black)']
                                    }
                                }
                            ]
                        }
                    }
                ]
            }
        ]
    },
    resolve: {
        extensions: ['.ts', '.js']
    },
    plugins: [
        new MiniCssExtractPlugin({
            filename: "css/[name].css"
        }),
        new CopyPlugin({
            patterns: [
                {
                    from: path.resolve(__dirname, "static").replace(/\\/g, "/"),
                    to: path.resolve(__dirname, "..", "backend", "src", "main", "webapp", "assets", "static"),
                    noErrorOnMissing: true
                }
            ]
        }),
        new RemoveEmptyScriptsPlugin(),
        new SpriteLoaderPlugin()
    ]
}

Pokud nyní spustíte příkaz "npm run build", tak by se vám ve složce backend/src/main/webapp/assets měl vygenerovat soubor "icon-sprite.svg". Poté si můžete v prohlížeči zobrazit hlavní stránku a měli byste ikony, které se zobrazují pomocí SVG spritu, vidět.

Domovská stránka s ikonami

Spuštění Webpack DEV serveru

Při vývoji samozřejmě nechceme při každé úpravě frontend kódu spouštět příkaz "npm run build" a čekat až se aplikace sestaví. Proto si nyní ukážeme, jak můžeme spustit Webpack DEV server a servírovat frontend kód odsud.

V souboru webpack.dev.js máme již vše připravené, abychom Webpack DEV server mohli spustit. Zbývá nám jen v souboru package.json definovat příkaz, pomocí kterého to uděláme. Nastavíme mu například název "dev". Následující ukázka ukazuje upravený soubor package.json.

  • frontend
{
    "name": "frontend",
    "version": "1.0.0",
    "main": "index.js",
    "scripts": {
        "build": "webpack --config webpack.prod.js --mode production",
        "dev": "webpack serve --config webpack.dev.js --mode development"
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "description": "",
    "devDependencies": {
        "copy-webpack-plugin": "^12.0.2",
        "css-loader": "^7.1.2",
        "css-minimizer-webpack-plugin": "^7.0.0",
        "less": "^4.2.0",
        "less-loader": "^12.2.0",
        "mini-css-extract-plugin": "^2.9.0",
        "postcss": "^8.4.38",
        "postcss-loader": "^8.1.1",
        "postcss-preset-env": "^9.5.14",
        "svg-sprite-loader": "^6.0.11",
        "svgo-loader": "^4.0.0",
        "ts-loader": "^9.5.1",
        "typescript": "^5.4.5",
        "webpack": "^5.91.0",
        "webpack-cli": "^5.1.4",
        "webpack-dev-server": "^5.0.4",
        "webpack-merge": "^5.10.0",
        "webpack-remove-empty-scripts": "^1.0.4"
    }
}
icon-sprite.svg
(()=>{"use strict";var t={};t.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(t){if("object"==typeof window)return window}}(),(()=>{var r;t.g.importScripts&&(r=t.g.location+"");var e=t.g.document;if(!r&&e&&(e.currentScript&&(r=e.currentScript.src),!r)){var i=e.getElementsByTagName("script");if(i.length)for(var c=i.length-1;c>-1&&(!r||!/^http(s?):/.test(r));)r=i[c--].src}if(!r)throw new Error("Automatic publicPath is not supported in this browser");r=r.replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),t.p=r})(),t.p,t.p,t.p})();

Pokud po upravení souboru package.json spustíte příkaz "npm run dev", tak se vám Webpack DEV server spustí. Namísto toho, aby se výsledné soubory generovaly do nějaké složky, budou nyní servírovány z DEV serveru. Také se budou automaticky aktualizovat když něco upravíme, aniž bychom museli DEV server spouštět znovu.

Pokud si chcete ověřit, že se DEV server spustil, tak můžete ve webovém prohlížeči navštívit třeba http://localhost:3000/js/calculator.js. Měl by se vám zobrazit vygenerovaný JavaScript kód souboru calculator.js.

Servirování assetů z Webpack DEV serveru

Dostali jsme se k hlavní části tohoto článku. Musíme nějakým způsobem zajistit, aby se v naší servletové aplikaci webové soubory namísto ze složky webapp/assets servírovaly z DEV serveru. Uděláme to pomocí filtru. Můžeme jej pojmenovat třeba jako "ProxyFilter", protože se v podstatě bude jednat o takového prostředníka mezi webovou aplikací a Webpack DEV serverem.

V následující ukázce si můžete prohlédnout okomentovaný kód filtru. Obsahuje dva inicializační parametry: DEV_SERVER_URL a DEV_MODE. Parametr DEV_SERVER_URL určuje adresu DEV serveru a parametr DEV_MODE určuje, zda se mají soubory servírovat z DEV serveru nebo z assets složky. Pro vývoj můžeme mít atribut DEV_MODE nastavený na true a pro produkci na false. Pokud je parametr DEV_MODE nastaven na true, tak se pro příchozí requesty, směřující na složku assets, nasměrují namísto na složku webapp/assets na DEV server.

  • backend
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class ProxyFilter implements Filter {
    // URL DEV serveru
    private String devServerURL;
    // určuje, zda je zapnutý development mód
    private boolean devModeEnabled;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // nastavení podle inicializační parametrů
        devServerURL = filterConfig.getInitParameter("DEV_SERVER_URL");
        if (devServerURL == null) devServerURL = "http://localhost:3000";
        devModeEnabled = "true".equals(filterConfig.getInitParameter("DEV_MODE"));
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // pokud není zapnutý development mód, tak se webové soubory mohou servírovat ze složky assets
        if (!devModeEnabled) {
            chain.doFilter(request, response);
            return;
        }
        
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        
        // získání cesty z requestu
        String path = httpRequest.getRequestURI().substring(httpRequest.getContextPath().length());
        
        // pokud cesta začíná na "/assets/", tak se vrátí soubor z DEV serveru namísto ze složky assets
        if (path.startsWith("/assets/")) {
            URL url = new URL(devServerURL + path.replaceFirst("/assets", ""));
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod(httpRequest.getMethod());
            
            for (String headerName : connection.getHeaderFields().keySet()) {
                if (headerName != null) {
                    httpResponse.setHeader(headerName, connection.getHeaderField(headerName));
                }
            }
            
            httpResponse.setStatus(connection.getResponseCode());
            connection.getInputStream().transferTo(httpResponse.getOutputStream());
        } else {
            chain.doFilter(request, response);        	
        }
    }
}

V souboru web.xml můžeme náš vytvořený filtr nadefinovat a namapovat na "/assets/*", jak ukazuje následující ukázka. Pomocí inicializačního parametru DEV_SERVER_URL definujeme URL adresu, na kterém DEV server běží a pomocí parametru DEV_MODE nastavujeme, že se má DEV server použít.

  • backend/src/main/webapp/WEB-INF
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://Java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" 
id="WebApp_ID" version="3.0">
    <servlet>
        <servlet-name>HomeServlet</servlet-name>
        <servlet-class>HomeServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>HomeServlet</servlet-name>
        <url-pattern>/home</url-pattern>
    </servlet-mapping>
    
    <servlet>
        <servlet-name>CalculatorServlet</servlet-name>
        <servlet-class>CalculatorServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>CalculatorServlet</servlet-name>
        <url-pattern>/kalkulacka</url-pattern>
    </servlet-mapping>
    
    <filter>
        <filter-name>ProxyFilter</filter-name>
        <filter-class>ProxyFilter</filter-class>
        <init-param>
            <param-name>DEV_SERVER_URL</param-name>
            <param-value>http://localhost:3000</param-value>
        </init-param>
        <init-param>
            <param-name>DEV_MODE</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>ProxyFilter</filter-name>
        <url-pattern>/assets/*</url-pattern>
    </filter-mapping>
</web-app>

Po restartu aplikace by se vám teď soubory namísto ze složky webapp/assets měli servírovat z Webpack DEV serveru. Pokud se chcete ujistit, že tomu tak opravdu je, tak si složku assets ve složce webapp můžete klidně smazat.

Díky filtru, který jsme si na závěr tohoto článku vytvořili, již máte možnost, jak do vašich servletových webových aplikací integrovat Webpack. Jak jsem psal na začátku, to bylo cílem tohoto článku. Konfiguraci Webpacku jsem tu moc detailně nepopisoval, takže pokud s Webpackem teprve začínáte, můžete navštívit tento web, který jsem o Webpacku vytvořil. Najdete tam krátký tutoriál, který vás konfigurací Webpacku postupně provede.