@barocss/browser 0.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.
@@ -0,0 +1,80 @@
1
+ import { IncrementalParser } from '@barocss/kit';
2
+ import { BrowserRuntime } from './browser-runtime';
3
+ /**
4
+ * Change detection system for DOM mutations
5
+ *
6
+ * ⚠️ BROWSER-ONLY: This class is designed for browser environments only.
7
+ * It uses MutationObserver and DOM APIs that are not available in Node.js.
8
+ *
9
+ * This class monitors DOM changes and automatically processes new CSS classes
10
+ * that are added to elements. It uses MutationObserver to detect:
11
+ * - Class attribute changes on existing elements
12
+ * - New elements being added to the DOM
13
+ * - Changes in child elements
14
+ *
15
+ * For server-side usage, use IncrementalParser directly with processClassesSync()
16
+ * or processClasses() methods.
17
+ */
18
+ export declare class ChangeDetector {
19
+ /** MutationObserver instance for DOM change detection */
20
+ private observer;
21
+ /** Reference to IncrementalParser for class processing */
22
+ private incrementalParser;
23
+ /** Reference to BrowserRuntime for CSS injection (optional) */
24
+ private BrowserRuntime?;
25
+ /** Set of elements that have already been processed to avoid duplicates */
26
+ private processedElements;
27
+ /**
28
+ * Create a new ChangeDetector instance
29
+ *
30
+ * @param incrementalParser - IncrementalParser instance for class processing
31
+ * @param BrowserRuntime - Optional BrowserRuntime instance for CSS injection
32
+ */
33
+ constructor(incrementalParser: IncrementalParser, BrowserRuntime?: BrowserRuntime);
34
+ /**
35
+ * Starts observing DOM changes for new CSS classes
36
+ *
37
+ * This method sets up a MutationObserver that monitors:
38
+ * - Attribute changes (specifically class attribute modifications)
39
+ * - Child list changes (new elements being added)
40
+ * - Subtree changes (changes in descendant elements)
41
+ *
42
+ * The observer automatically processes any new classes it discovers
43
+ * by adding them to the IncrementalParser's pending queue.
44
+ *
45
+ * @param root - The root element to observe (defaults to document.body)
46
+ * @param options - Configuration options including initial scan and onReady callback
47
+ * @returns The MutationObserver instance for external control
48
+ */
49
+ observe(root?: HTMLElement, options?: {
50
+ scan?: boolean;
51
+ onReady?: () => void;
52
+ }): MutationObserver;
53
+ /**
54
+ * Scan existing classes in the DOM and process them
55
+ */
56
+ private scanExistingClasses;
57
+ /**
58
+ * Processes an individual element and extracts new classes
59
+ *
60
+ * This method is called for each element discovered during DOM mutations.
61
+ * It:
62
+ * - Checks if the element has already been processed
63
+ * - Extracts all class names from the element's className
64
+ * - Filters out already processed classes
65
+ * - Adds new classes to the collection for batch processing
66
+ * - Marks the element as processed to avoid duplicates
67
+ *
68
+ * @param element - The HTML element to process
69
+ * @param newClasses - Set to collect newly discovered class names
70
+ */
71
+ private processElement;
72
+ /**
73
+ * Stops observing DOM changes and cleans up resources
74
+ *
75
+ * This method disconnects the MutationObserver and clears the
76
+ * observer reference to prevent memory leaks and allow the
77
+ * ChangeDetector to be properly garbage collected.
78
+ */
79
+ disconnect(): void;
80
+ }
@@ -0,0 +1,5 @@
1
+ export * from './browser-runtime';
2
+ export * from './change-detector';
3
+ export * from './style-partition-manager';
4
+ export * from './utils';
5
+ export * from './baro-boot';
@@ -0,0 +1,425 @@
1
+ import { parseResultCache as d, createContext as y, IncrementalParser as g, astCache as f } from "@barocss/kit";
2
+ class p {
3
+ constructor(t, e = 50, s = "barocss-style-partition-") {
4
+ this.partitions = [], this.categoryPartitions = /* @__PURE__ */ new Map(), this.partitionCounter = 0, this.maxRulesPerPartition = 50, this.classToPartitionMap = /* @__PURE__ */ new Map(), this.classToCategoryPartitionMap = /* @__PURE__ */ new Map(), this.styleIdPrefix = "barocss-style-partition-", this.insertionPoint = t, this.maxRulesPerPartition = e, this.styleIdPrefix = s, this.initializeDefaultPartition();
5
+ }
6
+ initializeDefaultPartition() {
7
+ this.createNewPartition();
8
+ }
9
+ createNewCategoryPartition(t) {
10
+ const e = {
11
+ id: this.styleIdPrefix + `-${t}`,
12
+ styles: [],
13
+ styleElement: document.createElement("style")
14
+ };
15
+ return e.styleElement.id = e.id, e.styleElement.setAttribute("data-barocss", "partition"), e.styleElement.setAttribute("data-category", t), this.insertionPoint.appendChild(e.styleElement), this.categoryPartitions.set(t, e), e;
16
+ }
17
+ createNewPartition() {
18
+ const t = {
19
+ id: this.styleIdPrefix + `-${this.partitionCounter++}`,
20
+ styles: [],
21
+ styleElement: document.createElement("style")
22
+ };
23
+ return this.partitions.push(t), t.styleElement.id = t.id, t.styleElement.setAttribute("data-barocss", "partition"), t.styleElement.setAttribute(
24
+ "data-partition-index",
25
+ this.partitionCounter.toString()
26
+ ), this.insertionPoint.appendChild(t.styleElement), t;
27
+ }
28
+ hasRule(t) {
29
+ return this.classToPartitionMap.has(t);
30
+ }
31
+ hasCategoryRule(t, e) {
32
+ return this.classToCategoryPartitionMap.get(t) === e;
33
+ }
34
+ setRuleCache(t, e) {
35
+ this.classToPartitionMap.set(t, e);
36
+ }
37
+ setCategoryRuleCache(t, e) {
38
+ this.classToCategoryPartitionMap.set(t, e);
39
+ }
40
+ get currentPartition() {
41
+ return this.partitions[this.partitions.length - 1];
42
+ }
43
+ getCategoryPartition(t) {
44
+ return this.categoryPartitions.get(t);
45
+ }
46
+ /**
47
+ * Escape CSS rule text
48
+ * - Properly escape special characters
49
+ * - Prevent CSS syntax errors
50
+ */
51
+ escapeCssRule(t) {
52
+ return t.replace(/\\\//g, "\\/");
53
+ }
54
+ addRule(t) {
55
+ if (this.hasRule(t))
56
+ return !1;
57
+ this.currentPartition.styles.length >= this.maxRulesPerPartition && this.createNewPartition();
58
+ const e = this.currentPartition, s = this.partitions.length - 1;
59
+ try {
60
+ const i = e.styleElement.sheet;
61
+ return i ? i.insertRule(this.escapeCssRule(t), i.cssRules.length) : e.styleElement.textContent += t + `
62
+ `, this.setRuleCache(t, s), e.styles.push(t), !0;
63
+ } catch (i) {
64
+ return console.warn(
65
+ `[StylePartitionManager] Failed to insert rule: ${t}`,
66
+ i
67
+ ), !1;
68
+ }
69
+ }
70
+ addCategoryRule(t, e) {
71
+ if (this.hasCategoryRule(t, e))
72
+ return !1;
73
+ let s = this.getCategoryPartition(e);
74
+ s || (s = this.createNewCategoryPartition(e));
75
+ try {
76
+ const i = s.styleElement.sheet;
77
+ i ? i.insertRule(this.escapeCssRule(t), i.cssRules.length) : s.styleElement.textContent += t + `
78
+ `;
79
+ } catch (i) {
80
+ return console.warn(
81
+ `[StylePartitionManager] Failed to insert rule in category: ${e} ${t}`,
82
+ i
83
+ ), !1;
84
+ }
85
+ return this.setCategoryRuleCache(t, e), s.styles.push(t), !0;
86
+ }
87
+ addRootRules(t) {
88
+ let e = this.getCategoryPartition("root");
89
+ e || (e = this.createNewCategoryPartition("root"));
90
+ try {
91
+ const s = e.styleElement.sheet;
92
+ for (const i of t)
93
+ s ? s.insertRule(this.escapeCssRule(i), s.cssRules.length) : e.styleElement.textContent += i + `
94
+ `;
95
+ } catch (s) {
96
+ return console.warn(
97
+ `[StylePartitionManager] Failed to insert rule in category: root ${t.join(`
98
+ `)}`,
99
+ s
100
+ ), { success: 0, failed: t.length };
101
+ }
102
+ return { success: t.length, failed: 0 };
103
+ }
104
+ addRules(t) {
105
+ let e = 0, s = 0;
106
+ for (const i of t) {
107
+ const r = d.get(i.cls)?.utility?.category;
108
+ if (r)
109
+ for (const o of i.cssList)
110
+ this.addCategoryRule(o, r);
111
+ else
112
+ for (const o of i.cssList)
113
+ this.addRule(o) ? e++ : s++;
114
+ }
115
+ return { success: e, failed: s };
116
+ }
117
+ /**
118
+ * 특정 규칙이 어느 파티션에 있는지 찾기
119
+ */
120
+ findRulePartition(t) {
121
+ const e = this.classToPartitionMap.get(t);
122
+ if (e !== void 0 && this.partitions[e])
123
+ return this.partitions[e];
124
+ const s = this.classToCategoryPartitionMap.get(t);
125
+ return s !== void 0 && this.categoryPartitions.get(s) || null;
126
+ }
127
+ updateRuleContent(t, e) {
128
+ const s = this.getCategoryPartition(t);
129
+ if (s)
130
+ s.styleElement.textContent = e;
131
+ else {
132
+ const i = this.createNewCategoryPartition(t);
133
+ console.log(`[StylePartitionManager] Created new partition for category: ${t}`), i.styleElement.textContent = e;
134
+ }
135
+ }
136
+ /**
137
+ * 모든 파티션 정리
138
+ */
139
+ cleanup() {
140
+ this.partitions.forEach((t) => {
141
+ t.styleElement.parentNode && t.styleElement.parentNode.removeChild(t.styleElement);
142
+ }), this.categoryPartitions.forEach((t) => {
143
+ t.styleElement.parentNode && t.styleElement.parentNode.removeChild(t.styleElement);
144
+ }), this.partitions = [], this.partitionCounter = 0, this.classToPartitionMap.clear(), this.classToCategoryPartitionMap.clear();
145
+ }
146
+ }
147
+ function C(a) {
148
+ return a ? a instanceof SVGAnimatedString ? a.baseVal.toString() : a.toString() : "";
149
+ }
150
+ function c(a) {
151
+ return a ? C(a).split(/\s+/).filter(Boolean) : [];
152
+ }
153
+ class P {
154
+ /**
155
+ * Create a new ChangeDetector instance
156
+ *
157
+ * @param incrementalParser - IncrementalParser instance for class processing
158
+ * @param BrowserRuntime - Optional BrowserRuntime instance for CSS injection
159
+ */
160
+ constructor(t, e) {
161
+ this.observer = null, this.processedElements = /* @__PURE__ */ new WeakSet(), this.incrementalParser = t, this.BrowserRuntime = e;
162
+ }
163
+ /**
164
+ * Starts observing DOM changes for new CSS classes
165
+ *
166
+ * This method sets up a MutationObserver that monitors:
167
+ * - Attribute changes (specifically class attribute modifications)
168
+ * - Child list changes (new elements being added)
169
+ * - Subtree changes (changes in descendant elements)
170
+ *
171
+ * The observer automatically processes any new classes it discovers
172
+ * by adding them to the IncrementalParser's pending queue.
173
+ *
174
+ * @param root - The root element to observe (defaults to document.body)
175
+ * @param options - Configuration options including initial scan and onReady callback
176
+ * @returns The MutationObserver instance for external control
177
+ */
178
+ observe(t = document.body, e) {
179
+ return typeof window > "u" ? new MutationObserver(() => {
180
+ }) : (this.observer && this.observer.disconnect(), this.observer = new MutationObserver((s) => {
181
+ const i = /* @__PURE__ */ new Set();
182
+ if (s.forEach((n) => {
183
+ if (n.type === "attributes" && n.attributeName === "class") {
184
+ const r = n.target;
185
+ r.className && c(r.className).forEach((l) => {
186
+ this.incrementalParser.isProcessed(l) || i.add(l);
187
+ });
188
+ }
189
+ n.type === "childList" && n.addedNodes.forEach((r) => {
190
+ r instanceof HTMLElement && (this.processElement(r, i), r.querySelectorAll("[class]").forEach((o) => {
191
+ this.processElement(o, i);
192
+ }));
193
+ });
194
+ }), i.size > 0) {
195
+ const n = Array.from(i), r = this.incrementalParser.processClasses(n);
196
+ this.BrowserRuntime?.applyParseResults(r);
197
+ }
198
+ }), this.observer.observe(t, {
199
+ attributes: !0,
200
+ subtree: !0,
201
+ attributeFilter: ["class"],
202
+ childList: !0
203
+ }), e?.scan && this.scanExistingClasses(t, e), this.observer);
204
+ }
205
+ /**
206
+ * Scan existing classes in the DOM and process them
207
+ */
208
+ scanExistingClasses(t, e) {
209
+ const s = /* @__PURE__ */ new Set();
210
+ t.className && c(t.className).forEach((r) => {
211
+ this.incrementalParser.isProcessed(r) || s.add(r);
212
+ });
213
+ const i = t.querySelectorAll("[class]");
214
+ for (const n of i)
215
+ if (n.className) {
216
+ const r = c(n.className);
217
+ for (const o of r)
218
+ this.incrementalParser.isProcessed(o) || s.add(o);
219
+ }
220
+ if (s.size > 0) {
221
+ const n = Array.from(s), r = this.incrementalParser.processClasses(n), o = r.filter((h) => d.get(h.cls)?.utility?.category === "layout"), l = r.filter((h) => d.get(h.cls)?.utility?.category !== "layout");
222
+ this.BrowserRuntime?.applyParseResults(o), e?.onReady?.(), this.BrowserRuntime?.applyParseResults(l);
223
+ }
224
+ }
225
+ /**
226
+ * Processes an individual element and extracts new classes
227
+ *
228
+ * This method is called for each element discovered during DOM mutations.
229
+ * It:
230
+ * - Checks if the element has already been processed
231
+ * - Extracts all class names from the element's className
232
+ * - Filters out already processed classes
233
+ * - Adds new classes to the collection for batch processing
234
+ * - Marks the element as processed to avoid duplicates
235
+ *
236
+ * @param element - The HTML element to process
237
+ * @param newClasses - Set to collect newly discovered class names
238
+ */
239
+ processElement(t, e) {
240
+ this.processedElements.has(t) || (t.className && c(t.className).forEach((i) => {
241
+ this.incrementalParser.isProcessed(i) || e.add(i);
242
+ }), this.processedElements.add(t));
243
+ }
244
+ /**
245
+ * Stops observing DOM changes and cleans up resources
246
+ *
247
+ * This method disconnects the MutationObserver and clears the
248
+ * observer reference to prevent memory leaks and allow the
249
+ * ChangeDetector to be properly garbage collected.
250
+ */
251
+ disconnect() {
252
+ this.observer && (this.observer.disconnect(), this.observer = null);
253
+ }
254
+ }
255
+ class m {
256
+ constructor(t = {}) {
257
+ this.cache = /* @__PURE__ */ new Map(), this.rootCache = /* @__PURE__ */ new Set(), this.isDestroyed = !1;
258
+ const e = {};
259
+ this.options = {
260
+ config: t.config || e,
261
+ styleId: t.styleId || "barocss-runtime",
262
+ insertionPoint: t.insertionPoint || "head",
263
+ maxRulesPerPartition: t.maxRulesPerPartition || 50
264
+ }, this.context = y(this.options.config), this.incrementalParser = new g(this.context), this.changeDetector = new P(this.incrementalParser, this), this.stylePartitionManager = new p(this.getInsertionPoint(), this.options.maxRulesPerPartition, `${this.options.styleId}-partition`), this.init();
265
+ }
266
+ // Debugging and logging helpers
267
+ /**
268
+ * Add debug logs (by level)
269
+ */
270
+ debugLog(t, e, s) {
271
+ (console[t] || console.log)(`[BrowserRuntime:${t.toUpperCase()}] ${e}`, s || "");
272
+ }
273
+ init() {
274
+ console.log("[BrowserRuntime] init"), this.injectPreflightCSS(), this.ensureCssVars();
275
+ }
276
+ injectPreflightCSS() {
277
+ if (this.options.config.preflight) {
278
+ const t = this.context.getPreflightCSS(this.options.config.preflight);
279
+ this.stylePartitionManager.updateRuleContent("preflight", t);
280
+ }
281
+ }
282
+ ensureCssVars() {
283
+ if (this.isDestroyed) return;
284
+ const t = this.context.themeToCssVars ? this.context.themeToCssVars() : ":root { /* CSS Variables will be generated here */ }";
285
+ this.stylePartitionManager.updateRuleContent("css-vars", t);
286
+ }
287
+ getInsertionPoint() {
288
+ if (this.options.insertionPoint instanceof HTMLElement)
289
+ return this.options.insertionPoint;
290
+ switch (this.options.insertionPoint) {
291
+ case "body":
292
+ return document.body || document.head;
293
+ case "head":
294
+ default:
295
+ return document.head;
296
+ }
297
+ }
298
+ /**
299
+ * Dynamically add one or more class names and generate/insert CSS
300
+ */
301
+ addClass(t) {
302
+ if (this.isDestroyed) return;
303
+ const e = this.normalizeClasses(t);
304
+ this.processClasses(e);
305
+ }
306
+ /**
307
+ * Process classes
308
+ */
309
+ processClasses(t) {
310
+ this.processClassesIncremental(t);
311
+ }
312
+ /**
313
+ * Process classes using incremental parsing
314
+ */
315
+ processClassesIncremental(t) {
316
+ const e = typeof window < "u", s = this.incrementalParser.processClasses(t);
317
+ console.log("[BrowserRuntime] results", s, ...t), this.applyParseResults(s, { isBrowser: e });
318
+ }
319
+ /**
320
+ * Public method to apply parser results, update internal caches, and inject CSS
321
+ */
322
+ applyParseResults(t, e) {
323
+ const s = [], i = [];
324
+ for (const n of t)
325
+ if (n.css && Array.isArray(n.cssList) && s.push(n), n.rootCss && Array.isArray(n.rootCssList))
326
+ for (const r of n.rootCssList)
327
+ this.rootCache.has(r) || (this.rootCache.add(r), i.push(r));
328
+ i.length > 0 && this.stylePartitionManager.addRootRules(i.filter(Boolean)), s.length > 0 && this.stylePartitionManager.addRules(s), this.debugLog("info", `Applied ${t.length} parser results`, {
329
+ cssRuleCount: s.length,
330
+ rootCssCount: i.length
331
+ });
332
+ }
333
+ /**
334
+ * MutationObserver instance method to automatically call addClass when class attributes change in DOM
335
+ */
336
+ observe(t = document.body, e) {
337
+ return this.changeDetector.observe(t, e);
338
+ }
339
+ normalizeClasses(t) {
340
+ return Array.isArray(t) ? t.flatMap((e) => e.split(/\s+/)) : t.split(/\s+/);
341
+ }
342
+ has(t) {
343
+ return this.cache.has(t);
344
+ }
345
+ getCss(t) {
346
+ return this.cache.get(t)?.cssList.join(`
347
+ `);
348
+ }
349
+ getAllCss() {
350
+ return Array.from(this.cache.values()).flatMap((e) => e.cssList).join(`
351
+ `);
352
+ }
353
+ getClasses() {
354
+ return Array.from(this.cache.keys());
355
+ }
356
+ /**
357
+ * Get comprehensive cache statistics
358
+ */
359
+ getCacheStats() {
360
+ return {
361
+ runtime: {
362
+ cachedClasses: this.cache.size,
363
+ rootCacheSize: this.rootCache.size
364
+ },
365
+ ast: f.getStats(),
366
+ incremental: this.incrementalParser.getStats()
367
+ };
368
+ }
369
+ /**
370
+ * Clear all caches (useful for debugging or memory management)
371
+ */
372
+ clearCaches() {
373
+ this.cache.clear(), this.rootCache.clear(), f.clear(), this.incrementalParser.clearProcessed(), this.stylePartitionManager.cleanup();
374
+ }
375
+ reset() {
376
+ this.cache.clear(), this.rootCache.clear(), this.stylePartitionManager.cleanup();
377
+ }
378
+ updateConfig(t) {
379
+ this.options.config = t, this.context = y(t);
380
+ const e = Array.from(this.cache.keys());
381
+ this.reset(), e.length > 0 && this.addClass(e);
382
+ }
383
+ removeClass(t) {
384
+ const e = this.normalizeClasses(t);
385
+ for (const s of e)
386
+ this.cache.delete(s);
387
+ }
388
+ destroy() {
389
+ this.stylePartitionManager.cleanup(), this.cache.clear(), this.rootCache.clear(), this.isDestroyed = !0;
390
+ }
391
+ getStats() {
392
+ return {
393
+ cachedClasses: this.cache.size,
394
+ styleElementId: this.options.styleId,
395
+ isDestroyed: this.isDestroyed,
396
+ config: this.options.config,
397
+ cacheStats: this.getCacheStats()
398
+ };
399
+ }
400
+ }
401
+ let u = null;
402
+ function R(a) {
403
+ return u || (u = new m(a)), u;
404
+ }
405
+ function b({ loadingClassName: a = "baro-boot", ...t } = {}) {
406
+ const e = `${a}-doing`, s = `${a}-done`;
407
+ try {
408
+ document.body.classList.add(e), R(t).observe(document.body, { scan: !0, onReady: () => {
409
+ document.body.classList.remove(e), document.body.classList.add(s);
410
+ } });
411
+ } catch (i) {
412
+ console.error("BaroCSS boot failed:", i);
413
+ }
414
+ }
415
+ const w = b;
416
+ export {
417
+ m as BrowserRuntime,
418
+ P as ChangeDetector,
419
+ p as StylePartitionManager,
420
+ b as baroBoot,
421
+ w as baroStart,
422
+ R as getRuntime,
423
+ C as normalizeClassName,
424
+ c as normalizeClassNameList
425
+ };
@@ -0,0 +1,7 @@
1
+ (function(o,l){typeof exports=="object"&&typeof module<"u"?l(exports,require("@barocss/kit")):typeof define=="function"&&define.amd?define(["exports","@barocss/kit"],l):(o=typeof globalThis<"u"?globalThis:o||self,l(o.BaroCSSBrowser={},o.BaroCSSKit))})(this,(function(o,l){"use strict";class y{constructor(t,e=50,s="barocss-style-partition-"){this.partitions=[],this.categoryPartitions=new Map,this.partitionCounter=0,this.maxRulesPerPartition=50,this.classToPartitionMap=new Map,this.classToCategoryPartitionMap=new Map,this.styleIdPrefix="barocss-style-partition-",this.insertionPoint=t,this.maxRulesPerPartition=e,this.styleIdPrefix=s,this.initializeDefaultPartition()}initializeDefaultPartition(){this.createNewPartition()}createNewCategoryPartition(t){const e={id:this.styleIdPrefix+`-${t}`,styles:[],styleElement:document.createElement("style")};return e.styleElement.id=e.id,e.styleElement.setAttribute("data-barocss","partition"),e.styleElement.setAttribute("data-category",t),this.insertionPoint.appendChild(e.styleElement),this.categoryPartitions.set(t,e),e}createNewPartition(){const t={id:this.styleIdPrefix+`-${this.partitionCounter++}`,styles:[],styleElement:document.createElement("style")};return this.partitions.push(t),t.styleElement.id=t.id,t.styleElement.setAttribute("data-barocss","partition"),t.styleElement.setAttribute("data-partition-index",this.partitionCounter.toString()),this.insertionPoint.appendChild(t.styleElement),t}hasRule(t){return this.classToPartitionMap.has(t)}hasCategoryRule(t,e){return this.classToCategoryPartitionMap.get(t)===e}setRuleCache(t,e){this.classToPartitionMap.set(t,e)}setCategoryRuleCache(t,e){this.classToCategoryPartitionMap.set(t,e)}get currentPartition(){return this.partitions[this.partitions.length-1]}getCategoryPartition(t){return this.categoryPartitions.get(t)}escapeCssRule(t){return t.replace(/\\\//g,"\\/")}addRule(t){if(this.hasRule(t))return!1;this.currentPartition.styles.length>=this.maxRulesPerPartition&&this.createNewPartition();const e=this.currentPartition,s=this.partitions.length-1;try{const i=e.styleElement.sheet;return i?i.insertRule(this.escapeCssRule(t),i.cssRules.length):e.styleElement.textContent+=t+`
2
+ `,this.setRuleCache(t,s),e.styles.push(t),!0}catch(i){return console.warn(`[StylePartitionManager] Failed to insert rule: ${t}`,i),!1}}addCategoryRule(t,e){if(this.hasCategoryRule(t,e))return!1;let s=this.getCategoryPartition(e);s||(s=this.createNewCategoryPartition(e));try{const i=s.styleElement.sheet;i?i.insertRule(this.escapeCssRule(t),i.cssRules.length):s.styleElement.textContent+=t+`
3
+ `}catch(i){return console.warn(`[StylePartitionManager] Failed to insert rule in category: ${e} ${t}`,i),!1}return this.setCategoryRuleCache(t,e),s.styles.push(t),!0}addRootRules(t){let e=this.getCategoryPartition("root");e||(e=this.createNewCategoryPartition("root"));try{const s=e.styleElement.sheet;for(const i of t)s?s.insertRule(this.escapeCssRule(i),s.cssRules.length):e.styleElement.textContent+=i+`
4
+ `}catch(s){return console.warn(`[StylePartitionManager] Failed to insert rule in category: root ${t.join(`
5
+ `)}`,s),{success:0,failed:t.length}}return{success:t.length,failed:0}}addRules(t){let e=0,s=0;for(const i of t){const r=l.parseResultCache.get(i.cls)?.utility?.category;if(r)for(const c of i.cssList)this.addCategoryRule(c,r);else for(const c of i.cssList)this.addRule(c)?e++:s++}return{success:e,failed:s}}findRulePartition(t){const e=this.classToPartitionMap.get(t);if(e!==void 0&&this.partitions[e])return this.partitions[e];const s=this.classToCategoryPartitionMap.get(t);return s!==void 0&&this.categoryPartitions.get(s)||null}updateRuleContent(t,e){const s=this.getCategoryPartition(t);if(s)s.styleElement.textContent=e;else{const i=this.createNewCategoryPartition(t);console.log(`[StylePartitionManager] Created new partition for category: ${t}`),i.styleElement.textContent=e}}cleanup(){this.partitions.forEach(t=>{t.styleElement.parentNode&&t.styleElement.parentNode.removeChild(t.styleElement)}),this.categoryPartitions.forEach(t=>{t.styleElement.parentNode&&t.styleElement.parentNode.removeChild(t.styleElement)}),this.partitions=[],this.partitionCounter=0,this.classToPartitionMap.clear(),this.classToCategoryPartitionMap.clear()}}function g(a){return a?a instanceof SVGAnimatedString?a.baseVal.toString():a.toString():""}function h(a){return a?g(a).split(/\s+/).filter(Boolean):[]}class p{constructor(t,e){this.observer=null,this.processedElements=new WeakSet,this.incrementalParser=t,this.BrowserRuntime=e}observe(t=document.body,e){return typeof window>"u"?new MutationObserver(()=>{}):(this.observer&&this.observer.disconnect(),this.observer=new MutationObserver(s=>{const i=new Set;if(s.forEach(n=>{if(n.type==="attributes"&&n.attributeName==="class"){const r=n.target;r.className&&h(r.className).forEach(u=>{this.incrementalParser.isProcessed(u)||i.add(u)})}n.type==="childList"&&n.addedNodes.forEach(r=>{r instanceof HTMLElement&&(this.processElement(r,i),r.querySelectorAll("[class]").forEach(c=>{this.processElement(c,i)}))})}),i.size>0){const n=Array.from(i),r=this.incrementalParser.processClasses(n);this.BrowserRuntime?.applyParseResults(r)}}),this.observer.observe(t,{attributes:!0,subtree:!0,attributeFilter:["class"],childList:!0}),e?.scan&&this.scanExistingClasses(t,e),this.observer)}scanExistingClasses(t,e){const s=new Set;t.className&&h(t.className).forEach(r=>{this.incrementalParser.isProcessed(r)||s.add(r)});const i=t.querySelectorAll("[class]");for(const n of i)if(n.className){const r=h(n.className);for(const c of r)this.incrementalParser.isProcessed(c)||s.add(c)}if(s.size>0){const n=Array.from(s),r=this.incrementalParser.processClasses(n),c=r.filter(f=>l.parseResultCache.get(f.cls)?.utility?.category==="layout"),u=r.filter(f=>l.parseResultCache.get(f.cls)?.utility?.category!=="layout");this.BrowserRuntime?.applyParseResults(c),e?.onReady?.(),this.BrowserRuntime?.applyParseResults(u)}}processElement(t,e){this.processedElements.has(t)||(t.className&&h(t.className).forEach(i=>{this.incrementalParser.isProcessed(i)||e.add(i)}),this.processedElements.add(t))}disconnect(){this.observer&&(this.observer.disconnect(),this.observer=null)}}class C{constructor(t={}){this.cache=new Map,this.rootCache=new Set,this.isDestroyed=!1;const e={};this.options={config:t.config||e,styleId:t.styleId||"barocss-runtime",insertionPoint:t.insertionPoint||"head",maxRulesPerPartition:t.maxRulesPerPartition||50},this.context=l.createContext(this.options.config),this.incrementalParser=new l.IncrementalParser(this.context),this.changeDetector=new p(this.incrementalParser,this),this.stylePartitionManager=new y(this.getInsertionPoint(),this.options.maxRulesPerPartition,`${this.options.styleId}-partition`),this.init()}debugLog(t,e,s){(console[t]||console.log)(`[BrowserRuntime:${t.toUpperCase()}] ${e}`,s||"")}init(){console.log("[BrowserRuntime] init"),this.injectPreflightCSS(),this.ensureCssVars()}injectPreflightCSS(){if(this.options.config.preflight){const t=this.context.getPreflightCSS(this.options.config.preflight);this.stylePartitionManager.updateRuleContent("preflight",t)}}ensureCssVars(){if(this.isDestroyed)return;const t=this.context.themeToCssVars?this.context.themeToCssVars():":root { /* CSS Variables will be generated here */ }";this.stylePartitionManager.updateRuleContent("css-vars",t)}getInsertionPoint(){if(this.options.insertionPoint instanceof HTMLElement)return this.options.insertionPoint;switch(this.options.insertionPoint){case"body":return document.body||document.head;case"head":default:return document.head}}addClass(t){if(this.isDestroyed)return;const e=this.normalizeClasses(t);this.processClasses(e)}processClasses(t){this.processClassesIncremental(t)}processClassesIncremental(t){const e=typeof window<"u",s=this.incrementalParser.processClasses(t);console.log("[BrowserRuntime] results",s,...t),this.applyParseResults(s,{isBrowser:e})}applyParseResults(t,e){const s=[],i=[];for(const n of t)if(n.css&&Array.isArray(n.cssList)&&s.push(n),n.rootCss&&Array.isArray(n.rootCssList))for(const r of n.rootCssList)this.rootCache.has(r)||(this.rootCache.add(r),i.push(r));i.length>0&&this.stylePartitionManager.addRootRules(i.filter(Boolean)),s.length>0&&this.stylePartitionManager.addRules(s),this.debugLog("info",`Applied ${t.length} parser results`,{cssRuleCount:s.length,rootCssCount:i.length})}observe(t=document.body,e){return this.changeDetector.observe(t,e)}normalizeClasses(t){return Array.isArray(t)?t.flatMap(e=>e.split(/\s+/)):t.split(/\s+/)}has(t){return this.cache.has(t)}getCss(t){return this.cache.get(t)?.cssList.join(`
6
+ `)}getAllCss(){return Array.from(this.cache.values()).flatMap(e=>e.cssList).join(`
7
+ `)}getClasses(){return Array.from(this.cache.keys())}getCacheStats(){return{runtime:{cachedClasses:this.cache.size,rootCacheSize:this.rootCache.size},ast:l.astCache.getStats(),incremental:this.incrementalParser.getStats()}}clearCaches(){this.cache.clear(),this.rootCache.clear(),l.astCache.clear(),this.incrementalParser.clearProcessed(),this.stylePartitionManager.cleanup()}reset(){this.cache.clear(),this.rootCache.clear(),this.stylePartitionManager.cleanup()}updateConfig(t){this.options.config=t,this.context=l.createContext(t);const e=Array.from(this.cache.keys());this.reset(),e.length>0&&this.addClass(e)}removeClass(t){const e=this.normalizeClasses(t);for(const s of e)this.cache.delete(s)}destroy(){this.stylePartitionManager.cleanup(),this.cache.clear(),this.rootCache.clear(),this.isDestroyed=!0}getStats(){return{cachedClasses:this.cache.size,styleElementId:this.options.styleId,isDestroyed:this.isDestroyed,config:this.options.config,cacheStats:this.getCacheStats()}}}let d=null;function m(a){return d||(d=new C(a)),d}function P({loadingClassName:a="baro-boot",...t}={}){const e=`${a}-doing`,s=`${a}-done`;try{document.body.classList.add(e),m(t).observe(document.body,{scan:!0,onReady:()=>{document.body.classList.remove(e),document.body.classList.add(s)}})}catch(i){console.error("BaroCSS boot failed:",i)}}const R=P;o.BrowserRuntime=C,o.ChangeDetector=p,o.StylePartitionManager=y,o.baroBoot=P,o.baroStart=R,o.getRuntime=m,o.normalizeClassName=g,o.normalizeClassNameList=h,Object.defineProperty(o,Symbol.toStringTag,{value:"Module"})}));
@@ -0,0 +1,51 @@
1
+ import { GenerateCssRulesResult } from '@barocss/kit';
2
+ export interface StylePartition {
3
+ id: string;
4
+ styles: string[];
5
+ styleElement: HTMLStyleElement;
6
+ }
7
+ export declare class StylePartitionManager {
8
+ private partitions;
9
+ private categoryPartitions;
10
+ private partitionCounter;
11
+ private maxRulesPerPartition;
12
+ private insertionPoint;
13
+ private classToPartitionMap;
14
+ private classToCategoryPartitionMap;
15
+ private styleIdPrefix;
16
+ constructor(insertionPoint: HTMLElement, maxRulesPerPartition?: number, styleIdPrefix?: string);
17
+ private initializeDefaultPartition;
18
+ private createNewCategoryPartition;
19
+ private createNewPartition;
20
+ hasRule(rule: string): boolean;
21
+ hasCategoryRule(rule: string, category: string): boolean;
22
+ setRuleCache(rule: string, partitionIndex: number): void;
23
+ setCategoryRuleCache(rule: string, category: string): void;
24
+ get currentPartition(): StylePartition;
25
+ getCategoryPartition(category: string): StylePartition | undefined;
26
+ /**
27
+ * Escape CSS rule text
28
+ * - Properly escape special characters
29
+ * - Prevent CSS syntax errors
30
+ */
31
+ private escapeCssRule;
32
+ addRule(rule: string): boolean;
33
+ addCategoryRule(rule: string, category: string): boolean;
34
+ addRootRules(rules: string[]): {
35
+ success: number;
36
+ failed: number;
37
+ };
38
+ addRules(rules: GenerateCssRulesResult[]): {
39
+ success: number;
40
+ failed: number;
41
+ };
42
+ /**
43
+ * 특정 규칙이 어느 파티션에 있는지 찾기
44
+ */
45
+ findRulePartition(rule: string): StylePartition | null;
46
+ updateRuleContent(category: string, ruleContent: string): void;
47
+ /**
48
+ * 모든 파티션 정리
49
+ */
50
+ cleanup(): void;
51
+ }
@@ -0,0 +1,2 @@
1
+ export declare function normalizeClassName(className: any): string;
2
+ export declare function normalizeClassNameList(className: any): string[];
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@barocss/browser",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "main": "./dist/index.umd.js",
6
+ "types": "./dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.es.js",
11
+ "require": "./dist/index.umd.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist/**/*",
16
+ "README.md",
17
+ "LICENSE"
18
+ ],
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "dependencies": {
23
+ "@barocss/kit": "0.0.2"
24
+ },
25
+ "devDependencies": {
26
+ "jsdom": "^26.1.0",
27
+ "vite": "^7.1.3",
28
+ "vite-plugin-dts": "^4.5.4",
29
+ "vitest": "^3.2.4"
30
+ },
31
+ "scripts": {
32
+ "build": "vite build",
33
+ "build:cdn": "vite build --config vite.cdn.config.ts",
34
+ "build:library": "pnpm run build && pnpm run build:cdn",
35
+ "test:watch": "vitest",
36
+ "//test": "vitest run",
37
+ "type-check": "tsc --noEmit",
38
+ "lint": "eslint src/**/*.ts"
39
+ },
40
+ "module": "./dist/index.es.js"
41
+ }