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) + }) +})