Skip to content

Commit c13dba9

Browse files
authored
Merge pull request #4 from sclark-bycore/main
fix: prevent scanner from traversing node_modules and enable config l…
2 parents a702c95 + 3d0e83b commit c13dba9

File tree

7 files changed

+686
-84
lines changed

7 files changed

+686
-84
lines changed

icon-sprite/README.md

Lines changed: 76 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,15 @@
11
<div align="center">
22

3-
# @react-zero-ui/icon-sprite
3+
# @react-zero-ui/icon-sprite
44

5-
[![MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://github.com/react-zero-ui/icon-sprite/blob/main/LICENSE) [![npm](https://img.shields.io/npm/v/@react-zero-ui/icon-sprite.svg)](https://www.npmjs.com/package/@react-zero-ui/icon-sprite)
5+
[![MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://github.com/react-zero-ui/icon-sprite/blob/main/LICENSE) [![npm](https://img.shields.io/npm/v/@react-zero-ui/icon-sprite.svg)](https://www.npmjs.com/package/@react-zero-ui/icon-sprite)
66

7-
8-
97
</div>
108

11-
> ![Note](https://img.shields.io/badge/Note-blue)
12-
> **Generates one SVG sprite containing only the icons you used** - Lucide + custom SVGs.
9+
> ![Note](https://img.shields.io/badge/Note-blue) > **Generates one SVG sprite containing only the icons you used** - Lucide + custom SVGs.
1310
> DX with real `<Icon/>` in dev ➡️ zero-runtime `<use/>` in prod.
1411
1512
> Part of the [React Zero-UI](https://github.com/react-zero-ui) ecosystem.
16-
1713
1814
---
1915

@@ -34,26 +30,26 @@
3430
Only icons actually used in your app are included.
3531

3632
## 🙏 Custom Icon Support
33+
3734
Drop SVGs into **`/public/zero-ui-icons/`**, then use `<CustomIcon />` with the filename (no `.svg`).
3835

39-
>![Tip](https://img.shields.io/badge/Tip-green)
40-
>```txt
41-
>📁/public
36+
> ![Tip](https://img.shields.io/badge/Tip-green)
37+
>
38+
> ```txt
39+
> 📁/public
4240
> └──📁/zero-ui-icons/
4341
> └──dog.svg
4442
> ```
43+
>
4544
> ```tsx
46-
>import { CustomIcon } from "@react-zero-ui/icon-sprite";
47-
>//❗The name MUST match the name of the file name (no .svg extension).
48-
><CustomIcon name="dog" size={24} />
49-
>```
50-
45+
> import { CustomIcon } from '@react-zero-ui/icon-sprite';
46+
> //❗The name MUST match the name of the file name (no .svg extension).
47+
> <CustomIcon name='dog' size={24} />;
48+
> ```
5149
52-
>![Info](https://img.shields.io/badge/Info-blue)
50+
> ![Info](https://img.shields.io/badge/Info-blue)
5351
> In dev you may see a brief FOUC using custom icons; this is removed in production.
5452
55-
56-
5753
---
5854
5955
## 📦 Installation
@@ -65,15 +61,18 @@ npm install @react-zero-ui/icon-sprite
6561
---
6662

6763
## ❗ Build Command
64+
6865
> ![Caution](https://img.shields.io/badge/Caution-red)
6966
> Run this before your app build so the sprite exists.
70-
>```bash
71-
>npx zero-icons
72-
>```
67+
>
68+
> ```bash
69+
> npx zero-icons
70+
> ```
7371
7472
This command builds the icons sprite for production.
7573
7674
Or add this to your `package.json` scripts:
75+
7776
```json
7877
{
7978
"scripts": {
@@ -82,14 +81,14 @@ Or add this to your `package.json` scripts:
8281
}
8382
}
8483
```
84+
8585
That's it!
8686

8787
---
8888

8989
## 🔨 Usage
9090

91-
> ![Warning](https://img.shields.io/badge/Warning-orange)
92-
> **Pass `size`, or both `width` and `height`, to ensure identical dev/prod rendering.**
91+
> ![Warning](https://img.shields.io/badge/Warning-orange) > **Pass `size`, or both `width` and `height`, to ensure identical dev/prod rendering.**
9392
> Dev defaults (Lucide 24×24) differ from sprite viewBoxes in production. Missing these props will **very likely** change the visual size in prod.
9493
9594
### For Lucide Icons:
@@ -104,14 +103,13 @@ import { ArrowRight, Mail } from "@react-zero-ui/icon-sprite";
104103
### Custom Icons:
105104

106105
Drop SVGs into **`/public/zero-ui-icons/`**, then use `<CustomIcon />` with the filename (no `.svg`).
106+
107107
```tsx
108-
import { CustomIcon } from "@react-zero-ui/icon-sprite";
108+
import { CustomIcon } from '@react-zero-ui/icon-sprite';
109109
//❗The name MUST match the name of the file name (without .svg).
110-
<CustomIcon name="dog" size={32} />
110+
<CustomIcon name='dog' size={32} />;
111111
```
112112

113-
114-
115113
---
116114

117115
## 🧪 How It Works (Under the Hood)
@@ -121,10 +119,10 @@ import { CustomIcon } from "@react-zero-ui/icon-sprite";
121119
In dev, each icon wrapper looks like this:
122120

123121
```tsx
124-
import { ArrowRight as DevIcon } from "lucide-react";
122+
import { ArrowRight as DevIcon } from 'lucide-react';
125123

126-
export const ArrowRight = (props) =>
127-
process.env.NODE_ENV === "development" ? (
124+
export const ArrowRight = props =>
125+
process.env.NODE_ENV === 'development' ? (
128126
<DevIcon {...props} />
129127
) : (
130128
<svg {...props}>
@@ -135,10 +133,10 @@ export const ArrowRight = (props) =>
135133

136134
This ensures:
137135

138-
* Dev uses Lucide's real React components (`lucide-react`)
139-
* Full props support (e.g. `strokeWidth`, `className`)
140-
* No caching issues from SVG sprites
141-
* No FOUC (Flash of Unstyled Content)
136+
- Dev uses Lucide's real React components (`lucide-react`)
137+
- Full props support (e.g. `strokeWidth`, `className`)
138+
- No caching issues from SVG sprites
139+
- No FOUC (Flash of Unstyled Content)
142140

143141
### ⚙️ Production Mode: Minimal Runtime, Maximum Speed
144142

@@ -150,6 +148,41 @@ At build time:
150148

151149
---
152150

151+
## ⚙️ Configuration
152+
153+
You can customize the scanner behavior by creating a `zero-ui.config.js` file in your project root:
154+
155+
```js
156+
// zero-ui.config.js
157+
export default {
158+
// Package name to scan for (default: "@react-zero-ui/icon-sprite")
159+
IMPORT_NAME: '@react-zero-ui/icon-sprite',
160+
161+
// Path where the sprite will be served (default: "/icons.svg")
162+
SPRITE_PATH: '/icons.svg',
163+
164+
// Directory to scan for icon usage (default: "src")
165+
ROOT_DIR: 'src',
166+
167+
// Directory containing custom SVG files (default: "zero-ui-icons")
168+
CUSTOM_SVG_DIR: 'zero-ui-icons',
169+
170+
// Output directory for the sprite (default: "public")
171+
OUTPUT_DIR: 'public',
172+
173+
// Icon names to ignore during scanning (default: ["CustomIcon"])
174+
IGNORE_ICONS: ['CustomIcon'],
175+
176+
// Directories to exclude from scanning (default: ["node_modules", ".git", "dist", "build", ".next", "out"])
177+
EXCLUDE_DIRS: ['node_modules', '.git', 'dist', 'build', '.next', 'out'],
178+
};
179+
```
180+
181+
> ![Note](https://img.shields.io/badge/Note-blue)
182+
> The scanner now defaults to scanning only the `src` directory and automatically excludes `node_modules` and other common build directories. This prevents build failures from dependencies with unsupported syntax (e.g., TypeScript decorators).
183+
184+
---
185+
153186
## ⚡️ Tooling
154187

155188
To generate everything:
@@ -160,22 +193,19 @@ npx zero-icons
160193

161194
This runs the full pipeline:
162195

163-
| Script | Purpose |
164-
| --- | --- |
165-
| `scan-icons.js` | Parse your codebase for used icons (`Icon` usage or named imports) |
166-
| `used-icons.js` | Collects a list of unique icon names |
196+
| Script | Purpose |
197+
| ----------------- | ------------------------------------------------------------------------------------------------------------ |
198+
| `scan-icons.js` | Parse your codebase for used icons (`Icon` usage or named imports) |
199+
| `used-icons.js` | Collects a list of unique icon names |
167200
| `build-sprite.js` | Uses [`svgstore`](https://github.com/DIYgod/svgstore) to generate `icons.svg` from used Lucide + custom SVGs |
168-
169201

170-
---
202+
---
171203

172204
## ✨ Why This Beats Icon Libraries Everywhere
173205

174-
* **DX-first in dev**: No flicker. No sprite caching. Live updates.
175-
* **Zero-runtime in production**: Sprites are native, fast, lightweight & highly Cached.
176-
* **Only ships the icons you actually use** - smallest possible sprite.
177-
* **Custom icon support**: Drop SVGs into `/public/zero-ui-icons/` and use `<CustomIcon />`
178-
206+
- **DX-first in dev**: No flicker. No sprite caching. Live updates.
207+
- **Zero-runtime in production**: Sprites are native, fast, lightweight & highly Cached.
208+
- **Only ships the icons you actually use** - smallest possible sprite.
209+
- **Custom icon support**: Drop SVGs into `/public/zero-ui-icons/` and use `<CustomIcon />`
179210

180211
Made with ❤️ for the React community by [@austin1serb](https://github.com/austin1serb)
181-

icon-sprite/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
],
3838
"scripts": {
3939
"build": "rm -rf dist && node scripts/gen-wrappers.js && tsc && node scripts/gen-dist.js",
40-
"test": "node tests/test-mapping.test.js",
40+
"test": "node tests/run-all-tests.js",
4141
"prepare": "npm run build && npm run test",
4242
"type-check": "tsc --noEmit | tee type-errors.log"
4343
},

icon-sprite/scripts/scan-icons.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { fileURLToPath } from "url";
55
import * as babel from "@babel/core";
66
import traverseImport from "@babel/traverse";
77
import * as t from "@babel/types";
8-
import { IMPORT_NAME, ROOT_DIR, IGNORE_ICONS } from "../dist/config.js";
8+
import { IMPORT_NAME, ROOT_DIR, IGNORE_ICONS, EXCLUDE_DIRS } from "../dist/config.js";
99

1010
// ESM __dirname shim
1111
const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -30,6 +30,10 @@ function collect(dir) {
3030
for (const file of fs.readdirSync(dir)) {
3131
const full = path.join(dir, file);
3232
if (fs.statSync(full).isDirectory()) {
33+
// Skip excluded directories
34+
if (EXCLUDE_DIRS.includes(file)) {
35+
continue;
36+
}
3337
collect(full);
3438
continue;
3539
}

icon-sprite/src/config.ts

Lines changed: 31 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,37 @@
11
// src/config.ts
2-
// import fs from "fs";
3-
// import path from "path";
4-
// import { pathToFileURL } from "url";
2+
import fs from "fs";
3+
import path from "path";
4+
import { pathToFileURL } from "url";
55

6-
// const DEFAULT_CONFIG = {
7-
// IMPORT_NAME: "@react-zero-ui/icon-sprite",
8-
// SPRITE_PATH: "/icons.svg",
9-
// ROOT_DIR: "",
10-
// CUSTOM_SVG_DIR: "zero-ui-icons",
11-
// OUTPUT_DIR: "public",
12-
// IGNORE_ICONS: ["CustomIcon"],
13-
// };
6+
const DEFAULT_CONFIG = {
7+
IMPORT_NAME: "@react-zero-ui/icon-sprite",
8+
SPRITE_PATH: "/icons.svg",
9+
ROOT_DIR: "src",
10+
CUSTOM_SVG_DIR: "zero-ui-icons",
11+
OUTPUT_DIR: "public",
12+
IGNORE_ICONS: ["CustomIcon"],
13+
EXCLUDE_DIRS: ["node_modules", ".git", "dist", "build", ".next", "out"],
14+
};
1415

15-
// let userConfig = {};
16-
// const configFile = path.resolve(process.cwd(), "zero-ui.config.js");
16+
let userConfig = {};
17+
const configFile = path.resolve(process.cwd(), "zero-ui.config.js");
1718

18-
// if (fs.existsSync(configFile)) {
19-
// try {
20-
// const mod = await import(pathToFileURL(configFile).href);
21-
// userConfig = mod.default ?? mod;
22-
// } catch (e) {
23-
// // @ts-expect-error
24-
// console.warn("⚠️ Failed to load zero-ui.config.js:", e.message);
25-
// }
26-
// }
19+
if (fs.existsSync(configFile)) {
20+
try {
21+
const mod = await import(pathToFileURL(configFile).href);
22+
userConfig = mod.default ?? mod;
23+
} catch (e) {
24+
// @ts-expect-error
25+
console.warn("⚠️ Failed to load zero-ui.config.js:", e.message);
26+
}
27+
}
2728

28-
// const merged = { ...DEFAULT_CONFIG, ...userConfig };
29+
const merged = { ...DEFAULT_CONFIG, ...userConfig };
2930

30-
// export const IMPORT_NAME = merged.IMPORT_NAME;
31-
// export const SPRITE_PATH = merged.SPRITE_PATH;
32-
// export const ROOT_DIR = merged.ROOT_DIR;
33-
// export const CUSTOM_SVG_DIR = merged.CUSTOM_SVG_DIR;
34-
// export const OUTPUT_DIR = merged.OUTPUT_DIR;
35-
// export const IGNORE_ICONS = merged.IGNORE_ICONS;
36-
37-
export const IMPORT_NAME = "@react-zero-ui/icon-sprite";
38-
export const SPRITE_PATH = "/icons.svg";
39-
export const ROOT_DIR = "";
40-
export const CUSTOM_SVG_DIR = "zero-ui-icons";
41-
export const OUTPUT_DIR = "public";
42-
export const IGNORE_ICONS = ["CustomIcon"];
31+
export const IMPORT_NAME = merged.IMPORT_NAME;
32+
export const SPRITE_PATH = merged.SPRITE_PATH;
33+
export const ROOT_DIR = merged.ROOT_DIR;
34+
export const CUSTOM_SVG_DIR = merged.CUSTOM_SVG_DIR;
35+
export const OUTPUT_DIR = merged.OUTPUT_DIR;
36+
export const IGNORE_ICONS = merged.IGNORE_ICONS;
37+
export const EXCLUDE_DIRS = merged.EXCLUDE_DIRS;

0 commit comments

Comments
 (0)