@knighted/css 1.0.0-rc.9 → 1.0.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/dist/cjs/generateTypes.cjs +214 -139
- package/dist/cjs/generateTypes.d.cts +65 -16
- package/dist/cjs/loader-helpers.cjs +6 -0
- package/dist/cjs/loader-helpers.d.cts +4 -0
- package/dist/cjs/loader.cjs +2 -0
- package/dist/cjs/loader.d.cts +2 -1
- package/dist/cjs/moduleGraph.cjs +5 -4
- package/dist/cjs/stableSelectors.cjs +29 -0
- package/dist/cjs/stableSelectors.d.cts +15 -0
- package/dist/cjs/stableSelectorsLiteral.cjs +8 -5
- package/dist/cjs/stableSelectorsLiteral.d.cts +2 -0
- package/dist/generateTypes.d.ts +65 -16
- package/dist/generateTypes.js +214 -139
- package/dist/loader-helpers.d.ts +4 -0
- package/dist/loader-helpers.js +3 -0
- package/dist/loader.d.ts +2 -1
- package/dist/loader.js +2 -0
- package/dist/moduleGraph.js +5 -4
- package/dist/stableSelectors.d.ts +15 -0
- package/dist/stableSelectors.js +28 -0
- package/dist/stableSelectorsLiteral.d.ts +2 -0
- package/dist/stableSelectorsLiteral.js +8 -5
- package/loader-queries.d.ts +6 -1
- package/package.json +11 -2
package/dist/generateTypes.js
CHANGED
|
@@ -5,10 +5,12 @@ import { createRequire } from 'node:module';
|
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
6
6
|
import { init, parse } from 'es-module-lexer';
|
|
7
7
|
import { moduleType } from 'node-module-type';
|
|
8
|
+
import { getTsconfig } from 'get-tsconfig';
|
|
9
|
+
import { createMatchPath } from 'tsconfig-paths';
|
|
8
10
|
import { cssWithMeta } from './css.js';
|
|
9
|
-
import { determineSelectorVariant, hasQueryFlag, TYPES_QUERY_FLAG, } from './loaderInternals.js';
|
|
10
11
|
import { buildStableSelectorsLiteral } from './stableSelectorsLiteral.js';
|
|
11
12
|
import { resolveStableNamespace } from './stableNamespace.js';
|
|
13
|
+
let activeCssWithMeta = cssWithMeta;
|
|
12
14
|
const DEFAULT_SKIP_DIRS = new Set([
|
|
13
15
|
'node_modules',
|
|
14
16
|
'.git',
|
|
@@ -32,12 +34,14 @@ const SUPPORTED_EXTENSIONS = new Set([
|
|
|
32
34
|
'.mjs',
|
|
33
35
|
'.cjs',
|
|
34
36
|
]);
|
|
37
|
+
let moduleTypeDetector = moduleType;
|
|
38
|
+
let importMetaUrlProvider = getImportMetaUrl;
|
|
35
39
|
function resolvePackageRoot() {
|
|
36
|
-
const detectedType =
|
|
40
|
+
const detectedType = moduleTypeDetector();
|
|
37
41
|
if (detectedType === 'commonjs' && typeof __dirname === 'string') {
|
|
38
42
|
return path.resolve(__dirname, '..');
|
|
39
43
|
}
|
|
40
|
-
const moduleUrl =
|
|
44
|
+
const moduleUrl = importMetaUrlProvider();
|
|
41
45
|
if (moduleUrl) {
|
|
42
46
|
return path.resolve(path.dirname(fileURLToPath(moduleUrl)), '..');
|
|
43
47
|
}
|
|
@@ -52,61 +56,55 @@ function getImportMetaUrl() {
|
|
|
52
56
|
}
|
|
53
57
|
}
|
|
54
58
|
const PACKAGE_ROOT = resolvePackageRoot();
|
|
55
|
-
const
|
|
56
|
-
const
|
|
59
|
+
const SELECTOR_REFERENCE = '.knighted-css';
|
|
60
|
+
const SELECTOR_MODULE_SUFFIX = '.knighted-css.ts';
|
|
57
61
|
export async function generateTypes(options = {}) {
|
|
58
62
|
const rootDir = path.resolve(options.rootDir ?? process.cwd());
|
|
59
63
|
const include = normalizeIncludeOptions(options.include, rootDir);
|
|
60
|
-
const
|
|
61
|
-
const
|
|
64
|
+
const cacheDir = path.resolve(options.outDir ?? path.join(rootDir, '.knighted-css'));
|
|
65
|
+
const tsconfig = loadTsconfigResolutionContext(rootDir);
|
|
62
66
|
await init;
|
|
63
|
-
await fs.mkdir(
|
|
64
|
-
await fs.mkdir(typesRoot, { recursive: true });
|
|
67
|
+
await fs.mkdir(cacheDir, { recursive: true });
|
|
65
68
|
const internalOptions = {
|
|
66
69
|
rootDir,
|
|
67
70
|
include,
|
|
68
|
-
|
|
69
|
-
typesRoot,
|
|
71
|
+
cacheDir,
|
|
70
72
|
stableNamespace: options.stableNamespace,
|
|
73
|
+
tsconfig,
|
|
71
74
|
};
|
|
72
75
|
return generateDeclarations(internalOptions);
|
|
73
76
|
}
|
|
74
77
|
async function generateDeclarations(options) {
|
|
75
78
|
const peerResolver = createProjectPeerResolver(options.rootDir);
|
|
76
79
|
const files = await collectCandidateFiles(options.include);
|
|
77
|
-
const
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
+
const selectorModulesManifestPath = path.join(options.cacheDir, 'selector-modules.json');
|
|
81
|
+
const previousSelectorManifest = await readManifest(selectorModulesManifestPath);
|
|
82
|
+
const nextSelectorManifest = {};
|
|
80
83
|
const selectorCache = new Map();
|
|
81
|
-
const
|
|
82
|
-
const declarations = [];
|
|
84
|
+
const processedSelectors = new Set();
|
|
83
85
|
const warnings = [];
|
|
84
|
-
let
|
|
86
|
+
let selectorModuleWrites = 0;
|
|
85
87
|
for (const filePath of files) {
|
|
86
88
|
const matches = await findSpecifierImports(filePath);
|
|
87
89
|
for (const match of matches) {
|
|
88
90
|
const cleaned = match.specifier.trim();
|
|
89
91
|
const inlineFree = stripInlineLoader(cleaned);
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (!query || !hasQueryFlag(query, TYPES_QUERY_FLAG)) {
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
96
|
-
if (processedSpecifiers.has(cleaned)) {
|
|
92
|
+
const { resource } = splitResourceAndQuery(inlineFree);
|
|
93
|
+
const selectorSource = extractSelectorSourceSpecifier(resource);
|
|
94
|
+
if (!selectorSource) {
|
|
97
95
|
continue;
|
|
98
96
|
}
|
|
99
97
|
const resolvedNamespace = resolveStableNamespace(options.stableNamespace);
|
|
100
|
-
const resolvedPath = await resolveImportPath(
|
|
98
|
+
const resolvedPath = await resolveImportPath(selectorSource, match.importer, options.rootDir, options.tsconfig);
|
|
101
99
|
if (!resolvedPath) {
|
|
102
|
-
warnings.push(`Unable to resolve ${
|
|
100
|
+
warnings.push(`Unable to resolve ${selectorSource} referenced by ${relativeToRoot(match.importer, options.rootDir)}.`);
|
|
103
101
|
continue;
|
|
104
102
|
}
|
|
105
103
|
const cacheKey = `${resolvedPath}::${resolvedNamespace}`;
|
|
106
104
|
let selectorMap = selectorCache.get(cacheKey);
|
|
107
105
|
if (!selectorMap) {
|
|
108
106
|
try {
|
|
109
|
-
const { css } = await
|
|
107
|
+
const { css } = await activeCssWithMeta(resolvedPath, {
|
|
110
108
|
cwd: options.rootDir,
|
|
111
109
|
peerResolver,
|
|
112
110
|
});
|
|
@@ -123,38 +121,28 @@ async function generateDeclarations(options) {
|
|
|
123
121
|
}
|
|
124
122
|
selectorCache.set(cacheKey, selectorMap);
|
|
125
123
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if (needsWrite) {
|
|
134
|
-
await fs.writeFile(targetPath, declaration, 'utf8');
|
|
135
|
-
writes += 1;
|
|
124
|
+
if (!isWithinRoot(resolvedPath, options.rootDir)) {
|
|
125
|
+
warnings.push(`Skipping selector module for ${relativeToRoot(resolvedPath, options.rootDir)} because it is outside the project root.`);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const manifestKey = buildSelectorModuleManifestKey(resolvedPath);
|
|
129
|
+
if (processedSelectors.has(manifestKey)) {
|
|
130
|
+
continue;
|
|
136
131
|
}
|
|
137
|
-
|
|
138
|
-
if (
|
|
139
|
-
|
|
132
|
+
const moduleWrite = await ensureSelectorModule(resolvedPath, selectorMap, previousSelectorManifest, nextSelectorManifest);
|
|
133
|
+
if (moduleWrite) {
|
|
134
|
+
selectorModuleWrites += 1;
|
|
140
135
|
}
|
|
141
|
-
|
|
136
|
+
processedSelectors.add(manifestKey);
|
|
142
137
|
}
|
|
143
138
|
}
|
|
144
|
-
const
|
|
145
|
-
await writeManifest(
|
|
146
|
-
const typesIndexPath = path.join(options.typesRoot, 'index.d.ts');
|
|
147
|
-
await writeTypesIndex(typesIndexPath, nextManifest, options.outDir);
|
|
148
|
-
if (Object.keys(nextManifest).length === 0) {
|
|
149
|
-
declarations.length = 0;
|
|
150
|
-
}
|
|
139
|
+
const selectorModulesRemoved = await removeStaleSelectorModules(previousSelectorManifest, nextSelectorManifest);
|
|
140
|
+
await writeManifest(selectorModulesManifestPath, nextSelectorManifest);
|
|
151
141
|
return {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
declarations,
|
|
142
|
+
selectorModulesWritten: selectorModuleWrites,
|
|
143
|
+
selectorModulesRemoved,
|
|
155
144
|
warnings,
|
|
156
|
-
|
|
157
|
-
typesIndexPath,
|
|
145
|
+
manifestPath: selectorModulesManifestPath,
|
|
158
146
|
};
|
|
159
147
|
}
|
|
160
148
|
function normalizeIncludeOptions(include, rootDir) {
|
|
@@ -212,18 +200,18 @@ async function findSpecifierImports(filePath) {
|
|
|
212
200
|
catch {
|
|
213
201
|
return [];
|
|
214
202
|
}
|
|
215
|
-
if (!source.includes(
|
|
203
|
+
if (!source.includes(SELECTOR_REFERENCE)) {
|
|
216
204
|
return [];
|
|
217
205
|
}
|
|
218
206
|
const matches = [];
|
|
219
207
|
const [imports] = parse(source, filePath);
|
|
220
208
|
for (const record of imports) {
|
|
221
209
|
const specifier = record.n ?? source.slice(record.s, record.e);
|
|
222
|
-
if (specifier && specifier.includes(
|
|
210
|
+
if (specifier && specifier.includes(SELECTOR_REFERENCE)) {
|
|
223
211
|
matches.push({ specifier, importer: filePath });
|
|
224
212
|
}
|
|
225
213
|
}
|
|
226
|
-
const requireRegex = /require\((['"])([^'"`]
|
|
214
|
+
const requireRegex = /require\((['"])([^'"`]+?\.knighted-css[^'"`]*)\1\)/g;
|
|
227
215
|
let reqMatch;
|
|
228
216
|
while ((reqMatch = requireRegex.exec(source)) !== null) {
|
|
229
217
|
const spec = reqMatch[2];
|
|
@@ -246,8 +234,23 @@ function splitResourceAndQuery(specifier) {
|
|
|
246
234
|
}
|
|
247
235
|
return { resource: trimmed.slice(0, queryIndex), query: trimmed.slice(queryIndex) };
|
|
248
236
|
}
|
|
237
|
+
function extractSelectorSourceSpecifier(specifier) {
|
|
238
|
+
const markerIndex = specifier.indexOf(SELECTOR_REFERENCE);
|
|
239
|
+
if (markerIndex < 0) {
|
|
240
|
+
return undefined;
|
|
241
|
+
}
|
|
242
|
+
const suffix = specifier.slice(markerIndex + SELECTOR_REFERENCE.length);
|
|
243
|
+
if (suffix.length > 0 && !/\.(?:[cm]?[tj]s|[tj]sx)$/.test(suffix)) {
|
|
244
|
+
return undefined;
|
|
245
|
+
}
|
|
246
|
+
const base = specifier.slice(0, markerIndex);
|
|
247
|
+
if (!base) {
|
|
248
|
+
return undefined;
|
|
249
|
+
}
|
|
250
|
+
return base;
|
|
251
|
+
}
|
|
249
252
|
const projectRequireCache = new Map();
|
|
250
|
-
async function resolveImportPath(resourceSpecifier, importerPath, rootDir) {
|
|
253
|
+
async function resolveImportPath(resourceSpecifier, importerPath, rootDir, tsconfig) {
|
|
251
254
|
if (!resourceSpecifier)
|
|
252
255
|
return undefined;
|
|
253
256
|
if (resourceSpecifier.startsWith('.')) {
|
|
@@ -256,6 +259,10 @@ async function resolveImportPath(resourceSpecifier, importerPath, rootDir) {
|
|
|
256
259
|
if (resourceSpecifier.startsWith('/')) {
|
|
257
260
|
return path.resolve(rootDir, resourceSpecifier.slice(1));
|
|
258
261
|
}
|
|
262
|
+
const tsconfigResolved = await resolveWithTsconfigPaths(resourceSpecifier, tsconfig);
|
|
263
|
+
if (tsconfigResolved) {
|
|
264
|
+
return tsconfigResolved;
|
|
265
|
+
}
|
|
259
266
|
const requireFromRoot = getProjectRequire(rootDir);
|
|
260
267
|
try {
|
|
261
268
|
return requireFromRoot.resolve(resourceSpecifier);
|
|
@@ -264,47 +271,29 @@ async function resolveImportPath(resourceSpecifier, importerPath, rootDir) {
|
|
|
264
271
|
return undefined;
|
|
265
272
|
}
|
|
266
273
|
}
|
|
267
|
-
function
|
|
268
|
-
|
|
269
|
-
return `knt-${digest}.d.ts`;
|
|
270
|
-
}
|
|
271
|
-
function formatModuleDeclaration(specifier, variant, selectors) {
|
|
272
|
-
const literalSpecifier = JSON.stringify(specifier);
|
|
273
|
-
const selectorType = formatSelectorType(selectors);
|
|
274
|
-
const header = `declare module ${literalSpecifier} {`;
|
|
275
|
-
const footer = '}';
|
|
276
|
-
if (variant === 'types') {
|
|
277
|
-
return `${header}
|
|
278
|
-
export const knightedCss: string
|
|
279
|
-
export const stableSelectors: ${selectorType}
|
|
280
|
-
${footer}
|
|
281
|
-
`;
|
|
282
|
-
}
|
|
283
|
-
const stableLine = ` export const stableSelectors: ${selectorType}`;
|
|
284
|
-
const shared = ` const combined: KnightedCssCombinedModule<Record<string, unknown>>
|
|
285
|
-
export const knightedCss: string
|
|
286
|
-
${stableLine}`;
|
|
287
|
-
if (variant === 'combined') {
|
|
288
|
-
return `${header}
|
|
289
|
-
${shared}
|
|
290
|
-
export default combined
|
|
291
|
-
${footer}
|
|
292
|
-
`;
|
|
293
|
-
}
|
|
294
|
-
return `${header}
|
|
295
|
-
${shared}
|
|
296
|
-
${footer}
|
|
297
|
-
`;
|
|
274
|
+
function buildSelectorModuleManifestKey(resolvedPath) {
|
|
275
|
+
return resolvedPath.split(path.sep).join('/');
|
|
298
276
|
}
|
|
299
|
-
function
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
277
|
+
function buildSelectorModulePath(resolvedPath) {
|
|
278
|
+
return `${resolvedPath}${SELECTOR_MODULE_SUFFIX}`;
|
|
279
|
+
}
|
|
280
|
+
function formatSelectorModuleSource(selectors) {
|
|
281
|
+
const header = '// Generated by @knighted/css/generate-types\n// Do not edit.\n';
|
|
303
282
|
const entries = Array.from(selectors.entries()).sort(([a], [b]) => a.localeCompare(b));
|
|
304
|
-
const lines = entries.map(([token, selector]) => `
|
|
305
|
-
|
|
283
|
+
const lines = entries.map(([token, selector]) => ` ${JSON.stringify(token)}: ${JSON.stringify(selector)},`);
|
|
284
|
+
const literal = lines.length > 0
|
|
285
|
+
? `{
|
|
306
286
|
${lines.join('\n')}
|
|
307
|
-
|
|
287
|
+
} as const`
|
|
288
|
+
: '{} as const';
|
|
289
|
+
return `${header}
|
|
290
|
+
export const stableSelectors = ${literal}
|
|
291
|
+
|
|
292
|
+
export type KnightedCssStableSelectors = typeof stableSelectors
|
|
293
|
+
export type KnightedCssStableSelectorToken = keyof typeof stableSelectors
|
|
294
|
+
|
|
295
|
+
export default stableSelectors
|
|
296
|
+
`;
|
|
308
297
|
}
|
|
309
298
|
function hashContent(content) {
|
|
310
299
|
return crypto.createHash('sha1').update(content).digest('hex');
|
|
@@ -321,13 +310,12 @@ async function readManifest(manifestPath) {
|
|
|
321
310
|
async function writeManifest(manifestPath, manifest) {
|
|
322
311
|
await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2), 'utf8');
|
|
323
312
|
}
|
|
324
|
-
async function
|
|
325
|
-
const stale = Object.entries(previous).filter(([
|
|
313
|
+
async function removeStaleSelectorModules(previous, next) {
|
|
314
|
+
const stale = Object.entries(previous).filter(([key]) => !next[key]);
|
|
326
315
|
let removed = 0;
|
|
327
316
|
for (const [, entry] of stale) {
|
|
328
|
-
const targetPath = path.join(outDir, entry.file);
|
|
329
317
|
try {
|
|
330
|
-
await fs.unlink(
|
|
318
|
+
await fs.unlink(entry.file);
|
|
331
319
|
removed += 1;
|
|
332
320
|
}
|
|
333
321
|
catch {
|
|
@@ -336,25 +324,6 @@ async function removeStaleDeclarations(previous, next, outDir) {
|
|
|
336
324
|
}
|
|
337
325
|
return removed;
|
|
338
326
|
}
|
|
339
|
-
async function writeTypesIndex(indexPath, manifest, outDir) {
|
|
340
|
-
const header = '// Generated by @knighted/css/generate-types\n// Do not edit.\n';
|
|
341
|
-
const references = Object.values(manifest)
|
|
342
|
-
.sort((a, b) => a.file.localeCompare(b.file))
|
|
343
|
-
.map(entry => {
|
|
344
|
-
const rel = path
|
|
345
|
-
.relative(path.dirname(indexPath), path.join(outDir, entry.file))
|
|
346
|
-
.split(path.sep)
|
|
347
|
-
.join('/');
|
|
348
|
-
return `/// <reference path="${rel}" />`;
|
|
349
|
-
});
|
|
350
|
-
const content = references.length > 0
|
|
351
|
-
? `${header}
|
|
352
|
-
${references.join('\n')}
|
|
353
|
-
`
|
|
354
|
-
: `${header}
|
|
355
|
-
`;
|
|
356
|
-
await fs.writeFile(indexPath, content, 'utf8');
|
|
357
|
-
}
|
|
358
327
|
function formatErrorMessage(error) {
|
|
359
328
|
if (error instanceof Error && typeof error.message === 'string') {
|
|
360
329
|
return error.message;
|
|
@@ -364,6 +333,23 @@ function formatErrorMessage(error) {
|
|
|
364
333
|
function relativeToRoot(filePath, rootDir) {
|
|
365
334
|
return path.relative(rootDir, filePath) || filePath;
|
|
366
335
|
}
|
|
336
|
+
function isWithinRoot(filePath, rootDir) {
|
|
337
|
+
const relative = path.relative(rootDir, filePath);
|
|
338
|
+
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
|
|
339
|
+
}
|
|
340
|
+
async function ensureSelectorModule(resolvedPath, selectors, previousManifest, nextManifest) {
|
|
341
|
+
const manifestKey = buildSelectorModuleManifestKey(resolvedPath);
|
|
342
|
+
const targetPath = buildSelectorModulePath(resolvedPath);
|
|
343
|
+
const source = formatSelectorModuleSource(selectors);
|
|
344
|
+
const hash = hashContent(source);
|
|
345
|
+
const previousEntry = previousManifest[manifestKey];
|
|
346
|
+
const needsWrite = previousEntry?.hash !== hash || !(await fileExists(targetPath));
|
|
347
|
+
if (needsWrite) {
|
|
348
|
+
await fs.writeFile(targetPath, source, 'utf8');
|
|
349
|
+
}
|
|
350
|
+
nextManifest[manifestKey] = { file: targetPath, hash };
|
|
351
|
+
return needsWrite;
|
|
352
|
+
}
|
|
367
353
|
async function fileExists(target) {
|
|
368
354
|
try {
|
|
369
355
|
await fs.access(target);
|
|
@@ -373,6 +359,78 @@ async function fileExists(target) {
|
|
|
373
359
|
return false;
|
|
374
360
|
}
|
|
375
361
|
}
|
|
362
|
+
async function resolveWithTsconfigPaths(specifier, tsconfig) {
|
|
363
|
+
if (!tsconfig) {
|
|
364
|
+
return undefined;
|
|
365
|
+
}
|
|
366
|
+
if (tsconfig.matchPath) {
|
|
367
|
+
const matched = tsconfig.matchPath(specifier);
|
|
368
|
+
if (matched && (await fileExists(matched))) {
|
|
369
|
+
return matched;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
if (tsconfig.absoluteBaseUrl && isNonRelativeSpecifier(specifier)) {
|
|
373
|
+
const candidate = path.join(tsconfig.absoluteBaseUrl, specifier.split('/').join(path.sep));
|
|
374
|
+
if (await fileExists(candidate)) {
|
|
375
|
+
return candidate;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
return undefined;
|
|
379
|
+
}
|
|
380
|
+
function loadTsconfigResolutionContext(rootDir, loader = getTsconfig) {
|
|
381
|
+
let result;
|
|
382
|
+
try {
|
|
383
|
+
result = loader(rootDir);
|
|
384
|
+
}
|
|
385
|
+
catch {
|
|
386
|
+
return undefined;
|
|
387
|
+
}
|
|
388
|
+
if (!result) {
|
|
389
|
+
return undefined;
|
|
390
|
+
}
|
|
391
|
+
const compilerOptions = result.config.compilerOptions ?? {};
|
|
392
|
+
const configDir = path.dirname(result.path);
|
|
393
|
+
const absoluteBaseUrl = compilerOptions.baseUrl
|
|
394
|
+
? path.resolve(configDir, compilerOptions.baseUrl)
|
|
395
|
+
: undefined;
|
|
396
|
+
const normalizedPaths = normalizeTsconfigPaths(compilerOptions.paths);
|
|
397
|
+
const matchPath = absoluteBaseUrl && normalizedPaths
|
|
398
|
+
? createMatchPath(absoluteBaseUrl, normalizedPaths)
|
|
399
|
+
: undefined;
|
|
400
|
+
if (!absoluteBaseUrl && !matchPath) {
|
|
401
|
+
return undefined;
|
|
402
|
+
}
|
|
403
|
+
return { absoluteBaseUrl, matchPath };
|
|
404
|
+
}
|
|
405
|
+
function normalizeTsconfigPaths(paths) {
|
|
406
|
+
if (!paths) {
|
|
407
|
+
return undefined;
|
|
408
|
+
}
|
|
409
|
+
const normalized = {};
|
|
410
|
+
for (const [pattern, replacements] of Object.entries(paths)) {
|
|
411
|
+
if (!replacements) {
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
const values = Array.isArray(replacements) ? replacements : [replacements];
|
|
415
|
+
if (values.length === 0) {
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
normalized[pattern] = values;
|
|
419
|
+
}
|
|
420
|
+
return Object.keys(normalized).length > 0 ? normalized : undefined;
|
|
421
|
+
}
|
|
422
|
+
function isNonRelativeSpecifier(specifier) {
|
|
423
|
+
if (!specifier) {
|
|
424
|
+
return false;
|
|
425
|
+
}
|
|
426
|
+
if (specifier.startsWith('.') || specifier.startsWith('/')) {
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
429
|
+
if (/^[a-z][\w+.-]*:/i.test(specifier)) {
|
|
430
|
+
return false;
|
|
431
|
+
}
|
|
432
|
+
return true;
|
|
433
|
+
}
|
|
376
434
|
function createProjectPeerResolver(rootDir) {
|
|
377
435
|
const resolver = getProjectRequire(rootDir);
|
|
378
436
|
return async (name) => {
|
|
@@ -415,7 +473,6 @@ export async function runGenerateTypesCli(argv = process.argv.slice(2)) {
|
|
|
415
473
|
rootDir: parsed.rootDir,
|
|
416
474
|
include: parsed.include,
|
|
417
475
|
outDir: parsed.outDir,
|
|
418
|
-
typesRoot: parsed.typesRoot,
|
|
419
476
|
stableNamespace: parsed.stableNamespace,
|
|
420
477
|
});
|
|
421
478
|
reportCliResult(result);
|
|
@@ -430,12 +487,11 @@ function parseCliArgs(argv) {
|
|
|
430
487
|
let rootDir = process.cwd();
|
|
431
488
|
const include = [];
|
|
432
489
|
let outDir;
|
|
433
|
-
let typesRoot;
|
|
434
490
|
let stableNamespace;
|
|
435
491
|
for (let i = 0; i < argv.length; i += 1) {
|
|
436
492
|
const arg = argv[i];
|
|
437
493
|
if (arg === '--help' || arg === '-h') {
|
|
438
|
-
return { rootDir, include, outDir,
|
|
494
|
+
return { rootDir, include, outDir, stableNamespace, help: true };
|
|
439
495
|
}
|
|
440
496
|
if (arg === '--root' || arg === '-r') {
|
|
441
497
|
const value = argv[++i];
|
|
@@ -461,14 +517,6 @@ function parseCliArgs(argv) {
|
|
|
461
517
|
outDir = value;
|
|
462
518
|
continue;
|
|
463
519
|
}
|
|
464
|
-
if (arg === '--types-root') {
|
|
465
|
-
const value = argv[++i];
|
|
466
|
-
if (!value) {
|
|
467
|
-
throw new Error('Missing value for --types-root');
|
|
468
|
-
}
|
|
469
|
-
typesRoot = value;
|
|
470
|
-
continue;
|
|
471
|
-
}
|
|
472
520
|
if (arg === '--stable-namespace') {
|
|
473
521
|
const value = argv[++i];
|
|
474
522
|
if (!value) {
|
|
@@ -482,7 +530,7 @@ function parseCliArgs(argv) {
|
|
|
482
530
|
}
|
|
483
531
|
include.push(arg);
|
|
484
532
|
}
|
|
485
|
-
return { rootDir, include, outDir,
|
|
533
|
+
return { rootDir, include, outDir, stableNamespace };
|
|
486
534
|
}
|
|
487
535
|
function printHelp() {
|
|
488
536
|
console.log(`Usage: knighted-css-generate-types [options]
|
|
@@ -490,32 +538,59 @@ function printHelp() {
|
|
|
490
538
|
Options:
|
|
491
539
|
-r, --root <path> Project root directory (default: cwd)
|
|
492
540
|
-i, --include <path> Additional directories/files to scan (repeatable)
|
|
493
|
-
--out-dir <path>
|
|
494
|
-
--types-root <path> Directory for generated @types entrypoint
|
|
541
|
+
--out-dir <path> Directory to store selector module manifest cache
|
|
495
542
|
--stable-namespace <name> Stable namespace prefix for generated selector maps
|
|
496
543
|
-h, --help Show this help message
|
|
497
544
|
`);
|
|
498
545
|
}
|
|
499
546
|
function reportCliResult(result) {
|
|
500
|
-
if (result.
|
|
501
|
-
console.log('[knighted-css]
|
|
547
|
+
if (result.selectorModulesWritten === 0 && result.selectorModulesRemoved === 0) {
|
|
548
|
+
console.log('[knighted-css] Selector modules are up to date.');
|
|
502
549
|
}
|
|
503
550
|
else {
|
|
504
|
-
console.log(`[knighted-css]
|
|
551
|
+
console.log(`[knighted-css] Selector modules updated: wrote ${result.selectorModulesWritten}, removed ${result.selectorModulesRemoved}.`);
|
|
505
552
|
}
|
|
506
|
-
console.log(`[knighted-css]
|
|
553
|
+
console.log(`[knighted-css] Manifest: ${result.manifestPath}`);
|
|
507
554
|
for (const warning of result.warnings) {
|
|
508
555
|
console.warn(`[knighted-css] ${warning}`);
|
|
509
556
|
}
|
|
510
557
|
}
|
|
558
|
+
function setCssWithMetaImplementation(impl) {
|
|
559
|
+
activeCssWithMeta = impl ?? cssWithMeta;
|
|
560
|
+
}
|
|
561
|
+
function setModuleTypeDetector(detector) {
|
|
562
|
+
moduleTypeDetector = detector ?? moduleType;
|
|
563
|
+
}
|
|
564
|
+
function setImportMetaUrlProvider(provider) {
|
|
565
|
+
importMetaUrlProvider = provider ?? getImportMetaUrl;
|
|
566
|
+
}
|
|
511
567
|
export const __generateTypesInternals = {
|
|
512
568
|
stripInlineLoader,
|
|
513
569
|
splitResourceAndQuery,
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
570
|
+
extractSelectorSourceSpecifier,
|
|
571
|
+
findSpecifierImports,
|
|
572
|
+
resolveImportPath,
|
|
573
|
+
resolvePackageRoot,
|
|
574
|
+
relativeToRoot,
|
|
575
|
+
collectCandidateFiles,
|
|
517
576
|
normalizeIncludeOptions,
|
|
577
|
+
normalizeTsconfigPaths,
|
|
578
|
+
setCssWithMetaImplementation,
|
|
579
|
+
setModuleTypeDetector,
|
|
580
|
+
setImportMetaUrlProvider,
|
|
581
|
+
isNonRelativeSpecifier,
|
|
582
|
+
createProjectPeerResolver,
|
|
583
|
+
getProjectRequire,
|
|
584
|
+
loadTsconfigResolutionContext,
|
|
585
|
+
resolveWithTsconfigPaths,
|
|
518
586
|
parseCliArgs,
|
|
519
587
|
printHelp,
|
|
520
588
|
reportCliResult,
|
|
589
|
+
buildSelectorModuleManifestKey,
|
|
590
|
+
buildSelectorModulePath,
|
|
591
|
+
formatSelectorModuleSource,
|
|
592
|
+
ensureSelectorModule,
|
|
593
|
+
removeStaleSelectorModules,
|
|
594
|
+
readManifest,
|
|
595
|
+
writeManifest,
|
|
521
596
|
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { KnightedCssCombinedModule } from './loader.js';
|
|
2
|
+
type KnightedCssCombinedExtras = Readonly<Record<string, unknown>>;
|
|
3
|
+
export declare function asKnightedCssCombinedModule<TModule, TExtras extends KnightedCssCombinedExtras = Record<never, never>>(module: unknown): KnightedCssCombinedModule<TModule, TExtras>;
|
|
4
|
+
export {};
|
package/dist/loader.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { LoaderDefinitionFunction, PitchLoaderDefinitionFunction } from 'webpack';
|
|
2
2
|
import { type CssOptions } from './css.js';
|
|
3
|
-
|
|
3
|
+
type KnightedCssCombinedExtras = Readonly<Record<string, unknown>>;
|
|
4
|
+
export type KnightedCssCombinedModule<TModule, TExtras extends KnightedCssCombinedExtras = Record<never, never>> = TModule & TExtras & {
|
|
4
5
|
knightedCss: string;
|
|
5
6
|
};
|
|
6
7
|
export interface KnightedCssVanillaOptions {
|
package/dist/loader.js
CHANGED
|
@@ -15,6 +15,7 @@ const loader = async function loader(source) {
|
|
|
15
15
|
namespace: resolvedNamespace,
|
|
16
16
|
resourcePath: this.resourcePath,
|
|
17
17
|
emitWarning: message => emitKnightedWarning(this, message),
|
|
18
|
+
target: 'js',
|
|
18
19
|
})
|
|
19
20
|
: undefined;
|
|
20
21
|
const injection = buildInjection(css, {
|
|
@@ -77,6 +78,7 @@ export const pitch = function pitch() {
|
|
|
77
78
|
namespace: resolvedNamespace,
|
|
78
79
|
resourcePath: this.resourcePath,
|
|
79
80
|
emitWarning: message => emitKnightedWarning(this, message),
|
|
81
|
+
target: 'js',
|
|
80
82
|
})
|
|
81
83
|
: undefined;
|
|
82
84
|
return createCombinedModule(request, css, {
|
package/dist/moduleGraph.js
CHANGED
|
@@ -191,7 +191,10 @@ function normalizeSpecifier(raw) {
|
|
|
191
191
|
if (!trimmed || trimmed.startsWith('\0')) {
|
|
192
192
|
return '';
|
|
193
193
|
}
|
|
194
|
-
const
|
|
194
|
+
const querySearchOffset = trimmed.startsWith('#') ? 1 : 0;
|
|
195
|
+
const remainder = trimmed.slice(querySearchOffset);
|
|
196
|
+
const queryMatchIndex = remainder.search(/[?#]/);
|
|
197
|
+
const queryIndex = queryMatchIndex === -1 ? -1 : querySearchOffset + queryMatchIndex;
|
|
195
198
|
const withoutQuery = queryIndex === -1 ? trimmed : trimmed.slice(0, queryIndex);
|
|
196
199
|
if (!withoutQuery) {
|
|
197
200
|
return '';
|
|
@@ -297,9 +300,7 @@ function createResolverFactory(cwd, extensions, scriptExtensions, graphOptions)
|
|
|
297
300
|
options.extensionAlias = extensionAlias;
|
|
298
301
|
}
|
|
299
302
|
const tsconfigOption = resolveResolverTsconfig(graphOptions?.tsConfig, cwd);
|
|
300
|
-
|
|
301
|
-
options.tsconfig = tsconfigOption;
|
|
302
|
-
}
|
|
303
|
+
options.tsconfig = tsconfigOption ?? 'auto';
|
|
303
304
|
return new ResolverFactory(options);
|
|
304
305
|
}
|
|
305
306
|
function buildExtensionAlias(scriptExtensions) {
|
|
@@ -5,9 +5,24 @@ export interface StableClassNameOptions extends StableSelectorOptions {
|
|
|
5
5
|
token?: string;
|
|
6
6
|
join?: (values: string[]) => string;
|
|
7
7
|
}
|
|
8
|
+
export interface MergeStableClassSingleInput extends StableSelectorOptions {
|
|
9
|
+
hashed: string | string[];
|
|
10
|
+
selector?: string;
|
|
11
|
+
token: string;
|
|
12
|
+
join?: (values: string[]) => string;
|
|
13
|
+
}
|
|
14
|
+
export interface MergeStableClassBatchInput<Hashed extends Record<string, string | string[]>, Selectors extends Record<string, string> | undefined = Record<string, string>> extends StableSelectorOptions {
|
|
15
|
+
hashed: Hashed;
|
|
16
|
+
selectors?: Selectors;
|
|
17
|
+
join?: (values: string[]) => string;
|
|
18
|
+
}
|
|
8
19
|
export declare function stableToken(token: string, options?: StableSelectorOptions): string;
|
|
9
20
|
export declare function stableClass(token: string, options?: StableSelectorOptions): string;
|
|
10
21
|
export declare function stableSelector(token: string, options?: StableSelectorOptions): string;
|
|
11
22
|
export declare function createStableClassFactory(options?: StableSelectorOptions): (token: string) => string;
|
|
12
23
|
export declare function stableClassName<T extends Record<string, string>>(styles: T, key: keyof T | string, options?: StableClassNameOptions): string;
|
|
13
24
|
export declare const stableClassFromModule: typeof stableClassName;
|
|
25
|
+
export declare function mergeStableClass(input: MergeStableClassSingleInput): string;
|
|
26
|
+
export declare function mergeStableClass<Hashed extends Record<string, string | string[]>>(input: MergeStableClassBatchInput<Hashed>): {
|
|
27
|
+
[Key in keyof Hashed]: string;
|
|
28
|
+
};
|
package/dist/stableSelectors.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const DEFAULT_NAMESPACE = 'knighted';
|
|
2
2
|
const defaultJoin = (values) => values.filter(Boolean).join(' ');
|
|
3
|
+
const toArray = (value) => Array.isArray(value) ? value : [value];
|
|
3
4
|
const normalizeToken = (token) => {
|
|
4
5
|
const sanitized = token
|
|
5
6
|
.trim()
|
|
@@ -34,3 +35,30 @@ export function stableClassName(styles, key, options) {
|
|
|
34
35
|
return join([hashed, stable]);
|
|
35
36
|
}
|
|
36
37
|
export const stableClassFromModule = stableClassName;
|
|
38
|
+
export function mergeStableClass(input) {
|
|
39
|
+
if ('token' in input) {
|
|
40
|
+
return mergeSingle(input);
|
|
41
|
+
}
|
|
42
|
+
return mergeBatch(input);
|
|
43
|
+
}
|
|
44
|
+
function mergeSingle(input) {
|
|
45
|
+
const join = input.join ?? defaultJoin;
|
|
46
|
+
const hashed = toArray(input.hashed);
|
|
47
|
+
const stable = input.selector?.trim().length
|
|
48
|
+
? input.selector
|
|
49
|
+
: stableClass(input.token, { namespace: input.namespace });
|
|
50
|
+
return join([...hashed, stable]);
|
|
51
|
+
}
|
|
52
|
+
function mergeBatch(input) {
|
|
53
|
+
const join = input.join ?? defaultJoin;
|
|
54
|
+
const output = {};
|
|
55
|
+
for (const key of Object.keys(input.hashed)) {
|
|
56
|
+
const hashedValue = input.hashed[key];
|
|
57
|
+
const selector = input.selectors?.[String(key)];
|
|
58
|
+
const stable = selector?.trim().length
|
|
59
|
+
? selector
|
|
60
|
+
: stableClass(String(key), { namespace: input.namespace });
|
|
61
|
+
output[key] = join([...toArray(hashedValue), stable]);
|
|
62
|
+
}
|
|
63
|
+
return output;
|
|
64
|
+
}
|