From 5813203043ba5c4f5b19628459cf46f937857194 Mon Sep 17 00:00:00 2001 From: Tomas Madariaga Date: Thu, 6 Aug 2026 11:39:53 +0200 Subject: [PATCH] fix(packages/sui-bundler): don't force sass-loader on plainCssPackages when linking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit linkLoaderConfigBuilder rewrote the last loader of every module rule whose test matched .css to inject @s-ui/sass-loader with the link importer — including the plainCssPackages passthrough rule (added in #1988), which is meant to skip sass-loader entirely for packages that ship pre-built plain CSS. This broke consumers linking local packages via -l/-L (a standard dev workflow in monorepos) whenever any plainCssPackages dependency shipped modern CSS syntax (e.g. Tailwind v4's @layer/@supports), since sass-loader can't parse it. Only rewrite rules that already use @s-ui/sass-loader, compared by resolved path so this also holds when @s-ui/sass-loader itself is linked (its resolved path won't contain the package name substring). Co-Authored-By: Claude Sonnet 5 --- .../loaders/linkLoaderConfigBuilder.js | 15 ++++ .../server/linkLoaderConfigBuilderSpec.js | 74 +++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 packages/sui-bundler/test/server/linkLoaderConfigBuilderSpec.js diff --git a/packages/sui-bundler/loaders/linkLoaderConfigBuilder.js b/packages/sui-bundler/loaders/linkLoaderConfigBuilder.js index d7c4294da..d9a7ea4ce 100644 --- a/packages/sui-bundler/loaders/linkLoaderConfigBuilder.js +++ b/packages/sui-bundler/loaders/linkLoaderConfigBuilder.js @@ -83,6 +83,21 @@ module.exports = ({config, packagesToLink, linkAll}) => { if (!regex.test('.css')) return rule + // Rules matching .css that don't already run through @s-ui/sass-loader + // (e.g. the plainCssPackages passthrough rule, see module-rules-sass.js) + // must be left alone — otherwise this force-injects the sass loader + // into plain CSS files, breaking packages that ship pre-built CSS + // (Tailwind v4 output, etc.) when they're consumed via -l/-L linking. + // Compared by resolved path rather than package-name substring so this + // also works when @s-ui/sass-loader itself is symlinked/linked (e.g. + // inside this monorepo), where its resolved path won't contain the + // package name. + const sassLoaderPath = require.resolve('@s-ui/sass-loader') + const usesSassLoader = use.some( + loaderUse => (typeof loaderUse === 'string' ? loaderUse : loaderUse.loader) === sassLoaderPath + ) + if (!usesSassLoader) return rule + return { ...rule, use: [...use.slice(0, -1), sassLoaderWithLinkImporter] diff --git a/packages/sui-bundler/test/server/linkLoaderConfigBuilderSpec.js b/packages/sui-bundler/test/server/linkLoaderConfigBuilderSpec.js new file mode 100644 index 000000000..0a98a04f8 --- /dev/null +++ b/packages/sui-bundler/test/server/linkLoaderConfigBuilderSpec.js @@ -0,0 +1,74 @@ +const {expect} = require('chai') + +const linkLoaderConfigBuilder = require('../../loaders/linkLoaderConfigBuilder.js') + +describe('linkLoaderConfigBuilder', () => { + const packagesToLink = [__dirname] + + it('injects the sass link importer into rules that already use @s-ui/sass-loader', () => { + const config = { + module: { + rules: [ + { + test: /(\.css|\.scss)$/, + exclude: /node_modules/, + use: [ + 'mini-css-extract-plugin-loader', + 'css-loader', + 'postcss-loader', + require.resolve('@s-ui/sass-loader') + ] + } + ] + }, + resolve: {alias: {}} + } + + const nextConfig = linkLoaderConfigBuilder({config, packagesToLink}) + const sassRule = nextConfig.module.rules[0] + + expect(sassRule.use).to.have.lengthOf(4) + expect(sassRule.use[3]).to.be.an('object') + expect(sassRule.use[3].loader).to.equal(require.resolve('@s-ui/sass-loader')) + expect(sassRule.use[3].options.sassOptions).to.have.property('importer') + }) + + it('leaves the plainCssPackages passthrough rule untouched', () => { + const config = { + module: { + rules: [ + { + test: /(\.css|\.scss)$/, + exclude: /node_modules[\\/](@adv-mt\/ui)[\\/]/, + use: [ + 'mini-css-extract-plugin-loader', + 'css-loader', + 'postcss-loader', + require.resolve('@s-ui/sass-loader') + ] + }, + { + test: /\.css$/, + include: /node_modules[\\/](@adv-mt\/ui)[\\/]/, + use: ['mini-css-extract-plugin-loader', 'css-loader'] + } + ] + }, + resolve: {alias: {}} + } + + const nextConfig = linkLoaderConfigBuilder({config, packagesToLink}) + const plainCssRule = nextConfig.module.rules[1] + + // Before the fix, this rule's last loader (css-loader) was replaced with + // @s-ui/sass-loader, breaking pre-built CSS (e.g. Tailwind v4 output) + // from plainCssPackages when consumed via -l/-L linking. + expect(plainCssRule.use).to.deep.equal(['mini-css-extract-plugin-loader', 'css-loader']) + }) + + it('returns the original config unmodified when there is nothing to link', () => { + const config = {module: {rules: []}} + const nextConfig = linkLoaderConfigBuilder({config, packagesToLink: [], linkAll: false}) + expect(nextConfig).to.equal(config) + }) +})