Skip to content

feat(sui-bundler): add plainCssPackages config to bypass sass-loader#1988

Merged
tomasmax merged 4 commits into
masterfrom
feat/plain-css-packages-support
Jun 29, 2026
Merged

feat(sui-bundler): add plainCssPackages config to bypass sass-loader#1988
tomasmax merged 4 commits into
masterfrom
feat/plain-css-packages-support

Conversation

@tomasmax

@tomasmax tomasmax commented May 26, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds two new config options to sui-bundler:

  1. plainCssPackages — allows consumer apps to specify npm packages whose .css files should be loaded with only css-loader, completely bypassing @s-ui/sass-loader and postcss-loader.
  2. cssInAppStyles — forces CSS modules from specified packages into the AppStyles chunk, preventing webpack's splitChunks from extracting them into separate async chunks that @s-ui/ssr doesn't inject — avoiding Flash of Unstyled Content (FOUC).

Problem

Sass parse errors on modern CSS

sui-bundler routes all .css and .scss files through the full Sass + PostCSS pipeline — including third-party packages that ship pre-compiled Tailwind CSS v4 output. Dart Sass interprets valid CSS constructs (like transition-property: transform, ...) as Sass spread syntax, causing fatal build errors:

```
Error: expected ";"

│ transition-property:transform,...;
│ ^^^

```

This blocks any consumer app from importing packages that emit modern CSS not compatible with the Sass parser (e.g. @adv-mt/ui which uses Tailwind CSS v4).

Flash of Unstyled Content

When importing CSS from packages like @adv-mt/ui/styles.css via JS, webpack's splitChunks extracts it into a separate async chunk (e.g. 7100.css) because it exceeds the 30KB threshold. @s-ui/ssr only injects the configured AppStyles chunk, so DS styles arrive ~1.5s after first paint — components render without styles and visually "jump".

Solution

Two new config keys under config.sui-bundler in the consumer's package.json:

{
  "config": {
    "sui-bundler": {
      "plainCssPackages": ["@adv-mt/ui", "@adv-mt/theme"],
      "cssInAppStyles": ["@adv-mt/ui", "@adv-mt/theme"]
    }
  }
}

plainCssPackages

When set, the bundler:

  1. Excludes those packages from the main Sass/PostCSS rule via a regex on node_modules/<package>/
  2. Adds a separate rule that matches only .css files from those packages, using just css-loader (+ MiniCssExtractPlugin.loader or style-loader depending on the webpack config)

cssInAppStyles

When set, the bundler adds a webpack plugin that:

  1. Hooks into afterOptimizeChunks in the compilation
  2. Finds all css/mini-extract modules from the configured packages
  3. Moves them from their assigned chunk into the AppStyles chunk, so @s-ui/ssr injects them in the initial HTML

Files changed

File Change
shared/module-rules-sass.js Core logic — builds exclude regex, creates separate plain CSS rule, exports array when needed
shared/plain-css-split-chunks.js Webpack plugin that moves CSS modules into AppStyles chunk
webpack.config.client.dev.js Uses shared sassRules (was duplicating inline rules)
webpack.config.dev.js Adds plainCssPackages support with style-loader (SPA dev mode)
webpack.config.prod.js Spreads sassRules as array + integrates cssInAppStyles plugin
webpack.config.lib.js Spreads sassRules as array
test/server/plainCssSplitChunksSpec.js Smoke tests for cssInAppStyles plugin
README.md Documents both new config options

Usage

{
  "config": {
    "sui-bundler": {
      "plainCssPackages": ["@adv-mt/ui", "@adv-mt/theme"],
      "cssInAppStyles": ["@adv-mt/ui", "@adv-mt/theme"]
    }
  }
}

Then import '@adv-mt/ui/styles.css' works without Sass errors, and the styles are bundled into AppStyles.css — no FOUC.

Test plan

  • Verified locally: @adv-mt/ui@2.3.0 CSS builds without Sass errors
  • Verified in consumer app (frontend-ma--web-app#7667): build passes, styles in AppStyles chunk, no FOUC
  • Lint passes (0 errors)
  • Smoke tests pass (plainCssSplitChunksSpec.js)
  • CI (this PR)

🤖 Generated with Claude Code

…s-loader

Adds a new `plainCssPackages` option to `sui-bundler` config that allows
projects to specify npm packages whose CSS files should skip the
sass-loader pipeline entirely.

Packages that ship pre-compiled CSS (e.g. Tailwind CSS v4 output) use
modern CSS syntax (@layer, @Property, nested rules) that Dart Sass
cannot parse. This causes `Error: expected ";"` at build time.

Usage in package.json:
```json
{
  "config": {
    "sui-bundler": {
      "plainCssPackages": ["@adv-mt/ui", "@adv-mt/theme"]
    }
  }
}
```

CSS files from listed packages are processed only by css-loader
(+ MiniCssExtractPlugin or style-loader), bypassing postcss-loader and
sass-loader.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
tomasmax and others added 3 commits June 25, 2026 13:13
…to AppStyles chunk

Prevents webpack from splitting CSS of configured packages into separate
async chunks, ensuring SSR injects them on first render (no FOUC).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…les chunk

Replace splitChunks cacheGroups approach with a webpack plugin that moves
css/mini-extract modules from configured packages into the AppStyles chunk
at afterOptimizeChunks. More reliable regardless of chunk size thresholds.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…les config

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@tomasmax tomasmax merged commit fe64816 into master Jun 29, 2026
2 checks passed
@tomasmax tomasmax deleted the feat/plain-css-packages-support branch June 29, 2026 07:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants