Tutorial · JavaScript

Webpack 5 configureren: Babel, CSS, SASS en PostCSS in 2026

10 min · Beginner · CSS, JavaScript · geverifieerd op 7 september 2026

Kort antwoord

In een Webpack 5-configuratie die je vandaag oppakt, breken er geruisloos twee dingen: node-sass, dat sass-loader 17 helemaal niet meer herkent, en @babel/preset-env zonder opgegeven target, dat sinds Babel 8 niets meer transpileert. Deze gids geeft de volledige configuratie, basis, Babel, CSS, SASS, PostCSS, productie, opnieuw uitgevoerd op 2 september 2026 met Webpack 5.110.3 en Node 22.

Deze gids vertrekt vanuit een simpele vaststelling: in 2026 open je een Webpack-project zelden om het aan te maken, maar om het te repareren. De vier onderdelen die problemen geven zijn altijd dezelfde — JavaScript transpileren, CSS laden, SCSS compileren, PostCSS toepassen. Ze staan hier bij elkaar, in één configuratie die is getest op Webpack 5.110.3 en Node 22.

Is Webpack in 2026 nog een goede keuze?

Die vraag verdient een eerlijk antwoord voordat je ook maar iets configureert, want ze bepaalt hoeveel werk je erin gaat steken.

Voor een nieuw project: nee. Vite is de standaardkeuze van Vue en van Laravel, en het is ook waar de documentatie van de meeste frontend-frameworks naar wijst. Een project in 2026 op Webpack starten komt neer op met de hand een configuratie schrijven die andere tools al kant-en-klaar leveren.

Voor een bestaand project: ja, en zonder schuldgevoel. Webpack is niet opgegeven: versie 5.110.3 is die van deze gids, de roadmap voor 2026 kondigt native CSS, ingebouwde TypeScript-transpilatie en HTML-entrypoints aan, en versie 6 staat gepland voor eind 2027. Een codebase met een zware configuratie, eigen loaders of specifieke plugins heeft geen enkele reden om halsoverkop te migreren.

De vuistregel: blijft je koude start onder de vijf seconden, dan heeft het onderwerp geen prioriteit. Duurt hij meer dan een halve minuut, dan levert migreren echt comfort op. De sectie migreren naar Vite of Rspack aan het eind van deze gids geeft de vertrekpunten.

De video’s in deze gids dateren van de oorspronkelijke publicatie in februari 2022. Ze laten de algemene aanpak zien, maar de actuele versies en code staan in de tekst hieronder.

De basis: entry, output en developmentserver

Webpack leest een entrybestand, volgt de import van bestand naar bestand en schrijft een uitvoerbestand weg. Al de rest is niet meer dan een aanvulling op dat mechanisme.

bash
mkdir -p src dist
npm init --yes
npm install --save-dev webpack webpack-cli webpack-dev-server

De minimale configuratie past in drie sleutels. entry wijst het vertrekpunt aan, output het gegenereerde bestand, devServer de map die tijdens de ontwikkeling wordt geserveerd.

webpack.config.js
const path = require('path');

module.exports = {
  entry: path.resolve(__dirname, 'src/index.js'),
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
  },
  devServer: {
    static: { directory: path.resolve(__dirname, 'dist') },
    port: 8080,
  },
};

Twee punten waar oude configuraties vaak de mist in gaan. output.path moet een absoluut pad zijn, vandaar de path.resolve(__dirname, …): een waarde als '/dist' wijst naar de root van de schijf. En sinds Webpack 5 vervangt devServer.static het oude contentBase.

Voeg de npm-scripts toe en start de server:

package.json
{
  "scripts": {
    "start": "webpack serve --mode development",
    "build": "webpack --mode production"
  }
}

Op de testmachine antwoordt npm start netjes met HTTP 200 op http://localhost:8080/, met webpack-dev-server 6.0.0:

bash
<i> [webpack-dev-server] Project is running at:
<i> [webpack-dev-server] Loopback: http://localhost:8080/, http://[::1]:8080/
<i> [webpack-dev-server] Content not from webpack is served from '/app/dist' directory

Babel: wat er is veranderd, en waarom je configuratie niets meer transpileert

Dit is het belangrijkste punt van deze update, en het punt dat oude projecten geruisloos breekt. Babel 8 heeft zijn standaardtarget gewijzigd. Een configuratie die @babel/preset-env opgeeft zonder target te noemen, transpileert helemaal niets meer.

