From fb04ceb1561f19b8aecf54011b651a0ed3f4ec12 Mon Sep 17 00:00:00 2001 From: KCM Date: Sun, 25 Jan 2026 10:29:46 -0600 Subject: [PATCH 1/2] docs: plugin. --- docs/loader.md | 2 + docs/plugin.md | 143 ++++++++++++++++++++++++++++++++++++++++ docs/type-generation.md | 2 + 3 files changed, 147 insertions(+) create mode 100644 docs/plugin.md diff --git a/docs/loader.md b/docs/loader.md index 53092db..6201fab 100644 --- a/docs/loader.md +++ b/docs/loader.md @@ -53,6 +53,8 @@ augmented exports to be present on the original JS/TS module. Use the resolver p to automatically append `?knighted-css` for any module import that has a generated sidecar `.d.ts` file. +See [docs/plugin.md](./plugin.md) for full resolver plugin documentation. + ```js // rspack.config.js import { knightedCssResolverPlugin } from '@knighted/css/plugin' diff --git a/docs/plugin.md b/docs/plugin.md new file mode 100644 index 0000000..78188de --- /dev/null +++ b/docs/plugin.md @@ -0,0 +1,143 @@ +# Resolver plugin (`knightedCssResolverPlugin`) + +`knightedCssResolverPlugin` is the resolver companion for declaration mode. It teaches your +bundler to rewrite module imports to `?knighted-css` (and `&combined` when applicable) when a +matching declaration sidecar exists, so runtime exports stay aligned with the generated types. + +Without the plugin, TypeScript may compile, but the bundler will load the original module +without the loader query. That means `knightedCss`, `stableSelectors`, or other injected exports +will be missing at runtime. + +## What it does + +- Scans resolved JS/TS module requests. +- Looks for a declaration sidecar (`.d.ts`) generated by `knighted-css-generate-types --mode declaration`. +- If a sidecar is found, rewrites the request to append `?knighted-css`. +- Optionally enforces strict sidecar matches using a manifest to avoid accidental rewrites. +- Optionally marks “combined” entries (via `combinedPaths`) so the query includes `&combined`. + +## Options + +```ts +import { KnightedCssResolverPlugin } from '@knighted/css/plugin' + +new KnightedCssResolverPlugin({ + rootDir: string, + tsconfig: string | Record, + conditions: string[], + extensions: string[], + debug: boolean, + combinedPaths: Array, + strictSidecar: boolean, + manifestPath: string, +}) +``` + +- `rootDir` (optional): Base directory used for resolver scoping. Defaults to `process.cwd()`. +- `tsconfig` (optional): Path to a tsconfig or an in-memory tsconfig object for path resolution. +- `conditions` (optional): Custom `package.json` export conditions to honor during resolution. +- `extensions` (optional): File extensions considered as script modules. Defaults to + `.ts`, `.tsx`, `.js`, `.jsx`, `.mts`, `.cts`, `.mjs`, `.cjs`. +- `debug` (optional): Logs rewrite decisions and a summary of cache hits/misses. +- `combinedPaths` (optional): List of strings or regexes. Any resolved path that matches will + receive `&combined` alongside `?knighted-css`. +- `strictSidecar` (optional): When true, only modules present in the manifest are rewritten. + Defaults to true when `manifestPath` is provided. +- `manifestPath` (optional): Path to the sidecar manifest generated by + `knighted-css-generate-types --manifest`. Used for strict matching. + +## Basic usage (declaration mode) + +```js +// rspack.config.js +import { KnightedCssResolverPlugin } from '@knighted/css/plugin' + +export default { + resolve: { + plugins: [new KnightedCssResolverPlugin()], + }, +} +``` + +```ts +// Type generation +knighted-css-generate-types --root . --include src --mode declaration +``` + +This lets you write clean imports while still receiving `knightedCss` at runtime: + +```ts +import Button, { knightedCss } from './button.js' +``` + +## Strict sidecar + manifest (recommended) + +Use strict sidecars to avoid rewriting modules that merely happen to have a `.d.ts` next to +them. This ensures only declaration mode sidecars generated by the CLI trigger rewrites. + +```sh +knighted-css-generate-types --root . --include src --mode declaration --manifest .knighted-css/knighted-manifest.json +``` + +```js +// rspack.config.js +import path from 'node:path' +import { KnightedCssResolverPlugin } from '@knighted/css/plugin' + +export default { + resolve: { + plugins: [ + new KnightedCssResolverPlugin({ + strictSidecar: true, + manifestPath: path.resolve('.knighted-css/knighted-manifest.json'), + }), + ], + }, +} +``` + +## Combined imports + +If you have modules that are consumed with combined exports (`?knighted-css&combined`), +set `combinedPaths` to ensure the resolver appends `&combined` during rewrites. + +Example of an import that relies on `&combined` at runtime: + +```ts +import { Card, knightedCss } from './combined-card.js' +``` + +And how you would use `combinedPaths` to support that: + +```js +import path from 'node:path' +import { KnightedCssResolverPlugin } from '@knighted/css/plugin' + +export default { + resolve: { + plugins: [ + new KnightedCssResolverPlugin({ + combinedPaths: [ + path.resolve('src/components/combined-card.tsx'), + /src\/views\/.*\.tsx$/, + ], + }), + ], + }, +} +``` + +## Debugging + +Enable `debug: true` to log each decision and a final summary that includes counts for +rewrites, cache hits, marker misses, and manifest misses. + +```js +import { KnightedCssResolverPlugin } from '@knighted/css/plugin' + +export default { + resolve: { + plugins: [new KnightedCssResolverPlugin({ debug: true })], + }, +} +``` diff --git a/docs/type-generation.md b/docs/type-generation.md index 3432aad..6f4673e 100644 --- a/docs/type-generation.md +++ b/docs/type-generation.md @@ -40,6 +40,7 @@ Wire it into `postinstall` or your build so new selectors land automatically. | `declaration` | Plain JS/TS imports | `.d.ts` augmentations next to modules | Required (append `?knighted-css`) | Cleaner imports when you accept resolver overhead | If you use declaration mode, prefer enabling strict sidecars + a manifest so the resolver only rewrites imports that the CLI generated. +See [docs/plugin.md](./plugin.md) for resolver plugin details and configuration options. ### Relationship to the loader @@ -80,6 +81,7 @@ import Button, { knightedCss, stableSelectors } from './button.js' > [!IMPORTANT] > Declaration mode requires a resolver plugin to append `?knighted-css` (and `&combined` when applicable) > at build time so runtime exports match the generated types. +> See [docs/plugin.md](./plugin.md) for resolver plugin configuration. ### Sidecar manifests + strict resolver mode From d9c0e4472c752d9e10d6b90330c510b3915fa1ce Mon Sep 17 00:00:00 2001 From: KCM Date: Sun, 25 Jan 2026 11:06:05 -0600 Subject: [PATCH 2/2] docs: .d.ts sidecar generation heuristic. --- docs/plugin.md | 45 ++++++++++++++++++++++++++++------------- docs/type-generation.md | 18 +++++++++++++++++ 2 files changed, 49 insertions(+), 14 deletions(-) diff --git a/docs/plugin.md b/docs/plugin.md index 78188de..5d79537 100644 --- a/docs/plugin.md +++ b/docs/plugin.md @@ -1,6 +1,6 @@ -# Resolver plugin (`knightedCssResolverPlugin`) +# Resolver plugin (`KnightedCssResolverPlugin`) -`knightedCssResolverPlugin` is the resolver companion for declaration mode. It teaches your +`KnightedCssResolverPlugin` is the resolver companion for declaration mode. It teaches your bundler to rewrite module imports to `?knighted-css` (and `&combined` when applicable) when a matching declaration sidecar exists, so runtime exports stay aligned with the generated types. @@ -16,21 +16,38 @@ will be missing at runtime. - Optionally enforces strict sidecar matches using a manifest to avoid accidental rewrites. - Optionally marks “combined” entries (via `combinedPaths`) so the query includes `&combined`. -## Options +## API + +The idiomatic usage is to instantiate the class: ```ts import { KnightedCssResolverPlugin } from '@knighted/css/plugin' -new KnightedCssResolverPlugin({ - rootDir: string, - tsconfig: string | Record, - conditions: string[], - extensions: string[], - debug: boolean, - combinedPaths: Array, - strictSidecar: boolean, - manifestPath: string, -}) +new KnightedCssResolverPlugin(options) +``` + +If you prefer a factory function, the package also exports `knightedCssResolverPlugin` which +returns the same plugin instance: + +```ts +import { knightedCssResolverPlugin } from '@knighted/css/plugin' + +knightedCssResolverPlugin(options) +``` + +### Options + +```ts +type KnightedCssResolverPluginOptions = { + rootDir?: string + tsconfig?: string | Record + conditions?: string[] + extensions?: string[] + debug?: boolean + combinedPaths?: Array + strictSidecar?: boolean + manifestPath?: string +} ``` - `rootDir` (optional): Base directory used for resolver scoping. Defaults to `process.cwd()`. @@ -67,7 +84,7 @@ knighted-css-generate-types --root . --include src --mode declaration This lets you write clean imports while still receiving `knightedCss` at runtime: ```ts -import Button, { knightedCss } from './button.js' +import { Button, knightedCss } from './button.js' ``` ## Strict sidecar + manifest (recommended) diff --git a/docs/type-generation.md b/docs/type-generation.md index 6f4673e..aa7d04a 100644 --- a/docs/type-generation.md +++ b/docs/type-generation.md @@ -83,6 +83,24 @@ import Button, { knightedCss, stableSelectors } from './button.js' > at build time so runtime exports match the generated types. > See [docs/plugin.md](./plugin.md) for resolver plugin configuration. +### How declaration sidecars are chosen + +The CLI only emits `.d.ts` sidecars for files that pass all of the following checks: + +- **Inside your include set**: it walks each `--include` entry recursively, skipping common build + folders (`node_modules`, `dist`, `build`, `.knighted-css`, etc.), and only considers script + files (`.ts`, `.tsx`, `.js`, `.jsx`, `.mts`, `.cts`, `.mjs`, `.cjs`). Existing `.d.ts` files + are ignored. +- **Within the project root**: if a candidate file resolves outside `--root`, the CLI skips it + and logs a warning. +- **Imports styles**: the file must import/require a style resource directly (e.g. `.css`, + `.scss`, `.sass`, `.less`) or resolve to one via tsconfig paths / resolver hooks. +- **Produces selectors**: the extracted CSS must be non-empty and yield at least one selector + token; otherwise the sidecar is skipped. + +In other words, declaration mode is opt-in by usage: only JS/TS/JSX/TSX modules that actually pull in +styles get a generated `.d.ts` augmentation. + ### Sidecar manifests + strict resolver mode Declaration mode emits `.d.ts` files with a `// @knighted-css` marker. If you want the resolver plugin