Skip to content

Commit 89399fa

Browse files
chrisbbreuerclaude
andcommitted
feat: inject Crosswind CSS into dashboard pages
Uses @cwcss/crosswind to extract Tailwind utility classes from rendered HTML and generate actual CSS. Injects a <style data-crosswind> tag into <head> with preflight reset + all utility styles. Dashboard pages now render with proper styling without needing Tailwind CDN. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 4f8098d commit 89399fa

File tree

3 files changed

+42
-1
lines changed

3 files changed

+42
-1
lines changed

bun.lock

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/registry/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@
2525
"test:commit-publish": "bun test src/commit-publish.test.ts",
2626
"typecheck": "bun --bun tsc --noEmit"
2727
},
28-
"dependencies": {},
28+
"dependencies": {
29+
"@cwcss/crosswind": "^0.1.6"
30+
},
2931
"devDependencies": {
3032
"bun-types": "latest",
3133
"typescript": "^5.7.0"

packages/registry/src/server.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { createAnalytics, type AnalyticsStorage, type AnalyticsCategory } from '
66
import { handleZigRoutes, createZigStorage } from './zig-routes'
77
import type { ZigPackageStorage } from './zig'
88
import { S3Client } from './storage/aws-client'
9+
import { CSSGenerator, defaultConfig } from '@cwcss/crosswind'
910

1011
/**
1112
* Lightweight .stx template renderer — processes server scripts, directives and expressions.
@@ -46,9 +47,42 @@ async function stxRender(filePath: string, props: Record<string, unknown> = {}):
4647

4748
// 2. Process directives
4849
templateContent = processBlock(templateContent, ctx)
50+
51+
// 3. Generate and inject Crosswind CSS from Tailwind utility classes
52+
templateContent = injectCrosswindCSS(templateContent)
53+
4954
return templateContent
5055
}
5156

57+
/**
58+
* Extract Tailwind class names from HTML, generate CSS via Crosswind, and inject <style> into <head>
59+
*/
60+
function injectCrosswindCSS(html: string): string {
61+
const classRegex = /class\s*=\s*["']([^"']+)["']/gi
62+
const classNames = new Set<string>()
63+
let m: RegExpExecArray | null
64+
while ((m = classRegex.exec(html)) !== null) {
65+
for (const cls of m[1].split(/\s+/)) {
66+
if (cls) classNames.add(cls)
67+
}
68+
}
69+
if (classNames.size === 0) return html
70+
71+
const gen = new CSSGenerator(defaultConfig)
72+
for (const cls of classNames) {
73+
try { gen.generate(cls) }
74+
catch { /* skip unknown classes */ }
75+
}
76+
const css = gen.toCSS(true, true)
77+
if (!css) return html
78+
79+
const styleTag = `<style data-crosswind="generated">${css}</style>`
80+
if (html.includes('</head>')) {
81+
return html.replace('</head>', `${styleTag}\n</head>`)
82+
}
83+
return `${styleTag}\n${html}`
84+
}
85+
5286
function processBlock(template: string, ctx: Record<string, any>): string {
5387
let output = ''
5488
const lines = template.split('\n')

0 commit comments

Comments
 (0)