react-icons-sprite 1.3.1 → 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 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 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` |
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
- 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.
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 };
@@ -1,4 +1,4 @@
1
- import { s as createCollector } from "./compute-icon-id-1Gop_fRM.mjs";
1
+ import { c as createCollector } from "./compute-icon-id-C42PyoDX.mjs";
2
2
  //#region src/collector.ts
3
3
  const collector = createCollector();
4
4
  //#endregion
@@ -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