Skip to content

Commit 603247b

Browse files
committed
fix(tasty): improve debug tool
1 parent db690e6 commit 603247b

File tree

3 files changed

+183
-103
lines changed

3 files changed

+183
-103
lines changed

src/tasty/debug.ts

Lines changed: 136 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -58,21 +58,6 @@ interface CacheStatus {
5858
metrics: CacheMetrics | null;
5959
}
6060

61-
interface GlobalBreakdown {
62-
css: string; // prettified tasty global CSS
63-
totalRules: number;
64-
totalCSSSize: number;
65-
selectors: {
66-
elements: string[];
67-
classes: string[];
68-
ids: string[];
69-
pseudoClasses: string[];
70-
mediaQueries: string[];
71-
keyframes: string[];
72-
other: string[];
73-
};
74-
}
75-
7661
interface Definitions {
7762
properties: string[]; // defined via @property
7863
keyframes: Array<{ name: string; refCount: number }>;
@@ -96,17 +81,26 @@ interface Summary {
9681
// Tasty CSS sizes
9782
activeCSSSize: number;
9883
unusedCSSSize: number;
99-
totalCSSSize: number; // tasty-only: active + unused + tasty global
84+
globalCSSSize: number; // injectGlobal() CSS
85+
rawCSSSize: number; // createGlobalStyle() CSS
86+
keyframesCSSSize: number; // @keyframes CSS
87+
propertyCSSSize: number; // @property CSS
88+
totalCSSSize: number; // all tasty CSS (active + unused + global + raw + keyframes + property)
10089

10190
// Tasty CSS payloads
10291
activeCSS: string;
10392
unusedCSS: string;
104-
allCSS: string; // tasty-only CSS (active + unused + tasty global)
93+
globalCSS: string; // injectGlobal() CSS
94+
rawCSS: string; // createGlobalStyle() CSS
95+
keyframesCSS: string; // @keyframes CSS
96+
propertyCSS: string; // @property CSS
97+
allCSS: string; // all tasty CSS combined
10598

106-
// Tasty global (createGlobalStyle)
107-
globalCSS: string;
108-
globalCSSSize: number;
99+
// Rule counts
109100
globalRuleCount: number;
101+
rawRuleCount: number;
102+
keyframesRuleCount: number;
103+
propertyRuleCount: number;
110104

111105
// Page-level CSS (across all stylesheets, not only tasty) — shown when includePageCSS != false
112106
page?: {
@@ -584,86 +578,88 @@ export const tastyDebug = {
584578
injector.instance.resetMetrics({ root });
585579
},
586580

587-
// 5) Tasty global CSS and selector analysis
588-
global(opts?: {
589-
root?: Document | ShadowRoot;
590-
log?: boolean;
591-
}): GlobalBreakdown {
592-
const { root = document, log = false } = opts || {};
593-
const allCSS = injector.instance.getCssText({ root });
594-
const rules = extractCSSRules(allCSS);
595-
596-
const globalRules = rules.filter((rule) => {
597-
const selectors = rule.selector.split(',').map((s) => s.trim());
598-
return !selectors.every((selector) => {
599-
const cleanSelector = selector.replace(/[.#:\s>+~[\]()]/g, ' ');
600-
const parts = cleanSelector.split(/\s+/).filter(Boolean);
601-
return parts.length > 0 && parts.every((part) => /^t\d+$/.test(part));
602-
});
603-
});
581+
// 5) Get CSS for specific global types
582+
getGlobalTypeCSS(
583+
type: 'global' | 'raw' | 'keyframes' | 'property',
584+
opts?: { root?: Document | ShadowRoot },
585+
): { css: string; ruleCount: number; size: number } {
586+
const { root = document } = opts || {};
587+
const registry = (injector.instance as any)['sheetManager']?.getRegistry(
588+
root,
589+
);
604590

605-
// Calculate raw CSS size for consistent size calculations
606-
const rawGlobalCSS = globalRules
607-
.map((rule) => `${rule.selector} { ${rule.declarations} }`)
608-
.join('\n');
609-
610-
// Use prettified CSS for display
611-
const css = prettifyCSS(rawGlobalCSS);
612-
613-
// Analyze selectors
614-
const selectors = {
615-
elements: [] as string[],
616-
classes: [] as string[],
617-
ids: [] as string[],
618-
pseudoClasses: [] as string[],
619-
mediaQueries: [] as string[],
620-
keyframes: [] as string[],
621-
other: [] as string[],
622-
};
591+
if (!registry) {
592+
return { css: '', ruleCount: 0, size: 0 };
593+
}
623594

624-
globalRules.forEach((rule) => {
625-
const selector = rule.selector;
626-
if (selector.startsWith('@media')) {
627-
selectors.mediaQueries.push(selector);
628-
} else if (selector.startsWith('@keyframes')) {
629-
selectors.keyframes.push(selector);
630-
} else if (
631-
selector.includes('.') &&
632-
!selector.includes('#') &&
633-
!selector.includes(':')
634-
) {
635-
selectors.classes.push(selector);
636-
} else if (
637-
selector.includes('#') &&
638-
!selector.includes('.') &&
639-
!selector.includes(':')
640-
) {
641-
selectors.ids.push(selector);
642-
} else if (selector.includes(':')) {
643-
selectors.pseudoClasses.push(selector);
644-
} else if (/^[a-zA-Z][a-zA-Z0-9]*$/.test(selector.trim())) {
645-
selectors.elements.push(selector);
646-
} else {
647-
selectors.other.push(selector);
595+
const cssChunks: string[] = [];
596+
let ruleCount = 0;
597+
598+
if (type === 'keyframes') {
599+
// Handle keyframes separately - they're stored in keyframesCache
600+
for (const [, entry] of registry.keyframesCache) {
601+
const info = entry.info;
602+
const sheet = registry.sheets[info.sheetIndex];
603+
const styleSheet = sheet?.sheet?.sheet;
604+
605+
if (styleSheet && info.ruleIndex < styleSheet.cssRules.length) {
606+
const rule = styleSheet.cssRules[info.ruleIndex] as
607+
| CSSRule
608+
| undefined;
609+
if (rule) {
610+
cssChunks.push(rule.cssText);
611+
ruleCount++;
612+
}
613+
} else if (info.cssText) {
614+
cssChunks.push(info.cssText);
615+
ruleCount++;
616+
}
648617
}
649-
});
650-
651-
const result = {
652-
css,
653-
totalRules: globalRules.length,
654-
totalCSSSize: rawGlobalCSS.length, // Use raw CSS length for consistent size calculations
655-
selectors,
656-
};
618+
} else {
619+
// Handle other global types stored in globalRules
620+
const prefix =
621+
type === 'global' ? 'global:' : type === 'raw' ? 'raw:' : 'property:';
622+
623+
for (const [key, ruleInfo] of registry.globalRules) {
624+
if (key.startsWith(prefix)) {
625+
const sheet = registry.sheets[ruleInfo.sheetIndex];
626+
const styleSheet = sheet?.sheet?.sheet;
627+
if (styleSheet) {
628+
const start = Math.max(0, ruleInfo.ruleIndex);
629+
const end = Math.min(
630+
styleSheet.cssRules.length - 1,
631+
(ruleInfo.endRuleIndex as number) ?? ruleInfo.ruleIndex,
632+
);
657633

658-
if (log) {
659-
console.group(`🌍 Global CSS (${result.totalRules} rules)`);
660-
console.log(`📊 Size: ${result.totalCSSSize} characters`);
661-
console.log('🎯 Selector breakdown:', result.selectors);
662-
console.log('🎨 CSS:\n' + result.css);
663-
console.groupEnd();
634+
if (
635+
start >= 0 &&
636+
end >= start &&
637+
start < styleSheet.cssRules.length
638+
) {
639+
for (let i = start; i <= end; i++) {
640+
const rule = styleSheet.cssRules[i] as CSSRule | undefined;
641+
if (rule) {
642+
cssChunks.push(rule.cssText);
643+
ruleCount++;
644+
}
645+
}
646+
}
647+
} else if (ruleInfo.cssText && ruleInfo.cssText.length) {
648+
// Fallback in environments without CSSOM access
649+
cssChunks.push(...ruleInfo.cssText);
650+
ruleCount += ruleInfo.cssText.length;
651+
}
652+
}
653+
}
664654
}
665655

666-
return result;
656+
const rawCSS = cssChunks.join('\n');
657+
658+
return {
659+
css: prettifyCSS(rawCSS),
660+
ruleCount,
661+
size: rawCSS.length,
662+
};
667663
},
668664

669665
// 6) Defined @property and keyframes
@@ -729,14 +725,19 @@ export const tastyDebug = {
729725
summary(opts?: SummaryOptions): Summary {
730726
const { root = document, log = false, includePageCSS = false } = opts || {};
731727
const cacheStatus = this.cache({ root });
732-
const globalBreakdown = this.global({ root });
733728
const definitions = this.defs({ root });
734729
const metrics = this.metrics({ root });
735730

736731
const activeCSS = this.css('active', { root, prettify: false });
737732
const unusedCSS = this.css('unused', { root, prettify: false });
738733
const allCSS = this.css('all', { root, prettify: false });
739734

735+
// Get CSS for each global type separately
736+
const globalData = this.getGlobalTypeCSS('global', { root });
737+
const rawData = this.getGlobalTypeCSS('raw', { root });
738+
const keyframesData = this.getGlobalTypeCSS('keyframes', { root });
739+
const propertyData = this.getGlobalTypeCSS('property', { root });
740+
740741
// Build cleanup summary from metrics
741742
const cleanupSummary = {
742743
enabled: !!metrics,
@@ -793,13 +794,22 @@ export const tastyDebug = {
793794
totalStyledClasses: cacheStatus.classes.all,
794795
activeCSSSize: activeCSS.length,
795796
unusedCSSSize: unusedCSS.length,
796-
totalCSSSize: allCSS.length, // This includes active + unused + global CSS
797+
globalCSSSize: globalData.size,
798+
rawCSSSize: rawData.size,
799+
keyframesCSSSize: keyframesData.size,
800+
propertyCSSSize: propertyData.size,
801+
totalCSSSize: allCSS.length,
797802
activeCSS: prettifyCSS(activeCSS),
798803
unusedCSS: prettifyCSS(unusedCSS),
804+
globalCSS: globalData.css,
805+
rawCSS: rawData.css,
806+
keyframesCSS: keyframesData.css,
807+
propertyCSS: propertyData.css,
799808
allCSS: prettifyCSS(allCSS),
800-
globalCSS: globalBreakdown.css,
801-
globalCSSSize: globalBreakdown.totalCSSSize,
802-
globalRuleCount: globalBreakdown.totalRules,
809+
globalRuleCount: globalData.ruleCount,
810+
rawRuleCount: rawData.ruleCount,
811+
keyframesRuleCount: keyframesData.ruleCount,
812+
propertyRuleCount: propertyData.ruleCount,
803813
page,
804814
metrics,
805815
definedProperties: definitions.properties,
@@ -825,9 +835,34 @@ export const tastyDebug = {
825835
console.log(` • Active CSS: ${summary.activeCSSSize} characters`);
826836
console.log(` • Unused CSS: ${summary.unusedCSSSize} characters`);
827837
console.log(
828-
` • Global CSS: ${summary.globalCSSSize} characters (${summary.globalRuleCount} rules)`,
838+
` • Global CSS (injectGlobal): ${summary.globalCSSSize} characters (${summary.globalRuleCount} rules)`,
839+
);
840+
console.log(
841+
` • Raw CSS (createGlobalStyle): ${summary.rawCSSSize} characters (${summary.rawRuleCount} rules)`,
842+
);
843+
console.log(
844+
` • Keyframes CSS: ${summary.keyframesCSSSize} characters (${summary.keyframesRuleCount} rules)`,
829845
);
830-
console.log(` • Total CSS: ${summary.totalCSSSize} characters`);
846+
console.log(
847+
` • Property CSS: ${summary.propertyCSSSize} characters (${summary.propertyRuleCount} rules)`,
848+
);
849+
850+
// Show breakdown calculation
851+
const calculatedTotal =
852+
summary.activeCSSSize +
853+
summary.unusedCSSSize +
854+
summary.globalCSSSize +
855+
summary.rawCSSSize +
856+
summary.keyframesCSSSize +
857+
summary.propertyCSSSize;
858+
console.log(` • Calculated Total: ${calculatedTotal} characters`);
859+
console.log(` • Actual Total: ${summary.totalCSSSize} characters`);
860+
861+
if (Math.abs(calculatedTotal - summary.totalCSSSize) > 100) {
862+
console.warn(
863+
` ⚠️ Size mismatch: ${Math.abs(calculatedTotal - summary.totalCSSSize)} characters difference`,
864+
);
865+
}
831866

832867
if (page) {
833868
console.log(`📄 Page CSS:`);

src/tasty/injector/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,16 @@ export function injectGlobal(
130130
return getGlobalInjector().injectGlobal(rules, options);
131131
}
132132

133+
/**
134+
* Internal method for createGlobalStyle - not exported publicly
135+
*/
136+
function injectCreateGlobalStyle(
137+
rules: StyleResult[],
138+
options?: { root?: Document | ShadowRoot },
139+
): GlobalInjectResult {
140+
return getGlobalInjector().injectCreateGlobalStyle(rules, options);
141+
}
142+
133143
/**
134144
* Inject keyframes and return object with toString() and dispose()
135145
*/

src/tasty/injector/injector.ts

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,41 @@ export class StyleInjector {
258258
};
259259
}
260260

261+
/**
262+
* Inject styles from createGlobalStyle (separate from injectGlobal for tracking)
263+
*/
264+
injectCreateGlobalStyle(
265+
rules: StyleResult[],
266+
options?: { root?: Document | ShadowRoot },
267+
): GlobalInjectResult {
268+
const root = options?.root || document;
269+
const registry = this.sheetManager.getRegistry(root);
270+
271+
if (!rules || rules.length === 0) {
272+
return { dispose: () => {} };
273+
}
274+
275+
// Use a different prefix to distinguish from injectGlobal
276+
const key = `raw:${this.globalRuleCounter++}`;
277+
278+
const info = this.sheetManager.insertGlobalRule(
279+
registry,
280+
rules as unknown as StyleRule[],
281+
key,
282+
root,
283+
);
284+
285+
if (registry.metrics) {
286+
registry.metrics.totalInsertions++;
287+
}
288+
289+
return {
290+
dispose: () => {
291+
if (info) this.sheetManager.deleteGlobalRule(registry, key);
292+
},
293+
};
294+
}
295+
261296
/**
262297
* Dispose of a className
263298
*/
@@ -567,8 +602,8 @@ export class StyleInjector {
567602
const css = this.interpolateTemplate();
568603
if (css.trim()) {
569604
const styleResults = this.parseCSSToStyleResults(css);
570-
// Bind the global inject method to the outer injector instance
571-
const result = injector.injectGlobal(styleResults as any, {
605+
// Use a special method for createGlobalStyle to distinguish from injectGlobal
606+
const result = injector.injectCreateGlobalStyle(styleResults as any, {
572607
root: this.props.root,
573608
});
574609
this.disposeFunction = result.dispose;

0 commit comments

Comments
 (0)