@pathscale/rsbuild-plugin-ui-css-purge 0.9.2 → 0.9.4

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.
package/README.md CHANGED
@@ -1,131 +1,141 @@
1
- # @pathscale/rebuild-plugin-ui-css-purge
1
+ # @pathscale/rsbuild-plugin-ui-css-purge
2
2
 
3
- Three-level CSS purge for `@pathscale/ui` consumers. Analyzes JSX usage at build time, cross-references with a component class manifest, and strips unused CSS rules, attribute selectors, and custom properties.
3
+ Database-first CSS purge for `@pathscale/ui` consumers. It scans consumer JSX with SWC, cross-references a versioned component purge database, and removes only selectors whose ownership and unused state are known.
4
4
 
5
- Runs as a postbuild step under Bun. Zero Node dependencies.
5
+ Runs as a postbuild step under Bun.
6
6
 
7
- ## How it works
7
+ ## How It Works
8
8
 
9
- The purge operates in two phases across two repositories:
9
+ The purge operates in two phases across two repositories.
10
10
 
11
- **Phase 1 (lib-side, `@pathscale/ui`):** Each component ships a `.classnames.ts` file declaring all CSS classes it uses, organized by slot (`base`, `variant`, `size`, `flag`, `color`, `attrs`). A prebuild script reads these files and produces `purge-manifest.json` a compact database mapping components to their class and attribute requirements.
11
+ **Lib side (`@pathscale/ui`):** Components ship `*.classes.ts` files that describe component-owned classes by slot and prop. The manifest generator also scans colocated component CSS for selectors, data/aria attributes, CSS variable references, keyframes, and component dependencies. It writes a versioned `purge-manifest.json` database:
12
12
 
13
- **Phase 2 (consumer-side, e.g. `honey.id`):** After `rsbuild build`, the postbuild script scans the consumer's JSX source with [swc](https://swc.rs/), finds all `@pathscale/ui` component usages with their prop values, and cross-references with the manifest to determine exactly which CSS classes and attribute selectors are needed. Three purge levels run in sequence:
13
+ ```ts
14
+ interface PurgeDatabaseV2 {
15
+ version: 2;
16
+ components: Record<string, ComponentPurgeRecord>;
17
+ shared: {
18
+ selectors: { selector: string; components: string[] }[];
19
+ cssVars: Record<string, { declaredBy: string[]; referencedBy: string[] }>;
20
+ keyframes: Record<string, { declaredBy: string[]; referencedBy: string[] }>;
21
+ };
22
+ }
23
+ ```
14
24
 
