@knighted/css 1.0.0-alpha.0 → 1.0.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  [![codecov](https://codecov.io/gh/knightedcodemonkey/css/graph/badge.svg?token=q93Qqwvq6l)](https://codecov.io/gh/knightedcodemonkey/css)
5
5
  [![NPM version](https://img.shields.io/npm/v/@knighted/css.svg)](https://www.npmjs.com/package/@knighted/css)
6
6
 
7
- `@knighted/css` is a build-time helper that walks a JavaScript/TypeScript module graph, finds every CSS-like dependency (plain CSS, Sass/SCSS, Less, vanilla-extract), compiles them, and returns a single concatenated stylesheet string. It is designed to power zero-runtime styling workflows like Lit custom elements, server-side rendering, or pre-rendering pipelines where you need all CSS for a specific entry point without running a full bundler.
7
+ `@knighted/css` is a build-time helper that walks a JavaScript/TypeScript module graph, finds every CSS-like dependency (plain CSS, Sass/SCSS, Less, vanilla-extract), compiles them, and returns a single concatenated stylesheet string. It is designed for workflows where you want fully materialized styles ahead of time—feeding Lit components, server-rendered routes, static site builds, or any pipeline that needs all CSS for a specific entry point without running a full bundler.
8
8
 
9
9
  ## Features
10
10
 
@@ -23,12 +23,10 @@
23
23
  ## Installation
24
24
 
25
25
  ```bash
26
- npm install @knighted/css \
27
- sass less \
28
- @vanilla-extract/css @vanilla-extract/integration @vanilla-extract/recipes
26
+ npm install @knighted/css
29
27
  ```
30
28
 
31
- Only install the peers you need—if your project never touches Less, you can skip `less`, etc.
29
+ Install the peers your project is using, for example `less`, or `sass`, etc.
32
30
 
33
31
  ## Quick Start
34
32
 
@@ -73,17 +71,18 @@ Typical customizations:
73
71
 
74
72
  ## Examples
75
73
 
76
- ### Extract styles for Lit components
74
+ ### Generate standalone stylesheets
77
75
 
78
76
  ```ts
79
77
  import { writeFile } from 'node:fs/promises'
80
78
  import { css } from '@knighted/css'
81
79
 
82
- const sheet = await css('./src/lit/my-widget.ts', {
83
- lightningcss: { minify: true, targets: { chrome: 120 } },
80
+ // Build-time script that gathers all CSS imported by a React route
81
+ const sheet = await css('./src/routes/marketing-page.tsx', {
82
+ lightningcss: { minify: true, targets: { chrome: 120, safari: 17 } },
84
83
  })
85
84
 
86
- await writeFile('./dist/my-widget.css', sheet)
85
+ await writeFile('./dist/marketing-page.css', sheet)
87
86
  ```
88
87
 
89
88
  ### Inline CSS during SSR
@@ -99,6 +98,52 @@ export async function render(url: string) {
99
98
  }
100
99
  ```
101
100
 
101
+ ### Bundler loader (`?knighted-css` query)
102
+
103
+ When using Webpack/Rspack, add the provided loader so importing a module with a specific query also returns the compiled stylesheet:
104
+
105
+ ```js
106
+ // webpack.config.js
107
+ module.exports = {
108
+ module: {
109
+ rules: [
110
+ {
111
+ test: /\.[jt]sx?$/,
112
+ resourceQuery: /knighted-css/,
113
+ use: [
114
+ {
115
+ loader: '@knighted/css/loader',
116
+ options: {
117
+ exportName: 'reactStyles', // optional (default: "knightedCss")
118
+ lightningcss: { minify: true }, // all css() options supported
119
+ },
120
+ },
121
+ ],
122
+ },
123
+ ],
124
+ },
125
+ }
126
+ ```
127
+
128
+ ```ts
129
+ // lit wrapper
130
+ import { LitElement, html, unsafeCSS } from 'lit'
131
+ import { Button, reactStyles } from './button.tsx?knighted-css'
132
+
133
+ export class ButtonWrapper extends LitElement {
134
+ static styles = [unsafeCSS(reactStyles)]
135
+ render() {
136
+ return html`<${Button} />`
137
+ }
138
+ }
139
+
140
+ // per-import override:
141
+ // import { Button, cardCss } from './button.tsx?knighted-css&exportName=cardCss'
142
+ // (exportName in the query wins over the loader option)
143
+ ```
144
+
145
+ The loader appends `export const reactStyles = "/* compiled css */"` to the module, so you can wire it directly into Lit’s `static styles` or any other runtime.
146
+
102
147
  ## Scripts
103
148
 
104
149
  - `npm run build` – Produce CJS/ESM outputs via `@knighted/duel`.
package/dist/cjs/css.cjs CHANGED
@@ -5,15 +5,17 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.DEFAULT_EXTENSIONS = void 0;
7
7
  exports.css = css;
8
+ exports.cssWithMeta = cssWithMeta;
8
9
  const node_path_1 = __importDefault(require("node:path"));
9
10
  const node_fs_1 = require("node:fs");
10
11
  const dependency_tree_1 = __importDefault(require("dependency-tree"));
11
12
  const lightningcss_1 = require("lightningcss");
12
13
  exports.DEFAULT_EXTENSIONS = ['.css', '.scss', '.sass', '.less', '.css.ts'];
13
- /**
14
- * Extract and compile all CSS-like dependencies for a given module.
15
- */
16
14
  async function css(entry, options = {}) {
15
+ const { css: output } = await cssWithMeta(entry, options);
16
+ return output;
17
+ }
18
+ async function cssWithMeta(entry, options = {}) {
17
19
  const cwd = options.cwd ? node_path_1.default.resolve(options.cwd) : process.cwd();
18
20
  const entryPath = await resolveEntry(entry, cwd, options.resolver);
19
21
  const extensions = (options.extensions ?? exports.DEFAULT_EXTENSIONS).map(ext => ext.toLowerCase());
@@ -24,7 +26,7 @@ async function css(entry, options = {}) {
24
26
  dependencyTreeOptions: options.dependencyTree,
25
27
  });
26
28
  if (files.length === 0) {
27
- return '';
29
+ return { css: '', files: [] };
28
30
  }
29
31
  const chunks = [];
30
32
  for (const file of files) {
@@ -46,7 +48,7 @@ async function css(entry, options = {}) {
46
48
  });
47
49
  output = code.toString();
48
50
  }
49
- return output;
51
+ return { css: output, files: files.map(file => file.path) };
50
52
  }
51
53
  async function resolveEntry(entry, cwd, resolver) {
52
54
  if (typeof resolver === 'function') {
@@ -18,5 +18,10 @@ export interface CssOptions {
18
18
  /**
19
19
  * Extract and compile all CSS-like dependencies for a given module.
20
20
  */
21
+ export interface CssResult {
22
+ css: string;
23
+ files: string[];
24
+ }
21
25
  export declare function css(entry: string, options?: CssOptions): Promise<string>;
26
+ export declare function cssWithMeta(entry: string, options?: CssOptions): Promise<CssResult>;
22
27
  export {};
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const css_js_1 = require("./css.cjs");
4
+ const DEFAULT_EXPORT_NAME = 'knightedCss';
5
+ const loader = async function loader(source) {
6
+ const rawOptions = (typeof this.getOptions === 'function' ? this.getOptions() : {});
7
+ const queryParams = typeof this.resourceQuery === 'string' && this.resourceQuery.startsWith('?')
8
+ ? new URLSearchParams(this.resourceQuery.slice(1))
9
+ : undefined;
10
+ const queryExportName = queryParams?.get('exportName')?.trim();
11
+ const isValidIdentifier = typeof queryExportName === 'string' &&
12
+ /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(queryExportName);
13
+ const { exportName = DEFAULT_EXPORT_NAME, ...cssOptions } = rawOptions;
14
+ const resolvedExportName = isValidIdentifier ? queryExportName : exportName;
15
+ const normalizedOptions = {
16
+ ...cssOptions,
17
+ cwd: cssOptions.cwd ?? this.rootContext ?? process.cwd(),
18
+ };
19
+ const { css, files } = await (0, css_js_1.cssWithMeta)(this.resourcePath, normalizedOptions);
20
+ const uniqueFiles = new Set([this.resourcePath, ...files]);
21
+ for (const file of uniqueFiles) {
22
+ this.addDependency(file);
23
+ }
24
+ const input = typeof source === 'string' ? source : source.toString('utf8');
25
+ const injection = `\n\nexport const ${resolvedExportName} = ${JSON.stringify(css)};\n`;
26
+ const output = `${input}${injection}`;
27
+ return output;
28
+ };
29
+ exports.default = loader;
@@ -0,0 +1,11 @@
1
+ import type { LoaderDefinitionFunction } from 'webpack';
2
+ import { type CssOptions } from './css.cjs';
3
+ export interface KnightedCssLoaderOptions extends CssOptions {
4
+ /**
5
+ * Named export that will contain the compiled CSS string.
6
+ * Defaults to "knightedCss".
7
+ */
8
+ exportName?: string;
9
+ }
10
+ declare const loader: LoaderDefinitionFunction<KnightedCssLoaderOptions>;
11
+ export default loader;
package/dist/css.d.ts CHANGED
@@ -18,5 +18,10 @@ export interface CssOptions {
18
18
  /**
19
19
  * Extract and compile all CSS-like dependencies for a given module.
20
20
  */
21
+ export interface CssResult {
22
+ css: string;
23
+ files: string[];
24
+ }
21
25
  export declare function css(entry: string, options?: CssOptions): Promise<string>;
26
+ export declare function cssWithMeta(entry: string, options?: CssOptions): Promise<CssResult>;
22
27
  export {};
package/dist/css.js CHANGED
@@ -3,10 +3,11 @@ import { promises as fs } from 'node:fs';
3
3
  import dependencyTree from 'dependency-tree';
4
4
  import { transform as lightningTransform, } from 'lightningcss';
5
5
  export const DEFAULT_EXTENSIONS = ['.css', '.scss', '.sass', '.less', '.css.ts'];
6
- /**
7
- * Extract and compile all CSS-like dependencies for a given module.
8
- */
9
6
  export async function css(entry, options = {}) {
7
+ const { css: output } = await cssWithMeta(entry, options);
8
+ return output;
9
+ }
10
+ export async function cssWithMeta(entry, options = {}) {
10
11
  const cwd = options.cwd ? path.resolve(options.cwd) : process.cwd();
11
12
  const entryPath = await resolveEntry(entry, cwd, options.resolver);
12
13
  const extensions = (options.extensions ?? DEFAULT_EXTENSIONS).map(ext => ext.toLowerCase());
@@ -17,7 +18,7 @@ export async function css(entry, options = {}) {
17
18
  dependencyTreeOptions: options.dependencyTree,
18
19
  });
19
20
  if (files.length === 0) {
20
- return '';
21
+ return { css: '', files: [] };
21
22
  }
22
23
  const chunks = [];
23
24
  for (const file of files) {
@@ -39,7 +40,7 @@ export async function css(entry, options = {}) {
39
40
  });
40
41
  output = code.toString();
41
42
  }
42
- return output;
43
+ return { css: output, files: files.map(file => file.path) };
43
44
  }
44
45
  async function resolveEntry(entry, cwd, resolver) {
45
46
  if (typeof resolver === 'function') {
@@ -0,0 +1,11 @@
1
+ import type { LoaderDefinitionFunction } from 'webpack';
2
+ import { type CssOptions } from './css.js';
3
+ export interface KnightedCssLoaderOptions extends CssOptions {
4
+ /**
5
+ * Named export that will contain the compiled CSS string.
6
+ * Defaults to "knightedCss".
7
+ */
8
+ exportName?: string;
9
+ }
10
+ declare const loader: LoaderDefinitionFunction<KnightedCssLoaderOptions>;
11
+ export default loader;
package/dist/loader.js ADDED
@@ -0,0 +1,27 @@
1
+ import { cssWithMeta } from './css.js';
2
+ const DEFAULT_EXPORT_NAME = 'knightedCss';
3
+ const loader = async function loader(source) {
4
+ const rawOptions = (typeof this.getOptions === 'function' ? this.getOptions() : {});
5
+ const queryParams = typeof this.resourceQuery === 'string' && this.resourceQuery.startsWith('?')
6
+ ? new URLSearchParams(this.resourceQuery.slice(1))
7
+ : undefined;
8
+ const queryExportName = queryParams?.get('exportName')?.trim();
9
+ const isValidIdentifier = typeof queryExportName === 'string' &&
10
+ /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(queryExportName);
11
+ const { exportName = DEFAULT_EXPORT_NAME, ...cssOptions } = rawOptions;
12
+ const resolvedExportName = isValidIdentifier ? queryExportName : exportName;
13
+ const normalizedOptions = {
14
+ ...cssOptions,
15
+ cwd: cssOptions.cwd ?? this.rootContext ?? process.cwd(),
16
+ };
17
+ const { css, files } = await cssWithMeta(this.resourcePath, normalizedOptions);
18
+ const uniqueFiles = new Set([this.resourcePath, ...files]);
19
+ for (const file of uniqueFiles) {
20
+ this.addDependency(file);
21
+ }
22
+ const input = typeof source === 'string' ? source : source.toString('utf8');
23
+ const injection = `\n\nexport const ${resolvedExportName} = ${JSON.stringify(css)};\n`;
24
+ const output = `${input}${injection}`;
25
+ return output;
26
+ };
27
+ export default loader;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knighted/css",
3
- "version": "1.0.0-alpha.0",
3
+ "version": "1.0.0-alpha.1",
4
4
  "description": "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.",
5
5
  "type": "module",
6
6
  "main": "./dist/css.js",
@@ -10,6 +10,11 @@
10
10
  "types": "./dist/css.d.ts",
11
11
  "import": "./dist/css.js",
12
12
  "require": "./dist/cjs/css.cjs"
13
+ },
14
+ "./loader": {
15
+ "types": "./dist/loader.d.ts",
16
+ "import": "./dist/loader.js",
17
+ "require": "./dist/cjs/loader.cjs"
13
18
  }
14
19
  },
15
20
  "engines": {
@@ -37,7 +42,8 @@
37
42
  ],
38
43
  "scripts": {
39
44
  "build": "duel",
40
- "test": "c8 --reporter=text --reporter=html tsx --test test/**/*.test.ts",
45
+ "check-types": "tsc --noEmit",
46
+ "test": "c8 --reporter=text --reporter=text-summary --reporter=lcov --include \"src/**/*.ts\" tsx --test test/**/*.test.ts",
41
47
  "prettier": "prettier --write .",
42
48
  "prettier:check": "prettier --check .",
43
49
  "lint": "oxlint src test",
@@ -66,7 +72,9 @@
66
72
  },
67
73
  "devDependencies": {
68
74
  "@knighted/duel": "^2.1.6",
75
+ "@rspack/core": "^1.0.0",
69
76
  "@types/less": "^3.0.8",
77
+ "@types/webpack": "^5.28.5",
70
78
  "@vanilla-extract/css": "^1.15.2",
71
79
  "@vanilla-extract/integration": "^8.0.6",
72
80
  "@vanilla-extract/recipes": "^0.5.7",