Debug Stitches RSC CSS Extraction
Troubleshoot build-time CSS extraction issues by tracing through the analysis, extraction, transformation, and generation pipeline.
Extraction Pipeline Overview
The build plugin processes files through four stages:
Source Code → Analyzer → Extractor → Transformer → CSS Generator
↓ ↓ ↓ ↓
AST analysis Static CSS Dynamic vars Final CSS
Quick Diagnostic
Check if File is Being Processed
Add debug logging to plugin config:
Next.js:
// next.config.js
const withStitchesRSC = require('@stitches-rsc/next-plugin');
module.exports = withStitchesRSC({
// Add files you expect to be processed
include: ['src', 'app', 'components'],
exclude: ['node_modules'],
})({
webpack: (config, { isServer }) => {
console.log('Processing files with extensions:', ['.tsx', '.ts', '.jsx', '.js']);
return config;
},
});
Vite:
// vite.config.ts
import stitchesRSC from '@stitches-rsc/vite-plugin';
export default defineConfig({
plugins: [
stitchesRSC({
include: ['src'],
exclude: ['node_modules'],
}),
],
});
Verify Stitches Import Detection
The analyzer looks for these import patterns:
// ✅ Detected
import { styled, css } from '@stitches-rsc/react';
import { createStitches } from '@stitches-rsc/react';
import * as Stitches from '@stitches-rsc/react';
// ❌ Not detected (would need config)
import { styled } from './stitches.config';
import { css } from '../lib/stitches';
Stage-by-Stage Debugging
Stage 1: Analyzer
The analyzer parses source code and finds Stitches usage.
Debug with:
import { analyzeSource } from '@stitches-rsc/plugin-common';
const source = `
import { styled } from '@stitches-rsc/react';
const Button = styled('button', {
backgroundColor: 'blue',
});
`;
const analysis = analyzeSource(source, 'Button.tsx');
console.log('Has Stitches import:', analysis.hasStitchesImport);
console.log('Configs found:', analysis.configs.length);
console.log('Usages found:', analysis.usages.length);
for (const usage of analysis.usages) {
console.log('Usage:', {
type: usage.type, // 'styled', 'css', 'globalCss', etc.
name: usage.name, // Variable name if assigned
start: usage.start, // Source position
hasDynamicValues: usage.hasDynamicValues,
});
}
Common issues:
- •
hasStitchesImport: false→ Import path not recognized - •
usages.length === 0→ Stitches calls not detected - •
hasDynamicValues: true→ Contains non-static values
Stage 2: Extractor
The extractor converts static usages to CSS rules.
Debug with:
import { analyzeSource, extractCss } from '@stitches-rsc/plugin-common';
const analysis = analyzeSource(source, 'Button.tsx');
const extraction = extractCss(analysis, {
config: {
prefix: 'app',
theme: {
colors: { primary: '#0070f3' },
},
},
});
console.log('Rules extracted:', extraction.rules.length);
console.log('Class names:', [...extraction.classNames.entries()]);
for (const rule of extraction.rules) {
console.log('Rule:', {
layer: rule.layer,
selector: rule.selector,
css: rule.css,
});
}
Common issues:
- •
rules.length === 0→ All usages have dynamic values - •Missing theme tokens → Config not passed correctly
- •Wrong class names → Hash collision or naming issue
Stage 3: Transformer
The transformer converts dynamic values to CSS variables.
Debug with:
import { analyzeSource, extractCss, transformSource } from '@stitches-rsc/plugin-common';
const analysis = analyzeSource(source, 'Button.tsx');
const extraction = extractCss(analysis, {});
const transformed = transformSource({
analysis,
extraction,
});
console.log('Transformed code:', transformed.code);
console.log('Dynamic variables:', transformed.dynamicVariables);
for (const dynVar of transformed.dynamicVariables) {
console.log('Dynamic var:', {
variableName: dynVar.variableName, // e.g., '--stitches-dyn-0'
expression: dynVar.expression, // Original JS expression
className: dynVar.className,
});
}
Common issues:
- •Dynamic variables not created → Expression not detected as dynamic
- •Wrong expression captured → AST traversal issue
Stage 4: CSS Generator
The generator produces final CSS with layers and scope.
Debug with:
import { generateFullCss } from '@stitches-rsc/plugin-common';
const css = generateFullCss(extraction, {
useScope: true,
useLayers: true,
minify: false,
layerPrefix: 'stitches',
});
console.log('Generated CSS:\n', css);
Common issues:
- •No
@layer→useLayers: false - •No
@scope→useScope: false - •Minified output hard to read → Set
minify: false
Full Pipeline Debug Script
Create debug-extraction.ts:
import { readFileSync } from 'fs';
import {
analyzeSource,
extractCss,
transformSource,
generateFullCss,
} from '@stitches-rsc/plugin-common';
const filename = process.argv[2];
if (!filename) {
console.error('Usage: npx tsx debug-extraction.ts <file>');
process.exit(1);
}
const source = readFileSync(filename, 'utf-8');
console.log('=== STAGE 1: ANALYSIS ===');
const analysis = analyzeSource(source, filename);
console.log('Has import:', analysis.hasStitchesImport);
console.log('Usages:', analysis.usages.length);
analysis.usages.forEach((u, i) => {
console.log(` [${i}] ${u.type} "${u.name}" dynamic=${u.hasDynamicValues}`);
});
console.log('\n=== STAGE 2: EXTRACTION ===');
const extraction = extractCss(analysis, {});
console.log('Rules:', extraction.rules.length);
extraction.rules.forEach((r, i) => {
console.log(` [${i}] ${r.layer}: ${r.selector}`);
});
console.log('\n=== STAGE 3: TRANSFORMATION ===');
const transformed = transformSource({ analysis, extraction });
console.log('Dynamic vars:', transformed.dynamicVariables.length);
transformed.dynamicVariables.forEach((v, i) => {
console.log(` [${i}] ${v.variableName} = ${v.expression}`);
});
console.log('\n=== STAGE 4: CSS OUTPUT ===');
const css = generateFullCss(extraction, {
useScope: true,
useLayers: true,
minify: false,
});
console.log(css);
Run with:
npx tsx debug-extraction.ts src/components/Button.tsx
Common Problems and Solutions
Problem: "No Stitches import detected"
Cause: Import path doesn't match expected patterns Solution: Use standard import paths:
import { styled } from '@stitches-rsc/react';
Problem: "Usage has dynamic values, skipping extraction"
Cause: Non-literal values in style object Solution: Make values static or let them become CSS variables:
// Dynamic - will use CSS variable
const Box = styled('div', {
margin: dynamicValue, // Becomes var(--stitches-dyn-X)
});
// Static - will be extracted
const Box = styled('div', {
margin: '16px',
});
Problem: "Token not found in theme"
Cause: Theme config not passed to extractor Solution: Ensure config is provided:
const extraction = extractCss(analysis, {
config: {
theme: {
colors: { primary: '#0070f3' },
},
},
});
Problem: "CSS not appearing in build output"
Causes:
- •File not in include path
- •File in exclude path
- •Build plugin not configured
Solution: Check plugin config paths match your file structure.
Additional Resources
See references/ast-patterns.md for AST detection patterns.