bash
npm install --save-dev @babel/core @babel/preset-env babel-loader
webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.m?js$/,
        exclude: /node_modules/,
        use: 'babel-loader',
      },
    ],
  },
};

Dit is de Babel-configuratie zoals je die in de meeste tutorials vindt, ook in de versie van deze pagina uit 2022:

.babelrc
{
  "presets": ["@babel/preset-env"]
}

Met Babel 8.0.1, toegepast op dit bronbestand:

src/index.js
const nombres = [1, 2, 3, 4];
const doubles = nombres.map((n) => n * 2);
const total = doubles?.reduce((a, b) => a + b, 0) ?? 0;
class Compteur { #valeur = total; lire() { return this.#valeur; } }
document.querySelector('#app').textContent = `Total : ${new Compteur().lire()}`;

levert ze een bundle van 173 bytes op waarin de arrow functions, de optional chaining ?., de nullish coalescing ?? en het privéveld #valeur nog gewoon intact staan:

dist/bundle.js
(()=>{const e=[1,2,3,4].map(e=>2*e),t=e?.reduce((e,t)=>e+t,0)??0;
document.querySelector("#app").textContent=`Total : ${(new class{#e=t;lire(){return this.#e}}).lire()}`})();

Babel draait dus, kost buildtijd en transformeert niets. De oplossing past in één sleutel: geef de target expliciet op.

.babelrc
{
  "presets": [
    ["@babel/preset-env", { "targets": { "ie": "11" } }]
  ]
}

Met die target levert dezelfde code 1,48 KiB ES5-uitvoer op, met de functies _typeof, _defineProperty en consorten die je van een echte transpilatie verwacht.

Er valt hier een nuance te begrijpen in plaats van een reflex toe te passen: met een moderne target als "defaults" blijft de uitvoer gelijk aan de invoer, en dat is normaal. Alle browsers die nog worden gevolgd kennen arrow functions, ?., ?? en privévelden native. Heb je geen oude browser te bedienen, dan valt er voor Babel in je buildketen weinig meer te doen. Hem eruit halen is een legitieme beslissing, geen shortcut.

CSS: stylesheets laden vanuit JavaScript

Twee loaders werken samen. css-loader lost de @import en de url() op en zet de stylesheet om in een JavaScript-module; style-loader injecteert die module bij het laden van de pagina in een <style>-tag.

bash
npm install --save-dev css-loader style-loader
webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: ['style-loader', 'css-loader'],
      },
    ],
  },
};

De volgorde telt, en wordt van rechts naar links gelezen: css-loader verwerkt het bestand, daarna injecteert style-loader het resultaat. Draai ze om en de build mislukt.

Nu hoef je de stylesheet alleen nog vanuit JavaScript te importeren:

src/index.js
import './style.css';

Na de build zit de kleur uit style.css inderdaad in dist/bundle.js: tijdens de ontwikkeling reist de CSS mee in het JavaScript. Voor productie haalt de volgende sectie hem naar een echt .css-bestand.

SASS: SCSS compileren en node-sass lozen

Als je een project oppakt dat vóór 2023 is opgezet, breekt de build hier. De historische instructie was npm install --save-dev sass-loader node-sass. Op Node 22 mislukt ze al bij de installatie: node-sass heeft geen binary voor deze Node-versie en de native build stopt, zonder dat sass-loader geïnstalleerd wordt:

bash
npm warn deprecated node-sass@9.0.0: Node Sass is no longer supported. Please use `sass` or `sass-embedded` instead.
npm error code 1
npm error path /app/node_modules/node-sass
npm error command failed
npm error command sh -c node scripts/build.js

En in een project waar node-sass al in node_modules stond, breekt de build, met een duidelijke fout en exitcode 1:

bash
ERROR in ./src/style.scss
Module build failed (from ./node_modules/sass-loader/dist/cjs/index.js):
Error: Unknown Sass implementation "node-sass".
    at getSassImplementation (/app/node_modules/sass-loader/dist/cjs/utils.js:168:9)

webpack 5.110.3 compiled with 1 error

Er zijn twee dingen gebeurd. Het pakket node-sass is verouderd — npm zegt het zelf: ‘Node Sass is no longer supported’ — en vooral heeft sass-loader 17 de ondersteuning van die implementatie geschrapt. Er is geen omweg: je moet overstappen op sass, de Dart-implementatie.

