@knighted/css 1.1.0-rc.7 → 1.1.0
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 +16 -0
- package/dist/cjs/generateTypes.cjs +87 -18
- package/dist/cjs/generateTypes.cjs.map +1 -1
- package/dist/cjs/generateTypes.d.cts +8 -2
- package/dist/cjs/loaderBridge.cjs +26 -1
- package/dist/cjs/loaderBridge.cjs.map +1 -1
- package/dist/cjs/loaderBridge.d.cts +1 -0
- package/dist/generateTypes.d.ts +8 -2
- package/dist/generateTypes.js +87 -18
- package/dist/generateTypes.js.map +1 -1
- package/dist/loaderBridge.d.ts +1 -0
- package/dist/loaderBridge.js +26 -1
- package/dist/loaderBridge.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -121,6 +121,22 @@ When the `.knighted-css` import targets a JavaScript/TypeScript module, the gene
|
|
|
121
121
|
import Button, { knightedCss, stableSelectors } from './button.knighted-css.js'
|
|
122
122
|
```
|
|
123
123
|
|
|
124
|
+
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:
|
|
125
|
+
|
|
126
|
+
```sh
|
|
127
|
+
knighted-css-generate-types --root . --include src --hashed
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
import Button, { knightedCss, selectors } from './button.knighted-css.js'
|
|
132
|
+
|
|
133
|
+
selectors.card // hashed CSS Modules class name
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
> [!IMPORTANT]
|
|
137
|
+
> `--hashed` requires wiring `@knighted/css/loader-bridge` to handle `?knighted-css` queries so
|
|
138
|
+
> the generated proxies can read `knightedCss` and `knightedCssModules` at build time.
|
|
139
|
+
|
|
124
140
|
Refer to [docs/type-generation.md](../../docs/type-generation.md) for CLI options and workflow tips.
|
|
125
141
|
|
|
126
142
|
### Combined + runtime selectors
|
|
@@ -87,6 +87,7 @@ async function generateTypes(options = {}) {
|
|
|
87
87
|
cacheDir,
|
|
88
88
|
stableNamespace: options.stableNamespace,
|
|
89
89
|
autoStable: options.autoStable,
|
|
90
|
+
hashed: options.hashed,
|
|
90
91
|
tsconfig,
|
|
91
92
|
resolver: options.resolver,
|
|
92
93
|
};
|
|
@@ -142,12 +143,14 @@ async function generateDeclarations(options) {
|
|
|
142
143
|
: undefined,
|
|
143
144
|
resolver: options.resolver,
|
|
144
145
|
});
|
|
145
|
-
selectorMap =
|
|
146
|
-
css
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
146
|
+
selectorMap = options.hashed
|
|
147
|
+
? collectSelectorTokensFromCss(css)
|
|
148
|
+
: (0, stableSelectorsLiteral_js_1.buildStableSelectorsLiteral)({
|
|
149
|
+
css,
|
|
150
|
+
namespace: resolvedNamespace,
|
|
151
|
+
resourcePath: resolvedPath,
|
|
152
|
+
emitWarning: message => warnings.push(message),
|
|
153
|
+
}).selectorMap;
|
|
151
154
|
}
|
|
152
155
|
catch (error) {
|
|
153
156
|
warnings.push(`Failed to extract CSS for ${relativeToRoot(resolvedPath, options.rootDir)}: ${formatErrorMessage(error)}`);
|
|
@@ -164,7 +167,7 @@ async function generateDeclarations(options) {
|
|
|
164
167
|
continue;
|
|
165
168
|
}
|
|
166
169
|
const proxyInfo = await resolveProxyInfo(manifestKey, selectorSource, resolvedPath, proxyInfoCache);
|
|
167
|
-
const moduleWrite = await ensureSelectorModule(resolvedPath, selectorMap, previousSelectorManifest, nextSelectorManifest, proxyInfo ?? undefined);
|
|
170
|
+
const moduleWrite = await ensureSelectorModule(resolvedPath, selectorMap, previousSelectorManifest, nextSelectorManifest, selectorSource, proxyInfo ?? undefined, options.hashed ?? false);
|
|
168
171
|
if (moduleWrite) {
|
|
169
172
|
selectorModuleWrites += 1;
|
|
170
173
|
}
|
|
@@ -356,29 +359,65 @@ function buildSelectorModulePath(resolvedPath) {
|
|
|
356
359
|
const base = ext ? resolvedPath.slice(0, -ext.length) : resolvedPath;
|
|
357
360
|
return `${base}${SELECTOR_MODULE_SUFFIX}`;
|
|
358
361
|
}
|
|
359
|
-
function formatSelectorModuleSource(selectors, proxyInfo) {
|
|
362
|
+
function formatSelectorModuleSource(selectors, proxyInfo, options = {}) {
|
|
360
363
|
const header = '// Generated by @knighted/css/generate-types\n// Do not edit.';
|
|
361
364
|
const entries = Array.from(selectors.entries()).sort(([a], [b]) => a.localeCompare(b));
|
|
365
|
+
const isHashed = options.hashed === true;
|
|
362
366
|
const lines = entries.map(([token, selector]) => ` ${JSON.stringify(token)}: ${JSON.stringify(selector)},`);
|
|
363
367
|
const literal = lines.length > 0
|
|
364
368
|
? `{
|
|
365
369
|
${lines.join('\n')}
|
|
366
370
|
} as const`
|
|
367
371
|
: '{} as const';
|
|
372
|
+
const typeLines = entries.map(([token]) => ` readonly ${JSON.stringify(token)}: string`);
|
|
373
|
+
const typeLiteral = typeLines.length > 0
|
|
374
|
+
? `{
|
|
375
|
+
${typeLines.join('\n')}
|
|
376
|
+
}`
|
|
377
|
+
: 'Record<string, string>';
|
|
368
378
|
const proxyLines = [];
|
|
379
|
+
const reexportLines = [];
|
|
380
|
+
const hashedSpecifier = options.selectorSource && options.resolvedPath
|
|
381
|
+
? buildProxyModuleSpecifier(options.resolvedPath, options.selectorSource)
|
|
382
|
+
: undefined;
|
|
369
383
|
if (proxyInfo) {
|
|
370
|
-
|
|
384
|
+
reexportLines.push(`export * from '${proxyInfo.moduleSpecifier}'`);
|
|
371
385
|
if (proxyInfo.includeDefault) {
|
|
372
|
-
|
|
386
|
+
reexportLines.push(`export { default } from '${proxyInfo.moduleSpecifier}'`);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
if (isHashed) {
|
|
390
|
+
const sourceSpecifier = proxyInfo?.moduleSpecifier ?? hashedSpecifier;
|
|
391
|
+
if (sourceSpecifier) {
|
|
392
|
+
proxyLines.push(`import { knightedCss as __knightedCss, knightedCssModules as __knightedCssModules } from '${sourceSpecifier}?knighted-css'`);
|
|
393
|
+
proxyLines.push('export const knightedCss = __knightedCss');
|
|
394
|
+
proxyLines.push('export const knightedCssModules = __knightedCssModules');
|
|
373
395
|
}
|
|
396
|
+
}
|
|
397
|
+
else if (proxyInfo) {
|
|
374
398
|
proxyLines.push(`export { knightedCss } from '${proxyInfo.moduleSpecifier}?knighted-css'`);
|
|
375
399
|
}
|
|
376
|
-
const
|
|
377
|
-
const
|
|
400
|
+
const exportName = isHashed ? 'selectors' : 'stableSelectors';
|
|
401
|
+
const typeName = isHashed ? 'KnightedCssSelectors' : 'KnightedCssStableSelectors';
|
|
402
|
+
const tokenTypeName = isHashed
|
|
403
|
+
? 'KnightedCssSelectorToken'
|
|
404
|
+
: 'KnightedCssStableSelectorToken';
|
|
405
|
+
const defaultExport = proxyInfo ? '' : `\nexport default ${exportName}`;
|
|
406
|
+
const selectorBlock = isHashed
|
|
407
|
+
? `export const ${exportName} = __knightedCssModules as ${typeLiteral}
|
|
408
|
+
|
|
409
|
+
export type ${typeName} = typeof ${exportName}
|
|
410
|
+
export type ${tokenTypeName} = keyof typeof ${exportName}${defaultExport}`
|
|
411
|
+
: `export const ${exportName} = ${literal}
|
|
378
412
|
|
|
379
|
-
export type
|
|
380
|
-
export type
|
|
381
|
-
const sections = [
|
|
413
|
+
export type ${typeName} = typeof ${exportName}
|
|
414
|
+
export type ${tokenTypeName} = keyof typeof ${exportName}${defaultExport}`;
|
|
415
|
+
const sections = [
|
|
416
|
+
header,
|
|
417
|
+
proxyLines.join('\n'),
|
|
418
|
+
reexportLines.join('\n'),
|
|
419
|
+
selectorBlock,
|
|
420
|
+
].filter(Boolean);
|
|
382
421
|
return `${sections.join('\n\n')}
|
|
383
422
|
`;
|
|
384
423
|
}
|
|
@@ -424,10 +463,14 @@ function isWithinRoot(filePath, rootDir) {
|
|
|
424
463
|
const relative = node_path_1.default.relative(rootDir, filePath);
|
|
425
464
|
return relative === '' || (!relative.startsWith('..') && !node_path_1.default.isAbsolute(relative));
|
|
426
465
|
}
|
|
427
|
-
async function ensureSelectorModule(resolvedPath, selectors, previousManifest, nextManifest, proxyInfo) {
|
|
466
|
+
async function ensureSelectorModule(resolvedPath, selectors, previousManifest, nextManifest, selectorSource, proxyInfo, hashed) {
|
|
428
467
|
const manifestKey = buildSelectorModuleManifestKey(resolvedPath);
|
|
429
468
|
const targetPath = buildSelectorModulePath(resolvedPath);
|
|
430
|
-
const source = formatSelectorModuleSource(selectors, proxyInfo
|
|
469
|
+
const source = formatSelectorModuleSource(selectors, proxyInfo, {
|
|
470
|
+
hashed,
|
|
471
|
+
selectorSource,
|
|
472
|
+
resolvedPath,
|
|
473
|
+
});
|
|
431
474
|
const hash = hashContent(source);
|
|
432
475
|
const previousEntry = previousManifest[manifestKey];
|
|
433
476
|
const needsWrite = previousEntry?.hash !== hash || !(await fileExists(targetPath));
|
|
@@ -578,6 +621,22 @@ function isStyleResource(filePath) {
|
|
|
578
621
|
const normalized = filePath.toLowerCase();
|
|
579
622
|
return STYLE_EXTENSIONS.some(ext => normalized.endsWith(ext));
|
|
580
623
|
}
|
|
624
|
+
function collectSelectorTokensFromCss(css) {
|
|
625
|
+
const tokens = new Set();
|
|
626
|
+
const pattern = /\.([A-Za-z_-][A-Za-z0-9_-]*)\b/g;
|
|
627
|
+
let match;
|
|
628
|
+
while ((match = pattern.exec(css)) !== null) {
|
|
629
|
+
const token = match[1];
|
|
630
|
+
if (token) {
|
|
631
|
+
tokens.add(token);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
const map = new Map();
|
|
635
|
+
for (const token of tokens) {
|
|
636
|
+
map.set(token, token);
|
|
637
|
+
}
|
|
638
|
+
return map;
|
|
639
|
+
}
|
|
581
640
|
async function resolveProxyInfo(manifestKey, selectorSource, resolvedPath, cache) {
|
|
582
641
|
if (isStyleResource(resolvedPath)) {
|
|
583
642
|
return null;
|
|
@@ -681,6 +740,7 @@ async function runGenerateTypesCli(argv = process.argv.slice(2)) {
|
|
|
681
740
|
outDir: parsed.outDir,
|
|
682
741
|
stableNamespace: parsed.stableNamespace,
|
|
683
742
|
autoStable: parsed.autoStable,
|
|
743
|
+
hashed: parsed.hashed,
|
|
684
744
|
resolver,
|
|
685
745
|
});
|
|
686
746
|
reportCliResult(result);
|
|
@@ -697,6 +757,7 @@ function parseCliArgs(argv) {
|
|
|
697
757
|
let outDir;
|
|
698
758
|
let stableNamespace;
|
|
699
759
|
let autoStable = false;
|
|
760
|
+
let hashed = false;
|
|
700
761
|
let resolver;
|
|
701
762
|
for (let i = 0; i < argv.length; i += 1) {
|
|
702
763
|
const arg = argv[i];
|
|
@@ -707,6 +768,10 @@ function parseCliArgs(argv) {
|
|
|
707
768
|
autoStable = true;
|
|
708
769
|
continue;
|
|
709
770
|
}
|
|
771
|
+
if (arg === '--hashed') {
|
|
772
|
+
hashed = true;
|
|
773
|
+
continue;
|
|
774
|
+
}
|
|
710
775
|
if (arg === '--root' || arg === '-r') {
|
|
711
776
|
const value = argv[++i];
|
|
712
777
|
if (!value) {
|
|
@@ -752,7 +817,10 @@ function parseCliArgs(argv) {
|
|
|
752
817
|
}
|
|
753
818
|
include.push(arg);
|
|
754
819
|
}
|
|
755
|
-
|
|
820
|
+
if (autoStable && hashed) {
|
|
821
|
+
throw new Error('Cannot combine --auto-stable with --hashed');
|
|
822
|
+
}
|
|
823
|
+
return { rootDir, include, outDir, stableNamespace, autoStable, hashed, resolver };
|
|
756
824
|
}
|
|
757
825
|
function printHelp() {
|
|
758
826
|
console.log(`Usage: knighted-css-generate-types [options]
|
|
@@ -763,6 +831,7 @@ Options:
|
|
|
763
831
|
--out-dir <path> Directory to store selector module manifest cache
|
|
764
832
|
--stable-namespace <name> Stable namespace prefix for generated selector maps
|
|
765
833
|
--auto-stable Enable autoStable when extracting CSS for selectors
|
|
834
|
+
--hashed Emit selectors backed by loader-bridge hashed modules
|
|
766
835
|
--resolver <path> Path or package name exporting a CssResolver
|
|
767
836
|
-h, --help Show this help message
|
|
768
837
|
`);
|