@knighted/css 1.1.1 → 1.2.0-rc.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 +49 -2
- package/dist/cjs/generateTypes.cjs +349 -38
- package/dist/cjs/generateTypes.cjs.map +1 -1
- package/dist/cjs/generateTypes.d.cts +33 -0
- package/dist/cjs/lexer.cjs +64 -2
- package/dist/cjs/lexer.cjs.map +1 -1
- package/dist/cjs/lexer.d.cts +1 -0
- package/dist/cjs/plugin.cjs +485 -0
- package/dist/cjs/plugin.cjs.map +1 -0
- package/dist/cjs/plugin.d.cts +135 -0
- package/dist/generateTypes.d.ts +33 -0
- package/dist/generateTypes.js +349 -38
- package/dist/generateTypes.js.map +1 -1
- package/dist/lexer.d.ts +1 -0
- package/dist/lexer.js +64 -2
- package/dist/lexer.js.map +1 -1
- package/dist/plugin.d.ts +135 -0
- package/dist/plugin.js +477 -0
- package/dist/plugin.js.map +1 -0
- package/package.json +9 -2
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@ I needed a single source of truth for UI components that could drop into both li
|
|
|
30
30
|
- Deterministic selector duplication via `autoStable`: duplicate matching class selectors with a stable namespace (default `knighted-`) in both plain CSS and CSS Modules exports.
|
|
31
31
|
- Pluggable resolver/filter hooks for custom module resolution (e.g., Rspack/Vite/webpack aliases) or selective inclusion.
|
|
32
32
|
- First-class loader (`@knighted/css/loader`) so bundlers can import compiled CSS alongside their modules via `?knighted-css`.
|
|
33
|
-
- Built-in type generation CLI (`knighted-css-generate-types`) that emits `.knighted-css.*` selector manifests so TypeScript
|
|
33
|
+
- 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.
|
|
34
34
|
|
|
35
35
|
## Requirements
|
|
36
36
|
|
|
@@ -107,7 +107,7 @@ See [docs/loader.md](../../docs/loader.md) for the full configuration, combined
|
|
|
107
107
|
|
|
108
108
|
### Type generation hook (`*.knighted-css*`)
|
|
109
109
|
|
|
110
|
-
Run `knighted-css-generate-types` so every specifier that ends with `.knighted-css` produces a sibling manifest containing literal selector tokens:
|
|
110
|
+
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):
|
|
111
111
|
|
|
112
112
|
```ts
|
|
113
113
|
import stableSelectors from './button.module.scss.knighted-css.js'
|
|
@@ -142,6 +142,53 @@ selectors.card // hashed CSS Modules class name
|
|
|
142
142
|
> include class names that are not exported by the module (e.g. sprinkles output), while the
|
|
143
143
|
> runtime `selectors` map only includes exported locals from the loader bridge.
|
|
144
144
|
|
|
145
|
+
Prefer module-level imports without the double extension? Use declaration mode to emit `.d.ts` augmentations next to JS/TS modules that import styles:
|
|
146
|
+
|
|
147
|
+
```sh
|
|
148
|
+
knighted-css-generate-types --root . --include src --mode declaration
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
import Button, { knightedCss, stableSelectors } from './button.js'
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
See [docs/type-generation.md](../../docs/type-generation.md#mode-quick-reference) for a quick comparison of module vs declaration mode tradeoffs.
|
|
156
|
+
|
|
157
|
+
> [!IMPORTANT]
|
|
158
|
+
> Declaration mode requires a resolver plugin to append `?knighted-css` (and `&combined` when applicable)
|
|
159
|
+
> at build time so runtime exports match the generated types.
|
|
160
|
+
|
|
161
|
+
Install the resolver plugin via `@knighted/css/plugin` and wire it into your bundler resolver:
|
|
162
|
+
|
|
163
|
+
```js
|
|
164
|
+
// rspack.config.js
|
|
165
|
+
import { knightedCssResolverPlugin } from '@knighted/css/plugin'
|
|
166
|
+
|
|
167
|
+
export default {
|
|
168
|
+
resolve: {
|
|
169
|
+
plugins: [knightedCssResolverPlugin()],
|
|
170
|
+
},
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
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`):
|
|
175
|
+
|
|
176
|
+
```js
|
|
177
|
+
import path from 'node:path'
|
|
178
|
+
import { knightedCssResolverPlugin } from '@knighted/css/plugin'
|
|
179
|
+
|
|
180
|
+
export default {
|
|
181
|
+
resolve: {
|
|
182
|
+
plugins: [
|
|
183
|
+
knightedCssResolverPlugin({
|
|
184
|
+
strictSidecar: true,
|
|
185
|
+
manifestPath: path.resolve('.knighted-css/knighted-manifest.json'),
|
|
186
|
+
}),
|
|
187
|
+
],
|
|
188
|
+
},
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
145
192
|
Refer to [docs/type-generation.md](../../docs/type-generation.md) for CLI options and workflow tips.
|
|
146
193
|
|
|
147
194
|
### Combined + runtime selectors
|
|
@@ -66,6 +66,7 @@ function getImportMetaUrl() {
|
|
|
66
66
|
}
|
|
67
67
|
const SELECTOR_REFERENCE = '.knighted-css';
|
|
68
68
|
const SELECTOR_MODULE_SUFFIX = '.knighted-css.ts';
|
|
69
|
+
const DECLARATION_SUFFIX = '.d.ts';
|
|
69
70
|
const STYLE_EXTENSIONS = css_js_1.DEFAULT_EXTENSIONS.map(ext => ext.toLowerCase());
|
|
70
71
|
const SCRIPT_EXTENSIONS = Array.from(SUPPORTED_EXTENSIONS);
|
|
71
72
|
const RESOLUTION_EXTENSIONS = Array.from(new Set([...SCRIPT_EXTENSIONS, ...STYLE_EXTENSIONS]));
|
|
@@ -75,11 +76,16 @@ const EXTENSION_FALLBACKS = {
|
|
|
75
76
|
'.cjs': ['.cts', '.cjs', '.js', '.ts', '.tsx'],
|
|
76
77
|
'.jsx': ['.tsx', '.jsx'],
|
|
77
78
|
};
|
|
79
|
+
const DECLARATION_MODE_WARNING = 'Declaration mode requires a resolver plugin to append ?knighted-css (and &combined when applicable) so runtime exports match the generated types.';
|
|
78
80
|
async function generateTypes(options = {}) {
|
|
79
81
|
const rootDir = await resolveRootDir(node_path_1.default.resolve(options.rootDir ?? process.cwd()));
|
|
80
82
|
const include = normalizeIncludeOptions(options.include, rootDir);
|
|
81
83
|
const cacheDir = node_path_1.default.resolve(options.outDir ?? node_path_1.default.join(rootDir, '.knighted-css'));
|
|
82
84
|
const tsconfig = loadTsconfigResolutionContext(rootDir);
|
|
85
|
+
const mode = options.mode ?? 'module';
|
|
86
|
+
const manifestPath = options.manifestPath
|
|
87
|
+
? node_path_1.default.resolve(rootDir, options.manifestPath)
|
|
88
|
+
: undefined;
|
|
83
89
|
await promises_1.default.mkdir(cacheDir, { recursive: true });
|
|
84
90
|
const internalOptions = {
|
|
85
91
|
rootDir,
|
|
@@ -90,6 +96,8 @@ async function generateTypes(options = {}) {
|
|
|
90
96
|
hashed: options.hashed,
|
|
91
97
|
tsconfig,
|
|
92
98
|
resolver: options.resolver,
|
|
99
|
+
mode,
|
|
100
|
+
manifestPath,
|
|
93
101
|
};
|
|
94
102
|
return generateDeclarations(internalOptions);
|
|
95
103
|
}
|
|
@@ -108,79 +116,176 @@ async function generateDeclarations(options) {
|
|
|
108
116
|
const selectorModulesManifestPath = node_path_1.default.join(options.cacheDir, 'selector-modules.json');
|
|
109
117
|
const previousSelectorManifest = await readManifest(selectorModulesManifestPath);
|
|
110
118
|
const nextSelectorManifest = {};
|
|
119
|
+
const sidecarManifest = {};
|
|
111
120
|
const selectorCache = new Map();
|
|
112
121
|
const processedSelectors = new Set();
|
|
113
122
|
const proxyInfoCache = new Map();
|
|
114
123
|
const warnings = [];
|
|
115
124
|
let selectorModuleWrites = 0;
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const selectorSource = extractSelectorSourceSpecifier(resource);
|
|
123
|
-
if (!selectorSource) {
|
|
125
|
+
if (options.mode === 'declaration') {
|
|
126
|
+
warnings.push(DECLARATION_MODE_WARNING);
|
|
127
|
+
}
|
|
128
|
+
if (options.mode === 'declaration') {
|
|
129
|
+
for (const filePath of files) {
|
|
130
|
+
if (!isScriptResource(filePath)) {
|
|
124
131
|
continue;
|
|
125
132
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
133
|
+
if (!isWithinRoot(filePath, options.rootDir)) {
|
|
134
|
+
warnings.push(`Skipping declaration output for ${relativeToRoot(filePath, options.rootDir)} because it is outside the project root.`);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const manifestKey = buildSelectorModuleManifestKey(filePath);
|
|
138
|
+
if (processedSelectors.has(manifestKey)) {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
const hasStyles = await hasStyleImports(filePath, {
|
|
142
|
+
rootDir: options.rootDir,
|
|
143
|
+
tsconfig: options.tsconfig,
|
|
144
|
+
resolver: options.resolver,
|
|
145
|
+
resolverFactory,
|
|
146
|
+
});
|
|
147
|
+
if (!hasStyles) {
|
|
148
|
+
processedSelectors.add(manifestKey);
|
|
130
149
|
continue;
|
|
131
150
|
}
|
|
132
|
-
const
|
|
151
|
+
const resolvedNamespace = (0, stableNamespace_js_1.resolveStableNamespace)(options.stableNamespace);
|
|
152
|
+
const cacheKey = `${filePath}::${resolvedNamespace}::declaration`;
|
|
133
153
|
let selectorMap = selectorCache.get(cacheKey);
|
|
134
154
|
if (!selectorMap) {
|
|
135
155
|
try {
|
|
136
|
-
|
|
137
|
-
const { css } = await activeCssWithMeta(resolvedPath, {
|
|
156
|
+
let cssResult = await activeCssWithMeta(filePath, {
|
|
138
157
|
cwd: options.rootDir,
|
|
139
158
|
peerResolver,
|
|
140
159
|
autoStable: options.autoStable ? { namespace: resolvedNamespace } : undefined,
|
|
141
|
-
lightningcss: options.autoStable && shouldUseCssModules
|
|
142
|
-
? { cssModules: true }
|
|
143
|
-
: undefined,
|
|
144
160
|
resolver: options.resolver,
|
|
145
161
|
});
|
|
162
|
+
if (cssResult.files.length === 0 || cssResult.css.trim().length === 0) {
|
|
163
|
+
processedSelectors.add(manifestKey);
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (options.autoStable &&
|
|
167
|
+
cssResult.files.some(file => isCssModuleResource(file))) {
|
|
168
|
+
cssResult = await activeCssWithMeta(filePath, {
|
|
169
|
+
cwd: options.rootDir,
|
|
170
|
+
peerResolver,
|
|
171
|
+
autoStable: options.autoStable
|
|
172
|
+
? { namespace: resolvedNamespace }
|
|
173
|
+
: undefined,
|
|
174
|
+
lightningcss: { cssModules: true },
|
|
175
|
+
resolver: options.resolver,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
146
178
|
selectorMap = options.hashed
|
|
147
|
-
? collectSelectorTokensFromCss(css)
|
|
179
|
+
? collectSelectorTokensFromCss(cssResult.css)
|
|
148
180
|
: (0, stableSelectorsLiteral_js_1.buildStableSelectorsLiteral)({
|
|
149
|
-
css,
|
|
181
|
+
css: cssResult.css,
|
|
150
182
|
namespace: resolvedNamespace,
|
|
151
|
-
resourcePath:
|
|
183
|
+
resourcePath: filePath,
|
|
152
184
|
emitWarning: message => warnings.push(message),
|
|
153
185
|
}).selectorMap;
|
|
154
186
|
}
|
|
155
187
|
catch (error) {
|
|
156
|
-
warnings.push(`Failed to extract CSS for ${relativeToRoot(
|
|
188
|
+
warnings.push(`Failed to extract CSS for ${relativeToRoot(filePath, options.rootDir)}: ${formatErrorMessage(error)}`);
|
|
189
|
+
processedSelectors.add(manifestKey);
|
|
157
190
|
continue;
|
|
158
191
|
}
|
|
159
192
|
selectorCache.set(cacheKey, selectorMap);
|
|
160
193
|
}
|
|
161
|
-
if (!
|
|
162
|
-
|
|
194
|
+
if (!selectorMap || selectorMap.size === 0) {
|
|
195
|
+
processedSelectors.add(manifestKey);
|
|
163
196
|
continue;
|
|
164
197
|
}
|
|
165
|
-
const
|
|
166
|
-
if (
|
|
198
|
+
const proxyInfo = await resolveDeclarationProxyInfo(manifestKey, filePath, proxyInfoCache);
|
|
199
|
+
if (!proxyInfo) {
|
|
200
|
+
processedSelectors.add(manifestKey);
|
|
167
201
|
continue;
|
|
168
202
|
}
|
|
169
|
-
const
|
|
170
|
-
|
|
203
|
+
const moduleWrite = await ensureDeclarationModule(filePath, selectorMap, previousSelectorManifest, nextSelectorManifest, proxyInfo, options.hashed ?? false);
|
|
204
|
+
if (options.manifestPath) {
|
|
205
|
+
sidecarManifest[manifestKey] = { file: buildDeclarationPath(filePath) };
|
|
206
|
+
}
|
|
171
207
|
if (moduleWrite) {
|
|
172
208
|
selectorModuleWrites += 1;
|
|
173
209
|
}
|
|
174
210
|
processedSelectors.add(manifestKey);
|
|
175
211
|
}
|
|
176
212
|
}
|
|
213
|
+
else {
|
|
214
|
+
for (const filePath of files) {
|
|
215
|
+
const matches = await findSpecifierImports(filePath);
|
|
216
|
+
for (const match of matches) {
|
|
217
|
+
const cleaned = match.specifier.trim();
|
|
218
|
+
const inlineFree = stripInlineLoader(cleaned);
|
|
219
|
+
const { resource } = splitResourceAndQuery(inlineFree);
|
|
220
|
+
const selectorSource = extractSelectorSourceSpecifier(resource);
|
|
221
|
+
if (!selectorSource) {
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
const resolvedNamespace = (0, stableNamespace_js_1.resolveStableNamespace)(options.stableNamespace);
|
|
225
|
+
const resolvedPath = await resolveImportPath(selectorSource, match.importer, options.rootDir, options.tsconfig, options.resolver, resolverFactory, RESOLUTION_EXTENSIONS);
|
|
226
|
+
if (!resolvedPath) {
|
|
227
|
+
warnings.push(`Unable to resolve ${selectorSource} referenced by ${relativeToRoot(match.importer, options.rootDir)}.`);
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
const cacheKey = `${resolvedPath}::${resolvedNamespace}`;
|
|
231
|
+
let selectorMap = selectorCache.get(cacheKey);
|
|
232
|
+
if (!selectorMap) {
|
|
233
|
+
try {
|
|
234
|
+
const shouldUseCssModules = resolvedPath.endsWith('.module.css');
|
|
235
|
+
const { css } = await activeCssWithMeta(resolvedPath, {
|
|
236
|
+
cwd: options.rootDir,
|
|
237
|
+
peerResolver,
|
|
238
|
+
autoStable: options.autoStable
|
|
239
|
+
? { namespace: resolvedNamespace }
|
|
240
|
+
: undefined,
|
|
241
|
+
lightningcss: options.autoStable && shouldUseCssModules
|
|
242
|
+
? { cssModules: true }
|
|
243
|
+
: undefined,
|
|
244
|
+
resolver: options.resolver,
|
|
245
|
+
});
|
|
246
|
+
selectorMap = options.hashed
|
|
247
|
+
? collectSelectorTokensFromCss(css)
|
|
248
|
+
: (0, stableSelectorsLiteral_js_1.buildStableSelectorsLiteral)({
|
|
249
|
+
css,
|
|
250
|
+
namespace: resolvedNamespace,
|
|
251
|
+
resourcePath: resolvedPath,
|
|
252
|
+
emitWarning: message => warnings.push(message),
|
|
253
|
+
}).selectorMap;
|
|
254
|
+
}
|
|
255
|
+
catch (error) {
|
|
256
|
+
warnings.push(`Failed to extract CSS for ${relativeToRoot(resolvedPath, options.rootDir)}: ${formatErrorMessage(error)}`);
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
selectorCache.set(cacheKey, selectorMap);
|
|
260
|
+
}
|
|
261
|
+
if (!isWithinRoot(resolvedPath, options.rootDir)) {
|
|
262
|
+
warnings.push(`Skipping selector module for ${relativeToRoot(resolvedPath, options.rootDir)} because it is outside the project root.`);
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
const manifestKey = buildSelectorModuleManifestKey(resolvedPath);
|
|
266
|
+
if (processedSelectors.has(manifestKey)) {
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
const proxyInfo = await resolveProxyInfo(manifestKey, selectorSource, resolvedPath, proxyInfoCache);
|
|
270
|
+
const moduleWrite = await ensureSelectorModule(resolvedPath, selectorMap, previousSelectorManifest, nextSelectorManifest, selectorSource, proxyInfo ?? undefined, options.hashed ?? false);
|
|
271
|
+
if (moduleWrite) {
|
|
272
|
+
selectorModuleWrites += 1;
|
|
273
|
+
}
|
|
274
|
+
processedSelectors.add(manifestKey);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
177
278
|
const selectorModulesRemoved = await removeStaleSelectorModules(previousSelectorManifest, nextSelectorManifest);
|
|
178
279
|
await writeManifest(selectorModulesManifestPath, nextSelectorManifest);
|
|
280
|
+
if (options.manifestPath && options.mode === 'declaration') {
|
|
281
|
+
await writeSidecarManifest(options.manifestPath, sidecarManifest);
|
|
282
|
+
}
|
|
179
283
|
return {
|
|
180
284
|
selectorModulesWritten: selectorModuleWrites,
|
|
181
285
|
selectorModulesRemoved,
|
|
182
286
|
warnings,
|
|
183
287
|
manifestPath: selectorModulesManifestPath,
|
|
288
|
+
sidecarManifestPath: options.mode === 'declaration' ? options.manifestPath : undefined,
|
|
184
289
|
};
|
|
185
290
|
}
|
|
186
291
|
function normalizeIncludeOptions(include, rootDir) {
|
|
@@ -359,6 +464,50 @@ function buildSelectorModulePath(resolvedPath) {
|
|
|
359
464
|
const base = ext ? resolvedPath.slice(0, -ext.length) : resolvedPath;
|
|
360
465
|
return `${base}${SELECTOR_MODULE_SUFFIX}`;
|
|
361
466
|
}
|
|
467
|
+
function buildDeclarationModuleSpecifier(resolvedPath) {
|
|
468
|
+
const ext = node_path_1.default.extname(resolvedPath).toLowerCase();
|
|
469
|
+
const baseName = node_path_1.default.basename(resolvedPath, ext);
|
|
470
|
+
const mappedExt = ext === '.mjs' || ext === '.mts'
|
|
471
|
+
? '.mjs'
|
|
472
|
+
: ext === '.cjs' || ext === '.cts'
|
|
473
|
+
? '.cjs'
|
|
474
|
+
: '.js';
|
|
475
|
+
return `./${baseName}${mappedExt}`;
|
|
476
|
+
}
|
|
477
|
+
function buildDeclarationPath(resolvedPath) {
|
|
478
|
+
if (resolvedPath.endsWith(DECLARATION_SUFFIX)) {
|
|
479
|
+
return resolvedPath;
|
|
480
|
+
}
|
|
481
|
+
return `${resolvedPath}${DECLARATION_SUFFIX}`;
|
|
482
|
+
}
|
|
483
|
+
function formatSelectorTypeLiteral(selectors) {
|
|
484
|
+
const entries = Array.from(selectors.keys()).sort((a, b) => a.localeCompare(b));
|
|
485
|
+
const typeLines = entries.map(token => ` readonly ${JSON.stringify(token)}: string`);
|
|
486
|
+
return typeLines.length > 0
|
|
487
|
+
? `{
|
|
488
|
+
${typeLines.join('\n')}
|
|
489
|
+
}`
|
|
490
|
+
: 'Record<string, string>';
|
|
491
|
+
}
|
|
492
|
+
function formatDeclarationSource(selectors, proxyInfo, options = {}) {
|
|
493
|
+
const header = '// Generated by @knighted/css/generate-types\n// Do not edit.';
|
|
494
|
+
const isHashed = options.hashed === true;
|
|
495
|
+
const marker = isHashed ? '// @knighted-css:hashed' : '// @knighted-css';
|
|
496
|
+
const exportName = isHashed ? 'selectors' : 'stableSelectors';
|
|
497
|
+
const typeLiteral = formatSelectorTypeLiteral(selectors);
|
|
498
|
+
const shouldEmit = (name) => !proxyInfo.exportedNames?.has(name);
|
|
499
|
+
const lines = [
|
|
500
|
+
header,
|
|
501
|
+
marker,
|
|
502
|
+
'',
|
|
503
|
+
`declare module '${proxyInfo.moduleSpecifier}' {`,
|
|
504
|
+
shouldEmit('knightedCss') ? ' export const knightedCss: string' : '',
|
|
505
|
+
shouldEmit(exportName) ? ` export const ${exportName}: ${typeLiteral}` : '',
|
|
506
|
+
'}',
|
|
507
|
+
'export {}',
|
|
508
|
+
].filter(Boolean);
|
|
509
|
+
return `${lines.join('\n')}\n`;
|
|
510
|
+
}
|
|
362
511
|
function formatSelectorModuleSource(selectors, proxyInfo, options = {}) {
|
|
363
512
|
const header = '// Generated by @knighted/css/generate-types\n// Do not edit.';
|
|
364
513
|
const entries = Array.from(selectors.entries()).sort(([a], [b]) => a.localeCompare(b));
|
|
@@ -436,6 +585,10 @@ async function readManifest(manifestPath) {
|
|
|
436
585
|
async function writeManifest(manifestPath, manifest) {
|
|
437
586
|
await promises_1.default.writeFile(manifestPath, JSON.stringify(manifest, null, 2), 'utf8');
|
|
438
587
|
}
|
|
588
|
+
async function writeSidecarManifest(manifestPath, manifest) {
|
|
589
|
+
await promises_1.default.mkdir(node_path_1.default.dirname(manifestPath), { recursive: true });
|
|
590
|
+
await promises_1.default.writeFile(manifestPath, JSON.stringify(manifest, null, 2), 'utf8');
|
|
591
|
+
}
|
|
439
592
|
async function removeStaleSelectorModules(previous, next) {
|
|
440
593
|
const stale = Object.entries(previous).filter(([key]) => !next[key]);
|
|
441
594
|
let removed = 0;
|
|
@@ -480,6 +633,19 @@ async function ensureSelectorModule(resolvedPath, selectors, previousManifest, n
|
|
|
480
633
|
nextManifest[manifestKey] = { file: targetPath, hash };
|
|
481
634
|
return needsWrite;
|
|
482
635
|
}
|
|
636
|
+
async function ensureDeclarationModule(resolvedPath, selectors, previousManifest, nextManifest, proxyInfo, hashed) {
|
|
637
|
+
const manifestKey = buildSelectorModuleManifestKey(resolvedPath);
|
|
638
|
+
const targetPath = buildDeclarationPath(resolvedPath);
|
|
639
|
+
const source = formatDeclarationSource(selectors, proxyInfo, { hashed });
|
|
640
|
+
const hash = hashContent(source);
|
|
641
|
+
const previousEntry = previousManifest[manifestKey];
|
|
642
|
+
const needsWrite = previousEntry?.hash !== hash || !(await fileExists(targetPath));
|
|
643
|
+
if (needsWrite) {
|
|
644
|
+
await promises_1.default.writeFile(targetPath, source, 'utf8');
|
|
645
|
+
}
|
|
646
|
+
nextManifest[manifestKey] = { file: targetPath, hash };
|
|
647
|
+
return needsWrite;
|
|
648
|
+
}
|
|
483
649
|
async function fileExists(target) {
|
|
484
650
|
try {
|
|
485
651
|
await promises_1.default.access(target);
|
|
@@ -621,6 +787,69 @@ function isStyleResource(filePath) {
|
|
|
621
787
|
const normalized = filePath.toLowerCase();
|
|
622
788
|
return STYLE_EXTENSIONS.some(ext => normalized.endsWith(ext));
|
|
623
789
|
}
|
|
790
|
+
function isVanillaExtractResource(filePath) {
|
|
791
|
+
const normalized = filePath.toLowerCase();
|
|
792
|
+
return (normalized.endsWith('.css.ts') ||
|
|
793
|
+
normalized.endsWith('.css.js') ||
|
|
794
|
+
normalized.endsWith('.css.mts') ||
|
|
795
|
+
normalized.endsWith('.css.cts') ||
|
|
796
|
+
normalized.endsWith('.css.mjs') ||
|
|
797
|
+
normalized.endsWith('.css.cjs'));
|
|
798
|
+
}
|
|
799
|
+
function isCssModuleResource(filePath) {
|
|
800
|
+
return /\.module\.(css|scss|sass|less)$/i.test(filePath);
|
|
801
|
+
}
|
|
802
|
+
function isScriptResource(filePath) {
|
|
803
|
+
const normalized = filePath.toLowerCase();
|
|
804
|
+
if (normalized.endsWith('.d.ts')) {
|
|
805
|
+
return false;
|
|
806
|
+
}
|
|
807
|
+
return SCRIPT_EXTENSIONS.some(ext => normalized.endsWith(ext));
|
|
808
|
+
}
|
|
809
|
+
async function hasStyleImports(filePath, options) {
|
|
810
|
+
let source;
|
|
811
|
+
try {
|
|
812
|
+
source = await promises_1.default.readFile(filePath, 'utf8');
|
|
813
|
+
}
|
|
814
|
+
catch {
|
|
815
|
+
return false;
|
|
816
|
+
}
|
|
817
|
+
const candidates = new Set();
|
|
818
|
+
try {
|
|
819
|
+
const analysis = await (0, lexer_js_1.analyzeModule)(source, filePath);
|
|
820
|
+
for (const specifier of analysis.imports) {
|
|
821
|
+
if (specifier) {
|
|
822
|
+
candidates.add(specifier);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
catch {
|
|
827
|
+
// fall back to regex scanning below
|
|
828
|
+
}
|
|
829
|
+
const importRegex = /(import|require)\s*(?:\(|[^'"`]*)(['"])([^'"`]+)\2/g;
|
|
830
|
+
let match;
|
|
831
|
+
while ((match = importRegex.exec(source)) !== null) {
|
|
832
|
+
const specifier = match[3];
|
|
833
|
+
if (specifier) {
|
|
834
|
+
candidates.add(specifier);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
for (const specifier of candidates) {
|
|
838
|
+
const cleaned = stripInlineLoader(specifier.trim());
|
|
839
|
+
const { resource } = splitResourceAndQuery(cleaned);
|
|
840
|
+
if (!resource) {
|
|
841
|
+
continue;
|
|
842
|
+
}
|
|
843
|
+
if (isStyleResource(resource) || isVanillaExtractResource(resource)) {
|
|
844
|
+
return true;
|
|
845
|
+
}
|
|
846
|
+
const resolved = await resolveImportPath(resource, filePath, options.rootDir, options.tsconfig, options.resolver, options.resolverFactory, RESOLUTION_EXTENSIONS);
|
|
847
|
+
if (resolved && (isStyleResource(resolved) || isVanillaExtractResource(resolved))) {
|
|
848
|
+
return true;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
return false;
|
|
852
|
+
}
|
|
624
853
|
function collectSelectorTokensFromCss(css) {
|
|
625
854
|
const tokens = new Set();
|
|
626
855
|
const pattern = /\.([A-Za-z_-][A-Za-z0-9_-]*)\b/g;
|
|
@@ -653,6 +882,21 @@ async function resolveProxyInfo(manifestKey, selectorSource, resolvedPath, cache
|
|
|
653
882
|
cache.set(manifestKey, proxyInfo);
|
|
654
883
|
return proxyInfo;
|
|
655
884
|
}
|
|
885
|
+
async function resolveDeclarationProxyInfo(manifestKey, resolvedPath, cache) {
|
|
886
|
+
const cached = cache.get(manifestKey);
|
|
887
|
+
if (cached !== undefined) {
|
|
888
|
+
return cached;
|
|
889
|
+
}
|
|
890
|
+
const defaultSignal = await getDefaultExportSignal(resolvedPath);
|
|
891
|
+
const exportedNames = await getNamedExports(resolvedPath);
|
|
892
|
+
const proxyInfo = {
|
|
893
|
+
moduleSpecifier: buildDeclarationModuleSpecifier(resolvedPath),
|
|
894
|
+
includeDefault: defaultSignal === 'has-default',
|
|
895
|
+
exportedNames,
|
|
896
|
+
};
|
|
897
|
+
cache.set(manifestKey, proxyInfo);
|
|
898
|
+
return proxyInfo;
|
|
899
|
+
}
|
|
656
900
|
function buildProxyModuleSpecifier(resolvedPath, selectorSource) {
|
|
657
901
|
const resolvedExt = node_path_1.default.extname(resolvedPath);
|
|
658
902
|
const baseName = node_path_1.default.basename(resolvedPath, resolvedExt);
|
|
@@ -670,6 +914,16 @@ async function getDefaultExportSignal(filePath) {
|
|
|
670
914
|
return 'unknown';
|
|
671
915
|
}
|
|
672
916
|
}
|
|
917
|
+
async function getNamedExports(filePath) {
|
|
918
|
+
try {
|
|
919
|
+
const source = await promises_1.default.readFile(filePath, 'utf8');
|
|
920
|
+
const analysis = await (0, lexer_js_1.analyzeModule)(source, filePath);
|
|
921
|
+
return new Set(analysis.exports ?? []);
|
|
922
|
+
}
|
|
923
|
+
catch {
|
|
924
|
+
return new Set();
|
|
925
|
+
}
|
|
926
|
+
}
|
|
673
927
|
function createProjectPeerResolver(rootDir) {
|
|
674
928
|
const resolver = getProjectRequire(rootDir);
|
|
675
929
|
return async (name) => {
|
|
@@ -742,6 +996,8 @@ async function runGenerateTypesCli(argv = process.argv.slice(2)) {
|
|
|
742
996
|
autoStable: parsed.autoStable,
|
|
743
997
|
hashed: parsed.hashed,
|
|
744
998
|
resolver,
|
|
999
|
+
mode: parsed.mode,
|
|
1000
|
+
manifestPath: parsed.manifestPath,
|
|
745
1001
|
});
|
|
746
1002
|
reportCliResult(result);
|
|
747
1003
|
}
|
|
@@ -759,10 +1015,20 @@ function parseCliArgs(argv) {
|
|
|
759
1015
|
let autoStable = false;
|
|
760
1016
|
let hashed = false;
|
|
761
1017
|
let resolver;
|
|
1018
|
+
let mode = 'module';
|
|
1019
|
+
let manifestPath;
|
|
762
1020
|
for (let i = 0; i < argv.length; i += 1) {
|
|
763
1021
|
const arg = argv[i];
|
|
764
1022
|
if (arg === '--help' || arg === '-h') {
|
|
765
|
-
return {
|
|
1023
|
+
return {
|
|
1024
|
+
rootDir,
|
|
1025
|
+
include,
|
|
1026
|
+
outDir,
|
|
1027
|
+
stableNamespace,
|
|
1028
|
+
autoStable,
|
|
1029
|
+
mode,
|
|
1030
|
+
help: true,
|
|
1031
|
+
};
|
|
766
1032
|
}
|
|
767
1033
|
if (arg === '--auto-stable') {
|
|
768
1034
|
autoStable = true;
|
|
@@ -796,6 +1062,14 @@ function parseCliArgs(argv) {
|
|
|
796
1062
|
outDir = value;
|
|
797
1063
|
continue;
|
|
798
1064
|
}
|
|
1065
|
+
if (arg === '--manifest') {
|
|
1066
|
+
const value = argv[++i];
|
|
1067
|
+
if (!value) {
|
|
1068
|
+
throw new Error('Missing value for --manifest');
|
|
1069
|
+
}
|
|
1070
|
+
manifestPath = value;
|
|
1071
|
+
continue;
|
|
1072
|
+
}
|
|
799
1073
|
if (arg === '--stable-namespace') {
|
|
800
1074
|
const value = argv[++i];
|
|
801
1075
|
if (!value) {
|
|
@@ -812,6 +1086,17 @@ function parseCliArgs(argv) {
|
|
|
812
1086
|
resolver = value;
|
|
813
1087
|
continue;
|
|
814
1088
|
}
|
|
1089
|
+
if (arg === '--mode') {
|
|
1090
|
+
const value = argv[++i];
|
|
1091
|
+
if (!value) {
|
|
1092
|
+
throw new Error('Missing value for --mode');
|
|
1093
|
+
}
|
|
1094
|
+
if (value !== 'module' && value !== 'declaration') {
|
|
1095
|
+
throw new Error(`Unknown mode: ${value}`);
|
|
1096
|
+
}
|
|
1097
|
+
mode = value;
|
|
1098
|
+
continue;
|
|
1099
|
+
}
|
|
815
1100
|
if (arg.startsWith('-')) {
|
|
816
1101
|
throw new Error(`Unknown flag: ${arg}`);
|
|
817
1102
|
}
|
|
@@ -820,20 +1105,35 @@ function parseCliArgs(argv) {
|
|
|
820
1105
|
if (autoStable && hashed) {
|
|
821
1106
|
throw new Error('Cannot combine --auto-stable with --hashed');
|
|
822
1107
|
}
|
|
823
|
-
|
|
1108
|
+
if (manifestPath && mode !== 'declaration') {
|
|
1109
|
+
throw new Error('Cannot use --manifest unless --mode is declaration');
|
|
1110
|
+
}
|
|
1111
|
+
return {
|
|
1112
|
+
rootDir,
|
|
1113
|
+
include,
|
|
1114
|
+
outDir,
|
|
1115
|
+
stableNamespace,
|
|
1116
|
+
autoStable,
|
|
1117
|
+
hashed,
|
|
1118
|
+
resolver,
|
|
1119
|
+
mode,
|
|
1120
|
+
manifestPath,
|
|
1121
|
+
};
|
|
824
1122
|
}
|
|
825
1123
|
function printHelp() {
|
|
826
1124
|
console.log(`Usage: knighted-css-generate-types [options]
|
|
827
1125
|
|
|
828
1126
|
Options:
|
|
829
|
-
-r, --root <path>
|
|
830
|
-
-i, --include <path>
|
|
831
|
-
--out-dir <path>
|
|
832
|
-
--stable-namespace <name>
|
|
833
|
-
--auto-stable
|
|
834
|
-
--hashed
|
|
835
|
-
--resolver <path>
|
|
836
|
-
|
|
1127
|
+
-r, --root <path> Project root directory (default: cwd)
|
|
1128
|
+
-i, --include <path> Additional directories/files to scan (repeatable)
|
|
1129
|
+
--out-dir <path> Directory to store selector module manifest cache
|
|
1130
|
+
--stable-namespace <name> Stable namespace prefix for generated selector maps
|
|
1131
|
+
--auto-stable Enable autoStable when extracting CSS for selectors
|
|
1132
|
+
--hashed Emit selectors backed by loader-bridge hashed modules
|
|
1133
|
+
--resolver <path> Path or package name exporting a CssResolver
|
|
1134
|
+
--mode <module|declaration> Emit selector modules (module) or declaration files (declaration)
|
|
1135
|
+
--manifest <path> Write a sidecar manifest (declaration mode only)
|
|
1136
|
+
-h, --help Show this help message
|
|
837
1137
|
`);
|
|
838
1138
|
}
|
|
839
1139
|
function reportCliResult(result) {
|
|
@@ -844,6 +1144,9 @@ function reportCliResult(result) {
|
|
|
844
1144
|
console.log(`[knighted-css] Selector modules updated: wrote ${result.selectorModulesWritten}, removed ${result.selectorModulesRemoved}.`);
|
|
845
1145
|
}
|
|
846
1146
|
console.log(`[knighted-css] Manifest: ${result.manifestPath}`);
|
|
1147
|
+
if (result.sidecarManifestPath) {
|
|
1148
|
+
console.log(`[knighted-css] Sidecar manifest: ${result.sidecarManifestPath}`);
|
|
1149
|
+
}
|
|
847
1150
|
for (const warning of result.warnings) {
|
|
848
1151
|
console.warn(`[knighted-css] ${warning}`);
|
|
849
1152
|
}
|
|
@@ -873,23 +1176,31 @@ exports.__generateTypesInternals = {
|
|
|
873
1176
|
setImportMetaUrlProvider,
|
|
874
1177
|
isNonRelativeSpecifier,
|
|
875
1178
|
isStyleResource,
|
|
1179
|
+
isCssModuleResource,
|
|
876
1180
|
resolveProxyInfo,
|
|
1181
|
+
resolveDeclarationProxyInfo,
|
|
877
1182
|
resolveWithExtensionFallback,
|
|
878
1183
|
resolveIndexFallback,
|
|
879
1184
|
createProjectPeerResolver,
|
|
880
1185
|
getProjectRequire,
|
|
881
1186
|
loadTsconfigResolutionContext,
|
|
882
1187
|
resolveWithTsconfigPaths,
|
|
1188
|
+
hasStyleImports,
|
|
883
1189
|
loadResolverModule,
|
|
884
1190
|
parseCliArgs,
|
|
885
1191
|
printHelp,
|
|
886
1192
|
reportCliResult,
|
|
887
1193
|
buildSelectorModuleManifestKey,
|
|
888
1194
|
buildSelectorModulePath,
|
|
1195
|
+
buildDeclarationModuleSpecifier,
|
|
889
1196
|
formatSelectorModuleSource,
|
|
1197
|
+
buildDeclarationPath,
|
|
1198
|
+
formatDeclarationSource,
|
|
1199
|
+
ensureDeclarationModule,
|
|
890
1200
|
ensureSelectorModule,
|
|
891
1201
|
removeStaleSelectorModules,
|
|
892
1202
|
readManifest,
|
|
893
1203
|
writeManifest,
|
|
1204
|
+
writeSidecarManifest,
|
|
894
1205
|
};
|
|
895
1206
|
//# sourceMappingURL=generateTypes.cjs.map
|