Skip to content

razzle.extend.js plugin filter is a no-op — string vs object comparison causes duplicate SCSS rules #794

@mpalomaki

Description

@mpalomaki

Summary

VLT's razzle.extend.js plugin filter compares objects to a string, which is always true, so the built-in SCSS plugin is never removed. This results in two duplicate SCSS webpack rules that cause SCSS imports to silently produce zero CSS output.

Environment

  • Volto 19.0.0-alpha.24 (monorepo at packages/volto)
  • volto-light-theme 7.7.0 (also confirmed in 7.6.0 — identical code)
  • Node 22.22.0, pnpm via corepack

Root Cause

VLT's razzle.extend.js line 2:

const newPlugins = defaultPlugins.filter((plugin) => plugin !== 'scss');

But Volto registers plugins as objects, not strings:

// Volto's razzle.config.js line 427:
const defaultPlugins = [
  { object: require('./webpack-plugins/webpack-less-plugin')({ registry }) },
  { object: require('./webpack-plugins/webpack-svg-plugin') },
  { object: require('./webpack-plugins/webpack-bundle-analyze-plugin') },
  { object: require('./webpack-plugins/webpack-scss-plugin') },  // <-- unnamed object
];

An object is never === to a string, so the filter removes nothing. The built-in SCSS plugin survives. VLT then pushes its own { name: 'scss', options: {...} } entry, resulting in two SCSS webpack rules for /\.(sa|sc)ss$/.

The duplicate rules cause SCSS imports to silently produce zero CSS output (likely due to duplicate MiniCssExtractPlugin.loader extraction conflicting).

Evidence

  1. Two SCSS rules exist in the client webpack config — both match /\.(sa|sc)ss$/, both have 5 loaders
  2. import './test.css' produces changed CSS output (new hash, larger file)
  3. import './test.scss' with trivial content produces identical CSS output (same hash, same size)
  4. sass.compile() from Node successfully compiles VLT's full main.scss to 334KB of CSS
  5. Removing the built-in SCSS plugin fixes the issue — VLT's SCSS compiles correctly

Suggested Fix

The filter was likely written for an older Volto/Razzle format where plugins were passed as strings (['less', 'svg', 'scss']). It needs to handle the object format:

const plugins = (defaultPlugins) => {
  const newPlugins = defaultPlugins.filter((plugin) => {
    if (typeof plugin === 'string') return plugin !== 'scss';
    if (typeof plugin === 'object' && plugin !== null && plugin.name === 'scss') return false;
    return true;
  });
  // ... rest unchanged

This also requires Volto to tag its SCSS plugin with a name:

-  { object: require('./webpack-plugins/webpack-scss-plugin') },
+  { name: 'scss', object: require('./webpack-plugins/webpack-scss-plugin') },

Alternatively, VLT could filter by checking the object structure (e.g., inspecting the plugin's modifyWebpackConfig function).

Workaround

We applied both changes locally (tag + filter fix) and VLT's SCSS now compiles natively through webpack. CSS bundle is correct at ~1.09MB (785KB Semantic UI + 303KB VLT).

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions