Use the knighted-css-generate-types CLI to generate modules for .knighted-css double-extension imports. For stylesheets, it emits a sibling module with literal selector tokens. For JavaScript/TypeScript module specifiers, the generated file acts as a unified proxy that re-exports the module’s exports plus knightedCss.
npx knighted-css-generate-types --root . --include srcTypical script entry:
{
"scripts": {
"types:css": "knighted-css-generate-types --root . --include src"
}
}Wire it into postinstall or your build so new selectors land automatically.
--root/-r– project root (defaults toprocess.cwd()).--include/-i– additional directories or files to scan (repeatable).--out-dir– directory for the selector module manifest cache (defaults to<root>/.knighted-css).--stable-namespace– namespace prefix shared by the generated selector maps and loader runtime.--auto-stable– enable auto-stable selector generation during extraction (mirrors the loader’s auto-stable behavior).--hashed– emit proxy modules that exportselectorsbacked by loader-bridge hashed class names (mutually exclusive with--auto-stable).--resolver– path or package name exporting aCssResolver(default export or namedresolver).
.knighted-css*imports include the generated selector map and, for module specifiers, re-exports plusknightedCss.?knighted-cssimports are purely runtime (see docs/loader.md). Append&typesonly when you also need the selector map at runtime; the compiler still reads the literal tokens from the generated modules.
For CSS Modules or Sass files that need stable selectors, import the generated .knighted-css module for types. For JS/TS component modules, the generated proxy already provides knightedCss alongside the exports, so you can rely on the proxy in place of separate ?knighted-css runtime imports when appropriate.
import selectors from './button.module.scss.knighted-css.js'
selectors.card // "knighted-card"import Button, { knightedCss, stableSelectors } from './button.knighted-css.js'
stableSelectors.card // "knighted-card"
knightedCss // compiled CSS stringUse --hashed when you want .knighted-css proxy modules to export selectors backed by
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
knightedCssModules from the bridge output.
Example CLI:
knighted-css-generate-types --root . --include src --hashedExample usage:
import Button, { knightedCss, selectors } from './button.knighted-css.js'
selectors.card // hashed class name from CSS Modules
knightedCss // compiled CSS stringBecause the generated module lives next to the source stylesheet, TypeScript’s normal resolution logic applies—no custom paths entries required. Use the manifest in conjunction with runtime helpers such as mergeStableClass or stableClassName to keep hashed class names in sync.
If you want the CLI to rerun during dev, hook it into Rspack’s watch pipeline. This keeps the generated .knighted-css proxy modules in sync whenever source files change. You can also scope the --include list using compiler.modifiedFiles to avoid rescanning the entire project on every rebuild.
// rspack.config.js
import { exec } from 'node:child_process'
export default {
// ... your existing config
plugins: [
{
apply(compiler) {
compiler.hooks.watchRun.tapPromise('knighted-css-generate-types', () => {
const modified = Array.from(compiler.modifiedFiles ?? [])
const includes = modified.length > 0 ? modified : ['src']
const includeArgs = includes.flatMap(entry => ['--include', entry])
const command = ['knighted-css-generate-types', '--root', '.', ...includeArgs]
return new Promise((resolve, reject) => {
exec(command.join(' '), error => {
if (error) {
reject(error)
return
}
resolve()
})
})
})
},
},
],
}Scope the --include paths to the folders that actually import .knighted-css to keep the watch step fast. When modifiedFiles is empty (for example on the first run), fall back to a stable include root like src.