react-icons-sprite 1.3.0 → 1.4.0-rc1
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 +41 -28
- package/dist/build-sprite-DEOFEWm0.mjs +472 -0
- package/dist/{collector-dvzR3TG1.mjs → collector-1bZHiz4N.mjs} +1 -1
- package/dist/compute-icon-id-C42PyoDX.mjs +84 -0
- package/dist/icon.d.mts +3 -4
- package/dist/index.d.mts +2 -2
- package/dist/plugin-options-DYfIgasR.d.mts +17 -0
- package/dist/transform-module-Cabatwdp.mjs +471 -0
- package/dist/vite/plugin.d.mts +4 -5
- package/dist/vite/plugin.mjs +5 -4
- package/dist/webpack/loader.d.mts +4 -1
- package/dist/webpack/loader.mjs +6 -4
- package/dist/webpack/plugin.d.mts +4 -5
- package/dist/webpack/plugin.mjs +3 -3
- package/package.json +44 -31
- package/dist/build-sprite-Dci5SlWn.mjs +0 -217
- package/dist/compute-icon-id-1Gop_fRM.mjs +0 -113
- package/dist/plugin-options-ByGrVpdG.d.mts +0 -9
- package/dist/transform-module-BakS0Tzh.mjs +0 -854
package/README.md
CHANGED
|
@@ -9,36 +9,37 @@ reconcile large, nested SVG trees.
|
|
|
9
9
|
|
|
10
10
|
## Supported icon libraries
|
|
11
11
|
|
|
12
|
-
Out of the box, imports from the following
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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` |
|
|
12
|
+
Out of the box, imports from the following icon sets are detected and transformed:
|
|
13
|
+
|
|
14
|
+
- `react-icons/*` packs (e.g. `react-icons/bi`, `react-icons/fa`, ...)
|
|
15
|
+
- `lucide-react`
|
|
16
|
+
- `@radix-ui/react-icons`
|
|
17
|
+
- `@heroicons/react` (including subpaths)
|
|
18
|
+
- `@tabler/icons-react`
|
|
19
|
+
- `phosphor-react`
|
|
20
|
+
- `@phosphor-icons/react`
|
|
21
|
+
- `react-feather`
|
|
22
|
+
- `react-bootstrap-icons`
|
|
23
|
+
- `grommet-icons`
|
|
24
|
+
- `@remixicon/react`
|
|
25
|
+
- `devicons-react`
|
|
26
|
+
- `@fortawesome/free-solid-svg-icons` and other Font Awesome icon packs, used with `@fortawesome/react-fontawesome`
|
|
27
|
+
- `@mui/icons-material`
|
|
28
|
+
- `@carbon/icons-react`
|
|
29
|
+
- `@ant-design/icons`
|
|
30
|
+
- `@fluentui/react-icons`
|
|
31
|
+
- `@primer/octicons-react`
|
|
32
|
+
- `@hugeicons/core-free-icons`, used with `@hugeicons/react`
|
|
33
|
+
- `@pillage-first/graphics`
|
|
37
34
|
|
|
38
35
|
> [!NOTE]
|
|
39
36
|
> `react-icons-sprite` does not bundle these libraries. You must install whichever icon packages you intend to use in
|
|
40
37
|
> your project.
|
|
41
38
|
|
|
39
|
+
Icon resolvers select the first matching version range using the icon package installed in the importing project.
|
|
40
|
+
This also applies to subpath imports such as `@fluentui/react-icons/svg/add` and `@mui/icons-material/Alarm`.
|
|
41
|
+
Additional layout changes can be supported by adding a `{ range, resolve }` entry in `src/packs/icon-resolvers.ts`.
|
|
42
|
+
|
|
42
43
|
## Motivation
|
|
43
44
|
|
|
44
45
|
By default, when you use an icon library like `react-icons`, each icon is a React component. For example:
|
|
@@ -132,8 +133,15 @@ This is a big win when you’re rendering icons in lists, tables, or maps where
|
|
|
132
133
|
|
|
133
134
|
### Performance comparison
|
|
134
135
|
|
|
135
|
-
|
|
136
|
-
|
|
136
|
+
Build-time transforms use Oxc parsing and targeted scope analysis to preserve comments, strings, and shadowed
|
|
137
|
+
bindings. Raw AST transfer is enabled when Oxc reports runtime support; otherwise standard parsing is used.
|
|
138
|
+
The raw-transfer API is experimental, so the Oxc version is pinned. From the repository root, run
|
|
139
|
+
`npm exec --workspace react-icons-sprite -- vitest bench --run` to benchmark direct icon usages, component references,
|
|
140
|
+
mixed usages, repeated usages with the real icon collector, and pass-through modules without supported icon imports.
|
|
141
|
+
Each transformed icon is registered once per module, including when it is imported under multiple aliases.
|
|
142
|
+
Import sources are checked once per declaration, aliases share icon metadata, and replacement strings are built
|
|
143
|
+
only for icons that are used. The benchmarks include 1, 12, 60, and 240 usages, plus a large import with one usage;
|
|
144
|
+
transforms per second depend heavily on module size, so compare the same fixture when measuring changes.
|
|
137
145
|
|
|
138
146
|
| icon (pack) | react-icons icon render mean time | react-icons-sprite icon render mean time | Relative difference |
|
|
139
147
|
|-------------------------|----------------------------------:|-----------------------------------------:|--------------------:|
|
|
@@ -202,6 +210,7 @@ module.exports = {
|
|
|
202
210
|
use: [
|
|
203
211
|
{
|
|
204
212
|
loader: require.resolve('react-icons-sprite/webpack/loader'),
|
|
213
|
+
// optional: options: { exclude: /^@pillage-first\/graphics$/ },
|
|
205
214
|
},
|
|
206
215
|
// put your ts/tsx loader after ours (e.g. swc-loader or ts-loader)
|
|
207
216
|
],
|
|
@@ -218,9 +227,13 @@ module.exports = {
|
|
|
218
227
|
|
|
219
228
|
### Options
|
|
220
229
|
|
|
221
|
-
All options are optional
|
|
230
|
+
All options are optional. In Vite, pass them to `reactIconsSprite()`. In Webpack and Rsbuild,
|
|
231
|
+
pass `outputDir` to the plugin and `exclude` to the loader options:
|
|
222
232
|
|
|
223
233
|
- `outputDir`: directory inside the bundler output where the sprite asset is emitted.
|
|
234
|
+
- `exclude`: a `RegExp` or list of regular expressions for icon import sources
|
|
235
|
+
that must remain inline. Use this for icons whose CSS targets child SVG
|
|
236
|
+
elements; those selectors cannot cross a sprite `<use>` reference.
|
|
224
237
|
|
|
225
238
|
### Rsbuild
|
|
226
239
|
|
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
import { r as kebabCase, t as computeIconId } from "./compute-icon-id-C42PyoDX.mjs";
|
|
2
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
import { createElement } from "react";
|
|
6
|
+
import { renderToStaticMarkup } from "react-dom/server";
|
|
7
|
+
import satisfies from "semver/functions/satisfies.js";
|
|
8
|
+
//#region src/packs/icon-resolvers.ts
|
|
9
|
+
const phosphorIconPathName = (name) => name.endsWith("Icon") ? name.slice(0, -4) : name;
|
|
10
|
+
const fluentIconPathName = (name) => {
|
|
11
|
+
const unsizedName = name.replace(/(?:12|16|20|24|28|32|48)?(?:Regular|Filled|Light|Resizable|Color)$/, "");
|
|
12
|
+
return kebabCase(unsizedName);
|
|
13
|
+
};
|
|
14
|
+
const lucideIconPathName = (name) => kebabCase(name.replace(/^Lucide/, "").replace(/Icon$/, "")).replace(/([a-z])(\d)/g, "$1-$2");
|
|
15
|
+
const exactResolvers = {
|
|
16
|
+
"lucide-react": [
|
|
17
|
+
{
|
|
18
|
+
range: ">=1.9.0 || >=0.262.0 <=0.263.1",
|
|
19
|
+
resolve: (pack, name) => `${pack}/dist/esm/icons/${lucideIconPathName(name)}.mjs`
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
range: ">=0.104.0",
|
|
23
|
+
resolve: (pack, name) => `${pack}/dist/esm/icons/${lucideIconPathName(name)}.js`
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
range: ">=0.0.0",
|
|
27
|
+
resolve: (pack) => `${pack}/dist/cjs/lucide-react.js`
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"@radix-ui/react-icons": [{
|
|
31
|
+
range: ">=2.0.0-0",
|
|
32
|
+
resolve: (pack, name) => `${pack}/dist/${name}.js`
|
|
33
|
+
}, {
|
|
34
|
+
range: ">=1.0.0",
|
|
35
|
+
resolve: (pack) => `${pack}/dist/react-icons.esm.js`
|
|
36
|
+
}],
|
|
37
|
+
"@tabler/icons-react": [
|
|
38
|
+
{
|
|
39
|
+
range: ">=3.0.0-beta.2",
|
|
40
|
+
resolve: (pack, name) => `${pack}/dist/esm/icons/${name}.mjs`
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
range: ">=2.1.0",
|
|
44
|
+
resolve: (pack, name) => `${pack}/dist/esm/icons/${name}.js`
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
range: ">=2.0.0-0",
|
|
48
|
+
resolve: (pack, name) => `${pack}/dist/esm/icons/${kebabCase(name.replace(/^Icon/, ""))}.js`
|
|
49
|
+
}
|
|
50
|
+
],
|
|
51
|
+
"@phosphor-icons/react": [
|
|
52
|
+
{
|
|
53
|
+
range: "2.1.8-rc.0",
|
|
54
|
+
resolve: (pack) => `${pack}/dist/index.cjs.js`
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
range: ">=2.1.8-rc.0",
|
|
58
|
+
resolve: (pack, name) => `${pack}/dist/ssr/${phosphorIconPathName(name)}.es.js`
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
range: "2.0.12",
|
|
62
|
+
resolve: (pack, name) => `${pack}/dist/csr/${phosphorIconPathName(name)}.mjs`
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
range: ">=2.1.3 || >=2.0.11 <2.1.0",
|
|
66
|
+
resolve: (pack, name) => `${pack}/dist/ssr/${phosphorIconPathName(name)}.mjs`
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
range: ">=2.1.0",
|
|
70
|
+
resolve: (pack, name) => `${pack}/dist/icons/${phosphorIconPathName(name)}.mjs`
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
range: ">=2.0.3",
|
|
74
|
+
resolve: (pack, name) => `${pack}/dist/icons/${phosphorIconPathName(name)}.es.js`
|
|
75
|
+
}
|
|
76
|
+
],
|
|
77
|
+
"phosphor-react": [{
|
|
78
|
+
range: ">=1.0.1",
|
|
79
|
+
resolve: (pack, name) => `${pack}/dist/icons/${name}.esm.js`
|
|
80
|
+
}, {
|
|
81
|
+
range: ">=0.1.0",
|
|
82
|
+
resolve: (pack) => `${pack}/dist/index.js`
|
|
83
|
+
}],
|
|
84
|
+
"react-bootstrap-icons": [{
|
|
85
|
+
range: ">=1.0.0-0",
|
|
86
|
+
resolve: (pack, name) => `${pack}/dist/icons/${kebabCase(name)}.js`
|
|
87
|
+
}],
|
|
88
|
+
"react-feather": [{
|
|
89
|
+
range: ">=1.0.2 <1.0.9 || >=1.1.0",
|
|
90
|
+
resolve: (pack, name) => `${pack}/dist/icons/${kebabCase(name)}.js`
|
|
91
|
+
}],
|
|
92
|
+
"grommet-icons": [{
|
|
93
|
+
range: ">=0.1.0",
|
|
94
|
+
resolve: (pack, name) => `${pack}/icons/${name}.js`
|
|
95
|
+
}],
|
|
96
|
+
"devicons-react": [
|
|
97
|
+
{
|
|
98
|
+
range: "1.2.3-canary-0.0.1 || 1.2.3-canary-0.0.2 || >=1.2.3-canary-0.0.5 <1.2.3 || >=1.2.4",
|
|
99
|
+
resolve: (pack, name) => `${pack}/lib/icons/${name}.js`
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
range: ">=1.1.2 <1.1.5 || >=1.1.7 <1.2.4",
|
|
103
|
+
resolve: (pack, name) => `${pack}/icons/${name}.js`
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
range: "1.0.7 || >=1.0.9 <1.1.2",
|
|
107
|
+
resolve: (pack) => `${pack}/index.js`
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
range: ">=0.1.0 <1.0.7 || 1.0.8",
|
|
111
|
+
resolve: (pack) => `${pack}/dist/index.js`
|
|
112
|
+
}
|
|
113
|
+
],
|
|
114
|
+
"@carbon/icons-react": [{
|
|
115
|
+
range: ">=11.0.0",
|
|
116
|
+
resolve: (pack, name) => `${pack}/lib/${name}.js`
|
|
117
|
+
}, {
|
|
118
|
+
range: ">=10.4.0",
|
|
119
|
+
resolve: (pack) => `${pack}/lib/index.js`
|
|
120
|
+
}],
|
|
121
|
+
"@ant-design/icons": [{
|
|
122
|
+
range: ">=4.0.0",
|
|
123
|
+
resolve: (pack, name) => `@ant-design/icons/lib/icons/${name === "default" ? pack.split("/").at(-1) : name}.js`
|
|
124
|
+
}],
|
|
125
|
+
"@fluentui/react-icons": [
|
|
126
|
+
{
|
|
127
|
+
range: ">=2.0.336",
|
|
128
|
+
resolve: (pack, name) => `@fluentui/react-icons/lib-cjs/atoms/svg/${pack.split("/svg/")[1] ?? fluentIconPathName(name)}.cjs`
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
range: ">=2.0.314",
|
|
132
|
+
resolve: (pack, name) => `@fluentui/react-icons/lib-cjs/atoms/svg/${pack.split("/svg/")[1] ?? fluentIconPathName(name)}.js`
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
range: ">=2.0.0",
|
|
136
|
+
resolve: () => "@fluentui/react-icons/lib-cjs/index.js"
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
range: ">=1.1.110",
|
|
140
|
+
resolve: (pack, name) => `${pack}/lib/esm/components/${name}.js`
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
range: ">=0.1.1 <1.0.0",
|
|
144
|
+
resolve: (pack, name) => `${pack}/lib-commonjs/components/${name}.js`
|
|
145
|
+
}
|
|
146
|
+
],
|
|
147
|
+
"@primer/octicons-react": [{
|
|
148
|
+
range: ">=19.30.0",
|
|
149
|
+
resolve: (pack, name) => `${pack}/dist/icons/${name}.mjs`
|
|
150
|
+
}, {
|
|
151
|
+
range: ">=10.0.0",
|
|
152
|
+
resolve: (pack) => `${pack}/dist/index.umd.js`
|
|
153
|
+
}],
|
|
154
|
+
"@hugeicons/core-free-icons": [
|
|
155
|
+
{
|
|
156
|
+
range: ">=1.2.0",
|
|
157
|
+
resolve: (pack, name) => pack === "@hugeicons/core-free-icons" ? `${pack}/${name}` : pack
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
range: ">=1.0.4",
|
|
161
|
+
resolve: () => "@hugeicons/core-free-icons/dist/esm/index.js"
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
range: ">=1.0.0",
|
|
165
|
+
resolve: (pack, name) => `@hugeicons/core-free-icons/dist/${name === "default" ? pack.split("/").at(-1) : name}.mjs`
|
|
166
|
+
}
|
|
167
|
+
],
|
|
168
|
+
"@mui/icons-material": [{
|
|
169
|
+
range: ">=5.0.0-0",
|
|
170
|
+
resolve: (pack, name) => pack === "@mui/icons-material" ? `${pack}/${name}` : pack
|
|
171
|
+
}],
|
|
172
|
+
"@heroicons/react": [{
|
|
173
|
+
range: ">=2.0.0-0",
|
|
174
|
+
resolve: (pack, name) => /^@heroicons\/react\/\d{2}\/(?:outline|solid)$/.test(pack) ? `${pack}/${name}` : pack
|
|
175
|
+
}, {
|
|
176
|
+
range: ">=0.1.0",
|
|
177
|
+
resolve: (pack, name) => /^@heroicons\/react\/(?:outline|solid)$/.test(pack) ? `${pack}/${name}.js` : pack
|
|
178
|
+
}],
|
|
179
|
+
"@remixicon/react": [{
|
|
180
|
+
range: ">=0.0.3 <4.1.0 || >=4.1.1",
|
|
181
|
+
resolve: (pack) => `${pack}/index.js`
|
|
182
|
+
}, {
|
|
183
|
+
range: "0.0.2",
|
|
184
|
+
resolve: (pack, name) => `${pack}/cjs/${name}.cjs`
|
|
185
|
+
}],
|
|
186
|
+
"@pillage-first/graphics": [{
|
|
187
|
+
range: ">=0.0.10",
|
|
188
|
+
resolve: (pack) => `${pack}/dist/index.mjs`
|
|
189
|
+
}, {
|
|
190
|
+
range: ">=0.0.0",
|
|
191
|
+
resolve: (pack) => `${pack}/dist/index.js`
|
|
192
|
+
}],
|
|
193
|
+
"react-icons": [{
|
|
194
|
+
range: ">=5.0.0",
|
|
195
|
+
resolve: (pack) => pack
|
|
196
|
+
}, {
|
|
197
|
+
range: ">=3.0.0",
|
|
198
|
+
resolve: (pack) => `${pack}/index.js`
|
|
199
|
+
}]
|
|
200
|
+
};
|
|
201
|
+
const fontAwesomeResolvers = [{
|
|
202
|
+
range: ">=5.1.0-0",
|
|
203
|
+
resolve: (pack, name) => `${pack}/${name}.js`
|
|
204
|
+
}];
|
|
205
|
+
const resolversFor = (pack) => {
|
|
206
|
+
const packageName = pack.split("/").slice(0, pack.startsWith("@") ? 2 : 1).join("/");
|
|
207
|
+
return /^@fortawesome\/[\w-]+-svg-icons$/.test(packageName) ? fontAwesomeResolvers : Object.hasOwn(exactResolvers, packageName) ? exactResolvers[packageName] : void 0;
|
|
208
|
+
};
|
|
209
|
+
const hasVersionedIconResolver = (pack) => resolversFor(pack) !== void 0;
|
|
210
|
+
const selectIconImportResolver = (pack, version) => {
|
|
211
|
+
return (version ? resolversFor(pack)?.find(({ range }) => satisfies(version, range, { includePrerelease: true })) : void 0)?.resolve ?? ((source) => source);
|
|
212
|
+
};
|
|
213
|
+
//#endregion
|
|
214
|
+
//#region src/sprite/render-icon.ts
|
|
215
|
+
const SVG_INNER_RE = /<svg\b[^>]*>([\s\S]*?)<\/svg>/i;
|
|
216
|
+
const VIEWBOX_RE = /viewBox=["']([^"']+)["']/i;
|
|
217
|
+
const SVG_OPEN_RE = /<svg\b([^>]*)>/i;
|
|
218
|
+
const SVG_ATTR_RE = /([:\w-]+)=("[^"]*"|'[^']*')/g;
|
|
219
|
+
const OMITTED_SVG_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
220
|
+
"xmlns",
|
|
221
|
+
"viewBox",
|
|
222
|
+
"width",
|
|
223
|
+
"height",
|
|
224
|
+
"id"
|
|
225
|
+
]);
|
|
226
|
+
const readPackageInfo = (packageRoot, packageName, cache) => {
|
|
227
|
+
let info = cache.packages.get(packageRoot);
|
|
228
|
+
if (!info) {
|
|
229
|
+
const manifest = JSON.parse(readFileSync(path.join(packageRoot, "package.json"), "utf8"));
|
|
230
|
+
info = {
|
|
231
|
+
manifest,
|
|
232
|
+
resolveImport: selectIconImportResolver(packageName, manifest.version)
|
|
233
|
+
};
|
|
234
|
+
cache.packages.set(packageRoot, info);
|
|
235
|
+
}
|
|
236
|
+
return info;
|
|
237
|
+
};
|
|
238
|
+
const parsePackageSpecifier = (specifier) => {
|
|
239
|
+
if (specifier.startsWith(".") || path.isAbsolute(specifier)) return null;
|
|
240
|
+
const parts = specifier.split("/");
|
|
241
|
+
const packageName = specifier.startsWith("@") ? `${parts[0]}/${parts[1]}` : parts[0];
|
|
242
|
+
const rest = parts.slice(specifier.startsWith("@") ? 2 : 1).join("/");
|
|
243
|
+
return {
|
|
244
|
+
packageName,
|
|
245
|
+
subpath: rest ? `./${rest}` : "."
|
|
246
|
+
};
|
|
247
|
+
};
|
|
248
|
+
const findPackageRoot = (packageName, baseDir, cache) => {
|
|
249
|
+
let current = path.resolve(baseDir);
|
|
250
|
+
const key = JSON.stringify([packageName, current]);
|
|
251
|
+
const cachedRoot = cache.roots.get(key);
|
|
252
|
+
if (cachedRoot !== void 0) return cachedRoot;
|
|
253
|
+
while (true) {
|
|
254
|
+
const candidate = path.join(current, "node_modules", packageName);
|
|
255
|
+
if (existsSync(path.join(candidate, "package.json"))) {
|
|
256
|
+
cache.roots.set(key, candidate);
|
|
257
|
+
return candidate;
|
|
258
|
+
}
|
|
259
|
+
const parent = path.dirname(current);
|
|
260
|
+
if (parent === current) {
|
|
261
|
+
cache.roots.set(key, null);
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
current = parent;
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
const pickExportTarget = (value) => {
|
|
268
|
+
if (typeof value === "string") return value;
|
|
269
|
+
if (!value || typeof value !== "object") return null;
|
|
270
|
+
const record = value;
|
|
271
|
+
return pickExportTarget(record.import) ?? pickExportTarget(record.default) ?? pickExportTarget(record.module) ?? pickExportTarget(record.require);
|
|
272
|
+
};
|
|
273
|
+
const resolveExportTarget = (exportsField, subpath) => {
|
|
274
|
+
if (typeof exportsField === "string" || Array.isArray(exportsField)) return subpath === "." ? pickExportTarget(exportsField) : null;
|
|
275
|
+
if (!exportsField || typeof exportsField !== "object") return null;
|
|
276
|
+
const exportsRecord = exportsField;
|
|
277
|
+
const exact = pickExportTarget(exportsRecord[subpath]);
|
|
278
|
+
if (exact) return exact;
|
|
279
|
+
for (const [key, value] of Object.entries(exportsRecord)) {
|
|
280
|
+
if (!key.includes("*")) continue;
|
|
281
|
+
const [prefix, suffix] = key.split("*");
|
|
282
|
+
if (!subpath.startsWith(prefix) || !subpath.endsWith(suffix)) continue;
|
|
283
|
+
const matched = subpath.slice(prefix.length, subpath.length - suffix.length);
|
|
284
|
+
const target = pickExportTarget(value);
|
|
285
|
+
if (target) return target.replaceAll("*", matched);
|
|
286
|
+
}
|
|
287
|
+
return null;
|
|
288
|
+
};
|
|
289
|
+
const fileExists = (filePath) => {
|
|
290
|
+
try {
|
|
291
|
+
return statSync(filePath).isFile();
|
|
292
|
+
} catch {
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
const resolveFileCandidate = (filePath) => {
|
|
297
|
+
return [
|
|
298
|
+
filePath,
|
|
299
|
+
`${filePath}.mjs`,
|
|
300
|
+
`${filePath}.js`,
|
|
301
|
+
`${filePath}.cjs`,
|
|
302
|
+
`${filePath}.jsx`,
|
|
303
|
+
`${filePath}.ts`,
|
|
304
|
+
`${filePath}.tsx`,
|
|
305
|
+
path.join(filePath, "index.mjs"),
|
|
306
|
+
path.join(filePath, "index.js"),
|
|
307
|
+
path.join(filePath, "index.cjs"),
|
|
308
|
+
path.join(filePath, "index.jsx"),
|
|
309
|
+
path.join(filePath, "index.ts"),
|
|
310
|
+
path.join(filePath, "index.tsx")
|
|
311
|
+
].find(fileExists) ?? null;
|
|
312
|
+
};
|
|
313
|
+
const resolveFromPackageRoot = (packageRoot, subpath, packageJson) => {
|
|
314
|
+
const exportTarget = resolveExportTarget(packageJson.exports, subpath);
|
|
315
|
+
if (exportTarget) {
|
|
316
|
+
const resolved = resolveFileCandidate(path.join(packageRoot, exportTarget));
|
|
317
|
+
if (resolved) return resolved;
|
|
318
|
+
}
|
|
319
|
+
if (subpath === ".") {
|
|
320
|
+
const entry = packageJson.module ?? packageJson.main;
|
|
321
|
+
if (entry) return resolveFileCandidate(path.join(packageRoot, entry));
|
|
322
|
+
}
|
|
323
|
+
return resolveFileCandidate(path.join(packageRoot, subpath));
|
|
324
|
+
};
|
|
325
|
+
const resolveFromBaseDir = (specifier, baseDir, cache) => {
|
|
326
|
+
const parsed = parsePackageSpecifier(specifier);
|
|
327
|
+
if (!parsed) return null;
|
|
328
|
+
const packageRoot = findPackageRoot(parsed.packageName, baseDir, cache);
|
|
329
|
+
return packageRoot ? resolveFromPackageRoot(packageRoot, parsed.subpath, readPackageInfo(packageRoot, parsed.packageName, cache).manifest) : null;
|
|
330
|
+
};
|
|
331
|
+
const resolveImportSpecifier = (specifier, options, cache) => {
|
|
332
|
+
if (path.isAbsolute(specifier)) {
|
|
333
|
+
const resolved = resolveFileCandidate(specifier);
|
|
334
|
+
return pathToFileURL(resolved ?? specifier).href;
|
|
335
|
+
}
|
|
336
|
+
if (specifier.startsWith(".") && options.importer) {
|
|
337
|
+
const importerPath = options.importer.split("?", 1)[0];
|
|
338
|
+
const resolved = resolveFileCandidate(path.resolve(path.dirname(importerPath), specifier));
|
|
339
|
+
if (resolved) return pathToFileURL(resolved).href;
|
|
340
|
+
}
|
|
341
|
+
if (!options.baseDir) return specifier;
|
|
342
|
+
const resolved = resolveFromBaseDir(specifier, options.baseDir, cache);
|
|
343
|
+
return resolved ? pathToFileURL(resolved).href : specifier;
|
|
344
|
+
};
|
|
345
|
+
const extractSymbolAttributes = (svgMarkup) => {
|
|
346
|
+
const openingAttributes = SVG_OPEN_RE.exec(svgMarkup)?.[1];
|
|
347
|
+
if (!openingAttributes) return "";
|
|
348
|
+
const attributes = [];
|
|
349
|
+
for (const [, name, value] of openingAttributes.matchAll(SVG_ATTR_RE)) {
|
|
350
|
+
if (OMITTED_SVG_ATTRIBUTES.has(name)) continue;
|
|
351
|
+
attributes.push(`${name}=${value}`);
|
|
352
|
+
}
|
|
353
|
+
return attributes.join(" ");
|
|
354
|
+
};
|
|
355
|
+
const pickExport = (moduleExports, exportName) => {
|
|
356
|
+
if (exportName === "default") return moduleExports.default;
|
|
357
|
+
const defaultExport = moduleExports.default;
|
|
358
|
+
const nestedExport = defaultExport && typeof defaultExport === "object" ? defaultExport[exportName] : void 0;
|
|
359
|
+
return moduleExports[exportName] ?? nestedExport ?? defaultExport;
|
|
360
|
+
};
|
|
361
|
+
const unwrapNestedDefaultExport = (value) => {
|
|
362
|
+
if (!value || typeof value !== "object" || !("default" in value)) return value;
|
|
363
|
+
if ("$$typeof" in value) return value;
|
|
364
|
+
return value.default;
|
|
365
|
+
};
|
|
366
|
+
const isRenderableComponent = (value) => {
|
|
367
|
+
if (typeof value === "function") return true;
|
|
368
|
+
if (!value || typeof value !== "object") return false;
|
|
369
|
+
return "$$typeof" in value;
|
|
370
|
+
};
|
|
371
|
+
const isFontAwesomeIconDefinition = (value) => {
|
|
372
|
+
if (!value || typeof value !== "object") return false;
|
|
373
|
+
const icon = value.icon;
|
|
374
|
+
if (!Array.isArray(icon) || icon.length < 5) return false;
|
|
375
|
+
return typeof icon[0] === "number" && typeof icon[1] === "number";
|
|
376
|
+
};
|
|
377
|
+
const isHugeiconsIconDefinition = (value) => {
|
|
378
|
+
if (!Array.isArray(value)) return false;
|
|
379
|
+
return value.every((item) => {
|
|
380
|
+
if (!Array.isArray(item) || item.length < 2) return false;
|
|
381
|
+
return typeof item[0] === "string" && typeof item[1] === "object";
|
|
382
|
+
});
|
|
383
|
+
};
|
|
384
|
+
const renderFontAwesomeIconDefinition = (iconDefinition) => {
|
|
385
|
+
const [width, height, , , svgPathData] = iconDefinition.icon;
|
|
386
|
+
return {
|
|
387
|
+
symbolBody: (Array.isArray(svgPathData) ? svgPathData : [svgPathData]).map((d) => `<path d="${d}"/>`).join(""),
|
|
388
|
+
viewBox: `0 0 ${width} ${height}`,
|
|
389
|
+
symbolAttributes: ""
|
|
390
|
+
};
|
|
391
|
+
};
|
|
392
|
+
const renderHugeiconsIconDefinition = (iconDefinition) => {
|
|
393
|
+
const children = [...iconDefinition].sort(([, a], [, b]) => {
|
|
394
|
+
const hasOpacityA = a.opacity !== void 0;
|
|
395
|
+
return b.opacity !== void 0 ? 1 : hasOpacityA ? -1 : 0;
|
|
396
|
+
}).map(([tag, attributes]) => {
|
|
397
|
+
const { key, ...rest } = attributes;
|
|
398
|
+
return createElement(tag, {
|
|
399
|
+
...rest,
|
|
400
|
+
key
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
const svgMarkup = renderToStaticMarkup(createElement("svg", {
|
|
404
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
405
|
+
viewBox: "0 0 24 24",
|
|
406
|
+
fill: "none",
|
|
407
|
+
color: "currentColor"
|
|
408
|
+
}, children));
|
|
409
|
+
const svgInner = SVG_INNER_RE.exec(svgMarkup)?.[1];
|
|
410
|
+
if (!svgInner) throw new Error("[react-icons-sprite] Unable to extract SVG content for Hugeicons icon.");
|
|
411
|
+
return {
|
|
412
|
+
symbolBody: svgInner,
|
|
413
|
+
viewBox: "0 0 24 24",
|
|
414
|
+
symbolAttributes: extractSymbolAttributes(svgMarkup)
|
|
415
|
+
};
|
|
416
|
+
};
|
|
417
|
+
const renderIconWithCache = async (pack, exportName, options, cache) => {
|
|
418
|
+
let packageRoot = null;
|
|
419
|
+
let packageInfo;
|
|
420
|
+
const packageName = parsePackageSpecifier(pack)?.packageName ?? pack;
|
|
421
|
+
if (!options.importPath && hasVersionedIconResolver(pack)) {
|
|
422
|
+
const baseDir = options.baseDir ?? process.cwd();
|
|
423
|
+
const importerDir = options.importer ? path.dirname(options.importer.split("?", 1)[0]) : baseDir;
|
|
424
|
+
packageRoot = findPackageRoot(packageName, importerDir, cache) ?? findPackageRoot(packageName, baseDir, cache);
|
|
425
|
+
if (packageRoot) packageInfo = readPackageInfo(packageRoot, packageName, cache);
|
|
426
|
+
}
|
|
427
|
+
const importPath = options.importPath ?? packageInfo?.resolveImport(pack, exportName) ?? pack;
|
|
428
|
+
let specifier = importPath;
|
|
429
|
+
if (packageRoot && packageInfo) {
|
|
430
|
+
const subpath = parsePackageSpecifier(importPath)?.subpath ?? ".";
|
|
431
|
+
specifier = resolveFromPackageRoot(packageRoot, subpath, packageInfo.manifest) ?? path.join(packageRoot, subpath === "." ? "index.js" : subpath);
|
|
432
|
+
}
|
|
433
|
+
if (pack === "lucide-react" && packageRoot && importPath.startsWith(`${pack}/`) && !fileExists(specifier)) specifier = path.join(packageRoot, `dist/esm/lucide-react${path.extname(specifier)}`);
|
|
434
|
+
const imported = await import(resolveImportSpecifier(specifier, options, cache));
|
|
435
|
+
const iconComponent = unwrapNestedDefaultExport(pickExport(imported, exportName === "default" && imported.default === void 0 && packageRoot && importPath !== pack && pack !== packageName ? pack.split("/").at(-1) ?? exportName : exportName));
|
|
436
|
+
if (isFontAwesomeIconDefinition(iconComponent)) return renderFontAwesomeIconDefinition(iconComponent);
|
|
437
|
+
if (isHugeiconsIconDefinition(iconComponent)) return renderHugeiconsIconDefinition(iconComponent);
|
|
438
|
+
if (!isRenderableComponent(iconComponent)) throw new Error(`[react-icons-sprite] Unable to render icon "${exportName}" from "${pack}". Expected a React component export.`);
|
|
439
|
+
const svgMarkup = renderToStaticMarkup(createElement(iconComponent));
|
|
440
|
+
const svgInner = SVG_INNER_RE.exec(svgMarkup)?.[1];
|
|
441
|
+
if (!svgInner) throw new Error(`[react-icons-sprite] Unable to extract SVG content for "${exportName}" from "${pack}".`);
|
|
442
|
+
return {
|
|
443
|
+
symbolBody: svgInner,
|
|
444
|
+
viewBox: VIEWBOX_RE.exec(svgMarkup)?.[1] ?? "0 0 24 24",
|
|
445
|
+
symbolAttributes: extractSymbolAttributes(svgMarkup)
|
|
446
|
+
};
|
|
447
|
+
};
|
|
448
|
+
const createIconRenderer = () => {
|
|
449
|
+
const cache = {
|
|
450
|
+
roots: /* @__PURE__ */ new Map(),
|
|
451
|
+
packages: /* @__PURE__ */ new Map()
|
|
452
|
+
};
|
|
453
|
+
return (pack, exportName, options = {}) => renderIconWithCache(pack, exportName, options, cache);
|
|
454
|
+
};
|
|
455
|
+
//#endregion
|
|
456
|
+
//#region src/sprite/build-sprite.ts
|
|
457
|
+
const buildSprite = async (icons, options = {}) => {
|
|
458
|
+
if (!icons.length) return "<svg xmlns=\"http://www.w3.org/2000/svg\" style=\"display:none\"></svg>";
|
|
459
|
+
const renderIcon = createIconRenderer();
|
|
460
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" style="display:none">${(await Promise.all(icons.map(async ({ pack, exportName, importer, importPath }) => {
|
|
461
|
+
const rendered = await renderIcon(pack, exportName, {
|
|
462
|
+
baseDir: options.baseDir,
|
|
463
|
+
importer,
|
|
464
|
+
importPath
|
|
465
|
+
});
|
|
466
|
+
const id = computeIconId(pack, exportName);
|
|
467
|
+
const symbolAttributes = rendered.symbolAttributes ? ` ${rendered.symbolAttributes}` : "";
|
|
468
|
+
return `<symbol id="${id}" viewBox="${rendered.viewBox}"${symbolAttributes}>${rendered.symbolBody}</symbol>`;
|
|
469
|
+
}))).join("")}</svg>`;
|
|
470
|
+
};
|
|
471
|
+
//#endregion
|
|
472
|
+
export { buildSprite as t };
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
//#region src/collector/create-collector.ts
|
|
2
|
+
const createCollectedIconKey = (icon) => {
|
|
3
|
+
const resolutionKey = icon.importPath ?? (icon.pack.startsWith(".") ? icon.importer : void 0);
|
|
4
|
+
return JSON.stringify([
|
|
5
|
+
icon.pack,
|
|
6
|
+
icon.exportName,
|
|
7
|
+
resolutionKey
|
|
8
|
+
]);
|
|
9
|
+
};
|
|
10
|
+
const createCollector = () => {
|
|
11
|
+
const collected = /* @__PURE__ */ new Map();
|
|
12
|
+
return {
|
|
13
|
+
add(pack, exportName, options = {}) {
|
|
14
|
+
const item = {
|
|
15
|
+
pack,
|
|
16
|
+
exportName,
|
|
17
|
+
...options
|
|
18
|
+
};
|
|
19
|
+
collected.set(createCollectedIconKey(item), item);
|
|
20
|
+
},
|
|
21
|
+
toList() {
|
|
22
|
+
return [...collected.values()];
|
|
23
|
+
},
|
|
24
|
+
clear() {
|
|
25
|
+
collected.clear();
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region src/packs/icon-sources.ts
|
|
31
|
+
const DEFAULT_ICON_SOURCES = [
|
|
32
|
+
/^react-icons\/[\w-]+$/,
|
|
33
|
+
/^@fortawesome\/[\w-]+-svg-icons$/,
|
|
34
|
+
/^lucide-react$/,
|
|
35
|
+
/^@heroicons\/react(?:\/.*)?$/,
|
|
36
|
+
/^@tabler\/icons-react$/,
|
|
37
|
+
/^@radix-ui\/react-icons$/,
|
|
38
|
+
/^phosphor-react$/,
|
|
39
|
+
/^@phosphor-icons\/react$/,
|
|
40
|
+
/^react-feather$/,
|
|
41
|
+
/^react-bootstrap-icons$/,
|
|
42
|
+
/^grommet-icons$/,
|
|
43
|
+
/^@remixicon\/react$/,
|
|
44
|
+
/^devicons-react$/,
|
|
45
|
+
/^@mui\/icons-material(?:\/.*)?$/,
|
|
46
|
+
/^@carbon\/icons-react$/,
|
|
47
|
+
/^@ant-design\/icons(?:\/.*)?$/,
|
|
48
|
+
/^@fluentui\/react-icons(?:\/svg\/[\w-]+)?$/,
|
|
49
|
+
/^@primer\/octicons-react$/,
|
|
50
|
+
/^@hugeicons\/core-free-icons(?:\/.*)?$/,
|
|
51
|
+
/^@pillage-first\/graphics$/
|
|
52
|
+
];
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region src/plugin-options.ts
|
|
55
|
+
const normalizeOutputPathSegment = (value) => {
|
|
56
|
+
return value.replace(/\\/g, "/").replace(/^\/+/, "").replace(/\/+$/, "");
|
|
57
|
+
};
|
|
58
|
+
const createIconSources = () => {
|
|
59
|
+
return DEFAULT_ICON_SOURCES;
|
|
60
|
+
};
|
|
61
|
+
const createExcludedIconSources = (exclude) => {
|
|
62
|
+
if (!exclude) return [];
|
|
63
|
+
return exclude instanceof RegExp ? [exclude] : exclude;
|
|
64
|
+
};
|
|
65
|
+
const createSpriteAssetName = (fileName, outputDir) => {
|
|
66
|
+
const normalizedOutputDir = outputDir ? normalizeOutputPathSegment(outputDir) : "";
|
|
67
|
+
const normalizedFileName = normalizeOutputPathSegment(fileName);
|
|
68
|
+
return normalizedOutputDir ? `${normalizedOutputDir}/${normalizedFileName}` : normalizedFileName;
|
|
69
|
+
};
|
|
70
|
+
//#endregion
|
|
71
|
+
//#region src/utils/kebab-case.ts
|
|
72
|
+
const kebabCase = (value) => {
|
|
73
|
+
return value.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[^a-z0-9]+/gi, "-").replace(/-{2,}/g, "-").replace(/^-|-$/g, "").toLowerCase();
|
|
74
|
+
};
|
|
75
|
+
//#endregion
|
|
76
|
+
//#region src/utils/compute-icon-id.ts
|
|
77
|
+
const normalizePackAlias = (pack) => {
|
|
78
|
+
return kebabCase(pack.replace(/^@/, ""));
|
|
79
|
+
};
|
|
80
|
+
const computeIconId = (pack, iconName) => {
|
|
81
|
+
return `ri-${normalizePackAlias(pack)}-${iconName}`;
|
|
82
|
+
};
|
|
83
|
+
//#endregion
|
|
84
|
+
export { createIconSources as a, createCollector as c, createExcludedIconSources as i, normalizePackAlias as n, createSpriteAssetName as o, kebabCase as r, DEFAULT_ICON_SOURCES as s, computeIconId as t };
|
package/dist/icon.d.mts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { JSX, SVGProps } from "react";
|
|
2
2
|
//#region src/icon.d.ts
|
|
3
|
-
type IconProps = SVGProps<SVGSVGElement> & {
|
|
3
|
+
export type IconProps = SVGProps<SVGSVGElement> & {
|
|
4
4
|
iconId: string;
|
|
5
5
|
};
|
|
6
|
-
declare const ReactIconsSpriteIcon: ({ iconId, ...rest }: IconProps) => JSX.Element;
|
|
7
|
-
//#endregion
|
|
8
|
-
export { IconProps, ReactIconsSpriteIcon };
|
|
6
|
+
export declare const ReactIconsSpriteIcon: ({ iconId, ...rest }: IconProps) => JSX.Element;
|
|
7
|
+
//#endregion
|