catom 2.1.0 → 2.2.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.
@@ -0,0 +1,90 @@
1
+ import { Properties } from 'csstype';
2
+ export { Properties as CSSProperties } from 'csstype';
3
+
4
+ /**
5
+ * CSS properties with optional pseudo selectors
6
+ */
7
+ interface CSSPropertiesWithPseudo extends Properties {
8
+ /**
9
+ * Pseudo selector styles
10
+ * @example
11
+ * ```ts
12
+ * { ':hover': { color: 'blue' } }
13
+ * ```
14
+ */
15
+ pseudo?: {
16
+ [selector: string]: Properties;
17
+ };
18
+ }
19
+ /**
20
+ * Input type for the css() function
21
+ * Supports standard CSS properties plus media and pseudo selectors
22
+ */
23
+ interface CSSInput extends Properties {
24
+ /**
25
+ * Media query styles - can also contain pseudo selectors
26
+ * @example
27
+ * ```ts
28
+ * css({
29
+ * color: 'red',
30
+ * media: {
31
+ * '(max-width: 768px)': {
32
+ * color: 'blue',
33
+ * pseudo: { ':hover': { color: 'darkblue' } }
34
+ * }
35
+ * }
36
+ * })
37
+ * ```
38
+ */
39
+ media?: {
40
+ [query: string]: CSSPropertiesWithPseudo;
41
+ };
42
+ /**
43
+ * Pseudo selector styles
44
+ * @example
45
+ * ```ts
46
+ * css({
47
+ * color: 'red',
48
+ * pseudo: {
49
+ * ':hover': { color: 'blue' },
50
+ * ':focus': { outline: '2px solid blue' }
51
+ * }
52
+ * })
53
+ * ```
54
+ */
55
+ pseudo?: {
56
+ [selector: string]: Properties;
57
+ };
58
+ }
59
+ /**
60
+ * Define atomic CSS styles that are extracted at compile time.
61
+ *
62
+ * This function is transformed by catom/vite plugin during build.
63
+ * At runtime, it only serves as a type-safe placeholder.
64
+ *
65
+ * @param styles - CSS properties object with optional media and pseudo selectors
66
+ * @returns A string of space-separated atomic class names (at build time)
67
+ *
68
+ * @example
69
+ * ```tsx
70
+ * import { css } from 'catom'
71
+ *
72
+ * const button = css({
73
+ * backgroundColor: 'blue',
74
+ * color: 'white',
75
+ * padding: '8px 16px',
76
+ * borderRadius: '4px',
77
+ * pseudo: {
78
+ * ':hover': { backgroundColor: 'darkblue' }
79
+ * }
80
+ * })
81
+ *
82
+ * // In your component:
83
+ * <button className={button}>Click me</button>
84
+ * ```
85
+ *
86
+ * @throws Error if called at runtime (indicates plugin misconfiguration)
87
+ */
88
+ declare function css(_styles: CSSInput): string;
89
+
90
+ export { type CSSInput, type CSSPropertiesWithPseudo, css };
@@ -0,0 +1,10 @@
1
+ // src/index.ts
2
+ function css(_styles) {
3
+ throw new Error(
4
+ '[catom] css() was called at runtime. This usually means the catom vite plugin is not configured correctly. Make sure to add the plugin to your vite.config.ts: import catom from "catom/vite"'
5
+ );
6
+ }
7
+
8
+ export { css };
9
+ //# sourceMappingURL=index.js.map
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/index.ts"],"names":[],"mappings":";AAmFO,SAAS,IAAI,OAAA,EAA2B;AAC7C,EAAA,MAAM,IAAI,KAAA;AAAA,IACR;AAAA,GAGF;AACF","file":"index.js","sourcesContent":["import type { Properties } from 'csstype'\n\n/**\n * CSS properties with optional pseudo selectors\n */\nexport interface CSSPropertiesWithPseudo extends Properties {\n /**\n * Pseudo selector styles\n * @example\n * ```ts\n * { ':hover': { color: 'blue' } }\n * ```\n */\n pseudo?: { [selector: string]: Properties }\n}\n\n/**\n * Input type for the css() function\n * Supports standard CSS properties plus media and pseudo selectors\n */\nexport interface CSSInput extends Properties {\n /**\n * Media query styles - can also contain pseudo selectors\n * @example\n * ```ts\n * css({\n * color: 'red',\n * media: {\n * '(max-width: 768px)': { \n * color: 'blue',\n * pseudo: { ':hover': { color: 'darkblue' } }\n * }\n * }\n * })\n * ```\n */\n media?: { [query: string]: CSSPropertiesWithPseudo }\n\n /**\n * Pseudo selector styles\n * @example\n * ```ts\n * css({\n * color: 'red',\n * pseudo: {\n * ':hover': { color: 'blue' },\n * ':focus': { outline: '2px solid blue' }\n * }\n * })\n * ```\n */\n pseudo?: { [selector: string]: Properties }\n}\n\n/**\n * Define atomic CSS styles that are extracted at compile time.\n *\n * This function is transformed by catom/vite plugin during build.\n * At runtime, it only serves as a type-safe placeholder.\n *\n * @param styles - CSS properties object with optional media and pseudo selectors\n * @returns A string of space-separated atomic class names (at build time)\n *\n * @example\n * ```tsx\n * import { css } from 'catom'\n *\n * const button = css({\n * backgroundColor: 'blue',\n * color: 'white',\n * padding: '8px 16px',\n * borderRadius: '4px',\n * pseudo: {\n * ':hover': { backgroundColor: 'darkblue' }\n * }\n * })\n *\n * // In your component:\n * <button className={button}>Click me</button>\n * ```\n *\n * @throws Error if called at runtime (indicates plugin misconfiguration)\n */\nexport function css(_styles: CSSInput): string {\n throw new Error(\n '[catom] css() was called at runtime. ' +\n 'This usually means the catom vite plugin is not configured correctly. ' +\n 'Make sure to add the plugin to your vite.config.ts: import catom from \"catom/vite\"'\n )\n}\n\n// Re-export the type for convenience\nexport type { Properties as CSSProperties } from 'csstype'\n"]}
@@ -0,0 +1,81 @@
1
+ import { Plugin } from 'vite';
2
+ import { Properties } from 'csstype';
3
+
4
+ /**
5
+ * Input type for the css() function
6
+ */
7
+ interface CSSInput extends Properties {
8
+ media?: {
9
+ [query: string]: Properties;
10
+ };
11
+ pseudo?: {
12
+ [selector: string]: Properties;
13
+ };
14
+ }
15
+ /**
16
+ * A single atomic CSS rule
17
+ */
18
+ interface CSSRule {
19
+ /** Unique class name (hash) */
20
+ hash: string;
21
+ /** CSS property in kebab-case */
22
+ property: string;
23
+ /** CSS value */
24
+ value: string;
25
+ /** Optional media query */
26
+ media?: string;
27
+ /** Optional pseudo selector (e.g., ':hover') */
28
+ pseudo?: string;
29
+ }
30
+ /**
31
+ * Plugin options
32
+ */
33
+ interface CatomPluginOptions {
34
+ /**
35
+ * File patterns to include for transformation
36
+ * @default /\.[jt]sx?$/
37
+ */
38
+ include?: string | RegExp | (string | RegExp)[];
39
+ /**
40
+ * File patterns to exclude from transformation
41
+ * @default /node_modules/
42
+ */
43
+ exclude?: string | RegExp | (string | RegExp)[];
44
+ /**
45
+ * Name of the css function to transform
46
+ * @default 'css'
47
+ */
48
+ functionName?: string;
49
+ }
50
+
51
+ /**
52
+ * Vite plugin for zero-runtime CSS-in-JS with atomic CSS generation
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * // vite.config.ts
57
+ * import { defineConfig } from 'vite'
58
+ * import catom from 'catom/vite'
59
+ *
60
+ * export default defineConfig({
61
+ * plugins: [catom()]
62
+ * })
63
+ * ```
64
+ *
65
+ * @example
66
+ * ```ts
67
+ * // In your app code
68
+ * import { css } from 'catom'
69
+ * import 'virtual:catom.css'
70
+ *
71
+ * const button = css({
72
+ * color: 'red',
73
+ * padding: '8px',
74
+ * pseudo: { ':hover': { color: 'blue' } }
75
+ * })
76
+ * // After transform: const button = "_a1b2c3 _d4e5f6 _g7h8i9"
77
+ * ```
78
+ */
79
+ declare function catomPlugin(options?: CatomPluginOptions): Plugin;
80
+
81
+ export { type CSSInput, type CSSRule, type CatomPluginOptions, catomPlugin, catomPlugin as default };
@@ -0,0 +1,416 @@
1
+ import { createFilter } from 'vite';
2
+ import { parseSync } from '@swc/core';
3
+
4
+ // src/vite/index.ts
5
+
6
+ // src/core/hash.ts
7
+ function murmur2(str) {
8
+ let h = 0;
9
+ let k;
10
+ let i = 0;
11
+ let len = str.length;
12
+ for (; len >= 4; ++i, len -= 4) {
13
+ k = str.charCodeAt(i) & 255 | (str.charCodeAt(++i) & 255) << 8 | (str.charCodeAt(++i) & 255) << 16 | (str.charCodeAt(++i) & 255) << 24;
14
+ k = /* Math.imul(k, m): */
15
+ (k & 65535) * 1540483477 + ((k >>> 16) * 59797 << 16);
16
+ k ^= /* k >>> r: */
17
+ k >>> 24;
18
+ h = /* Math.imul(k, m): */
19
+ (k & 65535) * 1540483477 + ((k >>> 16) * 59797 << 16) ^ /* Math.imul(h, m): */
20
+ (h & 65535) * 1540483477 + ((h >>> 16) * 59797 << 16);
21
+ }
22
+ switch (len) {
23
+ case 3:
24
+ h ^= (str.charCodeAt(i + 2) & 255) << 16;
25
+ // falls through
26
+ case 2:
27
+ h ^= (str.charCodeAt(i + 1) & 255) << 8;
28
+ // falls through
29
+ case 1:
30
+ h ^= str.charCodeAt(i) & 255;
31
+ h = /* Math.imul(h, m): */
32
+ (h & 65535) * 1540483477 + ((h >>> 16) * 59797 << 16);
33
+ }
34
+ h ^= h >>> 13;
35
+ h = /* Math.imul(h, m): */
36
+ (h & 65535) * 1540483477 + ((h >>> 16) * 59797 << 16);
37
+ return ((h ^ h >>> 15) >>> 0).toString(36);
38
+ }
39
+ var PREFIX_CHARS = new Set("0123456789-".split(""));
40
+ function makeCSSCompatible(hash) {
41
+ if (PREFIX_CHARS.has(hash[0])) {
42
+ return `_${hash}`;
43
+ }
44
+ return hash;
45
+ }
46
+ function generateHash(identity) {
47
+ return makeCSSCompatible(murmur2(identity));
48
+ }
49
+
50
+ // src/core/css-generator.ts
51
+ var KEBAB_CASE_REGEX = /([a-z0-9]|(?=[A-Z]))([A-Z])/g;
52
+ function toKebabCase(property) {
53
+ return property.replace(KEBAB_CASE_REGEX, "$1-$2").toLowerCase();
54
+ }
55
+ function createRuleIdentity(property, value, media, pseudo) {
56
+ const mediaPrefix = media ? `@${media.trim()}` : "";
57
+ const pseudoPrefix = pseudo ? pseudo.trim() : "";
58
+ const kebabProp = toKebabCase(property);
59
+ return `${mediaPrefix}${pseudoPrefix}${kebabProp}:${value};`;
60
+ }
61
+ function createCSSRule(property, value, media, pseudo) {
62
+ const stringValue = String(value).trim();
63
+ const kebabProperty = toKebabCase(property.trim());
64
+ const identity = createRuleIdentity(property, stringValue, media, pseudo);
65
+ const hash = generateHash(identity);
66
+ return {
67
+ hash,
68
+ property: kebabProperty,
69
+ value: stringValue,
70
+ media: media?.trim(),
71
+ pseudo: pseudo?.trim()
72
+ };
73
+ }
74
+ function deduplicateRules(rules) {
75
+ const seen = /* @__PURE__ */ new Map();
76
+ for (const rule of rules) {
77
+ const identity = createRuleIdentity(rule.property, rule.value, rule.media, rule.pseudo);
78
+ if (!seen.has(identity)) {
79
+ seen.set(identity, rule);
80
+ }
81
+ }
82
+ return Array.from(seen.values());
83
+ }
84
+ function groupRulesByDeclaration(rules) {
85
+ const groups = /* @__PURE__ */ new Map();
86
+ for (const rule of rules) {
87
+ const declaration = `${rule.property}:${rule.value};`;
88
+ const groupKey = `${rule.media || ""}|${rule.pseudo || ""}|${declaration}`;
89
+ if (!groups.has(groupKey)) {
90
+ groups.set(groupKey, {
91
+ declaration,
92
+ hashes: /* @__PURE__ */ new Set(),
93
+ media: rule.media,
94
+ pseudo: rule.pseudo
95
+ });
96
+ }
97
+ groups.get(groupKey).hashes.add(rule.hash);
98
+ }
99
+ return Array.from(groups.values());
100
+ }
101
+ function generateCSS(rules) {
102
+ const dedupedRules = deduplicateRules(rules);
103
+ const regularRules = [];
104
+ const pseudoRules = [];
105
+ const mediaRules = /* @__PURE__ */ new Map();
106
+ for (const rule of dedupedRules) {
107
+ if (rule.media) {
108
+ const existing = mediaRules.get(rule.media) || [];
109
+ existing.push(rule);
110
+ mediaRules.set(rule.media, existing);
111
+ } else if (rule.pseudo) {
112
+ pseudoRules.push(rule);
113
+ } else {
114
+ regularRules.push(rule);
115
+ }
116
+ }
117
+ const cssLines = [];
118
+ const regularGroups = groupRulesByDeclaration(regularRules);
119
+ for (const group of regularGroups.sort((a, b) => a.declaration.localeCompare(b.declaration))) {
120
+ const selectors = Array.from(group.hashes).sort().map((h) => `.${h}`).join(",\n");
121
+ cssLines.push(`${selectors} { ${group.declaration} }`);
122
+ }
123
+ const pseudoGroups = groupRulesByDeclaration(pseudoRules);
124
+ for (const group of pseudoGroups.sort((a, b) => a.declaration.localeCompare(b.declaration))) {
125
+ const selectors = Array.from(group.hashes).sort().map((h) => `.${h}${group.pseudo}`).join(",\n");
126
+ cssLines.push(`${selectors} { ${group.declaration} }`);
127
+ }
128
+ const sortedMediaQueries = Array.from(mediaRules.entries()).sort(
129
+ ([a], [b]) => a.localeCompare(b)
130
+ );
131
+ for (const [query, rules2] of sortedMediaQueries) {
132
+ const mediaGroups = groupRulesByDeclaration(rules2);
133
+ const mediaLines = [];
134
+ for (const group of mediaGroups.sort((a, b) => a.declaration.localeCompare(b.declaration))) {
135
+ const suffix = group.pseudo || "";
136
+ const selectors = Array.from(group.hashes).sort().map((h) => `.${h}${suffix}`).join(",\n");
137
+ mediaLines.push(` ${selectors} { ${group.declaration} }`);
138
+ }
139
+ cssLines.push(`@media ${query} {
140
+ ${mediaLines.join("\n")}
141
+ }`);
142
+ }
143
+ return cssLines.join("\n");
144
+ }
145
+ function isLiteral(expr) {
146
+ return expr.type === "StringLiteral" || expr.type === "NumericLiteral";
147
+ }
148
+ function getLiteralValue(expr) {
149
+ if (expr.type === "StringLiteral") {
150
+ return expr.value;
151
+ }
152
+ return expr.value;
153
+ }
154
+ function getPropertyKey(prop) {
155
+ if (prop.key.type === "Identifier") {
156
+ return prop.key.value;
157
+ }
158
+ if (prop.key.type === "StringLiteral") {
159
+ return prop.key.value;
160
+ }
161
+ return null;
162
+ }
163
+ function processPropertiesObject(obj, rules, media, pseudo) {
164
+ for (const prop of obj.properties) {
165
+ if (prop.type === "SpreadElement") {
166
+ if (prop.argument.type === "ObjectExpression") {
167
+ processPropertiesObject(prop.argument, rules, media, pseudo);
168
+ } else {
169
+ throw new Error(
170
+ `[catom] Spread elements must be object literals. Dynamic spreads are not supported at compile time.`
171
+ );
172
+ }
173
+ continue;
174
+ }
175
+ if (prop.type !== "KeyValueProperty") {
176
+ continue;
177
+ }
178
+ const keyName = getPropertyKey(prop);
179
+ if (!keyName) {
180
+ throw new Error(`[catom] Could not determine property key. Only identifiers and string literals are supported.`);
181
+ }
182
+ const value = prop.value;
183
+ if (keyName === "media") {
184
+ if (value.type !== "ObjectExpression") {
185
+ throw new Error(`[catom] 'media' property must be an object literal.`);
186
+ }
187
+ for (const mediaProp of value.properties) {
188
+ if (mediaProp.type !== "KeyValueProperty") continue;
189
+ const mediaQuery = getPropertyKey(mediaProp);
190
+ if (!mediaQuery) continue;
191
+ if (mediaProp.value.type !== "ObjectExpression") {
192
+ throw new Error(`[catom] Media query '${mediaQuery}' must contain an object literal.`);
193
+ }
194
+ processPropertiesObject(mediaProp.value, rules, mediaQuery, pseudo);
195
+ }
196
+ continue;
197
+ }
198
+ if (keyName === "pseudo") {
199
+ if (value.type !== "ObjectExpression") {
200
+ throw new Error(`[catom] 'pseudo' property must be an object literal.`);
201
+ }
202
+ for (const pseudoProp of value.properties) {
203
+ if (pseudoProp.type !== "KeyValueProperty") continue;
204
+ const pseudoSelector = getPropertyKey(pseudoProp);
205
+ if (!pseudoSelector) continue;
206
+ if (pseudoProp.value.type !== "ObjectExpression") {
207
+ throw new Error(`[catom] Pseudo selector '${pseudoSelector}' must contain an object literal.`);
208
+ }
209
+ processPropertiesObject(pseudoProp.value, rules, media, pseudoSelector);
210
+ }
211
+ continue;
212
+ }
213
+ let actualValue = value;
214
+ if (actualValue.type === "TsAsExpression") {
215
+ actualValue = actualValue.expression;
216
+ }
217
+ if (!isLiteral(actualValue)) {
218
+ throw new Error(
219
+ `[catom] Property '${keyName}' has a non-literal value. Only string and number literals are supported at compile time. Got: ${actualValue.type}`
220
+ );
221
+ }
222
+ const rule = createCSSRule(keyName, getLiteralValue(actualValue), media, pseudo);
223
+ rules.push(rule);
224
+ }
225
+ }
226
+ function processCSSCall(callExpr) {
227
+ const rules = [];
228
+ if (callExpr.arguments.length === 0) {
229
+ return { rules: [], classNames: "" };
230
+ }
231
+ const arg = callExpr.arguments[0];
232
+ if (arg.expression.type !== "ObjectExpression") {
233
+ throw new Error(
234
+ `[catom] css() must be called with an object literal. Got: ${arg.expression.type}`
235
+ );
236
+ }
237
+ processPropertiesObject(arg.expression, rules);
238
+ const classNames = rules.map((r) => r.hash).join(" ");
239
+ return { rules, classNames };
240
+ }
241
+ var SKIP_KEYS = /* @__PURE__ */ new Set(["span", "ctxt", "type", "value", "raw", "cooked"]);
242
+ function isASTNode(value) {
243
+ return typeof value === "object" && value !== null && typeof value.type === "string";
244
+ }
245
+ function walkAndTransform(node, functionName, allRules, replacements, visited = /* @__PURE__ */ new WeakSet()) {
246
+ if (!node || typeof node !== "object") return;
247
+ if (visited.has(node)) return;
248
+ visited.add(node);
249
+ if (isCallExpression(node)) {
250
+ const callee = node.callee;
251
+ if (callee.type === "Identifier" && callee.value === functionName) {
252
+ try {
253
+ const { rules, classNames } = processCSSCall(node);
254
+ allRules.push(...rules);
255
+ replacements.set(node.span.start, classNames);
256
+ } catch (error) {
257
+ const loc = node.span;
258
+ const prefix = loc ? `[${loc.start}:${loc.end}]` : "";
259
+ throw new Error(`${prefix} ${error.message}`);
260
+ }
261
+ }
262
+ }
263
+ for (const key of Object.keys(node)) {
264
+ if (SKIP_KEYS.has(key)) continue;
265
+ const value = node[key];
266
+ if (Array.isArray(value)) {
267
+ for (const item of value) {
268
+ if (isASTNode(item)) {
269
+ walkAndTransform(item, functionName, allRules, replacements, visited);
270
+ }
271
+ }
272
+ } else if (isASTNode(value)) {
273
+ walkAndTransform(value, functionName, allRules, replacements, visited);
274
+ }
275
+ }
276
+ }
277
+ function isCallExpression(node) {
278
+ return typeof node === "object" && node !== null && node.type === "CallExpression";
279
+ }
280
+ function transformCode(code, id, functionName = "css") {
281
+ const isTypeScript = /\.tsx?$/.test(id);
282
+ const isJSX = /\.[jt]sx$/.test(id);
283
+ let ast;
284
+ try {
285
+ ast = parseSync(code, {
286
+ syntax: isTypeScript ? "typescript" : "ecmascript",
287
+ tsx: isJSX && isTypeScript,
288
+ jsx: isJSX && !isTypeScript,
289
+ comments: true
290
+ });
291
+ } catch {
292
+ return { code, cssRules: [], transformed: false };
293
+ }
294
+ const allRules = [];
295
+ const replacements = /* @__PURE__ */ new Map();
296
+ walkAndTransform(ast, functionName, allRules, replacements);
297
+ if (replacements.size === 0) {
298
+ return { code, cssRules: [], transformed: false };
299
+ }
300
+ let result = code;
301
+ const sortedReplacements = Array.from(replacements.entries()).sort(
302
+ ([a], [b]) => b - a
303
+ // Sort descending so we replace from end to start
304
+ );
305
+ for (const [start, classNames] of sortedReplacements) {
306
+ const searchStart = start - 1;
307
+ const cssCallMatch = findCSSCallBounds(result, searchStart, functionName);
308
+ if (cssCallMatch) {
309
+ const replacement = JSON.stringify(classNames);
310
+ result = result.slice(0, cssCallMatch.start) + replacement + result.slice(cssCallMatch.end);
311
+ }
312
+ }
313
+ return {
314
+ code: result,
315
+ cssRules: allRules,
316
+ transformed: true
317
+ };
318
+ }
319
+ function findCSSCallBounds(code, startPos, functionName) {
320
+ const searchWindow = 50;
321
+ const searchStart = Math.max(0, startPos - searchWindow);
322
+ const searchEnd = Math.min(code.length, startPos + searchWindow);
323
+ const searchRegion = code.slice(searchStart, searchEnd);
324
+ const funcPattern = new RegExp(`\\b${functionName}\\s*\\(`);
325
+ const match = funcPattern.exec(searchRegion);
326
+ if (!match) return null;
327
+ const callStart = searchStart + match.index;
328
+ const parenStart = callStart + match[0].length - 1;
329
+ let depth = 1;
330
+ let i = parenStart + 1;
331
+ while (i < code.length && depth > 0) {
332
+ const char = code[i];
333
+ if (char === "(") depth++;
334
+ else if (char === ")") depth--;
335
+ i++;
336
+ }
337
+ if (depth !== 0) return null;
338
+ return { start: callStart, end: i };
339
+ }
340
+
341
+ // src/vite/index.ts
342
+ var VIRTUAL_MODULE_ID = "virtual:catom.css";
343
+ var RESOLVED_VIRTUAL_MODULE_ID = "\0" + VIRTUAL_MODULE_ID;
344
+ function catomPlugin(options = {}) {
345
+ const {
346
+ include = /\.[jt]sx?$/,
347
+ exclude = /node_modules/,
348
+ functionName = "css"
349
+ } = options;
350
+ const moduleCSS = /* @__PURE__ */ new Map();
351
+ let filter;
352
+ return {
353
+ name: "vite-plugin-catom",
354
+ // Ensure we run before other transforms
355
+ enforce: "pre",
356
+ configResolved() {
357
+ filter = createFilter(include, exclude);
358
+ },
359
+ buildStart() {
360
+ moduleCSS.clear();
361
+ },
362
+ resolveId(id) {
363
+ if (id === VIRTUAL_MODULE_ID) {
364
+ return RESOLVED_VIRTUAL_MODULE_ID;
365
+ }
366
+ return null;
367
+ },
368
+ load(id) {
369
+ if (id === RESOLVED_VIRTUAL_MODULE_ID) {
370
+ const allRules = [];
371
+ for (const state of moduleCSS.values()) {
372
+ allRules.push(...state.rules);
373
+ }
374
+ const css = generateCSS(allRules);
375
+ return css;
376
+ }
377
+ return null;
378
+ },
379
+ transform(code, id) {
380
+ if (!filter(id)) {
381
+ return null;
382
+ }
383
+ if (!code.includes(functionName + "(")) {
384
+ return null;
385
+ }
386
+ try {
387
+ const result = transformCode(code, id, functionName);
388
+ if (!result.transformed) {
389
+ return null;
390
+ }
391
+ moduleCSS.set(id, {
392
+ rules: result.cssRules,
393
+ timestamp: Date.now()
394
+ });
395
+ return {
396
+ code: result.code,
397
+ map: null
398
+ // TODO: Add source map support
399
+ };
400
+ } catch (error) {
401
+ const message = error instanceof Error ? error.message : String(error);
402
+ this.error(`[catom] Error transforming ${id}: ${message}`);
403
+ }
404
+ },
405
+ // Handle module removal (for dev server)
406
+ watchChange(id) {
407
+ if (moduleCSS.has(id)) {
408
+ moduleCSS.delete(id);
409
+ }
410
+ }
411
+ };
412
+ }
413
+
414
+ export { catomPlugin, catomPlugin as default };
415
+ //# sourceMappingURL=index.js.map
416
+ //# sourceMappingURL=index.js.map