react-icons-sprite 0.9.2-rc.1 → 1.0.0-rc.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.
@@ -1,4 +1,4 @@
1
- import { r as resolveIconImport, t as computeIconId } from "./compute-icon-id-C96eIC66.mjs";
1
+ import { r as resolveIconImport, t as computeIconId } from "./compute-icon-id-DKiwIqyH.mjs";
2
2
  import { createElement } from "react";
3
3
  import { renderToStaticMarkup } from "react-dom/server";
4
4
  //#region src/sprite/render-icon.ts
@@ -1,4 +1,4 @@
1
- import { i as createCollector } from "./compute-icon-id-C96eIC66.mjs";
1
+ import { i as createCollector } from "./compute-icon-id-DKiwIqyH.mjs";
2
2
  //#region src/collector.ts
3
3
  const collector = createCollector();
4
4
  //#endregion
@@ -43,12 +43,23 @@ const DEFAULT_ICON_SOURCES = [
43
43
  /^@mui\/icons-material(?:\/.*)?$/,
44
44
  /^@carbon\/icons-react$/
45
45
  ];
46
- const resolvers = {
47
- "lucide-react": (_pack, name) => `lucide-react/dist/esm/icons/${kebabCase(name)}.js`,
48
- "@tabler/icons-react": (_pack, name) => `@tabler/icons-react/dist/esm/icons/${name}.mjs`
46
+ const exactResolvers = {
47
+ "lucide-react": (pack, name) => `${pack}/dist/esm/icons/${kebabCase(name)}.js`,
48
+ "@radix-ui/react-icons": (pack, name) => `${pack}/${name}`,
49
+ "@tabler/icons-react": (pack, name) => `${pack}/dist/esm/icons/${name}.mjs`,
50
+ "@phosphor-icons/react": (pack, name) => `${pack}/dist/ssr/${name}.es.js`,
51
+ "phosphor-react": (pack, name) => `${pack}/dist/icons/${name}.esm.js`,
52
+ "react-feather": (pack, name) => `${pack}/dist/icons/${kebabCase(name)}`,
53
+ "react-bootstrap-icons": (pack, name) => `${pack}/dist/icons/${kebabCase(name)}`,
54
+ "@carbon/icons-react": (pack, name) => `${pack}/lib/${name}.js`
49
55
  };
50
56
  const resolveIconImport = (pack, exportName) => {
51
- return resolvers[pack]?.(pack, exportName) ?? pack;
57
+ const exactResolver = exactResolvers[pack];
58
+ if (exactResolver) return exactResolver(pack, exportName);
59
+ if (/^@mui\/icons-material(?:\/.*)?$/.test(pack)) return pack.split("/").length > 2 ? pack : `${pack}/${exportName}`;
60
+ if (/^@heroicons\/react\/(?:\d{2})\/(?:outline|solid)$/.test(pack)) return `${pack}/${exportName}`;
61
+ if (/^@fortawesome\/[\w-]+-svg-icons$/.test(pack)) return `${pack}/${exportName}`;
62
+ return pack;
52
63
  };
53
64
  //#endregion
54
65
  //#region src/utils/compute-icon-id.ts
@@ -0,0 +1,410 @@
1
+ import { n as DEFAULT_ICON_SOURCES, t as computeIconId } from "./compute-icon-id-DKiwIqyH.mjs";
2
+ import MagicString from "magic-string";
3
+ //#region src/transform/edit-applier.ts
4
+ const applyEdits = (code, edits) => {
5
+ const magicString = new MagicString(code);
6
+ for (const edit of edits) {
7
+ if (edit.type === "replace") magicString.overwrite(edit.from, edit.to, edit.value);
8
+ if (edit.type === "insert") magicString.appendLeft(edit.pos, edit.value);
9
+ if (edit.type === "remove") magicString.remove(edit.from, edit.to);
10
+ }
11
+ return magicString;
12
+ };
13
+ //#endregion
14
+ //#region src/transform/edit-builder.ts
15
+ const buildEdits = (usages, componentName, usedSymbols, register) => {
16
+ const edits = [];
17
+ const iconIdCache = /* @__PURE__ */ new Map();
18
+ for (const usage of usages) {
19
+ edits.push({
20
+ type: "replace",
21
+ from: usage.range[0],
22
+ to: usage.range[1],
23
+ value: componentName
24
+ });
25
+ if (usage.kind === "opening") {
26
+ if (!usage.hasIconId) {
27
+ const key = `${usage.pack}:${usage.exportName}`;
28
+ let iconId = iconIdCache.get(key);
29
+ if (!iconId) {
30
+ iconId = computeIconId(usage.pack, usage.exportName);
31
+ iconIdCache.set(key, iconId);
32
+ }
33
+ edits.push({
34
+ type: "insert",
35
+ pos: usage.range[1],
36
+ value: ` iconId="${iconId}"`
37
+ });
38
+ }
39
+ usedSymbols.add(usage.local);
40
+ register(usage.pack, usage.exportName);
41
+ }
42
+ }
43
+ return edits;
44
+ };
45
+ //#endregion
46
+ //#region src/transform/fast-filter.ts
47
+ const fastFilter = (code) => {
48
+ if (!code.includes("<")) return false;
49
+ if (!code.includes("import")) return false;
50
+ return true;
51
+ };
52
+ //#endregion
53
+ //#region src/transform/transform-module.ts
54
+ const ICON_SOURCE = "react-icons-sprite";
55
+ const ICON_COMPONENT_NAME = "ReactIconsSpriteIcon";
56
+ const FONTAWESOME_REACT_PACK = "@fortawesome/react-fontawesome";
57
+ const isFontAwesomeIconPack = (pack) => {
58
+ return /^@fortawesome\/[\w-]+-svg-icons$/.test(pack);
59
+ };
60
+ const sourceMatches = (source, pack) => {
61
+ source.lastIndex = 0;
62
+ return source.test(pack);
63
+ };
64
+ const IMPORT_RE = /import\s+([^;]+?)\s+from\s+(['"])([^'"]+)\2\s*;?/g;
65
+ const trimRange = (value, offset) => {
66
+ let start = 0;
67
+ let end = value.length;
68
+ while (start < end && /\s/.test(value[start])) start += 1;
69
+ while (end > start && /\s/.test(value[end - 1])) end -= 1;
70
+ return start === end ? null : [offset + start, offset + end];
71
+ };
72
+ const parseNamedSpecifiers = (specifier, specifierOffset, imports) => {
73
+ const start = specifier.indexOf("{");
74
+ const end = specifier.lastIndexOf("}");
75
+ if (start === -1 || end === -1 || end <= start) return;
76
+ let segmentStart = start + 1;
77
+ for (let index = start + 1; index <= end; index += 1) {
78
+ if (index !== end && specifier[index] !== ",") continue;
79
+ const range = trimRange(specifier.slice(segmentStart, index), specifierOffset + segmentStart);
80
+ segmentStart = index + 1;
81
+ if (!range) continue;
82
+ const text = specifier.slice(range[0] - specifierOffset, range[1] - specifierOffset);
83
+ if (text.startsWith("type ")) continue;
84
+ const aliasMatch = /^(.*?)\s+as\s+([A-Za-z_$][\w$]*)$/.exec(text);
85
+ const exportName = aliasMatch ? aliasMatch[1].trim() : text.trim();
86
+ const local = aliasMatch ? aliasMatch[2] : exportName;
87
+ if (exportName && local) imports.push({
88
+ local,
89
+ exportName,
90
+ range
91
+ });
92
+ }
93
+ };
94
+ const countImportSpecifiers = (specifier) => {
95
+ let count = 0;
96
+ const braceStart = specifier.indexOf("{");
97
+ if ((braceStart === -1 ? specifier : specifier.slice(0, braceStart)).replace(/,$/, "").trim()) count += 1;
98
+ const braceEnd = specifier.lastIndexOf("}");
99
+ if (braceStart !== -1 && braceEnd > braceStart) {
100
+ const named = specifier.slice(braceStart + 1, braceEnd);
101
+ for (const segment of named.split(",")) if (segment.trim()) count += 1;
102
+ }
103
+ return count;
104
+ };
105
+ const scanImportsDetailed = (code, sources) => {
106
+ const imports = [];
107
+ IMPORT_RE.lastIndex = 0;
108
+ for (const match of code.matchAll(IMPORT_RE)) {
109
+ const [statement, specifier, , pack] = match;
110
+ if (!sources.some((source) => sourceMatches(source, pack))) continue;
111
+ if (specifier.trim().startsWith("type ")) continue;
112
+ const matchStart = match.index;
113
+ const specifierOffset = matchStart + statement.indexOf(specifier);
114
+ const specifiers = [];
115
+ const braceStart = specifier.indexOf("{");
116
+ const defaultRange = trimRange((braceStart === -1 ? specifier : specifier.slice(0, braceStart)).replace(/,$/, ""), specifierOffset);
117
+ if (defaultRange && !specifier.slice(defaultRange[0] - specifierOffset, defaultRange[1] - specifierOffset).startsWith("type ")) {
118
+ const local = specifier.slice(defaultRange[0] - specifierOffset, defaultRange[1] - specifierOffset);
119
+ if (local && /^[A-Za-z_$][\w$]*$/.test(local)) specifiers.push({
120
+ local,
121
+ exportName: "default",
122
+ range: defaultRange
123
+ });
124
+ }
125
+ parseNamedSpecifiers(specifier, specifierOffset, specifiers);
126
+ if (specifiers.length) imports.push({
127
+ pack,
128
+ declarationRange: [matchStart, matchStart + statement.length],
129
+ specifiers,
130
+ specifierCount: countImportSpecifiers(specifier)
131
+ });
132
+ }
133
+ return imports;
134
+ };
135
+ const buildScannedSymbolTable = (imports) => {
136
+ const table = /* @__PURE__ */ new Map();
137
+ for (const item of imports) for (const specifier of item.specifiers) table.set(specifier.local, {
138
+ pack: item.pack,
139
+ exportName: specifier.exportName
140
+ });
141
+ return table;
142
+ };
143
+ const scanSpriteIconImport = (code) => {
144
+ IMPORT_RE.lastIndex = 0;
145
+ for (const match of code.matchAll(IMPORT_RE)) {
146
+ const [, specifier, , source] = match;
147
+ if (source !== "react-icons-sprite") continue;
148
+ const specifiers = [];
149
+ parseNamedSpecifiers(specifier, match.index + match[0].indexOf(specifier), specifiers);
150
+ for (const item of specifiers) if (item.exportName === "ReactIconsSpriteIcon") return {
151
+ hasImport: true,
152
+ localName: item.local
153
+ };
154
+ }
155
+ return {
156
+ hasImport: false,
157
+ localName: ICON_COMPONENT_NAME
158
+ };
159
+ };
160
+ const escapeRegExp = (value) => {
161
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
162
+ };
163
+ const scanJsxIconUsages = (code, symbols) => {
164
+ if (!symbols.size) return [];
165
+ const names = [...symbols.keys()].map(escapeRegExp).join("|");
166
+ const tagRe = new RegExp(`<\\s*(/?)\\s*(${names})\\b`, "g");
167
+ const usages = [];
168
+ for (const match of code.matchAll(tagRe)) {
169
+ const [, closing, local] = match;
170
+ const symbol = symbols.get(local);
171
+ if (!symbol) continue;
172
+ const localStart = match.index + match[0].lastIndexOf(local);
173
+ const kind = closing ? "closing" : "opening";
174
+ let hasIconId = false;
175
+ if (!closing) {
176
+ const tagEnd = findJsxOpeningTagEnd(code, localStart + local.length);
177
+ hasIconId = tagEnd !== -1 && /\biconId\s*=/.test(code.slice(localStart + local.length, tagEnd));
178
+ }
179
+ usages.push({
180
+ local,
181
+ range: [localStart, localStart + local.length],
182
+ pack: symbol.pack,
183
+ exportName: symbol.exportName,
184
+ kind,
185
+ hasIconId
186
+ });
187
+ }
188
+ return usages;
189
+ };
190
+ const findJsxOpeningTagEnd = (code, start) => {
191
+ let quote = null;
192
+ let braceDepth = 0;
193
+ for (let index = start; index < code.length; index += 1) {
194
+ const char = code[index];
195
+ if (quote) {
196
+ if (char === quote && code[index - 1] !== "\\") quote = null;
197
+ continue;
198
+ }
199
+ if (char === "\"" || char === "'" || char === "`") {
200
+ quote = char;
201
+ continue;
202
+ }
203
+ if (char === "{") {
204
+ braceDepth += 1;
205
+ continue;
206
+ }
207
+ if (char === "}") {
208
+ braceDepth -= 1;
209
+ continue;
210
+ }
211
+ if (char === ">" && braceDepth === 0) return index;
212
+ }
213
+ return -1;
214
+ };
215
+ const scanFontAwesomeComponents = (code) => {
216
+ const locals = /* @__PURE__ */ new Set();
217
+ IMPORT_RE.lastIndex = 0;
218
+ for (const match of code.matchAll(IMPORT_RE)) {
219
+ const [, specifier, , source] = match;
220
+ if (source !== FONTAWESOME_REACT_PACK) continue;
221
+ const specifiers = [];
222
+ parseNamedSpecifiers(specifier, match.index + match[0].indexOf(specifier), specifiers);
223
+ for (const item of specifiers) if (item.exportName === "FontAwesomeIcon") locals.add(item.local);
224
+ }
225
+ return locals;
226
+ };
227
+ const scanFontAwesomeUsages = (code, symbols, componentLocals) => {
228
+ if (!componentLocals.size) return [];
229
+ const names = [...componentLocals].map(escapeRegExp).join("|");
230
+ const tagRe = new RegExp(`<\\s*(${names})\\b`, "g");
231
+ const usages = [];
232
+ for (const match of code.matchAll(tagRe)) {
233
+ const componentLocal = match[1];
234
+ const componentStart = match.index + match[0].lastIndexOf(componentLocal);
235
+ const tagEnd = findJsxOpeningTagEnd(code, componentStart + componentLocal.length);
236
+ if (tagEnd === -1) continue;
237
+ const attributes = code.slice(componentStart + componentLocal.length, tagEnd);
238
+ const iconMatch = /\sicon\s*=\s*\{\s*([A-Za-z_$][\w$]*)\s*\}/.exec(attributes);
239
+ if (!iconMatch || iconMatch.index === void 0) continue;
240
+ const symbol = symbols.get(iconMatch[1]);
241
+ if (!symbol || !isFontAwesomeIconPack(symbol.pack)) continue;
242
+ const attributeStart = componentStart + componentLocal.length + iconMatch.index;
243
+ usages.push({
244
+ componentLocal,
245
+ componentRange: [componentStart, componentStart + componentLocal.length],
246
+ iconAttributeRange: [attributeStart, attributeStart + iconMatch[0].length],
247
+ hasIconId: /\biconId\s*=/.test(attributes),
248
+ iconLocal: iconMatch[1],
249
+ pack: symbol.pack,
250
+ exportName: symbol.exportName
251
+ });
252
+ }
253
+ return usages;
254
+ };
255
+ const cleanupScannedImports = (code, imports, usedLocals) => {
256
+ const edits = [];
257
+ for (const item of imports) {
258
+ const usedSpecifiers = item.specifiers.filter((specifier) => usedLocals.has(specifier.local));
259
+ if (!usedSpecifiers.length) continue;
260
+ if (usedSpecifiers.length === item.specifiers.length && item.specifierCount === item.specifiers.length) {
261
+ edits.push({
262
+ type: "remove",
263
+ from: item.declarationRange[0],
264
+ to: extendToLineEnd(code, item.declarationRange[1])
265
+ });
266
+ continue;
267
+ }
268
+ for (const specifier of usedSpecifiers) {
269
+ const [from, to] = specifierRemovalRange(code, specifier.range);
270
+ edits.push({
271
+ type: "remove",
272
+ from,
273
+ to
274
+ });
275
+ }
276
+ }
277
+ return edits;
278
+ };
279
+ const cleanupScannedFontAwesomeComponentImports = (code, usedLocals) => {
280
+ const edits = [];
281
+ IMPORT_RE.lastIndex = 0;
282
+ for (const match of code.matchAll(IMPORT_RE)) {
283
+ const [statement, specifier, , source] = match;
284
+ if (source !== FONTAWESOME_REACT_PACK) continue;
285
+ const specifiers = [];
286
+ parseNamedSpecifiers(specifier, match.index + statement.indexOf(specifier), specifiers);
287
+ const removableSpecifiers = specifiers.filter((item) => item.exportName === "FontAwesomeIcon" && usedLocals.has(item.local));
288
+ if (!removableSpecifiers.length) continue;
289
+ if (removableSpecifiers.length === specifiers.length) {
290
+ edits.push({
291
+ type: "remove",
292
+ from: match.index,
293
+ to: extendToLineEnd(code, match.index + statement.length)
294
+ });
295
+ continue;
296
+ }
297
+ for (const specifier of removableSpecifiers) {
298
+ const [from, to] = specifierRemovalRange(code, specifier.range);
299
+ edits.push({
300
+ type: "remove",
301
+ from,
302
+ to
303
+ });
304
+ }
305
+ }
306
+ return edits;
307
+ };
308
+ const extendToLineEnd = (code, end) => {
309
+ let to = end;
310
+ while (to < code.length && /[ \t]/.test(code[to])) to += 1;
311
+ if (code[to] === "\r" && code[to + 1] === "\n") return to + 2;
312
+ if (code[to] === "\n") return to + 1;
313
+ return to;
314
+ };
315
+ const specifierRemovalRange = (code, [start, end]) => {
316
+ let from = start;
317
+ let to = end;
318
+ let before = start - 1;
319
+ while (before >= 0 && /\s/.test(code[before])) before -= 1;
320
+ if (before >= 0 && code[before] === ",") {
321
+ from = before;
322
+ return [from, to];
323
+ }
324
+ let after = end;
325
+ while (after < code.length && /\s/.test(code[after])) after += 1;
326
+ if (after < code.length && code[after] === ",") to = consumeTrailingWhitespace(code, after + 1);
327
+ return [from, to];
328
+ };
329
+ const consumeTrailingWhitespace = (code, start) => {
330
+ let to = start;
331
+ while (to < code.length && /\s/.test(code[to])) to += 1;
332
+ return to;
333
+ };
334
+ const transformModule = (code, id, register, sources = DEFAULT_ICON_SOURCES, options = {}) => {
335
+ const { sourceMap = false } = options;
336
+ if (!fastFilter(code)) return {
337
+ code,
338
+ map: null,
339
+ anyReplacements: false
340
+ };
341
+ const scannedImports = scanImportsDetailed(code, sources);
342
+ if (!scannedImports.length) return {
343
+ code,
344
+ map: null,
345
+ anyReplacements: false
346
+ };
347
+ const hasPotentialFontAwesomeUsage = code.includes(FONTAWESOME_REACT_PACK) && scannedImports.some((item) => isFontAwesomeIconPack(item.pack));
348
+ const table = buildScannedSymbolTable(scannedImports);
349
+ if (!table.size) return {
350
+ code,
351
+ map: null,
352
+ anyReplacements: false
353
+ };
354
+ const spriteIconImport = scanSpriteIconImport(code);
355
+ const usages = scanJsxIconUsages(code, table);
356
+ const fontAwesomeUsages = hasPotentialFontAwesomeUsage ? scanFontAwesomeUsages(code, table, scanFontAwesomeComponents(code)) : [];
357
+ if (!usages.length && !fontAwesomeUsages.length) return {
358
+ code,
359
+ map: null,
360
+ anyReplacements: false
361
+ };
362
+ const used = /* @__PURE__ */ new Set();
363
+ const usedFontAwesomeComponents = /* @__PURE__ */ new Set();
364
+ const edits = buildEdits(usages, spriteIconImport.localName, used, register);
365
+ const registeredFontAwesomeIcons = /* @__PURE__ */ new Set();
366
+ for (const usage of fontAwesomeUsages) {
367
+ edits.push({
368
+ type: "replace",
369
+ from: usage.componentRange[0],
370
+ to: usage.componentRange[1],
371
+ value: spriteIconImport.localName
372
+ });
373
+ if (!usage.hasIconId) edits.push({
374
+ type: "insert",
375
+ pos: usage.componentRange[1],
376
+ value: ` iconId="${computeIconId(usage.pack, usage.exportName)}"`
377
+ });
378
+ edits.push({
379
+ type: "remove",
380
+ from: usage.iconAttributeRange[0],
381
+ to: consumeTrailingWhitespace(code, usage.iconAttributeRange[1])
382
+ });
383
+ used.add(usage.iconLocal);
384
+ usedFontAwesomeComponents.add(usage.componentLocal);
385
+ const key = `${usage.pack}:${usage.exportName}`;
386
+ if (!registeredFontAwesomeIcons.has(key)) {
387
+ registeredFontAwesomeIcons.add(key);
388
+ register(usage.pack, usage.exportName);
389
+ }
390
+ }
391
+ const cleanupEdits = cleanupScannedImports(code, scannedImports, used);
392
+ const cleanupFontAwesomeEdits = cleanupScannedFontAwesomeComponentImports(code, usedFontAwesomeComponents);
393
+ const magicString = applyEdits(code, [
394
+ ...edits,
395
+ ...cleanupEdits,
396
+ ...cleanupFontAwesomeEdits
397
+ ]);
398
+ const importPrefix = `import { ${ICON_COMPONENT_NAME} } from "${ICON_SOURCE}";\n`;
399
+ if (!spriteIconImport.hasImport) magicString.prepend(importPrefix);
400
+ return {
401
+ code: magicString.toString(),
402
+ map: sourceMap ? magicString.generateMap({
403
+ source: id,
404
+ hires: true
405
+ }) : null,
406
+ anyReplacements: true
407
+ };
408
+ };
409
+ //#endregion
410
+ export { transformModule as t };
@@ -1,7 +1,7 @@
1
1
  import { REACT_ICONS_SPRITE_URL_PLACEHOLDER } from "../index.mjs";
2
- import { i as createCollector, n as DEFAULT_ICON_SOURCES } from "../compute-icon-id-C96eIC66.mjs";
3
- import { t as buildSprite } from "../build-sprite-BFhBc3Ev.mjs";
4
- import { t as transformModule } from "../transform-module-DzmqobWK.mjs";
2
+ import { i as createCollector, n as DEFAULT_ICON_SOURCES } from "../compute-icon-id-DKiwIqyH.mjs";
3
+ import { t as buildSprite } from "../build-sprite-BZCizCDt.mjs";
4
+ import { t as transformModule } from "../transform-module-Dn8lfegG.mjs";
5
5
  import { createHash } from "node:crypto";
6
6
  //#region src/vite/plugin.ts
7
7
  const reactIconsSprite = (options = {}) => {
@@ -1,5 +1,5 @@
1
- import { t as transformModule } from "../transform-module-DzmqobWK.mjs";
2
- import { t as collector } from "../collector-C6kn7NXC.mjs";
1
+ import { t as transformModule } from "../transform-module-Dn8lfegG.mjs";
2
+ import { t as collector } from "../collector-wdoob7qt.mjs";
3
3
  //#region src/webpack/loader.ts
4
4
  const reactIconsSpriteLoader = async function(source) {
5
5
  if (this.mode === "development") return source;
@@ -1,6 +1,6 @@
1
1
  import { REACT_ICONS_SPRITE_URL_PLACEHOLDER } from "../index.mjs";
2
- import { t as buildSprite } from "../build-sprite-BFhBc3Ev.mjs";
3
- import { t as collector } from "../collector-C6kn7NXC.mjs";
2
+ import { t as buildSprite } from "../build-sprite-BZCizCDt.mjs";
3
+ import { t as collector } from "../collector-wdoob7qt.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": "0.9.2-rc.1",
4
+ "version": "1.0.0-rc.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>",
@@ -65,21 +65,20 @@
65
65
  "react-dom": ">= 16"
66
66
  },
67
67
  "dependencies": {
68
- "magic-string": "0.30.21",
69
- "oxc-parser": "0.119.0"
68
+ "magic-string": "0.30.21"
70
69
  },
71
70
  "devDependencies": {
72
- "@carbon/icons-react": "11.76.0",
73
- "@types/node": "25.5.0",
71
+ "@carbon/icons-react": "11.81.0",
72
+ "@types/node": "25.9.1",
74
73
  "@types/react-dom": "19.2.3",
75
- "@typescript/native-preview": "7.0.0-dev.20260311.1",
76
- "react": "19.2.4",
77
- "react-dom": "19.2.4",
78
- "tsdown": "0.21.2",
79
- "typescript": "5.9.3",
80
- "vite": "8.0.0",
81
- "vitest": "4.1.0",
82
- "webpack": "5.105.4"
74
+ "@typescript/native-preview": "7.0.0-dev.20260521.1",
75
+ "react": "19.2.6",
76
+ "react-dom": "19.2.6",
77
+ "tsdown": "0.22.0",
78
+ "typescript": "6.0.3",
79
+ "vite": "8.0.14",
80
+ "vitest": "4.1.7",
81
+ "webpack": "5.107.1"
83
82
  },
84
83
  "keywords": [
85
84
  "vite",
@@ -1,290 +0,0 @@
1
- import { n as DEFAULT_ICON_SOURCES, t as computeIconId } from "./compute-icon-id-C96eIC66.mjs";
2
- import { parseSync } from "oxc-parser";
3
- import MagicString from "magic-string";
4
- //#region src/transform/edit-applier.ts
5
- const applyEdits = (code, edits) => {
6
- const magicString = new MagicString(code);
7
- for (const edit of edits) {
8
- if (edit.type === "replace") magicString.overwrite(edit.from, edit.to, edit.value);
9
- if (edit.type === "insert") magicString.appendLeft(edit.pos, edit.value);
10
- if (edit.type === "remove") magicString.remove(edit.from, edit.to);
11
- }
12
- return magicString;
13
- };
14
- //#endregion
15
- //#region src/transform/edit-builder.ts
16
- const buildEdits = (usages, componentName, usedSymbols, register) => {
17
- const edits = [];
18
- for (const usage of usages) {
19
- edits.push({
20
- type: "replace",
21
- from: usage.range[0],
22
- to: usage.range[1],
23
- value: componentName
24
- });
25
- if (usage.kind === "opening") {
26
- edits.push({
27
- type: "insert",
28
- pos: usage.range[1],
29
- value: ` iconId="${computeIconId(usage.pack, usage.exportName)}"`
30
- });
31
- usedSymbols.add(usage.local);
32
- register(usage.pack, usage.exportName);
33
- }
34
- }
35
- return edits;
36
- };
37
- //#endregion
38
- //#region src/transform/fast-filter.ts
39
- const fastFilter = (code) => {
40
- if (!code.includes("<")) return false;
41
- if (!code.includes("import")) return false;
42
- return true;
43
- };
44
- //#endregion
45
- //#region src/transform/import-scanner.ts
46
- const IMPORT_RE = /import\s+([^;]+?)\s+from\s+['"]([^'"]+)['"]/g;
47
- const scanIconImports = (code, sources) => {
48
- const imports = [];
49
- for (const match of code.matchAll(IMPORT_RE)) {
50
- const [, specifier, pack] = match;
51
- if (!sources.some((source) => source.test(pack))) continue;
52
- const names = specifier.replace(/[{}]/g, "").split(",").map((segment) => segment.trim()).filter(Boolean).map((segment) => segment.split(" as ")[1] ?? segment);
53
- imports.push({
54
- pack,
55
- names
56
- });
57
- }
58
- return imports;
59
- };
60
- //#endregion
61
- //#region src/transform/usage-scanner.ts
62
- const detectUsage = (code, names) => {
63
- if (!names.length) return false;
64
- const escaped = names.map((name) => name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
65
- return new RegExp(`<(${escaped.join("|")})\\b`).test(code);
66
- };
67
- //#endregion
68
- //#region src/transform/transform-module.ts
69
- const ICON_SOURCE = "react-icons-sprite";
70
- const ICON_COMPONENT_NAME = "ReactIconsSpriteIcon";
71
- const FONTAWESOME_REACT_PACK = "@fortawesome/react-fontawesome";
72
- const isFontAwesomeIconPack = (pack) => {
73
- return /^@fortawesome\/[\w-]+-svg-icons$/.test(pack);
74
- };
75
- const isObject = (value) => {
76
- return Boolean(value) && typeof value === "object";
77
- };
78
- const walkAst = (node, visit) => {
79
- if (!isObject(node)) return;
80
- visit(node);
81
- for (const value of Object.values(node)) {
82
- if (Array.isArray(value)) {
83
- for (const child of value) walkAst(child, visit);
84
- continue;
85
- }
86
- walkAst(value, visit);
87
- }
88
- };
89
- const buildSymbolTable = (program, sources) => {
90
- const table = /* @__PURE__ */ new Map();
91
- const body = program.body ?? [];
92
- for (const node of body) {
93
- if (!isObject(node) || node.type !== "ImportDeclaration") continue;
94
- const source = isObject(node.source) ? node.source : void 0;
95
- const pack = typeof source?.value === "string" ? source.value : void 0;
96
- if (!pack || !sources.some((sourceMatcher) => sourceMatcher.test(pack))) continue;
97
- const specifiers = node.specifiers ?? [];
98
- for (const specifier of specifiers) {
99
- if (!isObject(specifier) || !isObject(specifier.local)) continue;
100
- const localName = specifier.local.name;
101
- if (typeof localName !== "string") continue;
102
- if (specifier.type === "ImportSpecifier") {
103
- const imported = isObject(specifier.imported) ? specifier.imported : void 0;
104
- const importedName = typeof imported?.name === "string" ? imported.name : typeof imported?.value === "string" ? imported.value : void 0;
105
- if (importedName) table.set(localName, {
106
- pack,
107
- exportName: importedName
108
- });
109
- continue;
110
- }
111
- if (specifier.type === "ImportDefaultSpecifier") table.set(localName, {
112
- pack,
113
- exportName: "default"
114
- });
115
- }
116
- }
117
- return table;
118
- };
119
- const detectIconUsage = (program, symbols) => {
120
- const usages = [];
121
- walkAst(program, (node) => {
122
- if (node.type !== "JSXOpeningElement" && node.type !== "JSXClosingElement") return;
123
- const name = isObject(node.name) ? node.name : void 0;
124
- if (!name || name.type !== "JSXIdentifier" || typeof name.name !== "string") return;
125
- const symbol = symbols.get(name.name);
126
- if (!symbol) return;
127
- const range = name.range;
128
- if (!range || range.length !== 2) return;
129
- usages.push({
130
- local: name.name,
131
- range,
132
- pack: symbol.pack,
133
- exportName: symbol.exportName,
134
- kind: node.type === "JSXOpeningElement" ? "opening" : "closing"
135
- });
136
- });
137
- return usages;
138
- };
139
- const detectFontAwesomeComponents = (program) => {
140
- const componentLocals = /* @__PURE__ */ new Set();
141
- const body = program.body ?? [];
142
- for (const node of body) {
143
- if (!isObject(node) || node.type !== "ImportDeclaration") continue;
144
- if ((isObject(node.source) ? node.source : void 0)?.value !== FONTAWESOME_REACT_PACK) continue;
145
- const specifiers = node.specifiers ?? [];
146
- for (const specifier of specifiers) {
147
- if (!isObject(specifier) || specifier.type !== "ImportSpecifier" || !isObject(specifier.local) || !isObject(specifier.imported)) continue;
148
- const importedName = typeof specifier.imported.name === "string" ? specifier.imported.name : void 0;
149
- const localName = typeof specifier.local.name === "string" ? specifier.local.name : void 0;
150
- if (importedName === "FontAwesomeIcon" && localName) componentLocals.add(localName);
151
- }
152
- }
153
- return componentLocals;
154
- };
155
- const detectFontAwesomeIconUsages = (program, symbols, fontAwesomeComponentLocals) => {
156
- if (!fontAwesomeComponentLocals.size) return [];
157
- const usages = [];
158
- walkAst(program, (node) => {
159
- if (node.type !== "JSXOpeningElement") return;
160
- const name = isObject(node.name) ? node.name : void 0;
161
- if (!name || name.type !== "JSXIdentifier" || typeof name.name !== "string" || !fontAwesomeComponentLocals.has(name.name)) return;
162
- const componentRange = name.range;
163
- if (!componentRange || componentRange.length !== 2) return;
164
- const attributes = node.attributes ?? [];
165
- for (const attribute of attributes) {
166
- if (!isObject(attribute) || attribute.type !== "JSXAttribute") continue;
167
- const attributeName = isObject(attribute.name) ? attribute.name : void 0;
168
- if (!attributeName || attributeName.type !== "JSXIdentifier" || attributeName.name !== "icon") continue;
169
- const value = isObject(attribute.value) ? attribute.value : void 0;
170
- if (!value || value.type !== "JSXExpressionContainer") continue;
171
- const expression = isObject(value.expression) ? value.expression : void 0;
172
- if (!expression || expression.type !== "Identifier" || typeof expression.name !== "string") continue;
173
- const symbol = symbols.get(expression.name);
174
- if (!symbol || !isFontAwesomeIconPack(symbol.pack)) continue;
175
- const iconAttributeRange = attribute.range;
176
- if (!iconAttributeRange || iconAttributeRange.length !== 2) continue;
177
- usages.push({
178
- componentRange,
179
- iconAttributeRange,
180
- iconLocal: expression.name,
181
- pack: symbol.pack,
182
- exportName: symbol.exportName
183
- });
184
- break;
185
- }
186
- });
187
- return usages;
188
- };
189
- const cleanupImports = (program, symbols, usedLocals) => {
190
- const edits = [];
191
- const body = program.body ?? [];
192
- for (const node of body) {
193
- if (!isObject(node) || node.type !== "ImportDeclaration") continue;
194
- const specifiers = node.specifiers ?? [];
195
- const declarationRange = node.range;
196
- let hasUsedIconSpecifier = false;
197
- for (const specifier of specifiers) {
198
- if (!isObject(specifier) || !isObject(specifier.local)) continue;
199
- const localName = specifier.local.name;
200
- if (typeof localName === "string" && symbols.has(localName) && usedLocals.has(localName)) {
201
- hasUsedIconSpecifier = true;
202
- break;
203
- }
204
- }
205
- if (hasUsedIconSpecifier && declarationRange) edits.push({
206
- type: "remove",
207
- from: declarationRange[0],
208
- to: declarationRange[1]
209
- });
210
- }
211
- return edits;
212
- };
213
- const transformModule = (code, id, register, sources = DEFAULT_ICON_SOURCES) => {
214
- if (!fastFilter(code)) return {
215
- code,
216
- map: null,
217
- anyReplacements: false
218
- };
219
- const scanned = scanIconImports(code, sources);
220
- if (!scanned.length) return {
221
- code,
222
- map: null,
223
- anyReplacements: false
224
- };
225
- const hasJsxComponentUsage = detectUsage(code, scanned.flatMap((item) => item.names));
226
- const hasPotentialFontAwesomeUsage = code.includes(FONTAWESOME_REACT_PACK) && scanned.some((item) => isFontAwesomeIconPack(item.pack));
227
- if (!hasJsxComponentUsage && !hasPotentialFontAwesomeUsage) return {
228
- code,
229
- map: null,
230
- anyReplacements: false
231
- };
232
- const program = parseSync(id, code, {
233
- lang: "tsx",
234
- sourceType: "module",
235
- range: true
236
- }).program;
237
- const table = buildSymbolTable(program, sources);
238
- if (!table.size) return {
239
- code,
240
- map: null,
241
- anyReplacements: false
242
- };
243
- const usages = detectIconUsage(program, table);
244
- const fontAwesomeUsages = detectFontAwesomeIconUsages(program, table, detectFontAwesomeComponents(program));
245
- if (!usages.length && !fontAwesomeUsages.length) return {
246
- code,
247
- map: null,
248
- anyReplacements: false
249
- };
250
- const used = /* @__PURE__ */ new Set();
251
- const edits = buildEdits(usages, ICON_COMPONENT_NAME, used, register);
252
- const registeredFontAwesomeIcons = /* @__PURE__ */ new Set();
253
- for (const usage of fontAwesomeUsages) {
254
- edits.push({
255
- type: "replace",
256
- from: usage.componentRange[0],
257
- to: usage.componentRange[1],
258
- value: ICON_COMPONENT_NAME
259
- });
260
- edits.push({
261
- type: "insert",
262
- pos: usage.componentRange[1],
263
- value: ` iconId="${computeIconId(usage.pack, usage.exportName)}"`
264
- });
265
- edits.push({
266
- type: "remove",
267
- from: usage.iconAttributeRange[0],
268
- to: usage.iconAttributeRange[1]
269
- });
270
- used.add(usage.iconLocal);
271
- const key = `${usage.pack}:${usage.exportName}`;
272
- if (!registeredFontAwesomeIcons.has(key)) {
273
- registeredFontAwesomeIcons.add(key);
274
- register(usage.pack, usage.exportName);
275
- }
276
- }
277
- const cleanupEdits = cleanupImports(program, table, used);
278
- const magicString = applyEdits(code, [...edits, ...cleanupEdits]);
279
- if (!code.includes("react-icons-sprite")) magicString.prepend(`import { ${ICON_COMPONENT_NAME} } from "${ICON_SOURCE}";\n`);
280
- return {
281
- code: magicString.toString(),
282
- map: magicString.generateMap({
283
- source: id,
284
- hires: true
285
- }),
286
- anyReplacements: true
287
- };
288
- };
289
- //#endregion
290
- export { transformModule as t };