diff --git a/src/assets/blog/webpack-5-108.png b/src/assets/blog/webpack-5-108.png new file mode 100644 index 000000000000..57b5e556838f Binary files /dev/null and b/src/assets/blog/webpack-5-108.png differ diff --git a/src/content/blog/2026-06-25-webpack-5-108.mdx b/src/content/blog/2026-06-25-webpack-5-108.mdx new file mode 100644 index 000000000000..47306006c8b6 --- /dev/null +++ b/src/content/blog/2026-06-25-webpack-5-108.mdx @@ -0,0 +1,402 @@ +--- +title: Webpack 5.108 +sort: 20260618 +contributors: + - bjohansebas +--- + +import banner from "../../assets/blog/webpack-5-108.png"; + +webpack 5.108 release + +Webpack 5.108 is out, and it pushes two big stories forward. The headline is a brand-new `universal` target: a single preset that compiles one bundle which adapts at runtime to the browser, web workers, Node.js, Electron, and NW.js, replacing the hand-written `target: ["web", "node"]` setups people have been maintaining for years. The release also continues the native HTML work that started in 5.107: a `.html` file can now be used directly as an `entry`, webpack can emit an HTML file for plain JavaScript entries, and HTML modules now support Hot Module Replacement. + +Around those headlines, this release lands a substantial round of tree-shaking improvements (a new `optimization.inlineExports`, cross-module purity, and CommonJS re-export analysis), modernizes the code webpack generates for capable targets, and adds a typed `defineConfig` helper. + +Both the HTML and `universal` features are experimental and live behind opt-in flags, but the direction stays the same as 5.107: you should eventually be able to build a complete web app with zero extra loaders or plugins for HTML, CSS, and TypeScript. + +Explore what's new: + +- [**The `universal` Target**](#the-universal-target) +- [**Bun and Deno Targets**](#bun-and-deno-targets) +- [**HTML Modules: Entry Points and HMR**](#html-modules-entry-points-and-hmr) + - [HTML as an Entry Point](#html-as-an-entry-point) + - [HTML Output for JavaScript Entries](#html-output-for-javascript-entries) + - [Hot Module Replacement](#hot-module-replacement) + - [Customizing the HTML Parser](#customizing-the-html-parser) +- [**CSS Improvements**](#css-improvements) + - [`url()` Inside HTML `style` Attributes](#url-inside-html-style-attributes) + - [CSS in Node for Universal Builds](#css-in-node-for-universal-builds) +- [**Tree Shaking**](#tree-shaking) + - [`optimization.inlineExports`](#optimizationinlineexports) + - [Cross-Module Dead-Branch Skipping](#cross-module-dead-branch-skipping) + - [Cross-Module Purity](#cross-module-purity) + - [CommonJS Re-exports via `Object.defineProperty`](#commonjs-re-exports-via-objectdefineproperty) +- [**Automatic ES Module Detection**](#automatic-es-module-detection) +- [**Output and Runtime**](#output-and-runtime) + - [Modern Syntax in Generated Code](#modern-syntax-in-generated-code) + - [`output.strictModuleResolution`](#outputstrictmoduleresolution) + - [`[uniqueName]` Template Placeholder](#uniquename-template-placeholder) + - [Worker Chunk Filenames](#worker-chunk-filenames) +- [**Typed Configuration with `defineConfig`**](#typed-configuration-with-defineconfig) +- [**Bug Fixes**](#bug-fixes) + +## The `universal` Target + +Building code that runs in more than one environment used to mean hand-writing `target: ["web", "node"]` and then dealing with the rough edges yourself: choosing a chunk format that loads everywhere, wiring up a global object, and guarding every platform-specific API by hand. Webpack 5.108 turns that into a single preset. + +```js +// webpack.config.js +module.exports = { + target: "universal", +}; +``` + +[`target: "universal"`](/configuration/target/#universal) combines the `web`, `web worker`, `node`, `electron`, and `nwjs` platforms into one target and leaves each platform flag _neutral_ instead of locking the bundle to a single environment. Rather than compiling separate web and node bundles, you ship one bundle that figures out its surroundings at runtime and uses whatever the current platform provides. + +A bundle that has to load equally well in a browser and in Node needs a portable module format, so universal builds always output ECMAScript modules: [`experiments.outputModule`](/configuration/experiments/#experimentsoutputmodule) defaults to `true`, synchronous `require` is turned off, and Node's built-in modules stay available (resolved at runtime through ESM). [`output.globalObject`](/configuration/output/#outputglobalobject) also defaults to `globalThis` so runtime code has one global to reach for on every platform. + +To make a single bundle behave correctly everywhere, several pieces were made platform-aware and now branch on feature detection at runtime instead of being fixed at build time: + +- **Workers**: `new Worker(new URL(...))` resolves the `Worker` constructor from `worker_threads` in Node and from the global `Worker` on the web, so the same worker entry runs in both. +- **Externals**: `commonjs` and `node-commonjs` externals work from the ESM output (loaded defensively via `createRequire` obtained from `process.getBuiltinModule`, guarded so they never break in the browser), and `global` externals resolve against `globalThis`. Electron externals use `module-import` when the target supports ESM. +- **CSS for SSR**: on the server, where there is no DOM to inject into, styles are collected into a registry an SSR host can read (see [CSS in Node for Universal Builds](#css-in-node-for-universal-builds)). + +Plugins and loaders can detect a universal build at compile time through `compiler.platform.universal`, which is also `true` for the equivalent `target: ["web", "node"]`. + +For a complete, runnable setup, see the [`universal` example](https://github.com/webpack/webpack/tree/main/examples/universal) in the webpack repository. + +## Bun and Deno Targets + +Alongside `universal`, webpack 5.108 adds dedicated presets for two newer JavaScript runtimes. Each one configures ESM output and marks the runtime's own built-in modules as externals (so they're left for the runtime to provide instead of being bundled), which previously you had to set up by hand. + +[`target: "bun"`](/configuration/target/#string) builds for Bun, externalizing Bun's own `bun:*` modules and the Node.js built-ins it provides instead of bundling them. + +```js +// webpack.config.js +module.exports = { + target: "bun", +}; +``` + +[`target: "deno"`](/configuration/target/#string) builds for Deno. It resolves Node.js built-ins through the `node:` specifier that Deno requires, and keeps Deno's own import protocols (`npm:`, `jsr:`, `node:`, and `http(s)://` URLs) external so the runtime loads them. Like webpack's other version-aware targets, it accepts a version such as `deno2` or `deno1.40`. + +```js +// webpack.config.js +module.exports = { + target: "deno", // also "deno2", "deno1.40", ... +}; +``` + +## HTML Modules: Entry Points and HMR + +W> **This feature is experimental and partial.** Webpack 5.107 implemented the `html-loader` side of the story: importing an HTML file from JavaScript runs its tag references through the webpack pipeline. Webpack 5.108 adds the ability to use a `.html` file as an `entry`. Full parity with `html-webpack-plugin` is still in progress; the overall effort is tracked in issue [#536](https://github.com/webpack/webpack/issues/536). + +### HTML as an Entry Point + +With [`experiments.html`](/configuration/experiments/#experimentshtml) enabled, you can now point `entry` directly at an HTML file. Its `