npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@knighted/css

v1.2.0

Published

A build-time utility that traverses JavaScript/TypeScript module dependency graphs to extract, compile, and optimize all imported CSS into a single, in-memory string.

Readme

@knighted/css

CI codecov NPM version

@knighted/css walks your module graph, compiles every CSS-like dependency (plain CSS, Sass/SCSS, Less, vanilla-extract), and ships both the concatenated stylesheet string and optional .knighted-css.* imports that keep selectors typed. Use it with or without a bundler: run the css() API in scripts/SSR pipelines, or lean on the ?knighted-css loader query so bundlers import compiled CSS alongside modules. Either path yields fully materialized styles for Shadow DOM surfaces, server-rendered routes, static site builds, or any entry point that should inline CSS.

Why

I needed a single source of truth for UI components that could drop into both light DOM pages and Shadow DOM hosts, without losing encapsulated styling in the latter.

Quick Links

Features

  • Traverses module graphs with a built-in walker to find transitive style imports (bundler optional—works standalone or through bundler loaders), including static import attributes (with { type: "css" }) for extensionless or aliased specifiers.
  • Resolution parity via oxc-resolver: tsconfig paths, package exports + imports, and extension aliasing (e.g., .css.js.css.ts) are honored without wiring up a bundler.
  • Compiles *.css, *.scss, *.sass, *.less, and *.css.ts (vanilla-extract) files out of the box.
  • Optional post-processing via lightningcss for minification, prefixing, media query optimizations, or specificity boosts.
  • Deterministic selector duplication via autoStable: duplicate matching class selectors with a stable namespace (default knighted-) in both plain CSS and CSS Modules exports.
  • Pluggable resolver/filter hooks for custom module resolution (e.g., Rspack/Vite/webpack aliases) or selective inclusion.
  • First-class loader (@knighted/css/loader) so bundlers can import compiled CSS alongside their modules via ?knighted-css.
  • Built-in type generation CLI (knighted-css-generate-types) that emits .knighted-css.* selector manifests (module mode) or declaration augmentations (declaration mode) so TypeScript stays in lockstep with loader exports.

Requirements

  • Node.js >= 22.17.0
  • npm >= 10.9.0
  • Install peer toolchains you intend to use (sass, less, @vanilla-extract/integration, etc.).

Installation

npm install @knighted/css

Install the peers your project is using, for example less, or sass, etc.

Quick Start

// scripts/extract-styles.ts
import { css } from '@knighted/css'

const styles = await css('./src/components/app.ts', {
  cwd: process.cwd(),
  lightningcss: { minify: true },
})

console.log(styles)

Run it with tsx/node and you will see a fully inlined stylesheet for app.ts and every style import it references, regardless of depth.

API

type CssOptions = {
  extensions?: string[] // customize file extensions to scan
  cwd?: string // working directory (defaults to process.cwd())
  filter?: (filePath: string) => boolean
  autoStable?:
    | boolean
    | {
        namespace?: string
        include?: RegExp
        exclude?: RegExp
      }
  lightningcss?: boolean | LightningTransformOptions
  specificityBoost?: {
    visitor?: LightningTransformOptions<never>['visitor']
    strategy?: SpecificityStrategy
    match?: SpecificitySelector[]
  }
  moduleGraph?: ModuleGraphOptions
  resolver?: (
    specifier: string,
    ctx: { cwd: string; from?: string },
  ) => string | Promise<string | undefined>
  peerResolver?: (name: string) => Promise<unknown> // for custom module loading
}

async function css(entry: string, options?: CssOptions): Promise<string>

Entry points at a glance

Runtime loader hook (?knighted-css)

Import any module with the ?knighted-css query to receive the compiled stylesheet string:

import { knightedCss } from './button.js?knighted-css'

See docs/loader.md for the full configuration, combined imports, and &types runtime selector map guidance.

Type generation hook (*.knighted-css*)

Run knighted-css-generate-types so every specifier that ends with .knighted-css produces a sibling manifest containing literal selector tokens (module mode, the default):

import stableSelectors from './button.module.scss.knighted-css.js'

Need bespoke resolution? Pass --resolver to load a module exporting a CssResolver and apply it during type generation.

When the .knighted-css import targets a JavaScript/TypeScript module, the generated proxy also re-exports the module’s exports and knightedCss, so a single import can provide component exports, typed selectors, and the compiled stylesheet string:

import Button, { knightedCss, stableSelectors } from './button.knighted-css.js'

Need hashed class names instead of stable selectors? Run the CLI with --hashed to emit proxy modules that export selectors backed by knightedCssModules from the loader-bridge:

knighted-css-generate-types --root . --include src --hashed
import Button, { knightedCss, selectors } from './button.knighted-css.js'

selectors.card // hashed CSS Modules class name

[!IMPORTANT] --hashed requires wiring @knighted/css/loader-bridge to handle ?knighted-css queries so the generated proxies can read knightedCss and knightedCssModules at build time.

[!NOTE] --hashed builds the selector list from compiled CSS. The generated sidecar can therefore include class names that are not exported by the module (e.g. sprinkles output), while the runtime selectors map only includes exported locals from the loader bridge.

Prefer module-level imports without the double extension? Use declaration mode to emit .d.ts augmentations next to JS/TS modules that import styles:

knighted-css-generate-types --root . --include src --mode declaration
import Button, { knightedCss, stableSelectors } from './button.js'

See docs/type-generation.md for a quick comparison of module vs declaration mode tradeoffs.

