diff --git a/docs/type-generation.md b/docs/type-generation.md index ec3b933..8873cfd 100644 --- a/docs/type-generation.md +++ b/docs/type-generation.md @@ -60,6 +60,11 @@ Use `--hashed` when you want `.knighted-css` proxy modules to export `selectors` CSS Modules hashing instead of stable selector strings. This keeps the module and selector types while preserving hashed class names at runtime. +> [!NOTE] +> `--hashed` derives the selector list from the compiled CSS, so the generated sidecar can +> include class names that are not exported by the module (for example, sprinkles output from +> vanilla-extract). At runtime, `selectors` reflects only exported locals from the loader bridge, so the runtime map can be a subset of the generated sidecar. + > [!IMPORTANT] > `--hashed` requires the bundler to route `?knighted-css` imports through > `@knighted/css/loader-bridge`, so the proxy can read `knightedCss` and diff --git a/package-lock.json b/package-lock.json index 22eedbe..a2e2160 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11437,7 +11437,7 @@ }, "packages/css": { "name": "@knighted/css", - "version": "1.1.0", + "version": "1.1.1", "license": "MIT", "dependencies": { "es-module-lexer": "^2.0.0", @@ -11709,7 +11709,7 @@ "name": "@knighted/css-playwright-fixture", "version": "0.0.0", "dependencies": { - "@knighted/css": "1.1.0", + "@knighted/css": "1.1.1", "@knighted/jsx": "^1.7.5", "lit": "^3.2.1", "react": "^19.0.0", diff --git a/packages/css/README.md b/packages/css/README.md index 62cc476..8e154f3 100644 --- a/packages/css/README.md +++ b/packages/css/README.md @@ -137,6 +137,11 @@ selectors.card // hashed CSS Modules class name > `--hashed` requires wiring `@knighted/css/loader-bridge` to handle `?knighted-css` queries so > the generated proxies can read `knightedCss` and `knightedCssModules` at build time. +> [!NOTE] +> `--hashed` builds the selector list from compiled CSS. The generated sidecar can therefore +> include class names that are not exported by the module (e.g. sprinkles output), while the +> runtime `selectors` map only includes exported locals from the loader bridge. + Refer to [docs/type-generation.md](../../docs/type-generation.md) for CLI options and workflow tips. ### Combined + runtime selectors diff --git a/packages/css/package.json b/packages/css/package.json index aabcf05..1ecf065 100644 --- a/packages/css/package.json +++ b/packages/css/package.json @@ -1,6 +1,6 @@ { "name": "@knighted/css", - "version": "1.1.0", + "version": "1.1.1", "description": "A build-time utility that traverses JavaScript/TypeScript module dependency graphs to extract, compile, and optimize all imported CSS into a single, in-memory string.", "type": "module", "main": "./dist/css.js", diff --git a/packages/css/src/loaderBridge.ts b/packages/css/src/loaderBridge.ts index e61add5..6ee6590 100644 --- a/packages/css/src/loaderBridge.ts +++ b/packages/css/src/loaderBridge.ts @@ -383,14 +383,18 @@ function resolveCssModules( if (!locals || typeof locals !== 'object') continue return locals as Record } - const isStringMapLocal = (value: object): value is Record => { - const entries = Object.entries(value) - if (entries.length === 0) return false - return entries.every(([, entry]) => typeof entry === 'string') + const normalizeStringMapLocal = (value: object): Record | undefined => { + const entries = Object.entries(value).filter( + ([key]) => key !== 'default' && key !== '__esModule', + ) + if (entries.length === 0) return undefined + if (!entries.every(([, entry]) => typeof entry === 'string')) return undefined + return Object.fromEntries(entries) as Record } for (const candidate of candidates) { if (!candidate || typeof candidate !== 'object') continue - if (isStringMapLocal(candidate)) return candidate + const normalized = normalizeStringMapLocal(candidate) + if (normalized) return normalized } const collectNamedExportsLocal = ( value: unknown, @@ -443,7 +447,7 @@ function createBridgeModule(options: BridgeModuleOptions): string { `const __knightedResolveCss = ${resolveCssText.toString()};`, `const __knightedResolveCssModules = ${resolveCssModules.toString()};`, `const __knightedUpstreamLocals =\n __knightedResolveCssModules(__knightedUpstream, __knightedUpstream);`, - `const __knightedLocalsExport =\n __knightedUpstreamLocals ??\n __knightedResolveCssModules(__knightedLocals, __knightedLocals) ??\n __knightedLocals;`, + `const __knightedLocalsExport =\n __knightedUpstreamLocals ??\n __knightedResolveCssModules(__knightedLocals, __knightedLocals);`, `const __knightedBaseCss = __knightedResolveCss(__knightedDefault, __knightedUpstream);`, `const __knightedCss = [__knightedBaseCss, ${cssValues.join(', ')}].filter(Boolean).join('\\n');`, `export const ${DEFAULT_EXPORT_NAME} = __knightedCss;`, diff --git a/packages/css/test/loaderBridge.test.ts b/packages/css/test/loaderBridge.test.ts index 64a4eed..ac65634 100644 --- a/packages/css/test/loaderBridge.test.ts +++ b/packages/css/test/loaderBridge.test.ts @@ -114,6 +114,23 @@ test('resolveCssModules finds locals on module export', () => { }) }) +test('resolveCssModules ignores default string export', () => { + const module = { default: '.card{color:red}' } + assert.equal(__loaderBridgeInternals.resolveCssModules(module, module), undefined) +}) + +test('resolveCssModules omits default from named exports', () => { + const module = { + default: '.card{color:red}', + card: 'card_hash', + title: 'title_hash', + } + assert.deepEqual(__loaderBridgeInternals.resolveCssModules(module, module), { + card: 'card_hash', + title: 'title_hash', + }) +}) + test('pitch returns combined module wrapper when combined flag is present', async () => { const ctx = createMockContext({ resourceQuery: '?knighted-css&combined', diff --git a/packages/playwright/package.json b/packages/playwright/package.json index e29a522..5ba9c6b 100644 --- a/packages/playwright/package.json +++ b/packages/playwright/package.json @@ -31,7 +31,7 @@ "pretest": "npm run types && npm run build" }, "dependencies": { - "@knighted/css": "1.1.0", + "@knighted/css": "1.1.1", "@knighted/jsx": "^1.7.5", "lit": "^3.2.1", "react": "^19.0.0",