15
- | Level | What it does | Engine |
16
- |-------|-------------|--------|
17
- | L1 | Removes entire CSS rules whose class selectors aren't in the safelist | [purgecss](https://purgecss.com/) |
18
- | L2 | Within kept rules, strips `[data-*]` / `[aria-*]` attribute selectors not in the attr safelist | [postcss](https://postcss.org/) AST walk |
19
- | L3 | Iteratively removes CSS custom properties that are declared but never referenced | postcss AST walk |
25
+ **Consumer side:** After `rsbuild build`, the CLI scans source files for canonical `@pathscale/ui` usage, including aliases, deep imports, namespace imports, compound JSX such as `Modal.Root`, re-exports, spreads, and dynamic props. It builds usage facts and applies the database conservatively.
20
26
 
21
- ## Results
27
+ ## Purge Rules
22
28
 
23
- Tested on `honey.id` with 3 components having `.classnames.ts` files (Button, Breadcrumbs, Navbar):
29
+ | Rule | Behavior |
30
+ | --- | --- |
31
+ | Unused known component | Selectors owned only by that component can be removed. |
32
+ | Used component, unused prop variant | Selectors requiring that known unused class can be removed. |
33
+ | Runtime data/aria state | Kept when the owning used component selector is kept. |
34
+ | Unknown ownership | Kept by default. |
35
+ | Theme, reset, app selectors | Kept unless they are fully known unused component selectors. |
36
+ | CSS vars and keyframes | Removed only after selector purge proves they are unreferenced. |
24
37
 
25
- ```
26
- 440.7 KB raw CSS (before)
27
- 42.3 KB after L1 (class purge)
28
- 42.3 KB after L2 (attr purge)
29
- 27.7 KB after L3 (var cleanup)
30
- 4.4 KB brotli compressed
31
- ```
32
-
33
- 93.7% reduction in raw CSS size.
38
+ The selector walk uses PostCSS. Lightning CSS is used for final minification.
34
39
 
35
40
  ## Installation
36
41
 
37
42
  ```bash
38
- bun add -d @pathscale/rebuild-plugin-ui-css-purge
43
+ bun add -d @pathscale/rsbuild-plugin-ui-css-purge
39
44
  ```
40
45
 
41
46
  ## Usage
42
47
 
43
- ### Consumer project (postbuild purge)
48
+ ### Consumer Project
44
49
 
45
- Add to your build script in `package.json`:
50
+ Add the postbuild purge after `rsbuild build`:
46
51
 
47
52
  ```json
48
53
  {
49
- "scripts": {
50
- "build": "rsbuild build && bunx rebuild-plugin-ui-css-purge --manifest node_modules/@pathscale/ui/dist/purge-manifest.json"
51
- }
54
+ "scripts": {
55
+ "build": "rsbuild build && bunx rsbuild-plugin-ui-css-purge --dist dist --src src --manifest node_modules/@pathscale/ui/dist/purge-manifest.json"
56
+ }
52
57
  }
53
58
  ```
54
59
 
55
60
  Options:
56
61
 
57
62
  | Flag | Default | Description |
58
- |------|---------|-------------|
59
- | `--manifest` | (required) | Path to `purge-manifest.json` |
60
- | `--dist` | `./dist` | Directory containing built CSS files |
61
- | `--src` | `./src` | Consumer source directory to scan for JSX usage |
63
+ | --- | --- | --- |
64
+ | `--manifest` | required | Path to `purge-manifest.json`. |
65
+ | `--dist` | `./dist` | Directory containing built CSS files. |
66
+ | `--src` | `./src` | Consumer source directory to scan for JSX usage. |
62
67
 
63
- ### Lib-side (manifest generation)
68
+ ### Lib-Side Manifest Generation
64
69
 
65
- Run from `@pathscale/ui` as a prebuild step:
70
+ Run from `@pathscale/ui` as a build step:
66
71
 
67
72
  ```json
68
73
  {
69
- "scripts": {
70
- "prebuild": "bunx generate-manifest src/components --out dist/purge-manifest.json"
71
- }
74
+ "scripts": {
75
+ "prebuild": "bunx generate-manifest src/components --out dist/purge-manifest.json"
76
+ }
72
77
  }
73
78
  ```
74
79
 
75
- This scans all `*.classnames.ts` files and produces the manifest that consumers use.
80
+ This scans all `*.classes.ts` files and colocated component CSS files.
76
81
 
77
- ## The `.classnames.ts` convention
82
+ ## The `*.classes.ts` Convention
78
83
 
79
- Every component in `@pathscale/ui` gets a sibling `.classnames.ts` file exporting a `CLASSES` const. The component imports it and references every class through `CLASSES.*`. This makes static analysis trivial no JSX parsing needed to know which classes a component can produce.
84
+ Every component should export a `CLASSES` object that names all component-owned classes. Tailwind utilities are filtered out so the manifest tracks ownership of durable component selectors, not generic utility classes.
80
85
 
81
86
  ```ts
82
- // Button.classnames.ts
87
+ // button.classes.ts
83
88
  export const CLASSES = {
84
- base: "inline-flex items-center justify-center rounded-md font-medium",
85
- variant: {
86
- primary: "bg-primary text-white",
87
- secondary: "bg-secondary text-white",
88
- ghost: "bg-transparent",
89
- },
90
- size: {
91
- sm: "h-8 px-3 text-sm",
92
- md: "h-10 px-4 text-base",
93
- lg: "h-12 px-6 text-lg",
94
- },
95
- flag: {
96
- isDisabled: "opacity-50 cursor-not-allowed",
97
- },
89
+ base: "button inline-flex items-center",
90
+ variant: {
91
+ primary: "button--primary",
92
+ secondary: "button--secondary",
93
+ },
94
+ size: {
95
+ sm: "button--sm",
96
+ md: "button--md",
97
+ },
98
+ flag: {
99
+ disabled: "button--disabled",
100
+ },
101
+ attrs: {
102
+ open: { "data-open": "true" },
103
+ },
98
104
  } as const;
99
105
  ```
100
106
 
101
- **Slots:**
102
-
103
- | Slot | Shape | Purpose |
104
- |------|-------|---------|
105
- | `base` | `string \| string[]` | Always rendered when the component mounts |
106
- | `variant`, `size`, `color` | `{ enumValue: classString }` | Enum prop value maps to classes |
107
- | `flag` | `{ propName: classString }` | Boolean prop name maps to classes |
108
- | `attrs` | `{ propName: { attr: value } }` | L2 attribute selectors tied to props |
107
+ Compound components use a nested shape:
109
108
 
110
- Compound components use a nested shape: `CLASSES = { Root: { base, ... }, Item: { base, ... } }`.
109
+ ```ts
110
+ export const CLASSES = {
111
+ Root: { base: "modal" },
112
+ Panel: { base: "modal__panel" },
113
+ } as const;
114
+ ```
111
115
 
112
116
  ## Programmatic API
113
117
 
114
- The scanner and safelist builder are also exported for custom integrations:
115
-
116
118
  ```ts
117
- import { scanConsumerSource, buildSafelists } from "@pathscale/rebuild-plugin-ui-css-purge";
119
+ import {
120
+ buildSafelists,
121
+ purgeCssWithDatabase,
122
+ scanConsumerSource,
123
+ } from "@pathscale/rsbuild-plugin-ui-css-purge";
118
124
 
119
125
  const usages = await scanConsumerSource("/path/to/consumer/src");
120
126
  const manifest = JSON.parse(await Bun.file("purge-manifest.json").text());
121
- const { classSafelist, attrSafelist } = buildSafelists(usages, manifest);
127
+ const safelists = buildSafelists(usages, manifest);
128
+ const result = purgeCssWithDatabase(css, manifest, safelists);
129
+
130
+ console.log(result.report);
122
131
  ```
123
132
 
124
133
  ## Development
125
134
 
126
135
  ```bash
127
136
  bun install
128
- bun run build # dist/index.js + dist/postbuild-purge.js + type declarations
137
+ bun run test
138
+ bun run build
129
139
  bun run lint
130
140
  bun run format
131
141
  ```
@@ -1,9 +1,9 @@
1
1
  /**
2
- * Lib-side database generator.
2
+ * Lib-side purge database generator.
3
3
  *
4
- * Reads all `*.classes.ts` files from @pathscale/ui's component tree
5
- * and produces a `purge-manifest.json` that the consumer-side plugin uses
6
- * to build safelists.
4
+ * Reads all `*.classes.ts` files from @pathscale/ui's component tree and
5
+ * produces a versioned `purge-manifest.json` database consumed by the postbuild
6
+ * purge script.
7
7
  *
8
8
  * Usage: bun run src/generate-manifest.ts <path-to-ui-src/components>
9
9
  * Output: purge-manifest.json in cwd (or pass --out <path>)
package/dist/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
- export { extractUIImports, extractJSXUsages, buildSafelists, scanConsumerSource } from "./scan-consumer";
2
- export type { PropUsage, PurgeManifest, ComponentManifest, Safelists } from "./scan-consumer";
1
+ export { cleanUnusedVars, cleanUnusedVarsWithReport, normalizePurgeDatabase, purgeCssWithDatabase, } from "./postbuild-purge";
2
+ export { extractUIImports, extractJSXUsages, buildSafelists, scanConsumerSource, } from "./scan-consumer";
3
+ export type { ComponentManifest, ComponentPurgeRecord, PropUsage, PurgeDatabaseV2, PurgeManifest, Safelists, } from "./scan-consumer";