Skip to content

Latest commit

 

History

History
177 lines (138 loc) · 5.87 KB

File metadata and controls

177 lines (138 loc) · 5.87 KB

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:

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:

import { knightedCssResolverPlugin } from '@knighted/css/plugin'

knightedCssResolverPlugin(options)

Options

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. Use this when the request is handled by @knighted/css/loader-bridge (hashed CSS Modules) or whenever you need a wrapper module that re-exports the original JS/TS exports plus knightedCss.
  • 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)

// rspack.config.js
import { KnightedCssResolverPlugin } from '@knighted/css/plugin'

export default {
  resolve: {
    plugins: [new KnightedCssResolverPlugin()],
  },
}
// Type generation
knighted-css-generate-types --root . --include src --mode declaration

This lets you write clean imports while still receiving knightedCss at runtime:

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.

knighted-css-generate-types --root . --include src --mode declaration --manifest .knighted-css/knighted-manifest.json
// 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.

&combined is required when the underlying loader chain does not preserve the module’s original exports. The common case is declaration mode + --hashed, where requests flow through @knighted/css/loader-bridge and would otherwise only expose knightedCss/knightedCssModules. Appending &combined tells the loader to generate a small wrapper module that re-exports the original module and then appends the knightedCss exports.

You typically do not need combinedPaths when requests are handled by @knighted/css/loader (non-hashed declaration mode), because that loader appends exports directly onto the original module.

Outside of loader-bridge, &combined is still useful when you are not running knighted-css-generate-types but want a single runtime import that includes both the original JS/TS exports and knightedCss (for example, in runtime-only builds, tests, or tooling that cannot resolve the generated .knighted-css proxy modules).

Example of an import that relies on &combined at runtime:

import { Card, knightedCss } from './combined-card.js'

And how you would use combinedPaths to support that:

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.

import { KnightedCssResolverPlugin } from '@knighted/css/plugin'

export default {
  resolve: {
    plugins: [new KnightedCssResolverPlugin({ debug: true })],
  },
}