bash
npm uninstall node-sass
npm install --save-dev sass sass-loader
webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.s[ac]ss$/i,
        use: ['style-loader', 'css-loader', 'sass-loader'],
      },
    ],
  },
};

Er is geen enkele optie nodig: sass-loader detecteert sass automatisch. De keten wordt nog altijd van rechts naar links gelezen — SCSS naar CSS gecompileerd, dan geladen, dan geïnjecteerd.

src/style.scss
$couleur: #d62828;

#app {
  color: $couleur;
  display: flex;

  &:hover { text-decoration: underline; }
}

Met sass 1.103.1 slaagt de build en wordt de variabele netjes opgelost: color:#d62828 staat in de uitvoer.

PostCSS: prefixes en moderne syntaxis

Anders dan Babel heeft PostCSS al zijn nut behouden. Het voegt de vendor prefixes toe en brengt recente CSS-syntaxis terug naar een vorm die je doelbrowsers begrijpen.

bash
npm install --save-dev postcss postcss-loader postcss-preset-env
webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.s?[ac]ss$/i,
        use: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader'],
      },
    ],
  },
};
postcss.config.js
module.exports = {
  plugins: [require('postcss-preset-env')({ stage: 2 })],
};

postcss-loader staat tussen css-loader en sass-loader: de SCSS wordt gecompileerd, PostCSS werkt op de verkregen CSS, en daarna neemt css-loader het over.

Op de regel uit de vorige sectie, met user-select en inset erbij, laat de uitvoer zien wat er echt gebeurt:

dist/main.[contenthash].css
#app{color:#d62828;display:flex;-webkit-user-select:none;-moz-user-select:none;
user-select:none;top:0;right:0;bottom:0;left:0}
#app:hover{-webkit-text-decoration:underline;text-decoration:underline}

De prefixes worden toegevoegd en inset: 0 wordt uitgeschreven in vier eigenschappen. Dat is concrete winst, anders dan bij Babel op een moderne target.

De valkuil om te kennen. Veel configuraties, waaronder de versie van deze pagina uit 2022, geven de optie browsers: 'last 2 versions' mee aan postcss-preset-env. Die optie is deprecated, en de query zelf is een val: in browserslist betekent last 2 versions “de laatste twee versies van elke browser”, Internet Explorer inbegrepen. Nagegaan met caniuse-lite 1.0.30001810 geeft die query 30 targets terug, waaronder ie 11, ie 10, ie_mob en op_mini all. Gemeten resultaat op hetzelfde bestand:

css
/* met browsers: 'last 2 versions' — inclusief IE */
display:-webkit-box; display:-ms-flexbox; display:flex;

/* met de standaardtarget (defaults) — 32 targets, geen IE */
display:flex;

Beter is de optie browsers te schrappen en een veld browserslist in de package.json te zetten, dat door PostCSS, Babel en de rest van de keten wordt gelezen:

package.json
{
  "browserslist": ["defaults", "not dead"]
}

Naar productie: CSS extraheren, HTML genereren, hashen

Drie toevoegingen scheiden de developmentconfiguratie van een build die je kunt uitleveren. mini-css-extract-plugin haalt de CSS uit de JavaScript-bundle in een eigen bestand. html-webpack-plugin genereert de index.html en zet er de juiste bestandsnamen in. De [contenthash] en output.clean regelen de cache en het opruimen.

bash
npm install --save-dev mini-css-extract-plugin html-webpack-plugin

Hier is de volledige configuratie, dezelfde die voor alle metingen op deze pagina is gebruikt:

webpack.config.js
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

const enProduction = process.env.NODE_ENV === 'production';

