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..5d79537 --- /dev/null +++ b/docs/plugin.md @@ -0,0 +1,160 @@ +# 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`. + +## API + +The idiomatic usage is to instantiate the class: + +```ts +import { KnightedCssResolverPlugin } from '@knighted/css/plugin' + +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()`. +- `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..aa7d04a 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,25 @@ 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. + +### 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