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

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.
@@ -237,6 +237,123 @@ const scanJsxIconEdits = (code, symbols, componentName, usedSymbols, register, h
237
237
  count
238
238
  };
239
239
  };
240
+ const scanJsxReferencePropEdits = (code, symbols, componentName, usedSymbols, register, ignoredComponentLocals) => {
241
+ if (symbols.size === 0) return {
242
+ edits: [],
243
+ count: 0
244
+ };
245
+ const edits = [];
246
+ let count = 0;
247
+ for (let index = code.indexOf("<"); index !== -1; index = code.indexOf("<", index + 1)) {
248
+ let cursor = index + 1;
249
+ while (isWhitespace(code[cursor])) cursor += 1;
250
+ if (code[cursor] === "/") continue;
251
+ if (!isIdentifierStart(code[cursor])) continue;
252
+ const componentStart = cursor;
253
+ cursor += 1;
254
+ while (isIdentifierPart(code[cursor])) cursor += 1;
255
+ const componentLocal = code.slice(componentStart, cursor);
256
+ if (symbols.items[componentLocal] || ignoredComponentLocals.has(componentLocal)) continue;
257
+ const tagEnd = findJsxOpeningTagEnd(code, cursor);
258
+ if (tagEnd === -1) continue;
259
+ let attributeCursor = cursor;
260
+ while (attributeCursor < tagEnd) {
261
+ while (isWhitespace(code[attributeCursor])) attributeCursor += 1;
262
+ if (attributeCursor >= tagEnd || code[attributeCursor] === "/") break;
263
+ if (code[attributeCursor] === "{") {
264
+ const expressionEnd = findJsxExpressionEnd(code, attributeCursor);
265
+ attributeCursor = expressionEnd === -1 ? tagEnd : expressionEnd + 1;
266
+ continue;
267
+ }
268
+ if (!isIdentifierStart(code[attributeCursor])) {
269
+ attributeCursor += 1;
270
+ continue;
271
+ }
272
+ attributeCursor += 1;
273
+ while (isJsxAttributeNamePart(code[attributeCursor])) attributeCursor += 1;
274
+ while (isWhitespace(code[attributeCursor])) attributeCursor += 1;
275
+ if (code[attributeCursor] !== "=") continue;
276
+ attributeCursor += 1;
277
+ while (isWhitespace(code[attributeCursor])) attributeCursor += 1;
278
+ const valueStart = attributeCursor;
279
+ if (code[valueStart] === "\"" || code[valueStart] === "'") {
280
+ attributeCursor = skipQuotedString(code, valueStart);
281
+ continue;
282
+ }
283
+ if (code[valueStart] !== "{") continue;
284
+ const expressionEnd = findJsxExpressionEnd(code, valueStart);
285
+ if (expressionEnd === -1 || expressionEnd > tagEnd) {
286
+ attributeCursor = tagEnd;
287
+ continue;
288
+ }
289
+ attributeCursor = expressionEnd + 1;
290
+ const expression = code.slice(valueStart + 1, expressionEnd);
291
+ const iconMatch = /^(\s*)([A-Za-z_$][\w$]*)(\s*)$/.exec(expression);
292
+ if (!iconMatch) continue;
293
+ const iconLocal = iconMatch[2];
294
+ const symbol = symbols.items[iconLocal];
295
+ if (!symbol || isFontAwesomeIconPack(symbol.pack) || isHugeiconsIconPack(symbol.pack)) continue;
296
+ const iconLocalStart = valueStart + 1 + iconMatch[1].length;
297
+ edits.push({
298
+ type: "replace",
299
+ from: iconLocalStart,
300
+ to: iconLocalStart + iconLocal.length,
301
+ value: `(props) => <${componentName} {...props} iconId="${symbol.iconId}" />`
302
+ });
303
+ count += 1;
304
+ usedSymbols.add(iconLocal);
305
+ register(symbol.pack, symbol.exportName);
306
+ }
307
+ }
308
+ return {
309
+ edits,
310
+ count
311
+ };
312
+ };
313
+ const isJsxAttributeNamePart = (char) => {
314
+ return isIdentifierPart(char) || char === "-" || char === "." || char === ":";
315
+ };
316
+ const skipQuotedString = (code, start) => {
317
+ const quote = code[start];
318
+ let cursor = start + 1;
319
+ while (cursor < code.length) {
320
+ if (code[cursor] === "\\") {
321
+ cursor += 2;
322
+ continue;
323
+ }
324
+ if (code[cursor] === quote) return cursor + 1;
325
+ cursor += 1;
326
+ }
327
+ return code.length;
328
+ };
329
+ const findJsxExpressionEnd = (code, start) => {
330
+ let quote = null;
331
+ let braceDepth = 0;
332
+ for (let index = start; index < code.length; index += 1) {
333
+ const char = code[index];
334
+ if (quote) {
335
+ if (char === "\\") {
336
+ index += 1;
337
+ continue;
338
+ }
339
+ if (char === quote) quote = null;
340
+ continue;
341
+ }
342
+ if (char === "\"" || char === "'" || char === "`") {
343
+ quote = char;
344
+ continue;
345
+ }
346
+ if (char === "{") {
347
+ braceDepth += 1;
348
+ continue;
349
+ }
350
+ if (char === "}") {
351
+ braceDepth -= 1;
352
+ if (braceDepth === 0) return index;
353
+ }
354
+ }
355
+ return -1;
356
+ };
240
357
  const hasIconIdAttribute = (code, start, end) => {
241
358
  for (let index = start; index < end; index += 1) {
242
359
  if (code.charCodeAt(index) !== 105 || code.charCodeAt(index + 1) !== 99 || code.charCodeAt(index + 2) !== 111 || code.charCodeAt(index + 3) !== 110 || code.charCodeAt(index + 4) !== 73 || code.charCodeAt(index + 5) !== 100) continue;
@@ -466,6 +583,82 @@ const consumeTrailingWhitespace = (code, start) => {
466
583
  while (to < code.length && isWhitespaceCode(code.charCodeAt(to))) to += 1;
467
584
  return to;
468
585
  };
586
+ const editRange = (edit) => {
587
+ if (edit.type === "insert") return null;
588
+ return [edit.from, edit.to];
589
+ };
590
+ const rangeContains = ([start, end], index) => {
591
+ return index >= start && index < end;
592
+ };
593
+ const collectUsedLocalsOutsideRanges = (code, usedLocals, ignoredRanges) => {
594
+ const ranges = [...ignoredRanges].sort((left, right) => left[0] - right[0]);
595
+ const found = /* @__PURE__ */ new Set();
596
+ let rangeIndex = 0;
597
+ let quote = null;
598
+ let lineComment = false;
599
+ let blockComment = false;
600
+ for (let index = 0; index < code.length; index += 1) {
601
+ while (rangeIndex < ranges.length && ranges[rangeIndex][1] <= index) rangeIndex += 1;
602
+ const ignoredRange = ranges[rangeIndex];
603
+ if (ignoredRange && rangeContains(ignoredRange, index)) {
604
+ index = ignoredRange[1] - 1;
605
+ continue;
606
+ }
607
+ const char = code[index];
608
+ const next = code[index + 1];
609
+ if (lineComment) {
610
+ if (char === "\n" || char === "\r") lineComment = false;
611
+ continue;
612
+ }
613
+ if (blockComment) {
614
+ if (char === "*" && next === "/") {
615
+ blockComment = false;
616
+ index += 1;
617
+ }
618
+ continue;
619
+ }
620
+ if (quote) {
621
+ if (char === "\\") {
622
+ index += 1;
623
+ continue;
624
+ }
625
+ if (char === quote) quote = null;
626
+ continue;
627
+ }
628
+ if (char === "/" && next === "/") {
629
+ lineComment = true;
630
+ index += 1;
631
+ continue;
632
+ }
633
+ if (char === "/" && next === "*") {
634
+ blockComment = true;
635
+ index += 1;
636
+ continue;
637
+ }
638
+ if (char === "\"" || char === "'") {
639
+ quote = char;
640
+ continue;
641
+ }
642
+ if (!isIdentifierStart(char)) continue;
643
+ const start = index;
644
+ index += 1;
645
+ while (isIdentifierPart(code[index])) index += 1;
646
+ const local = code.slice(start, index);
647
+ if (usedLocals.has(local)) found.add(local);
648
+ index -= 1;
649
+ }
650
+ return found;
651
+ };
652
+ const filterRemovableUsedLocals = (code, imports, usedLocals, usageEdits) => {
653
+ const ignoredRanges = [...imports.map((item) => item.declarationRange), ...usageEdits.flatMap((edit) => {
654
+ const range = editRange(edit);
655
+ return range ? [range] : [];
656
+ })];
657
+ const liveLocals = collectUsedLocalsOutsideRanges(code, usedLocals, ignoredRanges);
658
+ const removable = /* @__PURE__ */ new Set();
659
+ for (const local of usedLocals) if (!liveLocals.has(local)) removable.add(local);
660
+ return removable;
661
+ };
469
662
  const transformModule = (code, id, register, sources = DEFAULT_ICON_SOURCES, options = {}) => {
470
663
  const { sourceMap = false } = options;
471
664
  if (!fastFilter(code)) return {
@@ -492,17 +685,21 @@ const transformModule = (code, id, register, sources = DEFAULT_ICON_SOURCES, opt
492
685
  localName: ICON_COMPONENT_NAME
493
686
  };
494
687
  const used = /* @__PURE__ */ new Set();
688
+ const fontAwesomeComponents = hasPotentialFontAwesomeUsage ? scanFontAwesomeComponents(code) : /* @__PURE__ */ new Set();
689
+ const hugeiconsComponents = hasPotentialHugeiconsUsage ? scanHugeiconsComponents(code) : /* @__PURE__ */ new Set();
495
690
  const usedFontAwesomeComponents = /* @__PURE__ */ new Set();
496
691
  const usedHugeiconsComponents = /* @__PURE__ */ new Set();
692
+ const ignoredIconPropComponentLocals = /* @__PURE__ */ new Set([...fontAwesomeComponents, ...hugeiconsComponents]);
497
693
  const jsxScan = scanJsxIconEdits(code, table, spriteIconImport.localName, used, register, code.includes("iconId"));
498
- const fontAwesomeUsages = hasPotentialFontAwesomeUsage ? scanFontAwesomeUsages(code, table, scanFontAwesomeComponents(code)) : [];
499
- const hugeiconsUsages = hasPotentialHugeiconsUsage ? scanHugeiconsUsages(code, table, scanHugeiconsComponents(code)) : [];
500
- if (jsxScan.count === 0 && !fontAwesomeUsages.length && !hugeiconsUsages.length) return {
694
+ const referencePropScan = scanJsxReferencePropEdits(code, table, spriteIconImport.localName, used, register, ignoredIconPropComponentLocals);
695
+ const fontAwesomeUsages = hasPotentialFontAwesomeUsage ? scanFontAwesomeUsages(code, table, fontAwesomeComponents) : [];
696
+ const hugeiconsUsages = hasPotentialHugeiconsUsage ? scanHugeiconsUsages(code, table, hugeiconsComponents) : [];
697
+ if (jsxScan.count === 0 && referencePropScan.count === 0 && !fontAwesomeUsages.length && !hugeiconsUsages.length) return {
501
698
  code,
502
699
  map: null,
503
700
  anyReplacements: false
504
701
  };
505
- const edits = jsxScan.edits;
702
+ const edits = [...jsxScan.edits, ...referencePropScan.edits];
506
703
  const registeredFontAwesomeIcons = /* @__PURE__ */ new Set();
507
704
  for (const usage of fontAwesomeUsages) {
508
705
  edits.push({
@@ -555,10 +752,11 @@ const transformModule = (code, id, register, sources = DEFAULT_ICON_SOURCES, opt
555
752
  register(usage.pack, usage.exportName);
556
753
  }
557
754
  }
558
- const cleanupEdits = cleanupScannedImports(code, scannedImports, used);
755
+ const removableUsed = filterRemovableUsedLocals(code, scannedImports, used, edits);
756
+ const cleanupEdits = cleanupScannedImports(code, scannedImports, removableUsed);
559
757
  const cleanupFontAwesomeEdits = usedFontAwesomeComponents.size ? cleanupScannedFontAwesomeComponentImports(code, usedFontAwesomeComponents) : [];
560
758
  const cleanupHugeiconsEdits = usedHugeiconsComponents.size ? cleanupScannedHugeiconsComponentImports(code, usedHugeiconsComponents) : [];
561
- const canUsePresortedEdits = cleanupFontAwesomeEdits.length === 0 && cleanupHugeiconsEdits.length === 0;
759
+ const canUsePresortedEdits = referencePropScan.count === 0 && cleanupFontAwesomeEdits.length === 0 && cleanupHugeiconsEdits.length === 0;
562
760
  const allEdits = canUsePresortedEdits ? [...cleanupEdits, ...edits] : [
563
761
  ...edits,
564
762
  ...cleanupEdits,
@@ -1,7 +1,7 @@
1
1
  import { REACT_ICONS_SPRITE_URL_PLACEHOLDER } from "../index.mjs";
2
2
  import { a as createCollector, r as DEFAULT_ICON_SOURCES } from "../compute-icon-id-Xx4G3fva.mjs";
3
3
  import { t as buildSprite } from "../build-sprite-CxqvU2LE.mjs";
4
- import { t as transformModule } from "../transform-module-1am5uV8-.mjs";
4
+ import { t as transformModule } from "../transform-module-3mbTFhoj.mjs";
5
5
  import { createHash } from "node:crypto";
6
6
  //#region src/vite/plugin.ts
7
7
  const reactIconsSprite = (options = {}) => {
@@ -1,4 +1,4 @@
1
- import { t as transformModule } from "../transform-module-1am5uV8-.mjs";
1
+ import { t as transformModule } from "../transform-module-3mbTFhoj.mjs";
2
2
  import { t as collector } from "../collector-CBRz9R_h.mjs";
3
3
  //#region src/webpack/loader.ts
4
4
  const reactIconsSpriteLoader = async function(source) {
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.1.1",
4
+ "version": "1.2.0-rc.2",
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>",