Webpack — the module bundler | 2. část

Marek Janča
Ackee
Published in
5 min readFeb 23, 2017

Webpack se stal v poslední době celkem skloňovanou technologií a často je srovnávan s dalšími utilitami jako je Gulp nebo Grunt. V tomto a několika dalších článcích se snažím vysvětlit princip a filosofii Webpacku a shrnout pár případů, kdy chceme a nechceme Webpack používat a jak jej používat.

Články vychází z textu, který jsem sepsal k ukázkovým konfiguracím Webpacku na Githubu, a které vznikly v rámci vzdělávacích meetingů u nás v Ackee ❤.

Konfigurace Webpacku

V tomto článku navážeme na článek přechozí a blíže si rozebereme konfiguraci Webpacku.

Konfigurační soubor Webpacku je Node.js modul, který exportuje konfigurační objekt. Můžeme v něm psát jakýkoli Javascriptový kód interpretovatelný Node.js, což nám poskytuje mnoho možností. Například můžeme soubory, které chceme nastavit jako vstupy Webpacku, načítat z filesystému dynamicky.

Výchozím názvem konfiguračního souboru je webpack.config.js (pokud není uveden v příkazu hledá Webpack tento soubor) a Webpack spustíme pomocí webpack --config ./configs/webpack.config.js .

Konfigurační objekt obsahuje 4 nejdůležitější klíče:

  • entry obsahuje nastavení vstupu Webpacku
  • output obsahuje nastavení výstupu Webpacku
  • module obsahuje nastavení práce nad moduly (zejména nastavení loaderů)
  • plugins obsahuje pluginy a jejich nastavení

Entry

Entry obsahuje nastavení vstupu Webpacku. Říká, odkud má Webpack začít vytvářet balíček (balíčky). Každý jeden soubor uvedený v entry je kořenem grafu závislostí, který Webpack projde a vytvoří výsledný balíček.

Pokud použijeme paralelu s Node.js, pak si lze každý soubor na vstupu Webpacku představit jako soubor, který předáváme Node.js k interpretaci. Výsledný balíček Webpack můžeme předat prohlížeči a bude fungovat jako validní modulární kód.

Typ entry

  • string — cesta k souboru => jeden entry point, jeden balíček
  • pole — pole cest k souborů => více entry pointů, jeden balíček
  • object — objekt pojmenovaných cest k souborů => více entry pointů, více balíčků

V rámci konfigurace může být uveden klíč context, relativní cesty v entry se pak vztahují vůči němu.

Pravidlo: jeden entry point na HTML stránku, SPA = jeden globální entry point, MPA = více entrypointů

Příklad konfigurace entry:

module.exports = {
context: path.resolve(__dirname),
entry: './index.js',
// entry: ['./index.js', './login.js'],
// entry: {
// index: './index.js',
// login: './login.js',
// },
}

Output

Output obsahuje nastavení výstupu Webpacku. Jedná se o objekt s klíči:

path

Cesta do složky, kam se uloží výsledek Webpacku. Může být ovlivněna pomocí nastavení context.

filename

Název výstupu:

  • pokud je vstupem string nebo pole, pak je zde uveden string, pod kterým se balíček uloží do path
  • pokud je vstupem objekt, pak se zde uvede template string, který říká, jak se mají výsledné balíčky jmenovat. Např. [name].js říká, že se mají balíčky jmenovat podle klíčů ze vstupního objektu. Může být použito také např.[chunkhash].js, což uloží balíček po hashem, který mu vypočítá Webpack.

publicPath

Veřejná cesta k souborům v rámci výstupu z Webpacku (jak jsme si řekli, mohou být výstupem i např. css soubory) používaná loadery (např url-loader) a pluginy. Jedná se o cestu, pod kterou bude balíček vystaven na serveru. Je závislá na kořenu webserveru, na kterém je balíček vystaven. Je-li tento kořen shodný s path, pak by měla být publicPath nastavena na / .

Příklad konfigurace output:

module.exports = {
context: path.resolve(__dirname),
entry: './index.js',
// entry: ['./index.js', './login.js'],
// entry: {
// index: './index.js',
// login: './login.js',
// },
output: {
path: path.join(__dirname, 'build'),
publicPath: '/',
filename: 'bundle.js',
// filename: '[name].js',
// filename: '[chunkhash].js',
}
}

Pokud chceme používat Webpack pouze na vytváření balíčků z modulárního ES5 Javascriptového kódu, pak nám stačí v konfiguraci uvést pouze entry a output.

Loadery

Loadery jsou transformace aplikované per module. Jsou aplikovány na moduly různých typů před tím, než je vytvořen balíček. V položce module se nastavují pravidla, která říkají, v jakém případě se má loader aplikovat.
Každé pravidlo se skládá z testu regulární výrazem, který říká, jestli se mají uvedené loadery na modul aplikovat či nikoliv. Loader má svůj název a nastavení pomocí options:

