@knighted/css 1.0.0-alpha.0 → 1.0.0-alpha.2
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 +82 -9
- package/dist/cjs/css.cjs +7 -5
- package/dist/cjs/css.d.cts +7 -2
- package/dist/cjs/loader.cjs +25 -0
- package/dist/cjs/loader.d.cts +6 -0
- package/dist/css.d.ts +7 -2
- package/dist/css.js +6 -5
- package/dist/index.d.ts +4 -0
- package/dist/loader-queries.d.ts +7 -0
- package/dist/loader.d.ts +4 -0
- package/dist/loader.js +23 -0
- package/package.json +24 -5
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](https://codecov.io/gh/knightedcodemonkey/css)
|
|
5
5
|
[](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
|
|
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
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
83
|
-
|
|
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/
|
|
85
|
+
await writeFile('./dist/marketing-page.css', sheet)
|
|
87
86
|
```
|
|
88
87
|
|
|
89
88
|
### Inline CSS during SSR
|
|
@@ -99,6 +98,80 @@ 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. Recommended DX: import your component as usual, and import the CSS separately via the query import.
|
|
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
|
+
lightningcss: { minify: true }, // all css() options supported
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
},
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
// lit wrapper
|
|
129
|
+
import { LitElement, html, unsafeCSS } from 'lit'
|
|
130
|
+
import { Button } from './button.tsx'
|
|
131
|
+
import { knightedCss as 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
|
+
// Prefer import aliasing when you need a different local name:
|
|
141
|
+
// import { knightedCss as cardCss } from './button.tsx?knighted-css'
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The loader appends `export const knightedCss = "/* compiled css */"` to the module when imported with `?knighted-css`. Keep your main module import separate to preserve its typing; use the query import only for the CSS string.
|
|
145
|
+
|
|
146
|
+
### Custom resolver (enhanced-resolve example)
|
|
147
|
+
|
|
148
|
+
If your project uses aliases or nonstandard resolution, plug in a custom resolver. Here’s how to use [`enhanced-resolve`](https://github.com/webpack/enhanced-resolve):
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
import { ResolverFactory } from 'enhanced-resolve'
|
|
152
|
+
import { css } from '@knighted/css'
|
|
153
|
+
|
|
154
|
+
const resolver = ResolverFactory.createResolver({
|
|
155
|
+
extensions: ['.ts', '.tsx', '.js'],
|
|
156
|
+
mainFiles: ['index'],
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
async function resolveWithEnhanced(id: string, cwd: string): Promise<string | undefined> {
|
|
160
|
+
return new Promise((resolve, reject) => {
|
|
161
|
+
resolver.resolve({}, cwd, id, {}, (err, result) => {
|
|
162
|
+
if (err) return reject(err)
|
|
163
|
+
resolve(result ?? undefined)
|
|
164
|
+
})
|
|
165
|
+
})
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const styles = await css('./src/routes/page.tsx', {
|
|
169
|
+
resolver: (specifier, { cwd }) => resolveWithEnhanced(specifier, cwd),
|
|
170
|
+
})
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
This keeps `@knighted/css` resolution in sync with your bundler’s alias/extension rules.
|
|
174
|
+
|
|
102
175
|
## Scripts
|
|
103
176
|
|
|
104
177
|
- `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') {
|
package/dist/cjs/css.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Options as DependencyTreeOpts } from 'dependency-tree';
|
|
2
2
|
import { type TransformOptions as LightningTransformOptions } from 'lightningcss';
|
|
3
3
|
export declare const DEFAULT_EXTENSIONS: string[];
|
|
4
4
|
type LightningCssConfig = boolean | Partial<Omit<LightningTransformOptions<never>, 'code'>>;
|
|
@@ -11,12 +11,17 @@ export interface CssOptions {
|
|
|
11
11
|
cwd?: string;
|
|
12
12
|
filter?: (filePath: string) => boolean;
|
|
13
13
|
lightningcss?: LightningCssConfig;
|
|
14
|
-
dependencyTree?: Partial<Omit<
|
|
14
|
+
dependencyTree?: Partial<Omit<DependencyTreeOpts, 'filename' | 'directory'>>;
|
|
15
15
|
resolver?: CssResolver;
|
|
16
16
|
peerResolver?: PeerLoader;
|
|
17
17
|
}
|
|
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,25 @@
|
|
|
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 cssOptions = rawOptions;
|
|
8
|
+
const normalizedOptions = {
|
|
9
|
+
...cssOptions,
|
|
10
|
+
cwd: cssOptions.cwd ?? this.rootContext ?? process.cwd(),
|
|
11
|
+
};
|
|
12
|
+
const { css, files } = await (0, css_js_1.cssWithMeta)(this.resourcePath, normalizedOptions);
|
|
13
|
+
const uniqueFiles = new Set([this.resourcePath, ...files]);
|
|
14
|
+
for (const file of uniqueFiles) {
|
|
15
|
+
this.addDependency(file);
|
|
16
|
+
}
|
|
17
|
+
const input = typeof source === 'string' ? source : source.toString('utf8');
|
|
18
|
+
const injection = `\n\nexport const ${DEFAULT_EXPORT_NAME} = ${JSON.stringify(css)};\n`;
|
|
19
|
+
const isStyleModule = this.resourcePath.endsWith('.css.ts');
|
|
20
|
+
const output = isStyleModule
|
|
21
|
+
? `${injection}export default {};\n`
|
|
22
|
+
: `${input}${injection}`;
|
|
23
|
+
return output;
|
|
24
|
+
};
|
|
25
|
+
exports.default = loader;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { LoaderDefinitionFunction } from 'webpack';
|
|
2
|
+
import { type CssOptions } from './css.cjs';
|
|
3
|
+
export interface KnightedCssLoaderOptions extends CssOptions {
|
|
4
|
+
}
|
|
5
|
+
declare const loader: LoaderDefinitionFunction<KnightedCssLoaderOptions>;
|
|
6
|
+
export default loader;
|
package/dist/css.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Options as DependencyTreeOpts } from 'dependency-tree';
|
|
2
2
|
import { type TransformOptions as LightningTransformOptions } from 'lightningcss';
|
|
3
3
|
export declare const DEFAULT_EXTENSIONS: string[];
|
|
4
4
|
type LightningCssConfig = boolean | Partial<Omit<LightningTransformOptions<never>, 'code'>>;
|
|
@@ -11,12 +11,17 @@ export interface CssOptions {
|
|
|
11
11
|
cwd?: string;
|
|
12
12
|
filter?: (filePath: string) => boolean;
|
|
13
13
|
lightningcss?: LightningCssConfig;
|
|
14
|
-
dependencyTree?: Partial<Omit<
|
|
14
|
+
dependencyTree?: Partial<Omit<DependencyTreeOpts, 'filename' | 'directory'>>;
|
|
15
15
|
resolver?: CssResolver;
|
|
16
16
|
peerResolver?: PeerLoader;
|
|
17
17
|
}
|
|
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') {
|
package/dist/index.d.ts
ADDED
package/dist/loader.d.ts
ADDED
package/dist/loader.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
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 cssOptions = rawOptions;
|
|
6
|
+
const normalizedOptions = {
|
|
7
|
+
...cssOptions,
|
|
8
|
+
cwd: cssOptions.cwd ?? this.rootContext ?? process.cwd(),
|
|
9
|
+
};
|
|
10
|
+
const { css, files } = await cssWithMeta(this.resourcePath, normalizedOptions);
|
|
11
|
+
const uniqueFiles = new Set([this.resourcePath, ...files]);
|
|
12
|
+
for (const file of uniqueFiles) {
|
|
13
|
+
this.addDependency(file);
|
|
14
|
+
}
|
|
15
|
+
const input = typeof source === 'string' ? source : source.toString('utf8');
|
|
16
|
+
const injection = `\n\nexport const ${DEFAULT_EXPORT_NAME} = ${JSON.stringify(css)};\n`;
|
|
17
|
+
const isStyleModule = this.resourcePath.endsWith('.css.ts');
|
|
18
|
+
const output = isStyleModule
|
|
19
|
+
? `${injection}export default {};\n`
|
|
20
|
+
: `${input}${injection}`;
|
|
21
|
+
return output;
|
|
22
|
+
};
|
|
23
|
+
export default loader;
|
package/package.json
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@knighted/css",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.2",
|
|
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",
|
|
7
|
-
"types": "./dist/
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
|
-
"types": "./dist/
|
|
10
|
+
"types": "./dist/index.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,16 @@
|
|
|
37
42
|
],
|
|
38
43
|
"scripts": {
|
|
39
44
|
"build": "duel",
|
|
40
|
-
"
|
|
45
|
+
"postbuild": "cp src/types/*.d.ts dist/",
|
|
46
|
+
"check-types": "tsc --noEmit",
|
|
47
|
+
"test": "c8 --reporter=text --reporter=text-summary --reporter=lcov --include \"src/**/*.ts\" tsx --test test/**/*.test.ts",
|
|
48
|
+
"build:fixture": "npx rspack --config test/fixtures/loader-rspack/rspack.config.js",
|
|
49
|
+
"preview:fixture": "npm run build:fixture && npx http-server test/fixtures/loader-rspack -p 4173",
|
|
50
|
+
"prebuild:fixture:playwright": "npm run build",
|
|
51
|
+
"build:fixture:playwright": "npx rspack --config test/fixtures/playwright/rspack.config.js",
|
|
52
|
+
"serve:fixture:playwright": "npx http-server test/fixtures/playwright/dist -p 4174",
|
|
53
|
+
"preview:fixture:playwright": "npm run build:fixture:playwright && npx http-server test/fixtures/playwright -p 4174",
|
|
54
|
+
"test:e2e": "npx playwright test",
|
|
41
55
|
"prettier": "prettier --write .",
|
|
42
56
|
"prettier:check": "prettier --check .",
|
|
43
57
|
"lint": "oxlint src test",
|
|
@@ -45,7 +59,7 @@
|
|
|
45
59
|
"prepare": "husky"
|
|
46
60
|
},
|
|
47
61
|
"dependencies": {
|
|
48
|
-
"dependency-tree": "^
|
|
62
|
+
"dependency-tree": "^11.2.0",
|
|
49
63
|
"lightningcss": "^1.30.2"
|
|
50
64
|
},
|
|
51
65
|
"peerDependencies": {
|
|
@@ -66,11 +80,16 @@
|
|
|
66
80
|
},
|
|
67
81
|
"devDependencies": {
|
|
68
82
|
"@knighted/duel": "^2.1.6",
|
|
83
|
+
"@playwright/test": "^1.48.2",
|
|
84
|
+
"@rspack/cli": "^1.0.0",
|
|
85
|
+
"@rspack/core": "^1.0.0",
|
|
69
86
|
"@types/less": "^3.0.8",
|
|
87
|
+
"@types/webpack": "^5.28.5",
|
|
70
88
|
"@vanilla-extract/css": "^1.15.2",
|
|
71
89
|
"@vanilla-extract/integration": "^8.0.6",
|
|
72
90
|
"@vanilla-extract/recipes": "^0.5.7",
|
|
73
91
|
"c8": "^10.1.2",
|
|
92
|
+
"http-server": "^14.1.1",
|
|
74
93
|
"husky": "^9.1.7",
|
|
75
94
|
"less": "^4.2.0",
|
|
76
95
|
"lint-staged": "^16.2.7",
|