Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
280 changes: 280 additions & 0 deletions src/content/guides/native-css.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
---
title: Native CSS
sort: 27
contributors:
- phoekerson
---

This guide shows how to use webpack native CSS handling with `experiments.css`.

T> `experiments.css` is still experimental. It is expected to become the default in webpack v6, but behavior can still change while development continues.

## Getting Started

Enable native CSS support in your webpack configuration:

**webpack.config.js**

```js
export default {
experiments: {
css: true,
},
};
```

With this option enabled, webpack can process CSS without adding `css-loader` and `mini-css-extract-plugin` for the basic flow.

## Importing CSS

After enabling the experiment, import `.css` files directly from JavaScript:

**src/index.js**

```js
import "./styles.css";

const element = document.createElement("h1");
element.textContent = "Hello native CSS";
document.body.appendChild(element);
```

**src/styles.css**

```css
h1 {
color: #1f6feb;
}
```

Webpack will process the CSS and include it in the build output.

## CSS Modules

Native CSS support also includes CSS Modules. The recommended approach is:

- keep `type: "css/auto"` for mixed CSS handling,
- use `.module.css` (or `.modules.css`) naming for CSS Modules files.

**src/button.module.css**

```css
.button {
background: #0d6efd;
color: white;
border: 0;
border-radius: 4px;
padding: 8px 12px;
}
```

**src/index.js**

```js
import * as styles from "./button.module.css";

const button = document.createElement("button");
button.className = styles.button;
button.textContent = "Click me";
document.body.appendChild(button);
```

T> CSS Modules class names are exported. By default, named exports are enabled for CSS modules.

You can customize CSS Modules behavior using parser and generator options:

**webpack.config.js**

```js
export default {
experiments: {
css: true,
},
module: {
parser: {
"css/module": {
namedExports: true,
},
},
generator: {
"css/module": {
exportsConvention: "camel-case-only",
localIdentName: "[uniqueName]-[id]-[local]",
},
},
},
};
```

## Production Build

With `experiments.css: true`, webpack provides native CSS extraction and content hashing for CSS assets in production builds.

Compared to the classic setup:

- Traditional approach: `css-loader` + `mini-css-extract-plugin`
- Native approach: `experiments.css` with built-in extraction behavior

This reduces configuration and keeps the CSS pipeline closer to webpack core features.

## Experimental Status & Known Limitations

`experiments.css` is explicitly experimental, so treat it as opt-in and test carefully before broad rollout.

Known points to keep in mind:

- APIs and behavior may still evolve before webpack v6 defaults.
- Some loader-specific options are not part of native CSS behavior (for example, loader-specific filters).
- If your project relies on advanced loader chains, validate each part before migrating fully.

## Migration Guide

If you currently use `css-loader`, `mini-css-extract-plugin`, and `style-loader`, migrate in small steps.

### 1) Start from a classic setup

**webpack.config.js**

```js
import MiniCssExtractPlugin from "mini-css-extract-plugin";

export default {
module: {
rules: [
{
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, "css-loader"],
},
],
},
plugins: [new MiniCssExtractPlugin()],
};
```

### 2) Switch to native CSS

**webpack.config.js**

```js
export default {
experiments: {
css: true,
},
};
```

### 3) Migrate `css-loader` options first

Most CSS Modules-related options should move to native parser/generator config.

**webpack.config.js**

```js
export default {
experiments: {
css: true,
},
module: {
parser: {
css: {
import: true,
url: true,
},
"css/module": {
namedExports: true,
},
},
generator: {
"css/module": {
exportsConvention: "camel-case-only",
localIdentName: "[local]-[hash:base64:6]",
},
},
},
};
```

Notes:

- `import` and `url` are native parser switches for CSS handling.
- `namedExports` controls CSS Modules exports behavior.
- `exportsConvention` and `localIdentName` provide class export/name shaping.
- `localIdentName` supports hash placeholders (for example `[hash:base64:6]`); you can tune hashing globally with [`output.hashFunction`](/configuration/output/#outputhashfunction), [`output.hashDigest`](/configuration/output/#outputhashdigest), [`output.hashDigestLength`](/configuration/output/#outputhashdigestlength), and [`output.hashSalt`](/configuration/output/#outputhashsalt).
- `css-loader` filter-style options are not available as direct equivalents; use webpack mechanisms such as `IgnorePlugin` when needed.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add more options here around localIdentName (hash function, digest and etc)


### 4) Replace `mini-css-extract-plugin`

When `experiments.css` is enabled, webpack provides native CSS extraction and content hash handling for CSS output files.

**webpack.config.js**

```diff
-import MiniCssExtractPlugin from "mini-css-extract-plugin";
-
export default {
+ experiments: {
+ css: true,
+ },
module: {
rules: [
{
test: /\.css$/i,
- use: [MiniCssExtractPlugin.loader, "css-loader"],
+ use: ["css-loader"],
},
],
},
- plugins: [new MiniCssExtractPlugin()],
};
```

You can remove:

- `MiniCssExtractPlugin.loader` from `module.rules`,
- the `new MiniCssExtractPlugin()` plugin instance.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add diff here to show it


### 5) Replace `style-loader` with `exportType: "style"`

If you used `style-loader` for runtime style injection, keep `css/auto` and use module naming (`.module.css` or `.modules.css`), then set `exportType: "style"`:

**webpack.config.js**

```js
export default {
experiments: {
css: true,
},
module: {
rules: [
{
test: /\.css$/i,
type: "css/auto",
parser: {
exportType: "style",
},
},
],
},
};
```

This mode injects a `<style>` element from the webpack runtime and covers the typical `style-loader` use case.

If you cannot rename files to the CSS Modules naming convention, you can use `type: "css/module"` directly for the relevant rule.

### 6) Keep imports unchanged

Your JS imports can stay the same:

```js
import "./styles.css";
import * as styles from "./button.module.css";
```

### 7) Validate output in development and production

Check that:

- styles are applied correctly in development,
- generated CSS files are emitted for production,
- CSS Modules exports match your existing usage.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add options (from css-loader and style-loader and mini-css-extract-plugin) for CSS modules here and how to migrate, most of options are already implemented, some of them should be solved in other way, feel free to start from css-loader, then mini-css-extract-plugin and then style-loader, you can find all supported options here:

style-loader now is exportType: "style"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I updated it

Loading