react-icons-sprite 1.2.0-rc.2 → 1.2.0-rc.3

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
@@ -173,7 +173,11 @@ import { defineConfig } from 'vite';
173
173
  import { reactIconsSprite } from 'react-icons-sprite/vite';
174
174
 
175
175
  export default defineConfig({
176
- plugins: [reactIconsSprite()],
176
+ plugins: [
177
+ reactIconsSprite({
178
+ // optional: outputDir: 'assets/sprites',
179
+ }),
180
+ ],
177
181
  });
178
182
  ```
179
183
 
@@ -206,12 +210,18 @@ module.exports = {
206
210
  },
207
211
  plugins: [
208
212
  reactIconsSprite({
209
- // optional: fileName: 'icons.svg'
213
+ // optional: outputDir: 'assets/sprites',
210
214
  }),
211
215
  ],
212
216
  };
213
217
  ```
214
218
 
219
+ ### Options
220
+
221
+ All options are optional:
222
+
223
+ - `outputDir`: directory inside the bundler output where the sprite asset is emitted.
224
+
215
225
  ### Rsbuild
216
226
 
217
227
  ```ts
@@ -1,5 +1,5 @@
1
- import { i as resolveIconImport, t as computeIconId } from "./compute-icon-id-Xx4G3fva.mjs";
2
- import { existsSync, readFileSync } from "node:fs";
1
+ import { o as resolveIconImport, t as computeIconId } from "./compute-icon-id-CGZBEP3i.mjs";
2
+ import { existsSync, readFileSync, statSync } from "node:fs";
3
3
  import path from "node:path";
4
4
  import { pathToFileURL } from "node:url";
5
5
  import { createElement } from "react";
@@ -57,14 +57,28 @@ const resolveExportTarget = (exportsField, subpath) => {
57
57
  }
58
58
  return null;
59
59
  };
60
- const fileExists = (filePath) => existsSync(filePath);
60
+ const fileExists = (filePath) => {
61
+ try {
62
+ return statSync(filePath).isFile();
63
+ } catch {
64
+ return false;
65
+ }
66
+ };
61
67
  const resolveFileCandidate = (filePath) => {
62
68
  return [
63
69
  filePath,
64
70
  `${filePath}.mjs`,
65
71
  `${filePath}.js`,
72
+ `${filePath}.cjs`,
73
+ `${filePath}.jsx`,
74
+ `${filePath}.ts`,
75
+ `${filePath}.tsx`,
66
76
  path.join(filePath, "index.mjs"),
67
- path.join(filePath, "index.js")
77
+ path.join(filePath, "index.js"),
78
+ path.join(filePath, "index.cjs"),
79
+ path.join(filePath, "index.jsx"),
80
+ path.join(filePath, "index.ts"),
81
+ path.join(filePath, "index.tsx")
68
82
  ].find(fileExists) ?? null;
69
83
  };
70
84
  const resolveFromBaseDir = (specifier, baseDir) => {
@@ -85,6 +99,15 @@ const resolveFromBaseDir = (specifier, baseDir) => {
85
99
  return resolveFileCandidate(path.join(packageRoot, parsed.subpath));
86
100
  };
87
101
  const resolveImportSpecifier = (specifier, options) => {
102
+ if (path.isAbsolute(specifier)) {
103
+ const resolved = resolveFileCandidate(specifier);
104
+ return pathToFileURL(resolved ?? specifier).href;
105
+ }
106
+ if (specifier.startsWith(".") && options.importer) {
107
+ const importerPath = options.importer.split("?", 1)[0];
108
+ const resolved = resolveFileCandidate(path.resolve(path.dirname(importerPath), specifier));
109
+ if (resolved) return pathToFileURL(resolved).href;
110
+ }
88
111
  if (!options.baseDir) return specifier;
89
112
  const resolved = resolveFromBaseDir(specifier, options.baseDir);
90
113
  return resolved ? pathToFileURL(resolved).href : specifier;
@@ -135,12 +158,7 @@ const renderFontAwesomeIconDefinition = (iconDefinition) => {
135
158
  };
136
159
  };
137
160
  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]) => {
161
+ const children = [...iconDefinition].sort(([, a], [, b]) => {
144
162
  const hasOpacityA = a.opacity !== void 0;
145
163
  return b.opacity !== void 0 ? 1 : hasOpacityA ? -1 : 0;
146
164
  }).map(([tag, attributes]) => {
@@ -149,7 +167,13 @@ const renderHugeiconsIconDefinition = (iconDefinition) => {
149
167
  ...rest,
150
168
  key
151
169
  });
152
- })));
170
+ });
171
+ const svgMarkup = renderToStaticMarkup(createElement("svg", {
172
+ xmlns: "http://www.w3.org/2000/svg",
173
+ viewBox: "0 0 24 24",
174
+ fill: "none",
175
+ color: "currentColor"
176
+ }, children));
153
177
  const svgInner = SVG_INNER_RE.exec(svgMarkup)?.[1];
154
178
  if (!svgInner) throw new Error("[react-icons-sprite] Unable to extract SVG content for Hugeicons icon.");
155
179
  return {
@@ -159,7 +183,7 @@ const renderHugeiconsIconDefinition = (iconDefinition) => {
159
183
  };
160
184
  };
161
185
  const renderIcon = async (pack, exportName, options = {}) => {
162
- const importPath = resolveIconImport(pack, exportName);
186
+ const importPath = options.importPath ?? resolveIconImport(pack, exportName);
163
187
  const imported = await import(resolveImportSpecifier(importPath, options));
164
188
  const iconComponent = unwrapNestedDefaultExport(pickExport(imported, exportName));
165
189
  if (isFontAwesomeIconDefinition(iconComponent)) return renderFontAwesomeIconDefinition(iconComponent);
@@ -178,8 +202,12 @@ const renderIcon = async (pack, exportName, options = {}) => {
178
202
  //#region src/sprite/build-sprite.ts
179
203
  const buildSprite = async (icons, options = {}) => {
180
204
  if (!icons.length) return "<svg xmlns=\"http://www.w3.org/2000/svg\" style=\"display:none\"></svg>";
181
- return `<svg xmlns="http://www.w3.org/2000/svg" style="display:none">${(await Promise.all(icons.map(async ({ pack, exportName }) => {
182
- const rendered = await renderIcon(pack, exportName, { baseDir: options.baseDir });
205
+ return `<svg xmlns="http://www.w3.org/2000/svg" style="display:none">${(await Promise.all(icons.map(async ({ pack, exportName, importer, importPath }) => {
206
+ const rendered = await renderIcon(pack, exportName, {
207
+ baseDir: options.baseDir,
208
+ importer,
209
+ importPath
210
+ });
183
211
  const id = computeIconId(pack, exportName);
184
212
  const symbolAttributes = rendered.symbolAttributes ? ` ${rendered.symbolAttributes}` : "";
185
213
  return `<symbol id="${id}" viewBox="${rendered.viewBox}"${symbolAttributes}>${rendered.symbolBody}</symbol>`;
@@ -1,4 +1,4 @@
1
- import { a as createCollector } from "./compute-icon-id-Xx4G3fva.mjs";
1
+ import { s as createCollector } from "./compute-icon-id-CGZBEP3i.mjs";
2
2
  //#region src/collector.ts
3
3
  const collector = createCollector();
4
4
  //#endregion
@@ -1,18 +1,25 @@
1
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
+ };
2
10
  const createCollector = () => {
3
- const collected = /* @__PURE__ */ new Set();
11
+ const collected = /* @__PURE__ */ new Map();
4
12
  return {
5
- add(pack, exportName) {
6
- collected.add(`${pack}:${exportName}`);
13
+ add(pack, exportName, options = {}) {
14
+ const item = {
15
+ pack,
16
+ exportName,
17
+ ...options
18
+ };
19
+ collected.set(createCollectedIconKey(item), item);
7
20
  },
8
21
  toList() {
9
- return [...collected].map((key) => {
10
- const [pack, exportName] = key.split(":");
11
- return {
12
- pack,
13
- exportName
14
- };
15
- });
22
+ return [...collected.values()];
16
23
  },
17
24
  clear() {
18
25
  collected.clear();
@@ -49,7 +56,8 @@ const DEFAULT_ICON_SOURCES = [
49
56
  ];
50
57
  const phosphorIconPathName = (name) => name.endsWith("Icon") ? name.slice(0, -4) : name;
51
58
  const fluentIconPathName = (name) => {
52
- return kebabCase(name.replace(/(?:12|16|20|24|28|32|48)?(?:Regular|Filled|Light|Resizable|Color)$/, ""));
59
+ const unsizedName = name.replace(/(?:12|16|20|24|28|32|48)?(?:Regular|Filled|Light|Resizable|Color)$/, "");
60
+ return kebabCase(unsizedName);
53
61
  };
54
62
  const exactResolvers = {
55
63
  "lucide-react": (pack, name) => `${pack}/dist/esm/icons/${kebabCase(name)}.mjs`,
@@ -80,6 +88,19 @@ const resolveIconImport = (pack, exportName) => {
80
88
  return pack;
81
89
  };
82
90
  //#endregion
91
+ //#region src/plugin-options.ts
92
+ const normalizeOutputPathSegment = (value) => {
93
+ return value.replace(/\\/g, "/").replace(/^\/+/, "").replace(/\/+$/, "");
94
+ };
95
+ const createIconSources = () => {
96
+ return DEFAULT_ICON_SOURCES;
97
+ };
98
+ const createSpriteAssetName = (fileName, outputDir) => {
99
+ const normalizedOutputDir = outputDir ? normalizeOutputPathSegment(outputDir) : "";
100
+ const normalizedFileName = normalizeOutputPathSegment(fileName);
101
+ return normalizedOutputDir ? `${normalizedOutputDir}/${normalizedFileName}` : normalizedFileName;
102
+ };
103
+ //#endregion
83
104
  //#region src/utils/compute-icon-id.ts
84
105
  const normalizePackAlias = (pack) => {
85
106
  return kebabCase(pack.replace(/^@/, ""));
@@ -88,4 +109,4 @@ const computeIconId = (pack, iconName) => {
88
109
  return `ri-${normalizePackAlias(pack)}-${iconName}`;
89
110
  };
90
111
  //#endregion
91
- export { createCollector as a, resolveIconImport as i, normalizePackAlias as n, DEFAULT_ICON_SOURCES as r, computeIconId as t };
112
+ export { DEFAULT_ICON_SOURCES as a, createSpriteAssetName as i, normalizePackAlias as n, resolveIconImport as o, createIconSources as r, createCollector as s, computeIconId as t };
@@ -0,0 +1,9 @@
1
+ //#region src/plugin-options.d.ts
2
+ type ReactIconsSpritePluginOptions = {
3
+ /**
4
+ * Optional directory inside the bundler output where the sprite asset should be emitted.
5
+ */
6
+ outputDir?: string;
7
+ };
8
+ //#endregion
9
+ export { ReactIconsSpritePluginOptions as t };
@@ -1,4 +1,4 @@
1
- import { n as normalizePackAlias, r as DEFAULT_ICON_SOURCES } from "./compute-icon-id-Xx4G3fva.mjs";
1
+ import { a as DEFAULT_ICON_SOURCES, n as normalizePackAlias } from "./compute-icon-id-CGZBEP3i.mjs";
2
2
  import MagicString from "magic-string";
3
3
  //#region src/transform/edit-applier.ts
4
4
  const applyEdits = (code, edits) => {
@@ -1,13 +1,7 @@
1
+ import { t as ReactIconsSpritePluginOptions } from "../plugin-options-ByGrVpdG.mjs";
1
2
  import { Plugin } from "vite";
2
3
  //#region src/vite/plugin.d.ts
3
- type ReactIconsSpriteVitePluginOptions = {
4
- /**
5
- * If passed, this exact string will be used for the emitted file name.
6
- * If fileName is omitted, name will be generated as `react-icons-sprite-[hash].svg.
7
- * This is useful when, for example, multiple sprite sheets are generated during client and server builds.
8
- */
9
- fileName?: string;
10
- };
4
+ type ReactIconsSpriteVitePluginOptions = ReactIconsSpritePluginOptions;
11
5
  declare const reactIconsSprite: (options?: ReactIconsSpriteVitePluginOptions) => Plugin;
12
6
  //#endregion
13
7
  export { ReactIconsSpriteVitePluginOptions, reactIconsSprite };
@@ -1,11 +1,12 @@
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-Xx4G3fva.mjs";
3
- import { t as buildSprite } from "../build-sprite-CxqvU2LE.mjs";
4
- import { t as transformModule } from "../transform-module-3mbTFhoj.mjs";
2
+ import { i as createSpriteAssetName, r as createIconSources, s as createCollector } from "../compute-icon-id-CGZBEP3i.mjs";
3
+ import { t as buildSprite } from "../build-sprite-DpqABaHa.mjs";
4
+ import { t as transformModule } from "../transform-module-DglDSarv.mjs";
5
5
  import { createHash } from "node:crypto";
6
6
  //#region src/vite/plugin.ts
7
7
  const reactIconsSprite = (options = {}) => {
8
- const { fileName } = options;
8
+ const { outputDir } = options;
9
+ const iconSources = createIconSources();
9
10
  const collector = createCollector();
10
11
  let root = process.cwd();
11
12
  return {
@@ -18,13 +19,13 @@ const reactIconsSprite = (options = {}) => {
18
19
  configResolved(config) {
19
20
  root = config.root;
20
21
  },
21
- transform(code, id) {
22
+ async transform(code, id) {
22
23
  const cleanId = id.split("?", 1)[0];
23
24
  if (!/\.(mjs|cjs|js|jsx|ts|tsx)$/.test(cleanId)) return null;
24
25
  try {
25
26
  const { code: next, map, anyReplacements } = transformModule(code, id, (pack, exportName) => {
26
- collector.add(pack, exportName);
27
- }, DEFAULT_ICON_SOURCES);
27
+ collector.add(pack, exportName, { importer: cleanId });
28
+ }, iconSources);
28
29
  if (!anyReplacements) return null;
29
30
  return {
30
31
  code: next,
@@ -41,7 +42,7 @@ const reactIconsSprite = (options = {}) => {
41
42
  const emitFileOptions = {
42
43
  type: "asset",
43
44
  source: spriteXml,
44
- fileName: fileName ? fileName : `react-icons-sprite-${generatedHash}.svg`
45
+ fileName: createSpriteAssetName(`react-icons-sprite-${generatedHash}.svg`, outputDir)
45
46
  };
46
47
  const assetId = this.emitFile(emitFileOptions);
47
48
  const finalUrl = `/${this.getFileName(assetId)}`;
@@ -1,14 +1,16 @@
1
- import { t as transformModule } from "../transform-module-3mbTFhoj.mjs";
2
- import { t as collector } from "../collector-CBRz9R_h.mjs";
1
+ import { r as createIconSources } from "../compute-icon-id-CGZBEP3i.mjs";
2
+ import { t as transformModule } from "../transform-module-DglDSarv.mjs";
3
+ import { t as collector } from "../collector-D0VUmoK8.mjs";
3
4
  //#region src/webpack/loader.ts
4
5
  const reactIconsSpriteLoader = async function(source) {
5
6
  if (this.mode === "development") return source;
6
7
  const id = this.resourcePath;
7
8
  try {
8
9
  if (!/\.(mjs|cjs|js|jsx|ts|tsx)$/i.test(id)) return source;
10
+ const iconSources = createIconSources();
9
11
  const { code, anyReplacements } = transformModule(String(source), id, (pack, exportName) => {
10
- collector.add(pack, exportName);
11
- });
12
+ collector.add(pack, exportName, { importer: id });
13
+ }, iconSources);
12
14
  if (!anyReplacements) return source;
13
15
  return code;
14
16
  } catch (err) {
@@ -1,15 +1,9 @@
1
+ import { t as ReactIconsSpritePluginOptions } from "../plugin-options-ByGrVpdG.mjs";
1
2
  import { Compiler } from "webpack";
2
3
  //#region src/webpack/plugin.d.ts
3
- type ReactIconsSpriteWebpackPluginOptions = {
4
- /**
5
- * If passed, this exact string will be used for the emitted file name.
6
- * If fileName is omitted, name will be generated as `react-icons-sprite-[hash].svg`.
7
- * This is useful when, for example, multiple sprite sheets are generated during client and server builds.
8
- */
9
- fileName?: string;
10
- };
4
+ type ReactIconsSpriteWebpackPluginOptions = ReactIconsSpritePluginOptions;
11
5
  declare class ReactIconsSpriteWebpackPlugin {
12
- private readonly fileName?;
6
+ private readonly outputDir?;
13
7
  constructor(options?: ReactIconsSpriteWebpackPluginOptions);
14
8
  apply(compiler: Compiler): void;
15
9
  }
@@ -1,12 +1,13 @@
1
1
  import { REACT_ICONS_SPRITE_URL_PLACEHOLDER } from "../index.mjs";
2
- import { t as buildSprite } from "../build-sprite-CxqvU2LE.mjs";
3
- import { t as collector } from "../collector-CBRz9R_h.mjs";
2
+ import { i as createSpriteAssetName } from "../compute-icon-id-CGZBEP3i.mjs";
3
+ import { t as buildSprite } from "../build-sprite-DpqABaHa.mjs";
4
+ import { t as collector } from "../collector-D0VUmoK8.mjs";
4
5
  import { createHash } from "node:crypto";
5
6
  //#region src/webpack/plugin.ts
6
7
  var ReactIconsSpriteWebpackPlugin = class {
7
- fileName;
8
+ outputDir;
8
9
  constructor(options = {}) {
9
- this.fileName = options.fileName;
10
+ this.outputDir = options.outputDir;
10
11
  }
11
12
  apply(compiler) {
12
13
  if (compiler.options.mode === "development") return;
@@ -20,7 +21,7 @@ var ReactIconsSpriteWebpackPlugin = class {
20
21
  }, async () => {
21
22
  const spriteXml = await buildSprite(collector.toList(), { baseDir: compiler.context });
22
23
  const generatedHash = createHash("sha256").update(spriteXml).digest("hex").slice(0, 8);
23
- const name = this.fileName ?? `react-icons-sprite-${generatedHash}.svg`;
24
+ const name = createSpriteAssetName(`react-icons-sprite-${generatedHash}.svg`, this.outputDir);
24
25
  const RawSource = compiler.webpack?.sources?.RawSource;
25
26
  if (!RawSource) throw new Error("[react-icons-sprite] Unable to access webpack RawSource");
26
27
  compilation.emitAsset(name, new RawSource(spriteXml));
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.2.0-rc.2",
4
+ "version": "1.2.0-rc.3",
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>",
@@ -88,18 +88,18 @@
88
88
  "react-dom": ">= 16"
89
89
  },
90
90
  "dependencies": {
91
- "magic-string": "0.30.21"
91
+ "magic-string": "1.1.0"
92
92
  },
93
93
  "devDependencies": {
94
- "@types/node": "26.1.1",
95
- "@types/react-dom": "19.2.3",
96
- "react": "19.2.7",
97
- "react-dom": "19.2.7",
98
- "tsdown": "0.22.7",
94
+ "@types/node": "26.2.0",
95
+ "@types/react-dom": "19.2.4",
96
+ "react": "19.2.8",
97
+ "react-dom": "19.2.8",
98
+ "tsdown": "0.22.14",
99
99
  "typescript": "7.0.2",
100
- "vite": "8.1.4",
100
+ "vite": "8.2.1",
101
101
  "vitest": "4.1.10",
102
- "webpack": "5.108.4"
102
+ "webpack": "5.109.2"
103
103
  },
104
104
  "keywords": [
105
105
  "vite",