Skip to content

@nx/angular-rspack-compiler doesn't pass Tailwind/PostCSS configuration to ComponentStylesheetBundler #34098

@faileon

Description

@faileon

Current Behavior

When using @nx/angular-rspack with Tailwind CSS, @apply directives in component styles (.css/.scss files referenced via styleUrl/styleUrls) are not processed. The raw @apply syntax appears unchanged in the browser.

Example:

/* app.component.css */
:host {
    @apply bg-red-500 hover:brightness-110;
}

Browser output:

/* The @apply directive is NOT processed */
:host {
    @apply bg-red-500 hover:brightness-110;
}

Global styles (styles.scss) work correctly because they go through Rspack's PostCSS loader chain which includes Tailwind.

Expected Behavior

@apply directives in component stylesheets should be processed by Tailwind CSS, matching the behavior of:

  • Angular CLI's esbuild-based builder (@angular/build)
  • The webpack-based @angular-devkit/build-angular

Root Cause

In @nx/angular-rspack-compiler, the setupCompilation function creates a ComponentStylesheetBundler but doesn't pass tailwindConfiguration or postcssConfiguration:

Current code (packages/angular-rspack-compiler/src/compilation/setup-compilation.ts or the compiled .js):

const componentStylesheetBundler = new ComponentStylesheetBundler({
    workspaceRoot: options.root,
    optimization: config.mode === 'production',
    // ... other options
    includePaths: options.includePaths,
    sass: options.sass,
    // MISSING: tailwindConfiguration
    // MISSING: postcssConfiguration
}, options.inlineStyleLanguage, false);

However, @angular/build's ComponentStylesheetBundler does support these options and uses them internally:

// From @angular/build/src/tools/esbuild/stylesheets/bundle-options.ts
const pluginFactory = new StylesheetPluginFactory({
    sourcemap: !!options.sourcemap,
    includePaths,
    inlineComponentData,
    tailwindConfiguration: options.tailwindConfiguration, // ← Used here
    postcssConfiguration: options.postcssConfiguration,   // ← Used here
    sass: options.sass,
}, cache);

Steps to Reproduce

  1. Create an Angular app with @nx/angular-rspack:

    npx create-nx-workspace@latest myapp --preset=angular-standalone --bundler=rspack
  2. Add Tailwind CSS:

    npm install -D tailwindcss postcss autoprefixer
    npx tailwindcss init
  3. Configure Tailwind in tailwind.config.js:

    module.exports = {
      content: ['./src/**/*.{html,ts}'],
      theme: { extend: {} },
      plugins: [],
    };
  4. Add Tailwind to src/styles.css:

    @tailwind base;
    @tailwind components;
    @tailwind utilities;
  5. Create a component with @apply in its stylesheet:

    // app.component.ts
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css'],
    })
    export class AppComponent {}
    /* app.component.css */
    :host {
        @apply bg-red-500 text-white p-4;
    }
  6. Run nx serve or nx build

  7. Inspect the component in browser DevTools → @apply bg-red-500 text-white p-4 appears unchanged

Environment

  • Nx: 22.3.3
  • @nx/angular-rspack: 22.3.3
  • @nx/angular-rspack-compiler: 22.3.3
  • Angular: 21.0.8
  • Tailwind CSS: 3.4.18
  • Node: 23.x
  • Package Manager: pnpm 9.15.3

Suggested Fix

Load Tailwind/PostCSS configuration in setupCompilation and pass it to ComponentStylesheetBundler, similar to how @angular/build does it:

// In packages/angular-rspack-compiler/src/compilation/setup-compilation.ts
import { 
    ComponentStylesheetBundler,
    generateSearchDirectories,
    loadPostcssConfiguration,
    findTailwindConfiguration,
} from '@angular/build/private';
import { createRequire } from 'node:module';

async function setupCompilation(config, options) {
    // ... existing code ...
    
    // Load PostCSS and Tailwind configuration for component styles
    const searchDirectories = await generateSearchDirectories([options.root]);
    const postcssConfiguration = await loadPostcssConfiguration(searchDirectories);
    
    // Skip tailwind configuration if postcss is customized
    let tailwindConfiguration;
    if (!postcssConfiguration) {
        const tailwindConfigPath = findTailwindConfiguration(searchDirectories);
        if (tailwindConfigPath) {
            const resolver = createRequire(tailwindConfigPath);
            try {
                tailwindConfiguration = {
                    file: tailwindConfigPath,
                    package: resolver.resolve('tailwindcss'),
                };
            } catch (e) {
                // Tailwind config found but package not installed - warning already shown by Angular build
            }
        }
    }
    
    const componentStylesheetBundler = new ComponentStylesheetBundler({
        workspaceRoot: options.root,
        // ... existing options ...
        tailwindConfiguration,    // ← Add this
        postcssConfiguration,     // ← Add this
    }, options.inlineStyleLanguage, false);
    
    // ... rest of the function
}