module.exports = {
module: {
rules: [
{
test: /\.js$/
use: [
{
loader: 'babel-loader',
options: {
presets: ['react']
}
}
]
},
{
test: /\.sass$/,
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
},
{
loader: 'sass-loader',
}
]
}
]
}
}

Pokud je uvedeno více loaderů, pak se na modul aplikují postupně od spodu. Ve výše uvedeném příkladu se na soubory končící .sass aplikuje postupně sass, css a style loader.

Loadery nastavené v konfiguraci jsou aplikovány automaticky pokud je splněn regulární výraz pro import modulu. Např. pro výše uvedenou konfiguraci by se aplikoval loader v případě require('../sass/main.sass') .

Loadery však nemusí být nutně nastaveny v konfiguraci, ale lze je aplikovat přímo v require klauzuli require('style-loader!css-loader!sass-loader!../sass/main.sass') . Toto použití loaderů je upřednostněno před konfigurací a jsou aplikovány od modulu směrem doleva. Konfigurace loaderu se pak provádí přes query string require('babel-loader?presets=['react']!./component.jsx') .

Pomocí loaderů lze tedy transpilovat JSX na ES5 js, ES6 js na ES5 js, Sass na CSS, obrázky na base64 stringy, json soubory na js objekty atd.

Vždy musí být výsledkem posledního loaderu v pipě Javascript (protože Webpack je Javascript module bundler).

Je tedy nutné převést všechny moduly, které nejsou Javascript interpretovatelný prohlížečem, na ES5 Javascript, protože Webpack primárně tvoří balíčky Javascriptu. Proto například style-loader vezme CSS a převede jej na Javascript, který dané CSS vloží dynamicky do hlavičky HTML dokumentu. Něco podobné se musí provést i s ostatními non-js moduly.

Pluginy

Pluginy jsou transformace per bundle (balíček). Používají se pro funkcionalitu, kterou není schopen provést loader. Takovou funkcionalitou může být například extrakce stylů, vyčlenění vendorů do samostaných balíčků, obfuskace (minifikace a uglyfikace) Javascriptu či nastavení globálních proměnných.
Pluginy jsou konfigurovány v položce plugins. Jedná se o pole, do kterého předáváme instance jednotlivých pluginů, protože můžeme chtít jeden plugin použít víckrát v různých případech.

Příklad použití pluginu:

module.exports = {
plugins: [
new ExtractTextPlugin({
name: 'bundle.css'
}),
new webpack.optimize.UglifyJsPlugin({
minimize: true
}),
]
}

Jak bylo uvedeno v přechozím článku, je možné vytvářet pomocí Webpacku i jiné balíčky než Javascript. Pokud například nechceme, aby se CSS přidávaly do js balíčku jako kód (což provede style-loader), ale byly vyextrahovány jako samostatný CSS bundle, můžeme použít výše uvedený ExtractTextPlugin. Webpack pak pomocí pluginu vygeneruje do output.path soubor bundle.css obsahující všechna CSS z Javascript balíčku, protože se plugin aplikuje nad celým js balíčkem.

Jak to celé zhruba funguje

Již rozumíme konfiguraci Webpacku a v jakých případech se aplikuje jaký druh tranformace, shrňme si průběh kompilace pomocí Webpacku.

  1. Webpack vytvoří grafy závislostí, jejichž kořeny jsou soubory uvedené v entry.
  2. Prochází postupně směřem do hloubky moduly, které jsou do entry pointů requirovány a aplikuje na ně podle stanovených pravidel loadery, které mohou dělat transpilaci či jinou transformaci modulů.
  3. Když Webpack projde graf závislostí pro vybraný entry point, už by mohl vytvořit balíček. Nejprve však aplikuje specifikované pluginy. Zde může docháze k minifikaci či uglyfikaci (obfuskaci) nebo třeba extrakci vendor balíčků.
  4. Nakonec Webpack uloží balíček podle specifikace v output.

Příklad konfigurace

const webpack = require('webpack');
const path = require('path');
const Extract = require('extract-text-webpack-plugin');
module.exports = {
context: path.resolve(__dirname),
entry: './index.js',
output: {
path: path.join(__dirname, 'build'),
filename: 'index.js',
},
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader: 'babel-loader',
options: {
presets: ['latest', 'react']
}
}
]
}, {
test: /\.sass$/,
loader: Extract.extract({
fallback: 'style-loader',
use: 'css-loader!sass-loader'
})
},
]
},
plugins: [
new Extract({
filename: 'bundled-sass.css',
}),
new webpack.optimize.UglifyJsPlugin({
minimize: true,
}),
new webpack.EnvironmentPlugin([
'NODE_ENV',
]),
]
};

V dalším článku

Jaký je rozdíl mezi Webpackem a Gulpem (Gruntem)? V jakých případech chceme či nechceme Webpack použít?

Další články z tohoto seriálu

--

--

Marek Janča
Ackee
Writer for

Front-end web developer at Ackee.cz ❤ IT, UX, coffee, art, architecture, music, books, movies, nature, mountains, nordic countries, slackline and running