react-icons-sprite 1.0.0-rc.1 → 1.0.0
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/dist/build-sprite-j9zt_17z.mjs +147 -0
- package/dist/{collector-wdoob7qt.mjs → collector-YV25X1xI.mjs} +1 -1
- package/dist/{compute-icon-id-DKiwIqyH.mjs → compute-icon-id-B590yK7l.mjs} +9 -6
- package/dist/{transform-module-Dn8lfegG.mjs → transform-module-zJo6_2rz.mjs} +80 -13
- package/dist/vite/plugin.mjs +8 -4
- package/dist/webpack/loader.mjs +2 -2
- package/dist/webpack/plugin.mjs +3 -3
- package/package.json +2 -2
- package/dist/build-sprite-BZCizCDt.mjs +0 -73
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { r as resolveIconImport, t as computeIconId } from "./compute-icon-id-B590yK7l.mjs";
|
|
2
|
+
import { existsSync, readFileSync } 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
|
+
//#region src/sprite/render-icon.ts
|
|
8
|
+
const SVG_INNER_RE = /<svg\b[^>]*>([\s\S]*?)<\/svg>/i;
|
|
9
|
+
const VIEWBOX_RE = /viewBox=["']([^"']+)["']/i;
|
|
10
|
+
const SVG_OPEN_RE = /<svg\b([^>]*)>/i;
|
|
11
|
+
const SVG_ATTR_RE = /([:\w-]+)=("[^"]*"|'[^']*')/g;
|
|
12
|
+
const OMITTED_SVG_ATTRIBUTES = new Set([
|
|
13
|
+
"xmlns",
|
|
14
|
+
"viewBox",
|
|
15
|
+
"width",
|
|
16
|
+
"height"
|
|
17
|
+
]);
|
|
18
|
+
const parsePackageSpecifier = (specifier) => {
|
|
19
|
+
if (specifier.startsWith(".") || path.isAbsolute(specifier)) return null;
|
|
20
|
+
const parts = specifier.split("/");
|
|
21
|
+
const packageName = specifier.startsWith("@") ? `${parts[0]}/${parts[1]}` : parts[0];
|
|
22
|
+
const rest = parts.slice(specifier.startsWith("@") ? 2 : 1).join("/");
|
|
23
|
+
return {
|
|
24
|
+
packageName,
|
|
25
|
+
subpath: rest ? `./${rest}` : "."
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
const findPackageRoot = (packageName, baseDir) => {
|
|
29
|
+
let current = path.resolve(baseDir);
|
|
30
|
+
while (true) {
|
|
31
|
+
const candidate = path.join(current, "node_modules", packageName);
|
|
32
|
+
if (existsSync(path.join(candidate, "package.json"))) return candidate;
|
|
33
|
+
const parent = path.dirname(current);
|
|
34
|
+
if (parent === current) return null;
|
|
35
|
+
current = parent;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
const pickExportTarget = (value) => {
|
|
39
|
+
if (typeof value === "string") return value;
|
|
40
|
+
if (!value || typeof value !== "object") return null;
|
|
41
|
+
const record = value;
|
|
42
|
+
return pickExportTarget(record.import) ?? pickExportTarget(record.default) ?? pickExportTarget(record.module) ?? pickExportTarget(record.require);
|
|
43
|
+
};
|
|
44
|
+
const resolveExportTarget = (exportsField, subpath) => {
|
|
45
|
+
if (typeof exportsField === "string" || Array.isArray(exportsField)) return subpath === "." ? pickExportTarget(exportsField) : null;
|
|
46
|
+
if (!exportsField || typeof exportsField !== "object") return null;
|
|
47
|
+
const exportsRecord = exportsField;
|
|
48
|
+
const exact = pickExportTarget(exportsRecord[subpath]);
|
|
49
|
+
if (exact) return exact;
|
|
50
|
+
for (const [key, value] of Object.entries(exportsRecord)) {
|
|
51
|
+
if (!key.includes("*")) continue;
|
|
52
|
+
const [prefix, suffix] = key.split("*");
|
|
53
|
+
if (!subpath.startsWith(prefix) || !subpath.endsWith(suffix)) continue;
|
|
54
|
+
const matched = subpath.slice(prefix.length, subpath.length - suffix.length);
|
|
55
|
+
const target = pickExportTarget(value);
|
|
56
|
+
if (target) return target.replaceAll("*", matched);
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
};
|
|
60
|
+
const fileExists = (filePath) => existsSync(filePath);
|
|
61
|
+
const resolveFileCandidate = (filePath) => {
|
|
62
|
+
return [
|
|
63
|
+
filePath,
|
|
64
|
+
`${filePath}.mjs`,
|
|
65
|
+
`${filePath}.js`,
|
|
66
|
+
path.join(filePath, "index.mjs"),
|
|
67
|
+
path.join(filePath, "index.js")
|
|
68
|
+
].find(fileExists) ?? null;
|
|
69
|
+
};
|
|
70
|
+
const resolveFromBaseDir = (specifier, baseDir) => {
|
|
71
|
+
const parsed = parsePackageSpecifier(specifier);
|
|
72
|
+
if (!parsed) return null;
|
|
73
|
+
const packageRoot = findPackageRoot(parsed.packageName, baseDir);
|
|
74
|
+
if (!packageRoot) return null;
|
|
75
|
+
const packageJson = JSON.parse(readFileSync(path.join(packageRoot, "package.json"), "utf8"));
|
|
76
|
+
const exportTarget = resolveExportTarget(packageJson.exports, parsed.subpath);
|
|
77
|
+
if (exportTarget) return resolveFileCandidate(path.join(packageRoot, exportTarget)) ?? null;
|
|
78
|
+
if (parsed.subpath === ".") {
|
|
79
|
+
const entry = packageJson.module ?? packageJson.main;
|
|
80
|
+
if (entry) return resolveFileCandidate(path.join(packageRoot, entry));
|
|
81
|
+
}
|
|
82
|
+
return resolveFileCandidate(path.join(packageRoot, parsed.subpath));
|
|
83
|
+
};
|
|
84
|
+
const resolveImportSpecifier = (specifier, options) => {
|
|
85
|
+
if (!options.baseDir) return specifier;
|
|
86
|
+
const resolved = resolveFromBaseDir(specifier, options.baseDir);
|
|
87
|
+
return resolved ? pathToFileURL(resolved).href : specifier;
|
|
88
|
+
};
|
|
89
|
+
const extractSymbolAttributes = (svgMarkup) => {
|
|
90
|
+
const openingAttributes = SVG_OPEN_RE.exec(svgMarkup)?.[1];
|
|
91
|
+
if (!openingAttributes) return "";
|
|
92
|
+
const attributes = [];
|
|
93
|
+
for (const [, name, value] of openingAttributes.matchAll(SVG_ATTR_RE)) {
|
|
94
|
+
if (OMITTED_SVG_ATTRIBUTES.has(name)) continue;
|
|
95
|
+
attributes.push(`${name}=${value}`);
|
|
96
|
+
}
|
|
97
|
+
return attributes.join(" ");
|
|
98
|
+
};
|
|
99
|
+
const pickExport = (moduleExports, exportName) => {
|
|
100
|
+
if (exportName === "default") return moduleExports.default;
|
|
101
|
+
return moduleExports[exportName] ?? moduleExports.default;
|
|
102
|
+
};
|
|
103
|
+
const isRenderableComponent = (value) => {
|
|
104
|
+
if (typeof value === "function") return true;
|
|
105
|
+
if (!value || typeof value !== "object") return false;
|
|
106
|
+
return "$$typeof" in value;
|
|
107
|
+
};
|
|
108
|
+
const isFontAwesomeIconDefinition = (value) => {
|
|
109
|
+
if (!value || typeof value !== "object") return false;
|
|
110
|
+
const icon = value.icon;
|
|
111
|
+
if (!Array.isArray(icon) || icon.length < 5) return false;
|
|
112
|
+
return typeof icon[0] === "number" && typeof icon[1] === "number";
|
|
113
|
+
};
|
|
114
|
+
const renderFontAwesomeIconDefinition = (iconDefinition) => {
|
|
115
|
+
const [width, height, , , svgPathData] = iconDefinition.icon;
|
|
116
|
+
return {
|
|
117
|
+
symbolBody: (Array.isArray(svgPathData) ? svgPathData : [svgPathData]).map((d) => `<path d="${d}"/>`).join(""),
|
|
118
|
+
viewBox: `0 0 ${width} ${height}`,
|
|
119
|
+
symbolAttributes: ""
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
const renderIcon = async (pack, exportName, options = {}) => {
|
|
123
|
+
const iconComponent = pickExport(await import(resolveImportSpecifier(resolveIconImport(pack, exportName), options)), exportName);
|
|
124
|
+
if (isFontAwesomeIconDefinition(iconComponent)) return renderFontAwesomeIconDefinition(iconComponent);
|
|
125
|
+
if (!isRenderableComponent(iconComponent)) throw new Error(`[react-icons-sprite] Unable to render icon "${exportName}" from "${pack}". Expected a React component export.`);
|
|
126
|
+
const svgMarkup = renderToStaticMarkup(createElement(iconComponent));
|
|
127
|
+
const svgInner = SVG_INNER_RE.exec(svgMarkup)?.[1];
|
|
128
|
+
if (!svgInner) throw new Error(`[react-icons-sprite] Unable to extract SVG content for "${exportName}" from "${pack}".`);
|
|
129
|
+
return {
|
|
130
|
+
symbolBody: svgInner,
|
|
131
|
+
viewBox: VIEWBOX_RE.exec(svgMarkup)?.[1] ?? "0 0 24 24",
|
|
132
|
+
symbolAttributes: extractSymbolAttributes(svgMarkup)
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
//#endregion
|
|
136
|
+
//#region src/sprite/build-sprite.ts
|
|
137
|
+
const buildSprite = async (icons, options = {}) => {
|
|
138
|
+
if (!icons.length) return "<svg xmlns=\"http://www.w3.org/2000/svg\" style=\"display:none\"></svg>";
|
|
139
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" style="display:none">${(await Promise.all(icons.map(async ({ pack, exportName }) => {
|
|
140
|
+
const rendered = await renderIcon(pack, exportName, { baseDir: options.baseDir });
|
|
141
|
+
const id = computeIconId(pack, exportName);
|
|
142
|
+
const symbolAttributes = rendered.symbolAttributes ? ` ${rendered.symbolAttributes}` : "";
|
|
143
|
+
return `<symbol id="${id}" viewBox="${rendered.viewBox}"${symbolAttributes}>${rendered.symbolBody}</symbol>`;
|
|
144
|
+
}))).join("")}</svg>`;
|
|
145
|
+
};
|
|
146
|
+
//#endregion
|
|
147
|
+
export { buildSprite as t };
|
|
@@ -43,15 +43,18 @@ const DEFAULT_ICON_SOURCES = [
|
|
|
43
43
|
/^@mui\/icons-material(?:\/.*)?$/,
|
|
44
44
|
/^@carbon\/icons-react$/
|
|
45
45
|
];
|
|
46
|
+
const phosphorIconPathName = (name) => name.endsWith("Icon") ? name.slice(0, -4) : name;
|
|
46
47
|
const exactResolvers = {
|
|
47
|
-
"lucide-react": (pack, name) => `${pack}/dist/esm/icons/${kebabCase(name)}.
|
|
48
|
-
"@radix-ui/react-icons": (pack
|
|
48
|
+
"lucide-react": (pack, name) => `${pack}/dist/esm/icons/${kebabCase(name)}.mjs`,
|
|
49
|
+
"@radix-ui/react-icons": (pack) => `${pack}/dist/react-icons.esm.js`,
|
|
49
50
|
"@tabler/icons-react": (pack, name) => `${pack}/dist/esm/icons/${name}.mjs`,
|
|
50
|
-
"@phosphor-icons/react": (pack, name) => `${pack}/dist/ssr/${name}
|
|
51
|
+
"@phosphor-icons/react": (pack, name) => `${pack}/dist/ssr/${phosphorIconPathName(name)}`,
|
|
51
52
|
"phosphor-react": (pack, name) => `${pack}/dist/icons/${name}.esm.js`,
|
|
52
|
-
"react-
|
|
53
|
-
"react-
|
|
54
|
-
"
|
|
53
|
+
"react-bootstrap-icons": (pack, name) => `${pack}/dist/icons/${kebabCase(name)}.js`,
|
|
54
|
+
"react-feather": (pack, name) => `${pack}/dist/icons/${kebabCase(name)}.js`,
|
|
55
|
+
"grommet-icons": (pack, name) => `${pack}/icons/${name}.js`,
|
|
56
|
+
"devicons-react": (pack, name) => `${pack}/icons/${name}`,
|
|
57
|
+
"@carbon/icons-react": (pack, name) => `${pack}/es/${name}.js`
|
|
55
58
|
};
|
|
56
59
|
const resolveIconImport = (pack, exportName) => {
|
|
57
60
|
const exactResolver = exactResolvers[pack];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as DEFAULT_ICON_SOURCES, t as computeIconId } from "./compute-icon-id-
|
|
1
|
+
import { n as DEFAULT_ICON_SOURCES, t as computeIconId } from "./compute-icon-id-B590yK7l.mjs";
|
|
2
2
|
import MagicString from "magic-string";
|
|
3
3
|
//#region src/transform/edit-applier.ts
|
|
4
4
|
const applyEdits = (code, edits) => {
|
|
@@ -10,6 +10,36 @@ const applyEdits = (code, edits) => {
|
|
|
10
10
|
}
|
|
11
11
|
return magicString;
|
|
12
12
|
};
|
|
13
|
+
const editStart = (edit) => {
|
|
14
|
+
return edit.type === "insert" ? edit.pos : edit.from;
|
|
15
|
+
};
|
|
16
|
+
const applyEditsToString = (code, edits) => {
|
|
17
|
+
if (!edits.length) return code;
|
|
18
|
+
const orderedEdits = [...edits].sort((left, right) => {
|
|
19
|
+
const startDelta = editStart(left) - editStart(right);
|
|
20
|
+
if (startDelta !== 0) return startDelta;
|
|
21
|
+
if (left.type === "insert" && right.type !== "insert") return 1;
|
|
22
|
+
if (left.type !== "insert" && right.type === "insert") return -1;
|
|
23
|
+
return 0;
|
|
24
|
+
});
|
|
25
|
+
let result = "";
|
|
26
|
+
let cursor = 0;
|
|
27
|
+
for (const edit of orderedEdits) {
|
|
28
|
+
if (edit.type === "insert") {
|
|
29
|
+
if (edit.pos <= cursor) {
|
|
30
|
+
result += edit.value;
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
result += code.slice(cursor, edit.pos) + edit.value;
|
|
34
|
+
cursor = edit.pos;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
result += code.slice(cursor, edit.from);
|
|
38
|
+
if (edit.type === "replace") result += edit.value;
|
|
39
|
+
cursor = edit.to;
|
|
40
|
+
}
|
|
41
|
+
return result + code.slice(cursor);
|
|
42
|
+
};
|
|
13
43
|
//#endregion
|
|
14
44
|
//#region src/transform/edit-builder.ts
|
|
15
45
|
const buildEdits = (usages, componentName, usedSymbols, register) => {
|
|
@@ -160,25 +190,43 @@ const scanSpriteIconImport = (code) => {
|
|
|
160
190
|
const escapeRegExp = (value) => {
|
|
161
191
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
162
192
|
};
|
|
193
|
+
const isIdentifierStart = (char) => {
|
|
194
|
+
return char !== void 0 && (char >= "A" && char <= "Z" || char >= "a" && char <= "z" || char === "_" || char === "$");
|
|
195
|
+
};
|
|
196
|
+
const isIdentifierPart = (char) => {
|
|
197
|
+
return char !== void 0 && (isIdentifierStart(char) || char >= "0" && char <= "9");
|
|
198
|
+
};
|
|
199
|
+
const isWhitespace = (char) => {
|
|
200
|
+
return char === " " || char === " " || char === "\n" || char === "\r" || char === "\f";
|
|
201
|
+
};
|
|
163
202
|
const scanJsxIconUsages = (code, symbols) => {
|
|
164
203
|
if (!symbols.size) return [];
|
|
165
|
-
const names = [...symbols.keys()].map(escapeRegExp).join("|");
|
|
166
|
-
const tagRe = new RegExp(`<\\s*(/?)\\s*(${names})\\b`, "g");
|
|
167
204
|
const usages = [];
|
|
168
|
-
for (
|
|
169
|
-
|
|
205
|
+
for (let index = 0; index < code.length; index += 1) {
|
|
206
|
+
if (code.charCodeAt(index) !== 60) continue;
|
|
207
|
+
let cursor = index + 1;
|
|
208
|
+
while (isWhitespace(code[cursor])) cursor += 1;
|
|
209
|
+
const closing = code[cursor] === "/";
|
|
210
|
+
if (closing) {
|
|
211
|
+
cursor += 1;
|
|
212
|
+
while (isWhitespace(code[cursor])) cursor += 1;
|
|
213
|
+
}
|
|
214
|
+
if (!isIdentifierStart(code[cursor])) continue;
|
|
215
|
+
const localStart = cursor;
|
|
216
|
+
cursor += 1;
|
|
217
|
+
while (isIdentifierPart(code[cursor])) cursor += 1;
|
|
218
|
+
const local = code.slice(localStart, cursor);
|
|
170
219
|
const symbol = symbols.get(local);
|
|
171
220
|
if (!symbol) continue;
|
|
172
|
-
const localStart = match.index + match[0].lastIndexOf(local);
|
|
173
221
|
const kind = closing ? "closing" : "opening";
|
|
174
222
|
let hasIconId = false;
|
|
175
223
|
if (!closing) {
|
|
176
|
-
const tagEnd = findJsxOpeningTagEnd(code,
|
|
177
|
-
hasIconId = tagEnd !== -1 &&
|
|
224
|
+
const tagEnd = findJsxOpeningTagEnd(code, cursor);
|
|
225
|
+
hasIconId = tagEnd !== -1 && hasIconIdAttribute(code, cursor, tagEnd);
|
|
178
226
|
}
|
|
179
227
|
usages.push({
|
|
180
228
|
local,
|
|
181
|
-
range: [localStart,
|
|
229
|
+
range: [localStart, cursor],
|
|
182
230
|
pack: symbol.pack,
|
|
183
231
|
exportName: symbol.exportName,
|
|
184
232
|
kind,
|
|
@@ -187,6 +235,16 @@ const scanJsxIconUsages = (code, symbols) => {
|
|
|
187
235
|
}
|
|
188
236
|
return usages;
|
|
189
237
|
};
|
|
238
|
+
const hasIconIdAttribute = (code, start, end) => {
|
|
239
|
+
for (let index = start; index < end; index += 1) {
|
|
240
|
+
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;
|
|
241
|
+
if (isIdentifierPart(code[index - 1]) || isIdentifierPart(code[index + 6])) continue;
|
|
242
|
+
let cursor = index + 6;
|
|
243
|
+
while (isWhitespace(code[cursor])) cursor += 1;
|
|
244
|
+
if (code[cursor] === "=") return true;
|
|
245
|
+
}
|
|
246
|
+
return false;
|
|
247
|
+
};
|
|
190
248
|
const findJsxOpeningTagEnd = (code, start) => {
|
|
191
249
|
let quote = null;
|
|
192
250
|
let braceDepth = 0;
|
|
@@ -351,7 +409,10 @@ const transformModule = (code, id, register, sources = DEFAULT_ICON_SOURCES, opt
|
|
|
351
409
|
map: null,
|
|
352
410
|
anyReplacements: false
|
|
353
411
|
};
|
|
354
|
-
const spriteIconImport = scanSpriteIconImport(code)
|
|
412
|
+
const spriteIconImport = code.includes("react-icons-sprite") ? scanSpriteIconImport(code) : {
|
|
413
|
+
hasImport: false,
|
|
414
|
+
localName: ICON_COMPONENT_NAME
|
|
415
|
+
};
|
|
355
416
|
const usages = scanJsxIconUsages(code, table);
|
|
356
417
|
const fontAwesomeUsages = hasPotentialFontAwesomeUsage ? scanFontAwesomeUsages(code, table, scanFontAwesomeComponents(code)) : [];
|
|
357
418
|
if (!usages.length && !fontAwesomeUsages.length) return {
|
|
@@ -389,13 +450,19 @@ const transformModule = (code, id, register, sources = DEFAULT_ICON_SOURCES, opt
|
|
|
389
450
|
}
|
|
390
451
|
}
|
|
391
452
|
const cleanupEdits = cleanupScannedImports(code, scannedImports, used);
|
|
392
|
-
const cleanupFontAwesomeEdits = cleanupScannedFontAwesomeComponentImports(code, usedFontAwesomeComponents);
|
|
393
|
-
const
|
|
453
|
+
const cleanupFontAwesomeEdits = usedFontAwesomeComponents.size ? cleanupScannedFontAwesomeComponentImports(code, usedFontAwesomeComponents) : [];
|
|
454
|
+
const allEdits = [
|
|
394
455
|
...edits,
|
|
395
456
|
...cleanupEdits,
|
|
396
457
|
...cleanupFontAwesomeEdits
|
|
397
|
-
]
|
|
458
|
+
];
|
|
398
459
|
const importPrefix = `import { ${ICON_COMPONENT_NAME} } from "${ICON_SOURCE}";\n`;
|
|
460
|
+
if (!sourceMap) return {
|
|
461
|
+
code: `${spriteIconImport.hasImport ? "" : importPrefix}${applyEditsToString(code, allEdits)}`,
|
|
462
|
+
map: null,
|
|
463
|
+
anyReplacements: true
|
|
464
|
+
};
|
|
465
|
+
const magicString = applyEdits(code, allEdits);
|
|
399
466
|
if (!spriteIconImport.hasImport) magicString.prepend(importPrefix);
|
|
400
467
|
return {
|
|
401
468
|
code: magicString.toString(),
|
package/dist/vite/plugin.mjs
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
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-
|
|
3
|
-
import { t as buildSprite } from "../build-sprite-
|
|
4
|
-
import { t as transformModule } from "../transform-module-
|
|
2
|
+
import { i as createCollector, n as DEFAULT_ICON_SOURCES } from "../compute-icon-id-B590yK7l.mjs";
|
|
3
|
+
import { t as buildSprite } from "../build-sprite-j9zt_17z.mjs";
|
|
4
|
+
import { t as transformModule } from "../transform-module-zJo6_2rz.mjs";
|
|
5
5
|
import { createHash } from "node:crypto";
|
|
6
6
|
//#region src/vite/plugin.ts
|
|
7
7
|
const reactIconsSprite = (options = {}) => {
|
|
8
8
|
const { fileName } = options;
|
|
9
9
|
const collector = createCollector();
|
|
10
|
+
let root = process.cwd();
|
|
10
11
|
return {
|
|
11
12
|
name: "vite-plugin-react-icons-sprite",
|
|
12
13
|
enforce: "pre",
|
|
@@ -14,6 +15,9 @@ const reactIconsSprite = (options = {}) => {
|
|
|
14
15
|
buildStart() {
|
|
15
16
|
collector.clear();
|
|
16
17
|
},
|
|
18
|
+
configResolved(config) {
|
|
19
|
+
root = config.root;
|
|
20
|
+
},
|
|
17
21
|
transform(code, id) {
|
|
18
22
|
const cleanId = id.split("?", 1)[0];
|
|
19
23
|
if (!/\.(mjs|cjs|js|jsx|ts|tsx)$/.test(cleanId)) return null;
|
|
@@ -32,7 +36,7 @@ const reactIconsSprite = (options = {}) => {
|
|
|
32
36
|
}
|
|
33
37
|
},
|
|
34
38
|
async generateBundle(_options, bundle) {
|
|
35
|
-
const spriteXml = await buildSprite(collector.toList());
|
|
39
|
+
const spriteXml = await buildSprite(collector.toList(), { baseDir: root });
|
|
36
40
|
const generatedHash = createHash("sha256").update(spriteXml).digest("hex").slice(0, 8);
|
|
37
41
|
const emitFileOptions = {
|
|
38
42
|
type: "asset",
|
package/dist/webpack/loader.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as transformModule } from "../transform-module-
|
|
2
|
-
import { t as collector } from "../collector-
|
|
1
|
+
import { t as transformModule } from "../transform-module-zJo6_2rz.mjs";
|
|
2
|
+
import { t as collector } from "../collector-YV25X1xI.mjs";
|
|
3
3
|
//#region src/webpack/loader.ts
|
|
4
4
|
const reactIconsSpriteLoader = async function(source) {
|
|
5
5
|
if (this.mode === "development") return source;
|
package/dist/webpack/plugin.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { REACT_ICONS_SPRITE_URL_PLACEHOLDER } from "../index.mjs";
|
|
2
|
-
import { t as buildSprite } from "../build-sprite-
|
|
3
|
-
import { t as collector } from "../collector-
|
|
2
|
+
import { t as buildSprite } from "../build-sprite-j9zt_17z.mjs";
|
|
3
|
+
import { t as collector } from "../collector-YV25X1xI.mjs";
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
5
|
//#region src/webpack/plugin.ts
|
|
6
6
|
var ReactIconsSpriteWebpackPlugin = class {
|
|
@@ -18,7 +18,7 @@ var ReactIconsSpriteWebpackPlugin = class {
|
|
|
18
18
|
name: pluginName,
|
|
19
19
|
stage
|
|
20
20
|
}, async () => {
|
|
21
|
-
const spriteXml = await buildSprite(collector.toList());
|
|
21
|
+
const spriteXml = await buildSprite(collector.toList(), { baseDir: compiler.context });
|
|
22
22
|
const generatedHash = createHash("sha256").update(spriteXml).digest("hex").slice(0, 8);
|
|
23
23
|
const name = this.fileName ?? `react-icons-sprite-${generatedHash}.svg`;
|
|
24
24
|
const RawSource = compiler.webpack?.sources?.RawSource;
|
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.0.0
|
|
4
|
+
"version": "1.0.0",
|
|
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>",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"@carbon/icons-react": "11.81.0",
|
|
72
72
|
"@types/node": "25.9.1",
|
|
73
73
|
"@types/react-dom": "19.2.3",
|
|
74
|
-
"@typescript/native-preview": "7.0.0-dev.
|
|
74
|
+
"@typescript/native-preview": "7.0.0-dev.20260522.1",
|
|
75
75
|
"react": "19.2.6",
|
|
76
76
|
"react-dom": "19.2.6",
|
|
77
77
|
"tsdown": "0.22.0",
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { r as resolveIconImport, t as computeIconId } from "./compute-icon-id-DKiwIqyH.mjs";
|
|
2
|
-
import { createElement } from "react";
|
|
3
|
-
import { renderToStaticMarkup } from "react-dom/server";
|
|
4
|
-
//#region src/sprite/render-icon.ts
|
|
5
|
-
const SVG_INNER_RE = /<svg\b[^>]*>([\s\S]*?)<\/svg>/i;
|
|
6
|
-
const VIEWBOX_RE = /viewBox=["']([^"']+)["']/i;
|
|
7
|
-
const SVG_OPEN_RE = /<svg\b([^>]*)>/i;
|
|
8
|
-
const SVG_ATTR_RE = /([:\w-]+)=("[^"]*"|'[^']*')/g;
|
|
9
|
-
const OMITTED_SVG_ATTRIBUTES = new Set([
|
|
10
|
-
"xmlns",
|
|
11
|
-
"viewBox",
|
|
12
|
-
"width",
|
|
13
|
-
"height"
|
|
14
|
-
]);
|
|
15
|
-
const extractSymbolAttributes = (svgMarkup) => {
|
|
16
|
-
const openingAttributes = SVG_OPEN_RE.exec(svgMarkup)?.[1];
|
|
17
|
-
if (!openingAttributes) return "";
|
|
18
|
-
const attributes = [];
|
|
19
|
-
for (const [, name, value] of openingAttributes.matchAll(SVG_ATTR_RE)) {
|
|
20
|
-
if (OMITTED_SVG_ATTRIBUTES.has(name)) continue;
|
|
21
|
-
attributes.push(`${name}=${value}`);
|
|
22
|
-
}
|
|
23
|
-
return attributes.join(" ");
|
|
24
|
-
};
|
|
25
|
-
const pickExport = (moduleExports, exportName) => {
|
|
26
|
-
if (exportName === "default") return moduleExports.default;
|
|
27
|
-
return moduleExports[exportName] ?? moduleExports.default;
|
|
28
|
-
};
|
|
29
|
-
const isRenderableComponent = (value) => {
|
|
30
|
-
if (typeof value === "function") return true;
|
|
31
|
-
if (!value || typeof value !== "object") return false;
|
|
32
|
-
return "$$typeof" in value;
|
|
33
|
-
};
|
|
34
|
-
const isFontAwesomeIconDefinition = (value) => {
|
|
35
|
-
if (!value || typeof value !== "object") return false;
|
|
36
|
-
const icon = value.icon;
|
|
37
|
-
if (!Array.isArray(icon) || icon.length < 5) return false;
|
|
38
|
-
return typeof icon[0] === "number" && typeof icon[1] === "number";
|
|
39
|
-
};
|
|
40
|
-
const renderFontAwesomeIconDefinition = (iconDefinition) => {
|
|
41
|
-
const [width, height, , , svgPathData] = iconDefinition.icon;
|
|
42
|
-
return {
|
|
43
|
-
symbolBody: (Array.isArray(svgPathData) ? svgPathData : [svgPathData]).map((d) => `<path d="${d}"/>`).join(""),
|
|
44
|
-
viewBox: `0 0 ${width} ${height}`,
|
|
45
|
-
symbolAttributes: ""
|
|
46
|
-
};
|
|
47
|
-
};
|
|
48
|
-
const renderIcon = async (pack, exportName) => {
|
|
49
|
-
const iconComponent = pickExport(await import(resolveIconImport(pack, exportName)), exportName);
|
|
50
|
-
if (isFontAwesomeIconDefinition(iconComponent)) return renderFontAwesomeIconDefinition(iconComponent);
|
|
51
|
-
if (!isRenderableComponent(iconComponent)) throw new Error(`[react-icons-sprite] Unable to render icon "${exportName}" from "${pack}". Expected a React component export.`);
|
|
52
|
-
const svgMarkup = renderToStaticMarkup(createElement(iconComponent));
|
|
53
|
-
const svgInner = SVG_INNER_RE.exec(svgMarkup)?.[1];
|
|
54
|
-
if (!svgInner) throw new Error(`[react-icons-sprite] Unable to extract SVG content for "${exportName}" from "${pack}".`);
|
|
55
|
-
return {
|
|
56
|
-
symbolBody: svgInner,
|
|
57
|
-
viewBox: VIEWBOX_RE.exec(svgMarkup)?.[1] ?? "0 0 24 24",
|
|
58
|
-
symbolAttributes: extractSymbolAttributes(svgMarkup)
|
|
59
|
-
};
|
|
60
|
-
};
|
|
61
|
-
//#endregion
|
|
62
|
-
//#region src/sprite/build-sprite.ts
|
|
63
|
-
const buildSprite = async (icons) => {
|
|
64
|
-
if (!icons.length) return "<svg xmlns=\"http://www.w3.org/2000/svg\" style=\"display:none\"></svg>";
|
|
65
|
-
return `<svg xmlns="http://www.w3.org/2000/svg" style="display:none">${(await Promise.all(icons.map(async ({ pack, exportName }) => {
|
|
66
|
-
const rendered = await renderIcon(pack, exportName);
|
|
67
|
-
const id = computeIconId(pack, exportName);
|
|
68
|
-
const symbolAttributes = rendered.symbolAttributes ? ` ${rendered.symbolAttributes}` : "";
|
|
69
|
-
return `<symbol id="${id}" viewBox="${rendered.viewBox}"${symbolAttributes}>${rendered.symbolBody}</symbol>`;
|
|
70
|
-
}))).join("")}</svg>`;
|
|
71
|
-
};
|
|
72
|
-
//#endregion
|
|
73
|
-
export { buildSprite as t };
|