Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/loader.md
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
160 changes: 160 additions & 0 deletions docs/plugin.md
Original file line number Diff line number Diff line change
@@ -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<string, unknown>
conditions?: string[]
extensions?: string[]
debug?: boolean
combinedPaths?: Array<string | RegExp>
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 })],
},
}
```
20 changes: 20 additions & 0 deletions docs/type-generation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down