Workaround

We've created a pnpm patch that implements the suggested fix:

diff --git a/dist/compilation/setup-compilation.js b/dist/compilation/setup-compilation.js
index 556fbb5456dc55dd0baa8553119c841aa3e096d8..34487b39f2cb9fecade5233003304f77c1edd6d3 100644
--- a/dist/compilation/setup-compilation.js
+++ b/dist/compilation/setup-compilation.js
@@ -7,6 +7,7 @@ const utils_1 = require("../utils");
 const private_1 = require("@angular/build/private");
 const targets_from_browsers_1 = require("../utils/targets-from-browsers");
 const private_2 = require("@angular/build/private");
+const node_module_1 = require("node:module");
 exports.DEFAULT_NG_COMPILER_OPTIONS = {
     suppressOutputPathCheck: true,
     outDir: undefined,
@@ -35,6 +36,30 @@ async function setupCompilation(config, options) {
             : {}),
     });
     const compilerOptions = tsCompilerOptions;
+    
+    // Load PostCSS and Tailwind configuration for component styles
+    // This enables @apply directives and other PostCSS features in component stylesheets
+    const searchDirectories = await (0, private_1.generateSearchDirectories)([options.root]);
+    const postcssConfiguration = await (0, private_1.loadPostcssConfiguration)(searchDirectories);
+    
+    // Skip tailwind configuration if postcss is customized
+    let tailwindConfiguration;
+    if (!postcssConfiguration) {
+        const tailwindConfigurationPath = (0, private_1.findTailwindConfiguration)(searchDirectories);
+        if (tailwindConfigurationPath) {
+            const resolver = (0, node_module_1.createRequire)(tailwindConfigurationPath);
+            try {
+                tailwindConfiguration = {
+                    file: tailwindConfigurationPath,
+                    package: resolver.resolve('tailwindcss'),
+                };
+            }
+            catch (e) {
+                // Tailwind config found but package not installed - warning already shown by Angular build
+            }
+        }
+    }
+    
     const componentStylesheetBundler = new private_1.ComponentStylesheetBundler({
         workspaceRoot: options.root,
         optimization: config.mode === 'production',
@@ -50,6 +75,8 @@ async function setupCompilation(config, options) {
         })),
         includePaths: options.includePaths,
         sass: options.sass,
+        tailwindConfiguration,
+        postcssConfiguration,
     }, options.inlineStyleLanguage, false);
     return {
         rootNames,

Additional Context

  • This issue affects only component styles (styleUrl, styleUrls)
  • Global styles in styles.scss work correctly (processed by Rspack's PostCSS loader)
  • The ComponentStylesheetBundler from @angular/build already supports these options
  • This is not an Rspack limitation - it's a missing feature in the Nx wrapper

Nx Report

Node           : 22.16.0
OS             : linux-x64
Native Target  : x86_64-linux
pnpm           : 9.15.3

nx                     : 22.3.3
@nx/js                 : 22.3.3
@nx/eslint             : 22.3.3
@nx/workspace          : 22.3.3
@nx/angular            : 22.3.3
@nx/jest               : 22.3.3
@nx/cypress            : 22.3.3
@nx/devkit             : 22.3.3
@nx/eslint-plugin      : 22.3.3
@nx/module-federation  : 22.3.3
@nx/nest               : 22.3.3
@nx/node               : 22.3.3
@nx/plugin             : 22.3.3
@nx/rspack             : 22.3.3
@nx/storybook          : 22.3.3
@nx/web                : 22.3.3
@nx/webpack            : 22.3.3
typescript             : 5.9.3
---------------------------------------
Registered Plugins:
@nx/eslint/plugin
@nx/rspack/plugin
---------------------------------------
Community plugins:
@jscutlery/semver     : 5.7.1
@ngrx/component-store : 21.0.1
@ngrx/effects         : 21.0.1
@ngrx/operators       : 21.0.1
@ngrx/router-store    : 21.0.1
@ngrx/store           : 21.0.1
@ngrx/store-devtools  : 21.0.1
@storybook/angular    : 10.1.11
angular-eslint        : 21.1.0
apollo-angular        : 12.1.0
---------------------------------------
Local workspace plugins:
@datera/nx
---------------------------------------
Cache Usage: 0.00 B / 11.56 GB

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions