@zslabs/atomic-css-modules 0.1.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.
Files changed (56) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/GUIDE.md +32 -0
  3. package/LICENSE +21 -0
  4. package/README.md +121 -0
  5. package/dist/atom-key.d.ts +19 -0
  6. package/dist/atom-key.js +72 -0
  7. package/dist/atom-key.js.map +1 -0
  8. package/dist/atoms.d.ts +8 -0
  9. package/dist/atoms.js +33 -0
  10. package/dist/atoms.js.map +1 -0
  11. package/dist/declaration-utils.d.ts +37 -0
  12. package/dist/declaration-utils.js +99 -0
  13. package/dist/declaration-utils.js.map +1 -0
  14. package/dist/file-scan-cache.d.ts +21 -0
  15. package/dist/file-scan-cache.js +122 -0
  16. package/dist/file-scan-cache.js.map +1 -0
  17. package/dist/hash.d.ts +15 -0
  18. package/dist/hash.js +38 -0
  19. package/dist/hash.js.map +1 -0
  20. package/dist/index.d.ts +9 -0
  21. package/dist/index.js +9 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/process.d.ts +2 -0
  24. package/dist/process.js +19 -0
  25. package/dist/process.js.map +1 -0
  26. package/dist/registry.d.ts +3 -0
  27. package/dist/registry.js +107 -0
  28. package/dist/registry.js.map +1 -0
  29. package/dist/report.d.ts +7 -0
  30. package/dist/report.js +67 -0
  31. package/dist/report.js.map +1 -0
  32. package/dist/rewrite.d.ts +2 -0
  33. package/dist/rewrite.js +482 -0
  34. package/dist/rewrite.js.map +1 -0
  35. package/dist/rule-context.d.ts +35 -0
  36. package/dist/rule-context.js +86 -0
  37. package/dist/rule-context.js.map +1 -0
  38. package/dist/scan.d.ts +2 -0
  39. package/dist/scan.js +177 -0
  40. package/dist/scan.js.map +1 -0
  41. package/dist/selector-utils.d.ts +34 -0
  42. package/dist/selector-utils.js +156 -0
  43. package/dist/selector-utils.js.map +1 -0
  44. package/dist/shorthand-groups.d.ts +7 -0
  45. package/dist/shorthand-groups.js +85 -0
  46. package/dist/shorthand-groups.js.map +1 -0
  47. package/dist/skip-comment.d.ts +6 -0
  48. package/dist/skip-comment.js +48 -0
  49. package/dist/skip-comment.js.map +1 -0
  50. package/dist/types.d.ts +152 -0
  51. package/dist/types.js +14 -0
  52. package/dist/types.js.map +1 -0
  53. package/dist/vite-plugin.d.ts +27 -0
  54. package/dist/vite-plugin.js +231 -0
  55. package/dist/vite-plugin.js.map +1 -0
  56. package/package.json +75 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@zslabs/atomic-css-modules` are documented here.
4
+
5
+ ## 0.1.0
6
+
7
+ - Initial release: Vite plugin and `processCssModules` core API
8
+ - Shared atomic registry via CSS Modules `composes`
9
+ - Eligibility for single local classes, simple trailing pseudos, and nested at-rules
10
+ - Shorthand/longhand conflict handling and `/* atomic: skip */`
package/GUIDE.md ADDED
@@ -0,0 +1,32 @@
1
+ # atomic-css-modules internals
2
+
3
+ Eligibility and compose constraints. For install and options, see the [package README](./README.md).
4
+
5
+ ## What selectors are supported
6
+
7
+ CSS Modules only allows `composes` on a **single local class**. Eligibility mirrors that:
8
+
9
+ | Shape | Atomized? |
10
+ | ------------------------------------------------------------- | ------------------------------------------ |
11
+ | `.card` | Yes |
12
+ | `.card:hover` / `.card::before` (one trailing simple pseudo) | Yes — pseudo is hoisted onto the atom |
13
+ | `.a, .b` when every member is a local class (± simple pseudo) | Yes — split into per-class compose targets |
14
+ | Combinators, compounds, elements, attributes, IDs, `:root` | No |
15
+ | `.a, div` (mixed group) | No |
16
+ | `:not()`, `:nth-child()`, chained `:hover:focus` | No |
17
+
18
+ A trailing simple pseudo-class or pseudo-element (no arguments) is eligible. Functional or multiple trailing pseudos are not: they cannot map to one shared atom plus Modules `composes`.
19
+
20
+ ## Shorthand and longhand
21
+
22
+ If a rule has a shorthand and any of its longhands (`margin` + `margin-left`), neither those properties atomize for that rule. Sibling longhands of the same shorthand (`font-family` + `font-size`) still do.
23
+
24
+ ## Size and compression
25
+
26
+ Raw registry bytes can shrink when the same declarations repeat. Over the wire, gzip/brotli often close the gap on small stylesheets; savings show up at scale when many components share a design-token surface. See [Caveats](./README.md#caveats) in the README.
27
+
28
+ ## Non-goals
29
+
30
+ - Cross-file cascade / specificity modeling
31
+ - Atomizing combinators or compounds while still using Modules `composes`
32
+ - Reimplementing CSS Modules compose resolution here
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Zach Schnackel
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,121 @@
1
+ # @zslabs/atomic-css-modules
2
+
3
+ Extracts repeated CSS Modules declarations into shared atomic classes via `composes`.
4
+
5
+ ## Requirements
6
+
7
+ - **Node.js** `>=20.17` or `>=22.5` (uses `path.matchesGlob`)
8
+ - **CSS Modules** source (`.module.css`)
9
+ - **Vite** `^8` when using the Vite plugin (`vite` is an optional peer)
10
+
11
+ The core `processCssModules` API has no Vite dependency.
12
+
13
+ ## Install
14
+
15
+ ```sh
16
+ npm install @zslabs/atomic-css-modules
17
+ ```
18
+
19
+ ```ts
20
+ import { defineConfig } from 'vite'
21
+ import { atomicCssModules } from '@zslabs/atomic-css-modules/vite'
22
+
23
+ export default defineConfig({
24
+ plugins: [
25
+ atomicCssModules({
26
+ include: ['src/**/*.module.css'],
27
+ }),
28
+ ],
29
+ })
30
+ ```
31
+
32
+ | Option | Default | Notes |
33
+ | ------------ | --------------------------------- | --------------------------------------------------- |
34
+ | `include` | `**/*.module.css` | Globs relative to the Vite root |
35
+ | `exclude` | `**/node_modules/**` | |
36
+ | `debugNames` | `true` in serve, `false` in build | Readable atoms (`color-red-a1b2c3`) vs short hashes |
37
+ | `report` | `"summary"` | `"verbose"` lists each skipped selector |
38
+ | `preprocess` | — | `(code, filePath) => code` before scan/atomize |
39
+
40
+ ## How it works
41
+
42
+ Matching declarations share one registry class. Single local classes are eligible, including simple pseudos and nested at-rules.
43
+
44
+ ```css
45
+ .card {
46
+ color: red;
47
+ &:hover {
48
+ color: blue;
49
+ }
50
+ @media (min-width: 600px) {
51
+ padding: 16px;
52
+ }
53
+ }
54
+ ```
55
+
56
+ becomes:
57
+
58
+ ```css
59
+ /* module */
60
+ .card {
61
+ composes: bn89ziv jtnf3j g1h5a7 from global;
62
+ }
63
+ ```
64
+
65
+ ```css
66
+ /* registry */
67
+ .bn89ziv {
68
+ color: red;
69
+ }
70
+
71
+ .jtnf3j:hover {
72
+ color: #00f;
73
+ }
74
+
75
+ @media (width >= 600px) {
76
+ .g1h5a7 {
77
+ padding: 16px;
78
+ }
79
+ }
80
+ ```
81
+
82
+ Selector eligibility, shorthand/longhand rules, and non-goals: [GUIDE.md](./GUIDE.md).
83
+
84
+ ## Skipping
85
+
86
+ Add `/* atomic: skip */` before a selector, or as the first comment in a rule block, to leave that rule alone. Use it when a declaration would atomize but sharing it would break cascade or you need the styles to stay on the local class.
87
+
88
+ `buildStart` logs atom count, skip reasons, and source vs registry bytes.
89
+
90
+ ## Caveats
91
+
92
+ - Only selectors that CSS Modules can `composes` are atomized (see the guide).
93
+ - Shorthand + longhand in the same rule skip those properties for that rule.
94
+ - Cross-file cascade and specificity are not modeled; if shared atoms would break ordering, use `/* atomic: skip */`.
95
+ - On small sheets, gzip/brotli often erase the atomic class win (many one-declaration rules compress worse than fewer larger ones). The payoff shows up when UI grows against a shared token surface: Modules CSS scales with components, the atom set scales with the design system.
96
+ - This is **0.x**: the supported surface below is stable within a minor when practical, but breaking changes may land before 1.0.
97
+
98
+ ## Public API
99
+
100
+ **Supported**
101
+
102
+ - `@zslabs/atomic-css-modules/vite` — `atomicCssModules`, plugin option types, registry virtual module ids
103
+ - `@zslabs/atomic-css-modules` — `processCssModules`, `ProcessOptions` / `ProcessResult` and related result types, `SKIP_REASONS` / report helpers used with the pipeline
104
+
105
+ **Internal** (exported for advanced use and tests; may change without a major bump)
106
+
107
+ - `FileScanCache`, `scanFiles`, `rewriteFiles`, `buildRegistryCss`, `ensureUniqueAtomNames`, `syncScanAtomHashes`, and lower-level atom/scan types
108
+
109
+ ```ts
110
+ import { processCssModules } from '@zslabs/atomic-css-modules'
111
+
112
+ const result = processCssModules([{ filePath: 'button.module.css', code }], {
113
+ registryImportPath: './atomic-registry.css',
114
+ composeFrom: 'file', // Vite plugin uses 'global'
115
+ sourceMap: true,
116
+ })
117
+ ```
118
+
119
+ ## License
120
+
121
+ MIT
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Builds the deterministic key/hash for one atomizable declaration. Pure
3
+ * function of (property, value, !important, pseudo, at-rule condition) so
4
+ * scan and rewrite always agree on an atom's class name without needing to
5
+ * share any other state.
6
+ */
7
+ import type { Declaration } from 'lightningcss';
8
+ import type { AtomCondition, AtomKey, AtomPseudo } from './types.js';
9
+ export interface BuildAtomKeyParams {
10
+ property: string;
11
+ declaration: Declaration;
12
+ important: boolean;
13
+ pseudo: AtomPseudo | null;
14
+ condition: AtomCondition;
15
+ }
16
+ export declare function buildAtomKey(params: BuildAtomKeyParams): AtomKey;
17
+ export declare function atomKeyString(key: AtomKey): string;
18
+ export declare function atomHashForKey(key: AtomKey): string;
19
+ export declare function atomClassNameForKey(key: AtomKey, declaration: Declaration, debugNames?: boolean): string;
@@ -0,0 +1,72 @@
1
+ import { transform } from 'lightningcss';
2
+ import { atomClassName, fnv1a } from './hash.js';
3
+ export function buildAtomKey(params) {
4
+ return {
5
+ property: params.property,
6
+ // Omit `loc` so identical values at different source positions dedupe.
7
+ valueFingerprint: JSON.stringify(params.declaration, (key, value) => (key === 'loc' ? undefined : value)),
8
+ important: params.important,
9
+ pseudo: params.pseudo,
10
+ condition: params.condition,
11
+ };
12
+ }
13
+ export function atomKeyString(key) {
14
+ return JSON.stringify(key);
15
+ }
16
+ export function atomHashForKey(key) {
17
+ return atomClassName(atomKeyString(key));
18
+ }
19
+ function slug(value) {
20
+ const slugged = value
21
+ .toLowerCase()
22
+ .replace(/[^a-z0-9]+/g, '-')
23
+ .replace(/^-+|-+$/g, '');
24
+ return slugged || 'x';
25
+ }
26
+ function printedDeclaration(declaration, important) {
27
+ const result = transform({
28
+ filename: 'atom.css',
29
+ code: Buffer.from('._{}'),
30
+ visitor: {
31
+ StyleSheetExit(sheet) {
32
+ sheet.rules = [
33
+ {
34
+ type: 'style',
35
+ value: {
36
+ loc: { source_index: 0, line: 0, column: 1 },
37
+ selectors: [[{ type: 'class', name: '_' }]],
38
+ declarations: important
39
+ ? { importantDeclarations: [declaration] }
40
+ : { declarations: [declaration] },
41
+ },
42
+ },
43
+ ];
44
+ return sheet;
45
+ },
46
+ },
47
+ });
48
+ const css = Buffer.from(result.code).toString('utf8');
49
+ const match = /\{([^}]+)\}/.exec(css);
50
+ return (match?.[1] ?? '').replace(/!important/g, '').trim();
51
+ }
52
+ function debugAtomClassName(key, declaration) {
53
+ const prefixes = [];
54
+ for (const wrapper of key.condition) {
55
+ prefixes.push(wrapper.kind === 'media' ? 'md' : wrapper.kind);
56
+ }
57
+ if (key.pseudo && 'kind' in key.pseudo)
58
+ prefixes.push(key.pseudo.kind);
59
+ const body = slug(printedDeclaration(declaration, key.important));
60
+ if (key.important)
61
+ prefixes.push('i');
62
+ const name = [...prefixes, body].join('-');
63
+ const base = /^[a-z]/i.test(name) ? name : `a-${name}`;
64
+ // Suffix with a short key hash so different at-rule conditions never collide.
65
+ return `${base}-${fnv1a(atomKeyString(key)).slice(0, 6)}`;
66
+ }
67
+ export function atomClassNameForKey(key, declaration, debugNames = false) {
68
+ if (debugNames)
69
+ return debugAtomClassName(key, declaration);
70
+ return atomHashForKey(key);
71
+ }
72
+ //# sourceMappingURL=atom-key.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atom-key.js","sourceRoot":"","sources":["../src/atom-key.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { Atom, ScanFileResult } from './types.js';
2
+ /**
3
+ * Ensures every atom has a distinct class name. First-seen key keeps its
4
+ * name; later keys that collide are reallocated (`-2`, `-3`, …).
5
+ */
6
+ export declare function ensureUniqueAtomNames(atoms: Map<string, Atom>): void;
7
+ /** Rebuilds `atomHashes` from `atomKeys` using the (possibly remapped) atom map. */
8
+ export declare function syncScanAtomHashes(scanFiles: readonly ScanFileResult[], atoms: Map<string, Atom>): void;
package/dist/atoms.js ADDED
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Atom map helpers: global class-name uniqueness and keeping scan rule
3
+ * hashes aligned with the map after remaps.
4
+ */
5
+ import { allocateUniqueClassName } from './hash.js';
6
+ /**
7
+ * Ensures every atom has a distinct class name. First-seen key keeps its
8
+ * name; later keys that collide are reallocated (`-2`, `-3`, …).
9
+ */
10
+ export function ensureUniqueAtomNames(atoms) {
11
+ const taken = new Set();
12
+ for (const atom of atoms.values()) {
13
+ const unique = allocateUniqueClassName(taken, atom.className);
14
+ taken.add(unique);
15
+ atom.hash = unique;
16
+ atom.className = unique;
17
+ }
18
+ }
19
+ /** Rebuilds `atomHashes` from `atomKeys` using the (possibly remapped) atom map. */
20
+ export function syncScanAtomHashes(scanFiles, atoms) {
21
+ for (const scanFile of scanFiles) {
22
+ for (const rule of scanFile.rules) {
23
+ rule.atomHashes = rule.atomKeys.map((keyString) => {
24
+ const atom = atoms.get(keyString);
25
+ if (!atom) {
26
+ throw new Error(`Missing atom for key while syncing hashes`);
27
+ }
28
+ return atom.hash;
29
+ });
30
+ }
31
+ }
32
+ }
33
+ //# sourceMappingURL=atoms.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atoms.js","sourceRoot":"","sources":["../src/atoms.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,uBAAuB,EAAE,MAAM,WAAW,CAAA;AAGnD;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,KAAwB;IAC5D,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAA;IAC/B,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;QAClC,MAAM,MAAM,GAAG,uBAAuB,CAAC,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC,CAAA;QAC7D,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;QACjB,IAAI,CAAC,IAAI,GAAG,MAAM,CAAA;QAClB,IAAI,CAAC,SAAS,GAAG,MAAM,CAAA;IACzB,CAAC;AACH,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,kBAAkB,CAChC,SAAoC,EACpC,KAAwB;IAExB,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QACjC,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,KAAK,EAAE,CAAC;YAClC,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE;gBAChD,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;gBACjC,IAAI,CAAC,IAAI,EAAE,CAAC;oBACV,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAA;gBAC9D,CAAC;gBACD,OAAO,IAAI,CAAC,IAAI,CAAA;YAClB,CAAC,CAAC,CAAA;QACJ,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Helpers for reading lightningcss `Declaration` / `DeclarationBlock` values.
3
+ */
4
+ import type { Declaration, DeclarationBlock } from 'lightningcss';
5
+ /** True for CSS Modules `composes`, whether lightningcss parsed it or left it unparsed. */
6
+ export declare function isComposesDeclaration(declaration: Declaration): boolean;
7
+ export type ComposesOrigin = {
8
+ kind: 'local';
9
+ } | {
10
+ kind: 'global';
11
+ } | {
12
+ kind: 'file';
13
+ path: string;
14
+ };
15
+ /** Where a `composes` declaration pulls class names from. */
16
+ export declare function composesOrigin(declaration: Declaration): ComposesOrigin | null;
17
+ /** Class names listed on a `composes` declaration. */
18
+ export declare function composesNames(declaration: Declaration): string[];
19
+ /** The `from` file path on a `composes` declaration, or null if it is local or global. */
20
+ export declare function composesFrom(declaration: Declaration): string | null;
21
+ /** The CSS property name a declaration represents, including custom properties and unparsed values. */
22
+ export declare function declarationProperty(declaration: Declaration): string;
23
+ export interface FlatDeclaration {
24
+ declaration: Declaration;
25
+ important: boolean;
26
+ }
27
+ /**
28
+ * lightningcss visitors surface `Option::None` as JSON `null`. Passing those
29
+ * objects back into `transform` fails for some types (notably `Specifier` on
30
+ * `var()` names, and `@import`/`@namespace` optionals). Drop nulls before we
31
+ * store or re-inject AST nodes.
32
+ */
33
+ export declare function stripAstNulls(declaration: Declaration): Declaration;
34
+ /** Deep-clone any LightningCSS AST value, dropping only JSON `null`s. */
35
+ export declare function stripNullsDeep<T>(value: T): T;
36
+ /** Flattens a rule's declaration block into a single ordered list, tagging each with its `!important` flag. */
37
+ export declare function flattenDeclarations(block: DeclarationBlock<Declaration> | undefined): FlatDeclaration[];
@@ -0,0 +1,99 @@
1
+ /** True for CSS Modules `composes`, whether lightningcss parsed it or left it unparsed. */
2
+ export function isComposesDeclaration(declaration) {
3
+ if (declaration.property === 'composes')
4
+ return true;
5
+ return (declaration.property === 'unparsed' &&
6
+ declaration.value.propertyId.property === 'composes');
7
+ }
8
+ /** Where a `composes` declaration pulls class names from. */
9
+ export function composesOrigin(declaration) {
10
+ if (declaration.property === 'composes') {
11
+ const from = declaration.value.from;
12
+ if (!from)
13
+ return { kind: 'local' };
14
+ if (from.type === 'global')
15
+ return { kind: 'global' };
16
+ if (from.type === 'file')
17
+ return { kind: 'file', path: from.value };
18
+ return { kind: 'local' };
19
+ }
20
+ if (!isComposesDeclaration(declaration) ||
21
+ declaration.property !== 'unparsed') {
22
+ return null;
23
+ }
24
+ let seenFrom = false;
25
+ for (const token of declaration.value.value) {
26
+ if (token.type !== 'token')
27
+ continue;
28
+ if (token.value.type === 'ident' && token.value.value === 'from') {
29
+ seenFrom = true;
30
+ continue;
31
+ }
32
+ if (!seenFrom)
33
+ continue;
34
+ if (token.value.type === 'ident' && token.value.value === 'global') {
35
+ return { kind: 'global' };
36
+ }
37
+ if (token.value.type === 'string')
38
+ return { kind: 'file', path: token.value.value };
39
+ }
40
+ return { kind: 'local' };
41
+ }
42
+ /** Class names listed on a `composes` declaration. */
43
+ export function composesNames(declaration) {
44
+ if (declaration.property === 'composes')
45
+ return [...declaration.value.names];
46
+ if (!isComposesDeclaration(declaration) ||
47
+ declaration.property !== 'unparsed') {
48
+ return [];
49
+ }
50
+ const names = [];
51
+ for (const token of declaration.value.value) {
52
+ if (token.type !== 'token')
53
+ continue;
54
+ if (token.value.type === 'ident') {
55
+ if (token.value.value === 'from')
56
+ break;
57
+ names.push(token.value.value);
58
+ }
59
+ }
60
+ return names;
61
+ }
62
+ /** The `from` file path on a `composes` declaration, or null if it is local or global. */
63
+ export function composesFrom(declaration) {
64
+ const origin = composesOrigin(declaration);
65
+ return origin?.kind === 'file' ? origin.path : null;
66
+ }
67
+ /** The CSS property name a declaration represents, including custom properties and unparsed values. */
68
+ export function declarationProperty(declaration) {
69
+ if (declaration.property === 'custom')
70
+ return declaration.value.name;
71
+ if (declaration.property === 'unparsed')
72
+ return declaration.value.propertyId.property;
73
+ return declaration.property;
74
+ }
75
+ /**
76
+ * lightningcss visitors surface `Option::None` as JSON `null`. Passing those
77
+ * objects back into `transform` fails for some types (notably `Specifier` on
78
+ * `var()` names, and `@import`/`@namespace` optionals). Drop nulls before we
79
+ * store or re-inject AST nodes.
80
+ */
81
+ export function stripAstNulls(declaration) {
82
+ return JSON.parse(JSON.stringify(declaration, (_key, value) => value === null ? undefined : value));
83
+ }
84
+ /** Deep-clone any LightningCSS AST value, dropping only JSON `null`s. */
85
+ export function stripNullsDeep(value) {
86
+ return JSON.parse(JSON.stringify(value, (_key, nested) => nested === null ? undefined : nested));
87
+ }
88
+ /** Flattens a rule's declaration block into a single ordered list, tagging each with its `!important` flag. */
89
+ export function flattenDeclarations(block) {
90
+ if (!block)
91
+ return [];
92
+ const flat = [];
93
+ for (const declaration of block.declarations ?? [])
94
+ flat.push({ declaration: stripAstNulls(declaration), important: false });
95
+ for (const declaration of block.importantDeclarations ?? [])
96
+ flat.push({ declaration: stripAstNulls(declaration), important: true });
97
+ return flat;
98
+ }
99
+ //# sourceMappingURL=declaration-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"declaration-utils.js","sourceRoot":"","sources":["../src/declaration-utils.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { Atom, AtomizationReport, ScanFileResult, ScanResult, SourceFile } from './types.js';
2
+ export declare class FileScanCache {
3
+ private readonly entries;
4
+ private debugNames;
5
+ /** Last rematerialized global atom map (class names globally unique). */
6
+ private mergedAtoms;
7
+ setDebugNames(debugNames: boolean): void;
8
+ /** True when the file was scanned. False when the cached scan was reused. */
9
+ update(file: SourceFile): boolean;
10
+ remove(filePath: string): void;
11
+ retain(filePaths: Iterable<string>): void;
12
+ getScan(filePath: string): ScanFileResult | undefined;
13
+ /** Globally unique atoms from the last {@link snapshot} call. */
14
+ getAtoms(): Map<string, Atom>;
15
+ snapshot(): {
16
+ files: SourceFile[];
17
+ scan: ScanResult;
18
+ registryCss: string;
19
+ report: AtomizationReport;
20
+ };
21
+ }
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Per-file scan cache so Vite can rebuild the registry from dirty files
3
+ * instead of re-parsing every CSS module on each HMR or transform.
4
+ */
5
+ import { ensureUniqueAtomNames, syncScanAtomHashes } from './atoms.js';
6
+ import { buildRegistryCss } from './registry.js';
7
+ import { summarizeAtomization } from './report.js';
8
+ import { scanFiles } from './scan.js';
9
+ /** 64-bit DJB2-pair string hash so content collisions are vanishingly rare. */
10
+ function contentHash(code) {
11
+ let hashA = 5381;
12
+ let hashB = 52711;
13
+ for (let i = 0; i < code.length; i++) {
14
+ const char = code.charCodeAt(i);
15
+ hashA = Math.imul(hashA, 33) ^ char;
16
+ hashB = Math.imul(hashB, 33) ^ char;
17
+ }
18
+ return `${(hashA >>> 0).toString(16).padStart(8, '0')}${(hashB >>> 0)
19
+ .toString(16)
20
+ .padStart(8, '0')}`;
21
+ }
22
+ function cloneAtom(atom) {
23
+ return {
24
+ hash: atom.hash,
25
+ className: atom.className,
26
+ key: atom.key,
27
+ keyString: atom.keyString,
28
+ declaration: atom.declaration,
29
+ };
30
+ }
31
+ export class FileScanCache {
32
+ entries = new Map();
33
+ debugNames = false;
34
+ /** Last rematerialized global atom map (class names globally unique). */
35
+ mergedAtoms = new Map();
36
+ setDebugNames(debugNames) {
37
+ if (this.debugNames === debugNames)
38
+ return;
39
+ this.debugNames = debugNames;
40
+ this.entries.clear();
41
+ this.mergedAtoms = new Map();
42
+ }
43
+ /** True when the file was scanned. False when the cached scan was reused. */
44
+ update(file) {
45
+ const hash = contentHash(file.code);
46
+ const existing = this.entries.get(file.filePath);
47
+ if (existing && existing.contentHash === hash)
48
+ return false;
49
+ const scanned = scanFiles([file], { debugNames: this.debugNames });
50
+ const scan = scanned.files[0];
51
+ if (!scan)
52
+ throw new Error(`scan produced no result for ${file.filePath}`);
53
+ this.entries.set(file.filePath, {
54
+ contentHash: hash,
55
+ code: file.code,
56
+ scan,
57
+ atoms: scanned.atoms,
58
+ skips: scanned.skips,
59
+ });
60
+ return true;
61
+ }
62
+ remove(filePath) {
63
+ this.entries.delete(filePath);
64
+ }
65
+ retain(filePaths) {
66
+ const keep = new Set(filePaths);
67
+ for (const filePath of this.entries.keys()) {
68
+ if (!keep.has(filePath))
69
+ this.entries.delete(filePath);
70
+ }
71
+ }
72
+ getScan(filePath) {
73
+ return this.entries.get(filePath)?.scan;
74
+ }
75
+ /** Globally unique atoms from the last {@link snapshot} call. */
76
+ getAtoms() {
77
+ return this.mergedAtoms;
78
+ }
79
+ snapshot() {
80
+ const files = [];
81
+ const scanFilesResult = [];
82
+ const atoms = new Map();
83
+ const skips = [];
84
+ for (const entry of this.entries.values()) {
85
+ files.push({ filePath: entry.scan.filePath, code: entry.code });
86
+ // Clone rules so remapped hashes do not mutate the per-file cache entry
87
+ // until we write them back via sync (we sync the clones used for rewrite).
88
+ scanFilesResult.push({
89
+ filePath: entry.scan.filePath,
90
+ rules: entry.scan.rules.map((rule) => ({
91
+ ...rule,
92
+ atomKeys: [...rule.atomKeys],
93
+ atomHashes: [...rule.atomHashes],
94
+ decisions: rule.decisions.map((decision) => ({ ...decision })),
95
+ })),
96
+ });
97
+ for (const [key, atom] of entry.atoms) {
98
+ if (!atoms.has(key))
99
+ atoms.set(key, cloneAtom(atom));
100
+ }
101
+ skips.push(...entry.skips);
102
+ }
103
+ ensureUniqueAtomNames(atoms);
104
+ syncScanAtomHashes(scanFilesResult, atoms);
105
+ // Persist remapped hashes onto cached scans so getScan matches rewrite.
106
+ for (const [index, entry] of [...this.entries.values()].entries()) {
107
+ const remapped = scanFilesResult[index];
108
+ if (remapped)
109
+ entry.scan = remapped;
110
+ }
111
+ this.mergedAtoms = atoms;
112
+ const scan = { atoms, files: scanFilesResult, skips };
113
+ const registryCss = buildRegistryCss(atoms.values());
114
+ return {
115
+ files,
116
+ scan,
117
+ registryCss,
118
+ report: summarizeAtomization(files, scan, registryCss),
119
+ };
120
+ }
121
+ }
122
+ //# sourceMappingURL=file-scan-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-scan-cache.js","sourceRoot":"","sources":["../src/file-scan-cache.ts"],"names":[],"mappings":"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"}
package/dist/hash.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Deterministic, dependency-free string hashing (FNV-1a) used to derive
3
+ * short, stable atomic class names from declaration fingerprints.
4
+ */
5
+ export declare function fnv1a(input: string): string;
6
+ /**
7
+ * FNV-1a encoded as base36, with a leading digit remapped to a letter
8
+ * so the name is a valid CSS class without a reserved prefix.
9
+ */
10
+ export declare function atomClassName(fingerprint: string): string;
11
+ /**
12
+ * Picks `desired` when free, otherwise appends `-2`, `-3`, … until unique.
13
+ * Used so class-name collisions never silently share a registry rule.
14
+ */
15
+ export declare function allocateUniqueClassName(taken: ReadonlySet<string>, desired: string): string;