---
name: webpack
description: 'Webpack configuration for s360-base-theme asset compilation. Use when: modifying ui/webpack.config.js, troubleshooting webpack build errors, adding new entry points, configuring loaders (scss, js, images, svg sprites), adding or changing output paths, understanding how compiled assets map to libraries.yml entries, running watch mode during development.'
---

# Webpack — s360-base-theme

## Overview

Webpack lives in `ui/webpack.config.js`. It compiles per-component SCSS and JS from `ui/src/` into `ui/dist/`. Each component produces its own CSS/JS file. The output files are referenced by the split `libraries/*.libraries.yml` files in Drupal.

Webpack runs **on the host machine** (not inside Lando) from the `ui/` Yarn workspace.

**Source:** `https://github.com/Square360/s360-base-theme`

## Key Design Decisions

- **Glob-based entries** — webpack discovers entry points automatically by scanning `src/**/*.js`, excluding partials (`_*`), stories, component files, tests, and minified files
- **No hand-crafted entry list** — adding a new `component-name.js` file automatically becomes an entry point on next build
- **Per-component output** — each component gets its own `dist/{type}/{name}/{name}.css` and `.js`, never bundled together
- **SVG sprites** — `src/images/icons/` is compiled into `dist/images/icons.svg` via `svg-sprite-loader`

## Running the Build

Always from `ui/`:

```bash
cd web/themes/custom/{theme}/ui
yarn develop        # webpack watch + storybook dev (concurrently) ← use during development
yarn webpack        # webpack watch only
yarn build          # production build (webpack + storybook)
```

## Entry Point Pattern

Webpack entry files are plain `.js` files that only import their corresponding SCSS:

```js
// src/templates/paragraph/card/component/paragraph.card.js
import './paragraph.card.scss';
```

If the component has a Drupal behavior, it goes here too:

```js
import './paragraph.card.scss';

(function (Drupal) {
  Drupal.behaviors.paragraphCard = {
    attach(context) {
      const items = once('paragraph-card', '.card', context);
      items.forEach((el) => {
        // behavior logic
      });
    },
  };
})(Drupal);
```

## Glob Entry Discovery

The webpack config uses `glob.sync()` to find entries:

```
src/**/!(_*|*.stories|*.component|*.min|*.test).js
```

- `_*` — excludes underscore-prefixed (partial) files
- `*.stories.js` — excluded (Storybook only)
- `*.component.js` — excluded (component-level files not meant as standalone entries)

The path from `src/` is preserved in the output, minus `templates/` and `/component/` path segments.

## Output Mapping

| Source | Output |
|--------|--------|
| `src/templates/paragraph/card/component/paragraph.card.js` | `dist/paragraph/card/paragraph.card.css` + `.js` |
| `src/templates/node/article/teaser/component/node.article.teaser.js` | `dist/node/article/teaser/node.article.teaser.css` + `.js` |
| `src/component/pager/pager.js` | `dist/component/pager/pager.css` + `.js` |
| `src/templates/navigation/main/menu.main.js` | `dist/navigation/main/menu.main.css` + `.js` |

## SCSS Pipeline

Each component SCSS **must** begin with:

```scss
@use "@s360-base-theme/ui-core/site-config";
@use "s360-scss-toolkit";
```

The SCSS loader chain: `sass-loader` → `postcss-loader` (autoprefixer) → `css-loader` → `MiniCssExtractPlugin.loader`

```js
{
  test: /\.s?[ac]ss/i,
  use: [
    { loader: MiniCssExtractPlugin.loader },
    { loader: 'css-loader', options: { url: { filter: ... } } },
    { loader: 'postcss-loader', options: { postcssOptions: { parser: 'postcss-scss', plugins: [autoprefixer({ grid: true })] } } },
    { loader: 'sass-loader', options: { implementation: require('sass'), sassOptions: { quietDeps: true } } }
  ]
}
```

Note: `quietDeps: true` suppresses deprecation warnings from dependencies.

## JavaScript Pipeline

Babel transpiles JS (excluding stories and component files):

```js
{
  test: /^(?!.*\.(stories|component)\.(js|ts)$).*\.(js|ts)$/,
  use: [{ loader: 'babel-loader' }],
  exclude: /node_modules/
}
```

`.babelrc` in `ui/` configures the presets for `@babel/preset-env`.

## SVG Sprites

SVG files in `src/images/icons/` are processed into a sprite sheet:

```js
{
  test: /\.(svg)$/,
  include: path.resolve('src/images/icons'),
  use: [{
    loader: 'svg-sprite-loader',
    options: { extract: true, spriteFilename: 'images/icons.svg' }
  }]
}
```

Reference icons in Twig:
```twig
<svg><use href="{{ base_path ~ directory }}/ui/dist/images/icons.svg#icon-name"></use></svg>
```

## Registering Output in libraries.yml

Each component's output is registered in the split library files in `libraries/`:

```yaml
# libraries/s360_base_theme.paragraph.libraries.yml
paragraph.card:
  css:
    component:
      ui/dist/paragraph/card/paragraph.card.css: {}
```

```yaml
# libraries/s360_base_theme.navigation.libraries.yml
menu.main:
  css:
    component:
      ui/dist/navigation/main/menu.main.css: {}
  js:
    ui/dist/navigation/main/menu.main.js: { attributes: { defer: true } }
  dependencies:
    - core/drupal
```

After changing library files: `lando drush cr`

## Aliases

The webpack config defines path aliases:

| Alias | Resolves to |
|-------|-------------|
| `SRC_IMAGES$` | `ui/src/images` |
| `vendors` | `ui/vendors` |
| `utils` | `ui/utils` |

Use in SCSS or JS: `import image from 'SRC_IMAGES/logo.svg'`

## Multiple Themes (Advanced)

If a project has multiple sub-themes sharing one build, copy the `WEBPACK_CONFIG` object in `module.exports`:

```js
module.exports = [
  WEBPACK_CONFIG,
  Object.assign({}, WEBPACK_CONFIG, {
    name: 'sub-theme',
    entry: getEntries(path.resolve('../../sub-theme/ui/src/**/!(_*|*.stories|*.component|*.min|*.test).js')),
    output: {
      path: path.resolve('../../sub-theme/ui/dist'),
      clean: true,
    },
  }),
];
```

## Common Issues

| Issue | Fix |
|-------|-----|
| New component not compiling | Ensure the `.js` entry file exists and imports `.scss`; restart watch |
| `@import` deprecation warning | Replace with `@use` — `@import` is banned |
| `@s360-base-theme/ui-core` not found | Run `yarn install` from workspace root, not `ui/` |
| CSS not loading in Drupal | Check library path matches `ui/dist/...` exactly; run `lando drush cr` |
| SVG sprite not resolving | Ensure SVG is in `src/images/icons/`, not `src/images/` |
| `quietDeps` errors | Sass deprecation in dependencies — `quietDeps: true` should suppress; upgrade `sass` if persists |