[!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.

Install the resolver plugin via @knighted/css/plugin and wire it into your bundler resolver:

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

export default {
  resolve: {
    plugins: [knightedCssResolverPlugin()],
  },
}

If you want the resolver to only match sidecars generated by the CLI, enable strict mode and provide a manifest (written by knighted-css-generate-types --manifest):

import path from 'node:path'
import { knightedCssResolverPlugin } from '@knighted/css/plugin'

export default {
  resolve: {
    plugins: [
      knightedCssResolverPlugin({
        strictSidecar: true,
        manifestPath: path.resolve('.knighted-css/knighted-manifest.json'),
      }),
    ],
  },
}

Refer to docs/type-generation.md for CLI options and workflow tips.

Combined + runtime selectors

Need the module exports, knightedCss, and a runtime stableSelectors map from one import? Use ?knighted-css&combined&types (plus optional &named-only). Example:

import { asKnightedCssCombinedModule } from '@knighted/css/loader-helpers'
import type { KnightedCssStableSelectors as ButtonStableSelectors } from './button.css.knighted-css.js'
import * as buttonModule from './button.js?knighted-css&combined&types'

const {
  default: Button,
  knightedCss,
  stableSelectors,
} = asKnightedCssCombinedModule<
  typeof import('./button.js'),
  { stableSelectors: Readonly<Record<keyof ButtonStableSelectors, string>> }
>(buttonModule)

stableSelectors.shell

[!TIP] If you run knighted-css-generate-types, prefer the double-extension proxy import shown above instead of ?knighted-css&combined and asKnightedCssCombinedModule.

[!NOTE] stableSelectors here is for runtime use; TypeScript still reads literal tokens from the generated .knighted-css.* modules. For a full decision matrix, see docs/combined-queries.md. Prefer importing asKnightedCssCombinedModule from @knighted/css/loader-helpers instead of grabbing it from @knighted/css/loader—the helper lives in a Node-free chunk so both browser and server bundles stay happy.

Examples

Generate standalone stylesheets

import { writeFile } from 'node:fs/promises'
import { css } from '@knighted/css'

// Build-time script that gathers all CSS imported by a React route
const sheet = await css('./src/routes/marketing-page.tsx', {
  lightningcss: { minify: true, targets: { chrome: 120, safari: 17 } },
})

await writeFile('./dist/marketing-page.css', sheet)

Inline CSS during SSR

import { renderToString } from 'react-dom/server'
import { css } from '@knighted/css'

export async function render(url: string) {
  const styles = await css('./src/routes/root.tsx')
  const html = renderToString(<App url={url} />)
  return `<!doctype html><style>${styles}</style>${html}`
}

Custom resolver (enhanced-resolve example)

The built-in walker already leans on oxc-resolver, so tsconfig paths, package exports conditions, and common extension aliases work out of the box. If you still need to mirror bespoke behavior (virtual modules, framework-specific loaders, etc.), plug in a custom resolver. Here’s how to use enhanced-resolve:

[!TIP] Hash-prefixed specifiers defined in package.json#imports resolve automatically—no extra loader or css() options required. Reach for a custom resolver only when you need behavior beyond what oxc-resolver already mirrors.

[!NOTE] Sass-specific prefixes such as pkg:#button live outside Node’s resolver and still need a shim. See docs/sass-import-aliases.md for a drop-in helper that strips those markers before @knighted/css walks the graph.

import { ResolverFactory } from 'enhanced-resolve'
import { css } from '@knighted/css'

const resolver = ResolverFactory.createResolver({
  extensions: ['.ts', '.tsx', '.js'],
  mainFiles: ['index'],
})

async function resolveWithEnhanced(id: string, cwd: string): Promise<string | undefined> {
  return new Promise((resolve, reject) => {
    resolver.resolve({}, cwd, id, {}, (err, result) => {
      if (err) return reject(err)
      resolve(result ?? undefined)
    })
  })
}

const styles = await css('./src/routes/page.tsx', {
  resolver: (specifier, { cwd }) => resolveWithEnhanced(specifier, cwd),
})

This keeps @knighted/css resolution in sync with your bundler’s alias/extension rules.

Specificity boost

Use specificityBoost to tweak selector behavior:

  • Strategies (built-in):
    • repeat-class duplicates the last class in matching selectors to raise specificity (useful when you need a real specificity bump).
    • append-where appends :where(.token) (zero specificity) for a harmless, order-based tie-breaker without changing matching.
  • Custom visitor: Supply your own Lightning CSS visitor via specificityBoost.visitor for full control.
  • match filtering: Provide match: (string | RegExp)[] to target selectors. Matches are OR’d; if any entry matches, the strategy applies. If omitted/empty, all selectors are eligible.

Example:

import { css } from '@knighted/css'

const styles = await css('./src/entry.ts', {
  lightningcss: { minify: true },
  specificityBoost: {
    match: ['.card', /^\.btn/], // OR match
    strategy: { type: 'repeat-class', times: 1 },
  },
})

If you omit match, the strategy applies to all selectors. Use append-where when you don’t want to change specificity; use repeat-class when you do.

[!NOTE] For the built-in strategies, the last class in a matching selector is the one that gets duplicated/appended. If you have multiple similar classes, tighten your match (string or RegExp) to target exactly the selector you want boosted.

[!TIP] See docs/specificity-boost-visitor.md for a concrete visitor example.

Demo

Want to see everything wired together? Check the full demo app at css-jsx-app.

[!TIP] This repo also includes a playwright workspace which serves as an end-to-end demo.

License

MIT © Knighted Code Monkey