react-icons-sprite 1.0.1 → 1.1.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.
- package/LICENSE.md +21 -0
- package/README.md +325 -0
- package/dist/{build-sprite-DgeWMz2D.mjs → build-sprite-CxqvU2LE.mjs} +46 -4
- package/dist/{collector-DxE_zcTH.mjs → collector-CBRz9R_h.mjs} +1 -1
- package/dist/{compute-icon-id-CVIo3z9m.mjs → compute-icon-id-Xx4G3fva.mjs} +17 -2
- package/dist/icon.d.mts +1 -5
- package/dist/index.d.mts +0 -1
- package/dist/{transform-module-CIpNj5Na.mjs → transform-module-1am5uV8-.mjs} +113 -6
- package/dist/vite/plugin.d.mts +0 -1
- package/dist/vite/plugin.mjs +3 -3
- package/dist/webpack/loader.d.mts +0 -1
- package/dist/webpack/loader.mjs +2 -2
- package/dist/webpack/plugin.d.mts +0 -1
- package/dist/webpack/plugin.mjs +2 -2
- package/package.json +105 -14
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright © 2025 Jure Rotar
|
|
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,325 @@
|
|
|
1
|
+
# react-icons-sprite
|
|
2
|
+
|
|
3
|
+
`react-icons-sprite` is a lightweight plugin for Vite and Webpack that turns React icon components into a single SVG
|
|
4
|
+
spritesheet and rewrites your code to reference those symbols via `<use>`.
|
|
5
|
+
|
|
6
|
+
It supports multiple React icon packages that export icons as individual React components. This approach both shrinks
|
|
7
|
+
your bundle (no more inlined React components for every icon) and reduces runtime overhead, since React no longer has to
|
|
8
|
+
reconcile large, nested SVG trees.
|
|
9
|
+
|
|
10
|
+
## Supported icon libraries
|
|
11
|
+
|
|
12
|
+
Out of the box, imports from the following libraries are detected and transformed. These are the versions currently
|
|
13
|
+
used by the demo apps and validation suite:
|
|
14
|
+
|
|
15
|
+
| Library | Validated version |
|
|
16
|
+
|---|---:|
|
|
17
|
+
| `react-icons/*` packs (e.g. `react-icons/bi`, `react-icons/fa`, ...) | `5.6.0` |
|
|
18
|
+
| `lucide-react` | `1.16.0` |
|
|
19
|
+
| `@radix-ui/react-icons` | `1.3.2` |
|
|
20
|
+
| `@heroicons/react` (v1 and v2 subpaths) | `2.2.0` |
|
|
21
|
+
| `@tabler/icons-react` | `3.44.0` |
|
|
22
|
+
| `phosphor-react` | `1.4.1` |
|
|
23
|
+
| `@phosphor-icons/react` | `2.1.10` |
|
|
24
|
+
| `react-feather` | `2.0.10` |
|
|
25
|
+
| `react-bootstrap-icons` | `1.11.6` |
|
|
26
|
+
| `grommet-icons` | `4.14.0` |
|
|
27
|
+
| `@remixicon/react` | `4.9.0` |
|
|
28
|
+
| `devicons-react` | `1.5.0` |
|
|
29
|
+
| `@fortawesome/free-solid-svg-icons` (and other Font Awesome icon packs) | `7.2.0` |
|
|
30
|
+
| `@fortawesome/react-fontawesome` | `3.3.1` |
|
|
31
|
+
| `@mui/icons-material` | `9.0.1` |
|
|
32
|
+
| `@carbon/icons-react` | `11.81.0` |
|
|
33
|
+
| `@ant-design/icons` | `6.2.5` |
|
|
34
|
+
| `@fluentui/react-icons` | `2.0.330` |
|
|
35
|
+
| `@primer/octicons-react` | `19.28.1` |
|
|
36
|
+
| `@hugeicons/react` with `@hugeicons/core-free-icons` | `1.1.6` / `4.2.0` |
|
|
37
|
+
|
|
38
|
+
> [!NOTE]
|
|
39
|
+
> `react-icons-sprite` does not bundle these libraries. You must install whichever icon packages you intend to use in
|
|
40
|
+
> your project.
|
|
41
|
+
|
|
42
|
+
## Motivation
|
|
43
|
+
|
|
44
|
+
By default, when you use an icon library like `react-icons`, each icon is a React component. For example:
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
import { LuWheat } from "react-icons/lu";
|
|
48
|
+
|
|
49
|
+
export function Example() {
|
|
50
|
+
return <LuWheat />;
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
looks harmless, but at build time this compiles to something like:
|
|
55
|
+
|
|
56
|
+
```javascript
|
|
57
|
+
import { b as e } from './iconBase-BU3rGdXB.js'
|
|
58
|
+
|
|
59
|
+
function t(t) {
|
|
60
|
+
return e({
|
|
61
|
+
tag: `svg`, attr: { viewBox: `0 0 640 512` }, child: [{
|
|
62
|
+
tag: `path`, attr: {
|
|
63
|
+
d: `M524.531,69.836a1.5,1.5,0,0,0-.764-.7A485.065,485.065,0,0,0,404.081,32.03a1.816,1.816,0,0,0-1.923.91,337.461,337.461,0,0,0-14.9,30.6,447.848,447.848,0,0,0-134.426,0,309.541,309.541,0,0,0-15.135-30.6,1.89,1.89,0,0,0-1.924-.91A483.689,483.689,0,0,0,116.085,69.137a1.712,1.712,0,0,0-.788.676C39.068,183.651,18.186,294.69,28.43,404.354a2.016,2.016,0,0,0,.765,1.375A487.666,487.666,0,0,0,176.02,479.918a1.9,1.9,0,0,0,2.063-.676A348.2,348.2,0,0,0,208.12,430.4a1.86,1.86,0,0,0-1.019-2.588,321.173,321.173,0,0,1-45.868-21.853,1.885,1.885,0,0,1-.185-3.126c3.082-2.309,6.166-4.711,9.109-7.137a1.819,1.819,0,0,1,1.9-.256c96.229,43.917,200.41,43.917,295.5,0a1.812,1.812,0,0,1,1.924.233c2.944,2.426,6.027,4.851,9.132,7.16a1.884,1.884,0,0,1-.162,3.126,301.407,301.407,0,0,1-45.89,21.83,1.875,1.875,0,0,0-1,2.611,391.055,391.055,0,0,0,30.014,48.815,1.864,1.864,0,0,0,2.063.7A486.048,486.048,0,0,0,610.7,405.729a1.882,1.882,0,0,0,.765-1.352C623.729,277.594,590.933,167.465,524.531,69.836ZM222.491,337.58c-28.972,0-52.844-26.587-52.844-59.239S193.056,219.1,222.491,219.1c29.665,0,53.306,26.82,52.843,59.239C275.334,310.993,251.924,337.58,222.491,337.58Zm195.38,0c-28.971,0-52.843-26.587-52.843-59.239S388.437,219.1,417.871,219.1c29.667,0,53.307,26.82,52.844,59.239C470.715,310.993,447.538,337.58,417.871,337.58Z`
|
|
64
|
+
}, child: []
|
|
65
|
+
}]
|
|
66
|
+
})(t)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function n(t) {
|
|
70
|
+
return e({
|
|
71
|
+
tag: `svg`, attr: { viewBox: `0 0 496 512` }, child: [{
|
|
72
|
+
tag: `path`, attr: {
|
|
73
|
+
d: `M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z`
|
|
74
|
+
}, child: []
|
|
75
|
+
}]
|
|
76
|
+
})(t)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ... potentially dozens more child components.
|
|
80
|
+
|
|
81
|
+
export { t, n };
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
That means:
|
|
85
|
+
|
|
86
|
+
- Every icon adds hundreds of characters of JSX to your bundle.
|
|
87
|
+
- At runtime, React must create and diff dozens of DOM nodes for every icon render.
|
|
88
|
+
- If you render 100 icons, React is reconciling potentially thousands of <path> elements.
|
|
89
|
+
|
|
90
|
+
Now compare that to output when using a spritesheet with `<use>`:
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
import { LuWheat } from "react-icons/lu";
|
|
94
|
+
|
|
95
|
+
export function Example() {
|
|
96
|
+
return <LuWheat />;
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
```javascript
|
|
101
|
+
import { b as n } from './icon-FONPSuqX.js';
|
|
102
|
+
|
|
103
|
+
var r = e(t());
|
|
104
|
+
const i = () => (0, r.jsx)(n, { iconId: `ri-react-icons-lu-LuWheat` });
|
|
105
|
+
export { i as IconWheat };
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`icon-FONPSuqX.js` in this case is our simple icon wrapper, which looks like this:
|
|
109
|
+
|
|
110
|
+
```javascript
|
|
111
|
+
var n = e(t());
|
|
112
|
+
const r = ({ iconId: e, ...t }) => {
|
|
113
|
+
let r = `assets/react-icons-sprite-C-JClopV.svg#${e}`;
|
|
114
|
+
return (0, n.jsxs)(`svg`, {
|
|
115
|
+
height: `1em`,
|
|
116
|
+
width: `1em`,
|
|
117
|
+
preserveAspectRatio: `xMidYMid meet`,
|
|
118
|
+
viewBox: `0 0 24 24`, ...t,
|
|
119
|
+
children: [(0, n.jsx)(`title`, { children: e }), (0, n.jsx)(`use`, { href: r })]
|
|
120
|
+
})
|
|
121
|
+
};
|
|
122
|
+
export { r as b };
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Runtime difference:
|
|
126
|
+
|
|
127
|
+
- React only reconciles two DOM nodes (`<svg>` + `<use>`).
|
|
128
|
+
- All heavy path data lives in the static spritesheet once, not duplicated across components.
|
|
129
|
+
- Updating 100 icons is as cheap as updating 100 `<use>` tags.
|
|
130
|
+
|
|
131
|
+
This is a big win when you’re rendering icons in lists, tables, or maps where dozens or hundreds of them appear at once.
|
|
132
|
+
|
|
133
|
+
### Performance comparison
|
|
134
|
+
|
|
135
|
+
The build-time transform is also intentionally small. The current Vitest benchmark transforms a component with 240 icon
|
|
136
|
+
usages at **32,805.97 ops/sec** (mean **0.0305 ms** per transform) on the machine listed below.
|
|
137
|
+
|
|
138
|
+
| icon (pack) | react-icons icon render mean time | react-icons-sprite icon render mean time | Relative difference |
|
|
139
|
+
|-------------------------|----------------------------------:|-----------------------------------------:|--------------------:|
|
|
140
|
+
| **FiCpu** (fi) | 0.188 ms | 0.048 ms | 74.6% reduction |
|
|
141
|
+
| **MdBuild** (md) | 0.198 ms | 0.048 ms | 76.0% reduction |
|
|
142
|
+
| **FaCamera** (fa) | 0.162 ms | 0.015 ms | 90.7% reduction |
|
|
143
|
+
| **IoAperture** (io5) | 0.029 ms | 0.015 ms | 49.7% reduction |
|
|
144
|
+
| **BiBell** (bi) | 0.023 ms | 0.014 ms | 38.5% reduction |
|
|
145
|
+
| **AiOutlineAlert** (ai) | 0.023 ms | 0.014 ms | 38.3% reduction |
|
|
146
|
+
| **BsAlarm** (bs) | 0.027 ms | 0.014 ms | 47.4% reduction |
|
|
147
|
+
| **RiAnchor** (ri) | 0.023 ms | 0.014 ms | 38.6% reduction |
|
|
148
|
+
| **CgArrows** (cg) | 0.029 ms | 0.014 ms | 52.0% reduction |
|
|
149
|
+
| **HiAcademicCap** (hi) | 0.023 ms | 0.014 ms | 38.6% reduction |
|
|
150
|
+
| **SiTypescript** (si) | 0.023 ms | 0.014 ms | 39.7% reduction |
|
|
151
|
+
| **TiThLarge** (ti) | 0.023 ms | 0.014 ms | 40.0% reduction |
|
|
152
|
+
|
|
153
|
+
* **Test details / machine:** **Lenovo Legion 5 Pro 16ACH6H** (Ryzen 7 5800H — 8 cores / 16 threads, base ≈ 3.2 GHz,
|
|
154
|
+
turbo ≈ 4.4 GHz, DDR4-3200 memory); Node.js v24.10.0.
|
|
155
|
+
* Differences will vary based on icons used in your application, but they will generally be the range of 50-75%
|
|
156
|
+
reduction in render time. Larger icons will generate a larger difference.
|
|
157
|
+
|
|
158
|
+
## Installation
|
|
159
|
+
|
|
160
|
+
Install the plugin via npm or yarn:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
npm install --save-dev react-icons-sprite
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Vite
|
|
167
|
+
|
|
168
|
+
Add the plugin to the `plugins` array in your Vite config.
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
// vite.config.ts
|
|
172
|
+
import { defineConfig } from 'vite';
|
|
173
|
+
import { reactIconsSprite } from 'react-icons-sprite/vite';
|
|
174
|
+
|
|
175
|
+
export default defineConfig({
|
|
176
|
+
plugins: [reactIconsSprite()],
|
|
177
|
+
});
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Webpack
|
|
181
|
+
|
|
182
|
+
Add the loader to transform modules that import icons and install the plugin to emit the sprite and rewrite the
|
|
183
|
+
placeholder URL.
|
|
184
|
+
|
|
185
|
+
```js
|
|
186
|
+
// webpack.config.js (v5)
|
|
187
|
+
const path = require('path');
|
|
188
|
+
const { reactIconsSprite } = require('react-icons-sprite/webpack');
|
|
189
|
+
|
|
190
|
+
module.exports = {
|
|
191
|
+
mode: 'production',
|
|
192
|
+
// ... your existing config
|
|
193
|
+
module: {
|
|
194
|
+
rules: [
|
|
195
|
+
{
|
|
196
|
+
test: /\.(mjs|cjs|js|jsx|ts|tsx)$/,
|
|
197
|
+
exclude: /node_modules/,
|
|
198
|
+
use: [
|
|
199
|
+
{
|
|
200
|
+
loader: require.resolve('react-icons-sprite/webpack/loader'),
|
|
201
|
+
},
|
|
202
|
+
// put your ts/tsx loader after ours (e.g. swc-loader or ts-loader)
|
|
203
|
+
],
|
|
204
|
+
},
|
|
205
|
+
],
|
|
206
|
+
},
|
|
207
|
+
plugins: [
|
|
208
|
+
reactIconsSprite({
|
|
209
|
+
// optional: fileName: 'icons.svg'
|
|
210
|
+
}),
|
|
211
|
+
],
|
|
212
|
+
};
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Rsbuild
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
// rsbuild.config.ts
|
|
219
|
+
import { defineConfig } from '@rsbuild/core';
|
|
220
|
+
import { pluginReact } from '@rsbuild/plugin-react';
|
|
221
|
+
import { ReactIconsSpriteWebpackPlugin } from 'react-icons-sprite/webpack';
|
|
222
|
+
|
|
223
|
+
export default defineConfig({
|
|
224
|
+
plugins: [pluginReact()],
|
|
225
|
+
tools: {
|
|
226
|
+
rspack: (config, { env }) => {
|
|
227
|
+
if (env === 'production') {
|
|
228
|
+
config.plugins?.push(new ReactIconsSpriteWebpackPlugin());
|
|
229
|
+
config.module?.rules?.push({
|
|
230
|
+
test: /\.(ts|tsx|js|jsx)$/,
|
|
231
|
+
use: [
|
|
232
|
+
{
|
|
233
|
+
loader: 'react-icons-sprite/webpack/loader',
|
|
234
|
+
},
|
|
235
|
+
],
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
});
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## How it works
|
|
244
|
+
|
|
245
|
+
In **development mode**, the plugin does nothing special. Icons are rendered as they normally would from your icon
|
|
246
|
+
library. This keeps hot module replacement (HMR) snappy — there’s no extra parsing of the codebase or regenerating of
|
|
247
|
+
the sprite on every save. If the plugin were to build the sprite during dev, it would need to constantly scan for icon
|
|
248
|
+
imports and rebuild the sheet, which is expensive and slows down iteration. So, in dev, you get the normal component
|
|
249
|
+
behavior.
|
|
250
|
+
|
|
251
|
+
In **build mode**, the plugin transforms your code. It parses each module, looks for imports from supported React icon
|
|
252
|
+
packages, and rewrites the JSX. Instead of rendering full inline `<svg>` trees, it replaces them with
|
|
253
|
+
`<ReactIconsSpriteIcon iconId="..." />`. While doing this, it collects every unique icon used across the project. After
|
|
254
|
+
the bundling step, the plugin renders all those icons once to static markup and generates a single SVG file containing
|
|
255
|
+
`<symbol>` definitions for each one. Finally, it rewrites your bundle to point every `<ReactIconsSpriteIcon>` at that
|
|
256
|
+
spritesheet using a `<use>` tag.
|
|
257
|
+
|
|
258
|
+
The result: during development you keep fast feedback loops, and in production you ship a single optimized sprite file
|
|
259
|
+
with lightweight `<use>` references.
|
|
260
|
+
|
|
261
|
+
### Usage with react-router framework mode
|
|
262
|
+
|
|
263
|
+
`react-router`'s framework mode allows you to pre-render pages. In order for `react-icons-sprite` to work with
|
|
264
|
+
pre-rendered pages, include the bellow `buildEnd` hook.
|
|
265
|
+
|
|
266
|
+
```ts
|
|
267
|
+
// react-router.config.ts
|
|
268
|
+
import type { Config } from '@react-router/dev/config';
|
|
269
|
+
import { REACT_ICONS_SPRITE_URL_PLACEHOLDER } from 'react-icons-sprite';
|
|
270
|
+
|
|
271
|
+
export const replaceReactIconsSpritePlaceholdersOnPreRenderedPages: NonNullable<
|
|
272
|
+
Config['buildEnd']
|
|
273
|
+
> = async ({ reactRouterConfig }) => {
|
|
274
|
+
const clientDir = resolve('build/client');
|
|
275
|
+
|
|
276
|
+
const [svgSpriteFile] = await Array.fromAsync(
|
|
277
|
+
glob('./build/client/react-icons-sprite-*.svg'),
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
const svgSpriteName = svgSpriteFile.replace(/build[/\\]client[/\\]?/, '/');
|
|
281
|
+
|
|
282
|
+
const preRenderedFileUrls =
|
|
283
|
+
(reactRouterConfig.prerender!.paths as string[]).map((path) => {
|
|
284
|
+
return resolve(clientDir, `.${path}`, 'index.html');
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
await Promise.all(
|
|
288
|
+
preRenderedFileUrls.map(async (filePath) => {
|
|
289
|
+
const content = await readFile(filePath, 'utf8');
|
|
290
|
+
|
|
291
|
+
if (!content.includes(REACT_ICONS_SPRITE_URL_PLACEHOLDER)) {
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const updatedContent = content.replaceAll(
|
|
296
|
+
REACT_ICONS_SPRITE_URL_PLACEHOLDER,
|
|
297
|
+
svgSpriteName,
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
await writeFile(filePath, updatedContent, 'utf8');
|
|
301
|
+
}),
|
|
302
|
+
);
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
const reactRouterConfig: Config = {
|
|
306
|
+
prerender: {
|
|
307
|
+
paths: [
|
|
308
|
+
// Your list of pages to pre-render
|
|
309
|
+
],
|
|
310
|
+
},
|
|
311
|
+
buildEnd: async (args) => {
|
|
312
|
+
await replaceReactIconsSpritePlaceholdersOnPreRenderedPages(args);
|
|
313
|
+
},
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
export default reactRouterConfig;
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
## Contributing
|
|
320
|
+
|
|
321
|
+
Contributions are welcome! Feel free to open an issue or submit a pull request.
|
|
322
|
+
|
|
323
|
+
## License
|
|
324
|
+
|
|
325
|
+
This project is licensed under the MIT License. See the [LICENSE](./LICENSE.md) file for details.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as resolveIconImport, t as computeIconId } from "./compute-icon-id-
|
|
1
|
+
import { i as resolveIconImport, t as computeIconId } from "./compute-icon-id-Xx4G3fva.mjs";
|
|
2
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
@@ -9,7 +9,7 @@ const SVG_INNER_RE = /<svg\b[^>]*>([\s\S]*?)<\/svg>/i;
|
|
|
9
9
|
const VIEWBOX_RE = /viewBox=["']([^"']+)["']/i;
|
|
10
10
|
const SVG_OPEN_RE = /<svg\b([^>]*)>/i;
|
|
11
11
|
const SVG_ATTR_RE = /([:\w-]+)=("[^"]*"|'[^']*')/g;
|
|
12
|
-
const OMITTED_SVG_ATTRIBUTES = new Set([
|
|
12
|
+
const OMITTED_SVG_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
13
13
|
"xmlns",
|
|
14
14
|
"viewBox",
|
|
15
15
|
"width",
|
|
@@ -74,7 +74,10 @@ const resolveFromBaseDir = (specifier, baseDir) => {
|
|
|
74
74
|
if (!packageRoot) return null;
|
|
75
75
|
const packageJson = JSON.parse(readFileSync(path.join(packageRoot, "package.json"), "utf8"));
|
|
76
76
|
const exportTarget = resolveExportTarget(packageJson.exports, parsed.subpath);
|
|
77
|
-
if (exportTarget)
|
|
77
|
+
if (exportTarget) {
|
|
78
|
+
const resolved = resolveFileCandidate(path.join(packageRoot, exportTarget));
|
|
79
|
+
if (resolved) return resolved;
|
|
80
|
+
}
|
|
78
81
|
if (parsed.subpath === ".") {
|
|
79
82
|
const entry = packageJson.module ?? packageJson.main;
|
|
80
83
|
if (entry) return resolveFileCandidate(path.join(packageRoot, entry));
|
|
@@ -100,6 +103,11 @@ const pickExport = (moduleExports, exportName) => {
|
|
|
100
103
|
if (exportName === "default") return moduleExports.default;
|
|
101
104
|
return moduleExports[exportName] ?? moduleExports.default;
|
|
102
105
|
};
|
|
106
|
+
const unwrapNestedDefaultExport = (value) => {
|
|
107
|
+
if (!value || typeof value !== "object" || !("default" in value)) return value;
|
|
108
|
+
if ("$$typeof" in value) return value;
|
|
109
|
+
return value.default;
|
|
110
|
+
};
|
|
103
111
|
const isRenderableComponent = (value) => {
|
|
104
112
|
if (typeof value === "function") return true;
|
|
105
113
|
if (!value || typeof value !== "object") return false;
|
|
@@ -111,6 +119,13 @@ const isFontAwesomeIconDefinition = (value) => {
|
|
|
111
119
|
if (!Array.isArray(icon) || icon.length < 5) return false;
|
|
112
120
|
return typeof icon[0] === "number" && typeof icon[1] === "number";
|
|
113
121
|
};
|
|
122
|
+
const isHugeiconsIconDefinition = (value) => {
|
|
123
|
+
if (!Array.isArray(value)) return false;
|
|
124
|
+
return value.every((item) => {
|
|
125
|
+
if (!Array.isArray(item) || item.length < 2) return false;
|
|
126
|
+
return typeof item[0] === "string" && typeof item[1] === "object";
|
|
127
|
+
});
|
|
128
|
+
};
|
|
114
129
|
const renderFontAwesomeIconDefinition = (iconDefinition) => {
|
|
115
130
|
const [width, height, , , svgPathData] = iconDefinition.icon;
|
|
116
131
|
return {
|
|
@@ -119,9 +134,36 @@ const renderFontAwesomeIconDefinition = (iconDefinition) => {
|
|
|
119
134
|
symbolAttributes: ""
|
|
120
135
|
};
|
|
121
136
|
};
|
|
137
|
+
const renderHugeiconsIconDefinition = (iconDefinition) => {
|
|
138
|
+
const svgMarkup = renderToStaticMarkup(createElement("svg", {
|
|
139
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
140
|
+
viewBox: "0 0 24 24",
|
|
141
|
+
fill: "none",
|
|
142
|
+
color: "currentColor"
|
|
143
|
+
}, [...iconDefinition].sort(([, a], [, b]) => {
|
|
144
|
+
const hasOpacityA = a.opacity !== void 0;
|
|
145
|
+
return b.opacity !== void 0 ? 1 : hasOpacityA ? -1 : 0;
|
|
146
|
+
}).map(([tag, attributes]) => {
|
|
147
|
+
const { key, ...rest } = attributes;
|
|
148
|
+
return createElement(tag, {
|
|
149
|
+
...rest,
|
|
150
|
+
key
|
|
151
|
+
});
|
|
152
|
+
})));
|
|
153
|
+
const svgInner = SVG_INNER_RE.exec(svgMarkup)?.[1];
|
|
154
|
+
if (!svgInner) throw new Error("[react-icons-sprite] Unable to extract SVG content for Hugeicons icon.");
|
|
155
|
+
return {
|
|
156
|
+
symbolBody: svgInner,
|
|
157
|
+
viewBox: "0 0 24 24",
|
|
158
|
+
symbolAttributes: extractSymbolAttributes(svgMarkup)
|
|
159
|
+
};
|
|
160
|
+
};
|
|
122
161
|
const renderIcon = async (pack, exportName, options = {}) => {
|
|
123
|
-
const
|
|
162
|
+
const importPath = resolveIconImport(pack, exportName);
|
|
163
|
+
const imported = await import(resolveImportSpecifier(importPath, options));
|
|
164
|
+
const iconComponent = unwrapNestedDefaultExport(pickExport(imported, exportName));
|
|
124
165
|
if (isFontAwesomeIconDefinition(iconComponent)) return renderFontAwesomeIconDefinition(iconComponent);
|
|
166
|
+
if (isHugeiconsIconDefinition(iconComponent)) return renderHugeiconsIconDefinition(iconComponent);
|
|
125
167
|
if (!isRenderableComponent(iconComponent)) throw new Error(`[react-icons-sprite] Unable to render icon "${exportName}" from "${pack}". Expected a React component export.`);
|
|
126
168
|
const svgMarkup = renderToStaticMarkup(createElement(iconComponent));
|
|
127
169
|
const svgInner = SVG_INNER_RE.exec(svgMarkup)?.[1];
|
|
@@ -41,9 +41,16 @@ const DEFAULT_ICON_SOURCES = [
|
|
|
41
41
|
/^@remixicon\/react$/,
|
|
42
42
|
/^devicons-react$/,
|
|
43
43
|
/^@mui\/icons-material(?:\/.*)?$/,
|
|
44
|
-
/^@carbon\/icons-react
|
|
44
|
+
/^@carbon\/icons-react$/,
|
|
45
|
+
/^@ant-design\/icons(?:\/.*)?$/,
|
|
46
|
+
/^@fluentui\/react-icons(?:\/svg\/[\w-]+)?$/,
|
|
47
|
+
/^@primer\/octicons-react$/,
|
|
48
|
+
/^@hugeicons\/core-free-icons(?:\/.*)?$/
|
|
45
49
|
];
|
|
46
50
|
const phosphorIconPathName = (name) => name.endsWith("Icon") ? name.slice(0, -4) : name;
|
|
51
|
+
const fluentIconPathName = (name) => {
|
|
52
|
+
return kebabCase(name.replace(/(?:12|16|20|24|28|32|48)?(?:Regular|Filled|Light|Resizable|Color)$/, ""));
|
|
53
|
+
};
|
|
47
54
|
const exactResolvers = {
|
|
48
55
|
"lucide-react": (pack, name) => `${pack}/dist/esm/icons/${kebabCase(name)}.mjs`,
|
|
49
56
|
"@radix-ui/react-icons": (pack) => `${pack}/dist/react-icons.esm.js`,
|
|
@@ -54,12 +61,20 @@ const exactResolvers = {
|
|
|
54
61
|
"react-feather": (pack, name) => `${pack}/dist/icons/${kebabCase(name)}.js`,
|
|
55
62
|
"grommet-icons": (pack, name) => `${pack}/icons/${name}.js`,
|
|
56
63
|
"devicons-react": (pack, name) => `${pack}/icons/${name}`,
|
|
57
|
-
"@carbon/icons-react": (pack, name) => `${pack}/es/${name}.js
|
|
64
|
+
"@carbon/icons-react": (pack, name) => `${pack}/es/${name}.js`,
|
|
65
|
+
"@ant-design/icons": (pack, name) => `${pack}/lib/icons/${name}.js`,
|
|
66
|
+
"@fluentui/react-icons": (pack, name) => `${pack}/lib-cjs/atoms/svg/${fluentIconPathName(name)}.js`,
|
|
67
|
+
"@primer/octicons-react": (pack) => pack,
|
|
68
|
+
"@hugeicons/core-free-icons": (pack, name) => `${pack}/${name}`
|
|
58
69
|
};
|
|
59
70
|
const resolveIconImport = (pack, exportName) => {
|
|
60
71
|
const exactResolver = exactResolvers[pack];
|
|
61
72
|
if (exactResolver) return exactResolver(pack, exportName);
|
|
62
73
|
if (/^@mui\/icons-material(?:\/.*)?$/.test(pack)) return pack.split("/").length > 2 ? pack : `${pack}/${exportName}`;
|
|
74
|
+
if (/^@ant-design\/icons\/.+$/.test(pack)) return `@ant-design/icons/lib/icons/${exportName === "default" ? pack.split("/").at(-1) : exportName}.js`;
|
|
75
|
+
const fluentSubpath = /^@fluentui\/react-icons\/svg\/(.+)$/.exec(pack);
|
|
76
|
+
if (fluentSubpath) return `@fluentui/react-icons/lib-cjs/atoms/svg/${fluentSubpath[1]}.js`;
|
|
77
|
+
if (/^@hugeicons\/core-free-icons\/.+$/.test(pack)) return pack;
|
|
63
78
|
if (/^@heroicons\/react\/(?:\d{2})\/(?:outline|solid)$/.test(pack)) return `${pack}/${exportName}`;
|
|
64
79
|
if (/^@fortawesome\/[\w-]+-svg-icons$/.test(pack)) return `${pack}/${exportName}`;
|
|
65
80
|
return pack;
|
package/dist/icon.d.mts
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import { JSX, SVGProps } from "react";
|
|
2
|
-
|
|
3
2
|
//#region src/icon.d.ts
|
|
4
3
|
type IconProps = SVGProps<SVGSVGElement> & {
|
|
5
4
|
iconId: string;
|
|
6
5
|
};
|
|
7
|
-
declare const ReactIconsSpriteIcon: ({
|
|
8
|
-
iconId,
|
|
9
|
-
...rest
|
|
10
|
-
}: IconProps) => JSX.Element;
|
|
6
|
+
declare const ReactIconsSpriteIcon: ({ iconId, ...rest }: IconProps) => JSX.Element;
|
|
11
7
|
//#endregion
|
|
12
8
|
export { IconProps, ReactIconsSpriteIcon };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as normalizePackAlias, r as DEFAULT_ICON_SOURCES } from "./compute-icon-id-
|
|
1
|
+
import { n as normalizePackAlias, r as DEFAULT_ICON_SOURCES } from "./compute-icon-id-Xx4G3fva.mjs";
|
|
2
2
|
import MagicString from "magic-string";
|
|
3
3
|
//#region src/transform/edit-applier.ts
|
|
4
4
|
const applyEdits = (code, edits) => {
|
|
@@ -52,9 +52,13 @@ const fastFilter = (code) => {
|
|
|
52
52
|
const ICON_SOURCE = "react-icons-sprite";
|
|
53
53
|
const ICON_COMPONENT_NAME = "ReactIconsSpriteIcon";
|
|
54
54
|
const FONTAWESOME_REACT_PACK = "@fortawesome/react-fontawesome";
|
|
55
|
+
const HUGEICONS_REACT_PACK = "@hugeicons/react";
|
|
55
56
|
const isFontAwesomeIconPack = (pack) => {
|
|
56
57
|
return /^@fortawesome\/[\w-]+-svg-icons$/.test(pack);
|
|
57
58
|
};
|
|
59
|
+
const isHugeiconsIconPack = (pack) => {
|
|
60
|
+
return /^@hugeicons\/core-free-icons(?:\/.*)?$/.test(pack);
|
|
61
|
+
};
|
|
58
62
|
const sourceMatches = (source, pack) => {
|
|
59
63
|
source.lastIndex = 0;
|
|
60
64
|
return source.test(pack);
|
|
@@ -74,7 +78,8 @@ const parseNamedSpecifiers = (specifier, specifierOffset, imports) => {
|
|
|
74
78
|
let segmentStart = start + 1;
|
|
75
79
|
for (let index = start + 1; index <= end; index += 1) {
|
|
76
80
|
if (index !== end && specifier[index] !== ",") continue;
|
|
77
|
-
const
|
|
81
|
+
const segment = specifier.slice(segmentStart, index);
|
|
82
|
+
const range = trimRange(segment, specifierOffset + segmentStart);
|
|
78
83
|
segmentStart = index + 1;
|
|
79
84
|
if (!range) continue;
|
|
80
85
|
const text = specifier.slice(range[0] - specifierOffset, range[1] - specifierOffset);
|
|
@@ -111,7 +116,8 @@ const scanImportsDetailed = (code, sources) => {
|
|
|
111
116
|
const specifierOffset = matchStart + statement.indexOf(specifier);
|
|
112
117
|
const specifiers = [];
|
|
113
118
|
const braceStart = specifier.indexOf("{");
|
|
114
|
-
const
|
|
119
|
+
const defaultText = (braceStart === -1 ? specifier : specifier.slice(0, braceStart)).replace(/,$/, "");
|
|
120
|
+
const defaultRange = trimRange(defaultText, specifierOffset);
|
|
115
121
|
if (defaultRange && !specifier.slice(defaultRange[0] - specifierOffset, defaultRange[1] - specifierOffset).startsWith("type ")) {
|
|
116
122
|
const local = specifier.slice(defaultRange[0] - specifierOffset, defaultRange[1] - specifierOffset);
|
|
117
123
|
if (local && /^[A-Za-z_$][\w$]*$/.test(local)) specifiers.push({
|
|
@@ -307,6 +313,47 @@ const scanFontAwesomeUsages = (code, symbols, componentLocals) => {
|
|
|
307
313
|
}
|
|
308
314
|
return usages;
|
|
309
315
|
};
|
|
316
|
+
const scanHugeiconsComponents = (code) => {
|
|
317
|
+
const locals = /* @__PURE__ */ new Set();
|
|
318
|
+
IMPORT_RE.lastIndex = 0;
|
|
319
|
+
for (const match of code.matchAll(IMPORT_RE)) {
|
|
320
|
+
const [, specifier, , source] = match;
|
|
321
|
+
if (source !== HUGEICONS_REACT_PACK) continue;
|
|
322
|
+
const specifiers = [];
|
|
323
|
+
parseNamedSpecifiers(specifier, match.index + match[0].indexOf(specifier), specifiers);
|
|
324
|
+
for (const item of specifiers) if (item.exportName === "HugeiconsIcon") locals.add(item.local);
|
|
325
|
+
}
|
|
326
|
+
return locals;
|
|
327
|
+
};
|
|
328
|
+
const scanHugeiconsUsages = (code, symbols, componentLocals) => {
|
|
329
|
+
if (!componentLocals.size) return [];
|
|
330
|
+
const names = [...componentLocals].map(escapeRegExp).join("|");
|
|
331
|
+
const tagRe = new RegExp(`<\\s*(${names})\\b`, "g");
|
|
332
|
+
const usages = [];
|
|
333
|
+
for (const match of code.matchAll(tagRe)) {
|
|
334
|
+
const componentLocal = match[1];
|
|
335
|
+
const componentStart = match.index + match[0].lastIndexOf(componentLocal);
|
|
336
|
+
const tagEnd = findJsxOpeningTagEnd(code, componentStart + componentLocal.length);
|
|
337
|
+
if (tagEnd === -1) continue;
|
|
338
|
+
const attributes = code.slice(componentStart + componentLocal.length, tagEnd);
|
|
339
|
+
const iconMatch = /\sicon\s*=\s*\{\s*([A-Za-z_$][\w$]*)\s*\}/.exec(attributes);
|
|
340
|
+
if (!iconMatch || iconMatch.index === void 0) continue;
|
|
341
|
+
const symbol = symbols.items[iconMatch[1]];
|
|
342
|
+
if (!symbol || !isHugeiconsIconPack(symbol.pack)) continue;
|
|
343
|
+
const attributeStart = componentStart + componentLocal.length + iconMatch.index;
|
|
344
|
+
usages.push({
|
|
345
|
+
componentLocal,
|
|
346
|
+
componentRange: [componentStart, componentStart + componentLocal.length],
|
|
347
|
+
iconAttributeRange: [attributeStart, attributeStart + iconMatch[0].length],
|
|
348
|
+
iconLocal: iconMatch[1],
|
|
349
|
+
pack: symbol.pack,
|
|
350
|
+
exportName: symbol.exportName,
|
|
351
|
+
iconId: symbol.iconId,
|
|
352
|
+
hasIconId: /\biconId\s*=/.test(attributes)
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
return usages;
|
|
356
|
+
};
|
|
310
357
|
const cleanupScannedImports = (code, imports, usedLocals) => {
|
|
311
358
|
const edits = [];
|
|
312
359
|
for (const item of imports) {
|
|
@@ -360,6 +407,35 @@ const cleanupScannedFontAwesomeComponentImports = (code, usedLocals) => {
|
|
|
360
407
|
}
|
|
361
408
|
return edits;
|
|
362
409
|
};
|
|
410
|
+
const cleanupScannedHugeiconsComponentImports = (code, usedLocals) => {
|
|
411
|
+
const edits = [];
|
|
412
|
+
IMPORT_RE.lastIndex = 0;
|
|
413
|
+
for (const match of code.matchAll(IMPORT_RE)) {
|
|
414
|
+
const [statement, specifier, , source] = match;
|
|
415
|
+
if (source !== HUGEICONS_REACT_PACK) continue;
|
|
416
|
+
const specifiers = [];
|
|
417
|
+
parseNamedSpecifiers(specifier, match.index + statement.indexOf(specifier), specifiers);
|
|
418
|
+
const removableSpecifiers = specifiers.filter((item) => item.exportName === "HugeiconsIcon" && usedLocals.has(item.local));
|
|
419
|
+
if (!removableSpecifiers.length) continue;
|
|
420
|
+
if (removableSpecifiers.length === specifiers.length) {
|
|
421
|
+
edits.push({
|
|
422
|
+
type: "remove",
|
|
423
|
+
from: match.index,
|
|
424
|
+
to: extendToLineEnd(code, match.index + statement.length)
|
|
425
|
+
});
|
|
426
|
+
continue;
|
|
427
|
+
}
|
|
428
|
+
for (const specifier of removableSpecifiers) {
|
|
429
|
+
const [from, to] = specifierRemovalRange(code, specifier.range);
|
|
430
|
+
edits.push({
|
|
431
|
+
type: "remove",
|
|
432
|
+
from,
|
|
433
|
+
to
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return edits;
|
|
438
|
+
};
|
|
363
439
|
const extendToLineEnd = (code, end) => {
|
|
364
440
|
let to = end;
|
|
365
441
|
while (to < code.length) {
|
|
@@ -404,6 +480,7 @@ const transformModule = (code, id, register, sources = DEFAULT_ICON_SOURCES, opt
|
|
|
404
480
|
anyReplacements: false
|
|
405
481
|
};
|
|
406
482
|
const hasPotentialFontAwesomeUsage = code.includes(FONTAWESOME_REACT_PACK) && scannedImports.some((item) => isFontAwesomeIconPack(item.pack));
|
|
483
|
+
const hasPotentialHugeiconsUsage = code.includes(HUGEICONS_REACT_PACK) && scannedImports.some((item) => isHugeiconsIconPack(item.pack));
|
|
407
484
|
const table = buildScannedSymbolTable(scannedImports);
|
|
408
485
|
if (!table.size) return {
|
|
409
486
|
code,
|
|
@@ -416,9 +493,11 @@ const transformModule = (code, id, register, sources = DEFAULT_ICON_SOURCES, opt
|
|
|
416
493
|
};
|
|
417
494
|
const used = /* @__PURE__ */ new Set();
|
|
418
495
|
const usedFontAwesomeComponents = /* @__PURE__ */ new Set();
|
|
496
|
+
const usedHugeiconsComponents = /* @__PURE__ */ new Set();
|
|
419
497
|
const jsxScan = scanJsxIconEdits(code, table, spriteIconImport.localName, used, register, code.includes("iconId"));
|
|
420
498
|
const fontAwesomeUsages = hasPotentialFontAwesomeUsage ? scanFontAwesomeUsages(code, table, scanFontAwesomeComponents(code)) : [];
|
|
421
|
-
|
|
499
|
+
const hugeiconsUsages = hasPotentialHugeiconsUsage ? scanHugeiconsUsages(code, table, scanHugeiconsComponents(code)) : [];
|
|
500
|
+
if (jsxScan.count === 0 && !fontAwesomeUsages.length && !hugeiconsUsages.length) return {
|
|
422
501
|
code,
|
|
423
502
|
map: null,
|
|
424
503
|
anyReplacements: false
|
|
@@ -450,13 +529,41 @@ const transformModule = (code, id, register, sources = DEFAULT_ICON_SOURCES, opt
|
|
|
450
529
|
register(usage.pack, usage.exportName);
|
|
451
530
|
}
|
|
452
531
|
}
|
|
532
|
+
const registeredHugeiconsIcons = /* @__PURE__ */ new Set();
|
|
533
|
+
for (const usage of hugeiconsUsages) {
|
|
534
|
+
edits.push({
|
|
535
|
+
type: "replace",
|
|
536
|
+
from: usage.componentRange[0],
|
|
537
|
+
to: usage.componentRange[1],
|
|
538
|
+
value: spriteIconImport.localName
|
|
539
|
+
});
|
|
540
|
+
if (!usage.hasIconId) edits.push({
|
|
541
|
+
type: "insert",
|
|
542
|
+
pos: usage.componentRange[1],
|
|
543
|
+
value: ` iconId="${usage.iconId}"`
|
|
544
|
+
});
|
|
545
|
+
edits.push({
|
|
546
|
+
type: "remove",
|
|
547
|
+
from: usage.iconAttributeRange[0],
|
|
548
|
+
to: consumeTrailingWhitespace(code, usage.iconAttributeRange[1])
|
|
549
|
+
});
|
|
550
|
+
used.add(usage.iconLocal);
|
|
551
|
+
usedHugeiconsComponents.add(usage.componentLocal);
|
|
552
|
+
const key = `${usage.pack}:${usage.exportName}`;
|
|
553
|
+
if (!registeredHugeiconsIcons.has(key)) {
|
|
554
|
+
registeredHugeiconsIcons.add(key);
|
|
555
|
+
register(usage.pack, usage.exportName);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
453
558
|
const cleanupEdits = cleanupScannedImports(code, scannedImports, used);
|
|
454
559
|
const cleanupFontAwesomeEdits = usedFontAwesomeComponents.size ? cleanupScannedFontAwesomeComponentImports(code, usedFontAwesomeComponents) : [];
|
|
455
|
-
const
|
|
560
|
+
const cleanupHugeiconsEdits = usedHugeiconsComponents.size ? cleanupScannedHugeiconsComponentImports(code, usedHugeiconsComponents) : [];
|
|
561
|
+
const canUsePresortedEdits = cleanupFontAwesomeEdits.length === 0 && cleanupHugeiconsEdits.length === 0;
|
|
456
562
|
const allEdits = canUsePresortedEdits ? [...cleanupEdits, ...edits] : [
|
|
457
563
|
...edits,
|
|
458
564
|
...cleanupEdits,
|
|
459
|
-
...cleanupFontAwesomeEdits
|
|
565
|
+
...cleanupFontAwesomeEdits,
|
|
566
|
+
...cleanupHugeiconsEdits
|
|
460
567
|
];
|
|
461
568
|
const importPrefix = `import { ${ICON_COMPONENT_NAME} } from "${ICON_SOURCE}";\n`;
|
|
462
569
|
if (!sourceMap) return {
|
package/dist/vite/plugin.d.mts
CHANGED
package/dist/vite/plugin.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { REACT_ICONS_SPRITE_URL_PLACEHOLDER } from "../index.mjs";
|
|
2
|
-
import { a as createCollector, r as DEFAULT_ICON_SOURCES } from "../compute-icon-id-
|
|
3
|
-
import { t as buildSprite } from "../build-sprite-
|
|
4
|
-
import { t as transformModule } from "../transform-module-
|
|
2
|
+
import { a as createCollector, r as DEFAULT_ICON_SOURCES } from "../compute-icon-id-Xx4G3fva.mjs";
|
|
3
|
+
import { t as buildSprite } from "../build-sprite-CxqvU2LE.mjs";
|
|
4
|
+
import { t as transformModule } from "../transform-module-1am5uV8-.mjs";
|
|
5
5
|
import { createHash } from "node:crypto";
|
|
6
6
|
//#region src/vite/plugin.ts
|
|
7
7
|
const reactIconsSprite = (options = {}) => {
|
package/dist/webpack/loader.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as transformModule } from "../transform-module-
|
|
2
|
-
import { t as collector } from "../collector-
|
|
1
|
+
import { t as transformModule } from "../transform-module-1am5uV8-.mjs";
|
|
2
|
+
import { t as collector } from "../collector-CBRz9R_h.mjs";
|
|
3
3
|
//#region src/webpack/loader.ts
|
|
4
4
|
const reactIconsSpriteLoader = async function(source) {
|
|
5
5
|
if (this.mode === "development") return source;
|
package/dist/webpack/plugin.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { REACT_ICONS_SPRITE_URL_PLACEHOLDER } from "../index.mjs";
|
|
2
|
-
import { t as buildSprite } from "../build-sprite-
|
|
3
|
-
import { t as collector } from "../collector-
|
|
2
|
+
import { t as buildSprite } from "../build-sprite-CxqvU2LE.mjs";
|
|
3
|
+
import { t as collector } from "../collector-CBRz9R_h.mjs";
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
5
|
//#region src/webpack/plugin.ts
|
|
6
6
|
var ReactIconsSpriteWebpackPlugin = class {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://www.schemastore.org/package.json",
|
|
3
3
|
"name": "react-icons-sprite",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.1.1",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "A lightweight Vite, Rsbuild and Webpack plugin for react-icons that builds a single SVG sprite and rewrites icons to <use>, reducing bundle size and runtime overhead.",
|
|
7
7
|
"author": "Jure Rotar <hello@jurerotar.com>",
|
|
@@ -46,7 +46,9 @@
|
|
|
46
46
|
}
|
|
47
47
|
},
|
|
48
48
|
"files": [
|
|
49
|
-
"dist"
|
|
49
|
+
"dist",
|
|
50
|
+
"README.md",
|
|
51
|
+
"LICENSE.md"
|
|
50
52
|
],
|
|
51
53
|
"scripts": {
|
|
52
54
|
"build": "tsdown",
|
|
@@ -55,12 +57,33 @@
|
|
|
55
57
|
"format:check": "biome format --no-errors-on-unmatched",
|
|
56
58
|
"lint": "biome lint --write --no-errors-on-unmatched",
|
|
57
59
|
"lint:check": "biome lint --no-errors-on-unmatched",
|
|
58
|
-
"type-check": "
|
|
60
|
+
"type-check": "tsc",
|
|
59
61
|
"test": "vitest",
|
|
60
62
|
"prepublishOnly": "npm run build",
|
|
61
63
|
"release": "npm publish --access public"
|
|
62
64
|
},
|
|
63
65
|
"peerDependencies": {
|
|
66
|
+
"@ant-design/icons": ">=6",
|
|
67
|
+
"@carbon/icons-react": ">=11",
|
|
68
|
+
"@fluentui/react-icons": ">=2",
|
|
69
|
+
"@fortawesome/free-solid-svg-icons": ">=7",
|
|
70
|
+
"@fortawesome/react-fontawesome": ">=3",
|
|
71
|
+
"@heroicons/react": ">=2",
|
|
72
|
+
"@hugeicons/core-free-icons": ">=4",
|
|
73
|
+
"@hugeicons/react": ">=1",
|
|
74
|
+
"@mui/icons-material": ">=9",
|
|
75
|
+
"@phosphor-icons/react": ">=2",
|
|
76
|
+
"@primer/octicons-react": ">=19",
|
|
77
|
+
"@radix-ui/react-icons": ">=1",
|
|
78
|
+
"@remixicon/react": ">=4",
|
|
79
|
+
"@tabler/icons-react": ">=3",
|
|
80
|
+
"devicons-react": ">=1",
|
|
81
|
+
"grommet-icons": ">=4",
|
|
82
|
+
"lucide-react": ">=1",
|
|
83
|
+
"phosphor-react": ">=1",
|
|
84
|
+
"react-bootstrap-icons": ">=1",
|
|
85
|
+
"react-feather": ">=2",
|
|
86
|
+
"react-icons": ">=5",
|
|
64
87
|
"react": ">= 16",
|
|
65
88
|
"react-dom": ">= 16"
|
|
66
89
|
},
|
|
@@ -68,17 +91,15 @@
|
|
|
68
91
|
"magic-string": "0.30.21"
|
|
69
92
|
},
|
|
70
93
|
"devDependencies": {
|
|
71
|
-
"@
|
|
72
|
-
"@types/node": "25.9.1",
|
|
94
|
+
"@types/node": "26.1.1",
|
|
73
95
|
"@types/react-dom": "19.2.3",
|
|
74
|
-
"
|
|
75
|
-
"react": "19.2.
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
"
|
|
79
|
-
"
|
|
80
|
-
"
|
|
81
|
-
"webpack": "5.107.1"
|
|
96
|
+
"react": "19.2.7",
|
|
97
|
+
"react-dom": "19.2.7",
|
|
98
|
+
"tsdown": "0.22.7",
|
|
99
|
+
"typescript": "7.0.2",
|
|
100
|
+
"vite": "8.1.4",
|
|
101
|
+
"vitest": "4.1.10",
|
|
102
|
+
"webpack": "5.108.4"
|
|
82
103
|
},
|
|
83
104
|
"keywords": [
|
|
84
105
|
"vite",
|
|
@@ -106,11 +127,81 @@
|
|
|
106
127
|
"@fortawesome/free-solid-svg-icons",
|
|
107
128
|
"@mui/icons-material",
|
|
108
129
|
"@carbon/icons-react",
|
|
130
|
+
"@ant-design/icons",
|
|
131
|
+
"@fluentui/react-icons",
|
|
132
|
+
"@primer/octicons-react",
|
|
133
|
+
"@hugeicons/react",
|
|
134
|
+
"@hugeicons/core-free-icons",
|
|
109
135
|
"react",
|
|
110
136
|
"icons",
|
|
111
137
|
"svg",
|
|
112
138
|
"svg-sprite",
|
|
113
139
|
"spritesheet",
|
|
114
140
|
"icon-sprite"
|
|
115
|
-
]
|
|
141
|
+
],
|
|
142
|
+
"peerDependenciesMeta": {
|
|
143
|
+
"@ant-design/icons": {
|
|
144
|
+
"optional": true
|
|
145
|
+
},
|
|
146
|
+
"@carbon/icons-react": {
|
|
147
|
+
"optional": true
|
|
148
|
+
},
|
|
149
|
+
"@fluentui/react-icons": {
|
|
150
|
+
"optional": true
|
|
151
|
+
},
|
|
152
|
+
"@fortawesome/free-solid-svg-icons": {
|
|
153
|
+
"optional": true
|
|
154
|
+
},
|
|
155
|
+
"@fortawesome/react-fontawesome": {
|
|
156
|
+
"optional": true
|
|
157
|
+
},
|
|
158
|
+
"@heroicons/react": {
|
|
159
|
+
"optional": true
|
|
160
|
+
},
|
|
161
|
+
"@hugeicons/core-free-icons": {
|
|
162
|
+
"optional": true
|
|
163
|
+
},
|
|
164
|
+
"@hugeicons/react": {
|
|
165
|
+
"optional": true
|
|
166
|
+
},
|
|
167
|
+
"@mui/icons-material": {
|
|
168
|
+
"optional": true
|
|
169
|
+
},
|
|
170
|
+
"@phosphor-icons/react": {
|
|
171
|
+
"optional": true
|
|
172
|
+
},
|
|
173
|
+
"@primer/octicons-react": {
|
|
174
|
+
"optional": true
|
|
175
|
+
},
|
|
176
|
+
"@radix-ui/react-icons": {
|
|
177
|
+
"optional": true
|
|
178
|
+
},
|
|
179
|
+
"@remixicon/react": {
|
|
180
|
+
"optional": true
|
|
181
|
+
},
|
|
182
|
+
"@tabler/icons-react": {
|
|
183
|
+
"optional": true
|
|
184
|
+
},
|
|
185
|
+
"devicons-react": {
|
|
186
|
+
"optional": true
|
|
187
|
+
},
|
|
188
|
+
"grommet-icons": {
|
|
189
|
+
"optional": true
|
|
190
|
+
},
|
|
191
|
+
"lucide-react": {
|
|
192
|
+
"optional": true
|
|
193
|
+
},
|
|
194
|
+
"phosphor-react": {
|
|
195
|
+
"optional": true
|
|
196
|
+
},
|
|
197
|
+
"react-bootstrap-icons": {
|
|
198
|
+
"optional": true
|
|
199
|
+
},
|
|
200
|
+
"react-feather": {
|
|
201
|
+
"optional": true
|
|
202
|
+
},
|
|
203
|
+
"react-icons": {
|
|
204
|
+
"optional": true
|
|
205
|
+
}
|
|
206
|
+
}
|
|
116
207
|
}
|