module.exports = {
  mode: enProduction ? 'production' : 'development',
  entry: path.resolve(__dirname, 'src/index.js'),
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: enProduction ? '[name].[contenthash].js' : '[name].js',
    clean: true,
  },
  module: {
    rules: [
      { test: /\.m?js$/, exclude: /node_modules/, use: 'babel-loader' },
      {
        test: /\.s?[ac]ss$/i,
        use: [
          enProduction ? MiniCssExtractPlugin.loader : 'style-loader',
          'css-loader',
          'postcss-loader',
          'sass-loader',
        ],
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({ template: path.resolve(__dirname, 'src/index.html') }),
    ...(enProduction ? [new MiniCssExtractPlugin({ filename: '[name].[contenthash].css' })] : []),
  ],
  devServer: {
    static: { directory: path.resolve(__dirname, 'dist') },
    port: 8080,
    hot: true,
  },
};

Wat je moet onthouden: MiniCssExtractPlugin.loader en style-loader sluiten elkaar uit. Je houdt style-loader voor development, voor het hot reloading, en schakelt in productie over op extractie.

bash
NODE_ENV=production npx webpack
bash
asset index.html 240 bytes [emitted] [minimized]
asset main.ca8a5113b21c83c25e3e.css 198 bytes [emitted] [immutable] (name: main)
asset main.b0cab13462d9bb7b12ba.js 99 bytes [emitted] [immutable] (name: main)
webpack 5.110.3 compiled successfully

De gegenereerde HTML verwijst zonder handwerk naar beide gehashte bestanden:

dist/index.html
<!doctype html><html lang=fr><head><meta charset=utf-8><title>Hello Webpack</title>
<script defer src=main.b0cab13462d9bb7b12ba.js></script>
<link href=main.ca8a5113b21c83c25e3e.css rel=stylesheet></head>
<body><div id=app></div></body></html>

Het alternatief zonder loader: de native CSS van Webpack

Webpack kan CSS laden zonder ook maar één loader, via een experimentele optie. Het is een van de werven die op de roadmap 2026 staan aangekondigd, bedoeld om in versie 6 het standaardgedrag te worden.

webpack.config.js
module.exports = {
  mode: 'production',
  entry: path.resolve(__dirname, 'src/index.js'),
  output: { path: path.resolve(__dirname, 'dist'), filename: 'bundle.js', clean: true },
  experiments: { css: true },
};

Zonder css-loader, zonder style-loader en zonder mini-css-extract-plugin levert de build wel degelijk een apart CSS-bestand op:

bash
asset bundle.js 71 bytes [emitted] [minimized] (name: main)
asset bundle.css 32 bytes [emitted] [minimized] (name: main)
webpack 5.110.3 compiled successfully in 1392 ms

Alleen geschikt voor projecten die niet meer dan eenvoudige CSS nodig hebben: de optie blijft experimenteel, en ze vervangt SASS noch PostCSS, die nog altijd hun loaders vereisen.

Migreren naar Vite of Rspack: waar begin je

Wijst de diagnose uit de eerste sectie je richting een migratie, dan zijn er twee wegen, en ze kosten niet evenveel.

Rspack is een herschrijving van Webpack in Rust die het configuratiemodel behoudt en breed compatibel blijft met de plugin-API. Dat is de goedkoopste weg wanneer de bestaande configuratie zwaar is: de begrippen entry en output en de regels voor loaders uit deze gids blijven gelden.

Vite dwingt je de configuratie opnieuw te doordenken, maar is de standaardkeuze van het ecosysteem. De migratie draait vooral om specifieke plugins en niet-standaard imports. De Franse gidsen bij de bronnen spreken van een werkdag voor een middelgroot Vue-project.

In beide gevallen is het werk uit deze gids niet verloren: precies weten wat elke loader in je huidige configuratie doet, is exact wat je in staat stelt te beslissen wat je meeneemt en wat je laat vallen.

Deze gids maakt deel uit van de fundamenten die zijn verzameld op de hub Webontwikkeling.

Veelgemaakte fouten

node-sass legt de build stil sass-loader 17 heeft die implementatie geschrapt: de build faalt op Unknown Sass implementation "node-sass" met exitcode 1. Vervang hem door het pakket sass.
@babel/preset-env zonder targets transpileert niets meer Sinds Babel 8 is de standaardtarget veranderd: moderne code komt er intact uit. Geef targets op, of een veld browserslist.
browsers: 'last 2 versions' haalt Internet Explorer binnen In browserslist geeft die query 30 targets terug, waaronder ie 11 en ie 10, met nutteloze -ms-flexbox-prefixes tot gevolg. De optie browsers is sowieso deprecated.
style-loader en MiniCssExtractPlugin in dezelfde regel Ze sluiten elkaar uit. Houd style-loader voor development en schakel in productie over op extractie.
output.path moet absoluut zijn Een waarde als '/dist' wijst naar de root van de schijf. Gebruik path.resolve(__dirname, 'dist').
De volgorde van de loaders wordt van rechts naar links gelezen ['style-loader', 'css-loader'] voert eerst css-loader uit. Omgekeerd mislukt de build.
Nieuwsbrief

Nieuwe tests, tutorials en projecten, per e-mail.

Reproduceerbare tests, geversioneerde code, gedateerde resultaten. Nooit spam.