mirror of
https://github.com/nexu-io/open-design.git
synced 2026-09-20 06:15:06 +08:00
66 lines
2.6 KiB
TypeScript
66 lines
2.6 KiB
TypeScript
import { readFileSync } from 'node:fs';
|
|
import { dirname, join } from 'node:path';
|
|
|
|
const packageCssImports = new Map([
|
|
['@open-design/components/styles.css', join(process.cwd(), '../../packages/components/src/styles.css')],
|
|
]);
|
|
|
|
const CSS_IMPORT = /@import\s+(?:url\(([^)]+)\)|(['"])([^'"]+)\2);/g;
|
|
|
|
function resolveCssImport(fromFile: string, rawSpecifier: string): string | null {
|
|
const specifier = rawSpecifier.trim().replace(/^['"]|['"]$/g, '');
|
|
if (!specifier.startsWith('./') && !specifier.startsWith('../')) {
|
|
return packageCssImports.get(specifier) ?? null;
|
|
}
|
|
return join(dirname(fromFile), specifier);
|
|
}
|
|
|
|
/**
|
|
* Walk the `@import` graph in source order and return the stylesheet as
|
|
* consecutive text segments that split exactly at `@import` statements (so at
|
|
* file boundaries). Each unresolvable import contributes nothing; a file that
|
|
* was already visited contributes nothing the second time.
|
|
*/
|
|
function expandCssSegments(filePath: string, seen = new Set<string>()): string[] {
|
|
if (seen.has(filePath)) {
|
|
return [];
|
|
}
|
|
seen.add(filePath);
|
|
|
|
const css = readFileSync(filePath, 'utf8');
|
|
const segments: string[] = [];
|
|
let cursor = 0;
|
|
for (const match of css.matchAll(CSS_IMPORT)) {
|
|
segments.push(css.slice(cursor, match.index));
|
|
const imported = resolveCssImport(filePath, match[3] ?? match[1] ?? '');
|
|
if (imported != null) {
|
|
segments.push(...expandCssSegments(imported, seen));
|
|
}
|
|
cursor = match.index + match[0].length;
|
|
}
|
|
segments.push(css.slice(cursor));
|
|
return segments;
|
|
}
|
|
|
|
/**
|
|
* The global `index.css` cascade, still in real import order, split at file
|
|
* boundaries: `readExpandedIndexCssSegments().join('') === readExpandedIndexCss()`.
|
|
*
|
|
* Use this — one `<style>` per segment, appended in order — when a jsdom test
|
|
* needs the cascade *parsed* rather than grepped. Document-order sheets cascade
|
|
* exactly like one concatenated sheet, and jsdom builds a rule-for-rule
|
|
* identical CSSOM either way, but the parse cost is not the same: jsdom's CSS
|
|
* parser (css-tree) keeps its token buffers sized to the largest text it has
|
|
* ever parsed and zero-fills them on every later parse. jsdom re-parses every
|
|
* selector and declaration as its own small fragment, so one 1.7 MB sheet makes
|
|
* each of those fragments pay for 1.7 MB, and the parse alone eats seconds of a
|
|
* test's timeout under CI load. Per-file sheets cap that at the largest file.
|
|
*/
|
|
export function readExpandedIndexCssSegments(): string[] {
|
|
return expandCssSegments(join(process.cwd(), 'src/index.css'));
|
|
}
|
|
|
|
export function readExpandedIndexCss(): string {
|
|
return readExpandedIndexCssSegments().join('');
|
|
}
|