vite-plugin-svg-symbols
Version:
Vite plugin for creating SVG symbol sprite
302 lines (300 loc) • 9.67 kB
JavaScript
import { join as A, extname as J, relative as x, sep as E, isAbsolute as Z, resolve as K, dirname as Q } from "node:path";
import { stat as H, readFile as tt, readdir as et, writeFile as st } from "node:fs/promises";
import { createHash as ot } from "node:crypto";
const nt = (e = [], r) => {
const c = /* @__PURE__ */ new Map(), p = async (s) => {
const m = await H(s);
if (c.has(s)) {
const [f, M] = c.get(s) || [0, []];
if (f === m.mtimeMs) {
const [i] = M;
return i;
}
}
const u = await tt(s, { encoding: "utf8" }), h = await (typeof r == "function" ? r(u, s) : Promise.resolve(u));
return c.set(s, [m.mtimeMs, [h]]), h;
}, o = async (s) => {
const m = await H(s);
if (c.has(s)) {
const [f, M] = c.get(s) || [0, []];
if (f === m.mtimeMs)
return M;
}
const u = [], h = await et(s, { withFileTypes: !0 });
for (const f of h) {
if (f.isDirectory()) {
const M = await o(A(s, f.name));
u.push(...M);
}
(e.length === 0 || e.includes(J(f.name))) && u.push(A(s, f.name));
}
return c.set(s, [m.mtimeMs, u]), u;
};
return {
store: c,
getDirContent: o,
getFileContent: p
};
}, rt = () => {
const e = {
isDevMode: !1,
isTestMode: !1,
root: "",
base: "",
assets: "",
aliases: {}
}, r = (o) => o.reduce((s, m) => (typeof m.find == "string" && (s[m.find] = m.replacement), s), {});
return {
config: e,
updateConfig: (o) => {
e.isDevMode = o.command === "serve", e.isTestMode = o.mode === "plugintest", e.root = o.root, e.base = o.base, e.assets = o.build.assetsDir, e.aliases = {
"@icons": A(e.root, "src", "icons"),
...r(o.resolve.alias)
};
},
findAliasName: (o) => {
const [s = []] = Object.entries(e.aliases).filter(([u, h]) => o.startsWith(h)).sort(([, u], [, h]) => h.length - u.length), [m = ""] = s;
return m.replace("@", "");
}
};
}, at = "\0virtual:", D = "\0virtual:symbol:", V = "\0virtual:svg:symbols", Y = ".svg?symbol", it = "svg:symbols@", C = "/@id/__vite-plugin-svg-symbols__.svg", ct = {
"aria-hidden": "true",
style: [
"position:absolute;",
"top:0;",
"left:0;",
"width:1px;",
"height:1px;",
"visibility:hidden;",
"opacity:0;"
].join("")
}, lt = [
"fill",
"stroke",
"stroke-dasharray",
"stroke-dashoffset",
"stroke-linecap",
"stroke-linejoin",
"stroke-miterlimit",
"stroke-opacity",
"stroke-width"
], dt = `declare module 'svg:symbols@[alias]' {
[content]
const symbols: Record<string, string>;
export default symbols;
}`, mt = `/**
* @see [see]
*/`, z = `/**
* Vite plugin for creating SVG symbol sprite
* @see [Documentation on Github](https://github.com/werty1001/vite-plugin-svg-symbols)
*/`, ut = `declare module '*.svg?symbol' {
const src: string
export default src
}`, pt = (e, r) => x(e, r).split(E).join("_").replace(/[-_]([a-z0-9])/gi, (p, o) => o.toUpperCase()).replace(".svg", "").concat("Icon"), G = (e, r = 8) => ot("sha256").update(e).digest("hex").slice(0, r), ft = (e, r = ".svg") => e.replace(r, "").trim().concat(r), X = (e) => `<svg xmlns="http://www.w3.org/2000/svg">${e}</svg>`, q = (e) => {
const [, r = ""] = e.split("#");
return r;
}, gt = (e, r) => x(r, e).replace(".svg", "").replace("node_modules".concat(E), "").replace("src".concat(E), "").split(E).join("-"), ht = (e, r, c, p) => {
const o = /<svg([^>]*)>([\s\S]*?)<\/svg>/, s = /([a-zA-Z-]+)\s*=\s*["']([^"']*)["']/g, [, m = "", u = ""] = e.match(o) || [], h = u.trim().replace(/>((\r\n|\n|\r)(\s+)?)</g, "><"), f = { viewBox: "0 0 24 24" }, M = m.matchAll(s);
if (M)
for (const _ of M) {
const [, j, I] = _;
f[j.trim()] = I.trim();
}
const i = p.reduce((_, j) => {
const I = f[j];
return I && _.push([j, `"${I}"`].join("=")), _;
}, []).join(" "), N = i ? `<g ${i}>${h}</g>` : h, B = f.viewBox;
return [`<symbol viewBox="${B}" id="${c}">${N}</symbol>`, {
symbolId: c,
symbolBody: N,
symbolViewBox: B,
svgBody: h,
svgPath: r,
svgAttrs: f
}];
}, yt = (e, r) => {
const c = new RegExp(r.concat("#([^'\"`]*)"), "gi"), p = Object.keys(e).reduce((o, s) => {
if (s.endsWith(".js") || s.endsWith(".mjs")) {
const u = e[s].code.match(c) || [];
o.push(...u);
}
return o;
}, []);
return new Set(p);
}, vt = (e, r, c) => {
Object.keys(e).forEach((p) => {
if (!p.endsWith(".js") && !p.endsWith(".mjs")) return;
const o = e[p];
o.code && (o.code = o.code.replace(r, c));
});
}, wt = ct, bt = lt, St = (e) => {
const {
fileName: r = "sprite-[hash]",
shouldInheritAttrs: c = bt,
shouldInjectToHtml: p = !1,
injectAttrs: o = wt,
transformIcon: s = (t) => t,
transformIconId: m = (t) => t,
transformSymbol: u = (t) => t,
transformSprite: h = (t) => t,
transformImportName: f = (t) => t,
transformImportComment: M = (t) => t
} = e || {}, { config: i, updateConfig: N, findAliasName: B } = rt(), { getDirContent: _, getFileContent: j } = nt([".svg"], s), I = /* @__PURE__ */ new Map(), L = async (t) => {
const n = i.isDevMode ? `?v=${Date.now()}` : "", a = await m(
gt(t, i.root),
t
);
return [C.concat(n), a].join("#");
}, O = async (t) => {
const [n, a] = await Promise.all([
j(t),
L(t)
]), [d, l] = ht(n, t, q(a), c), y = await u(d, l);
return I.set(t, y), a;
}, k = async (t) => {
const n = Array.from(I.keys()).sort((d, l) => {
const y = x(i.root, d), v = x(i.root, l);
return y > v ? 1 : -1;
});
let a = "";
for (const d of n) {
const l = I.get(d) || "";
if (!t) {
a += l;
continue;
}
const y = await L(d);
t.has(y) && (a += l);
}
return h(a);
}, F = /* @__PURE__ */ new Map(), W = async (t) => {
const n = i.aliases[`@${t}`] || "";
if (!n)
throw new Error([
`The alias @${t} is not defined.`,
"Please add it to your vite.config.ts"
].join(" "));
const a = await _(n), d = [], l = /* @__PURE__ */ new Map();
let y = "", v = "";
for (const w of a) {
const [g, b] = await Promise.all([
f(pt(n, w), w),
O(w)
]), T = l.get(g) || 0, S = g.concat(T > 0 ? String(T + 1) : "");
y += `export const ${S} = '${b}';
`, v += `${S}, `, d.push([w, S, b]), l.set(g, T + 1);
}
return F.set(t, d), y.concat(`export default { ${v}};`);
}, P = async () => {
let t = "";
for (const [a, d] of F.entries()) {
let l = "";
for (const [v, w, g] of d) {
const b = x(i.root, v), T = mt.replace("[see]", `[${b}](file://${v})`), S = await M(T, v), R = `export const ${w}: '#${q(g)}'`;
l += [S, R, `
`].join(`
`);
}
const y = dt.replace("[alias]", a).replace("[content]", l);
t += [z, y, `
`].join(`
`);
}
t += [
[z, ut].join(`
`)
].join(`
`);
const n = i.isTestMode ? A(i.root, "app", "client.d.ts") : A(i.root, "node_modules", "vite-plugin-svg-symbols", "client.d.ts");
return st(n, t);
};
let $, U = "";
return {
name: "vite-plugin-svg-symbols",
enforce: "pre",
closeWatcher() {
clearTimeout($);
},
configResolved(t) {
N(t);
},
configureServer({ watcher: t, middlewares: n, ws: a, moduleGraph: d }) {
const l = /* @__PURE__ */ new Set(), y = () => {
for (const w of l) {
const g = V.concat("@", w), b = d.getModuleById(g);
b && d.invalidateModule(b);
}
l.clear(), a.send({
type: "full-reload",
path: "*"
});
}, v = () => {
clearTimeout($), $ = setTimeout(y, 50);
};
t.on("all", (w, g) => {
if (!g.endsWith(".svg")) return;
const b = B(g);
b && (l.add(b), v());
}), n.use(C, async (w, g) => {
const b = await k(), T = X(b), S = G(T, 32), R = w.headers["if-none-match"];
if (S === R) {
g.writeHead(304, { "Content-Length": 0 }).end("");
return;
}
g.writeHead(200, {
"Content-Length": Buffer.byteLength(T),
"Content-Type": "image/svg+xml",
"Cache-Control": "no-cache",
Etag: S
}).end(T);
});
},
resolveId(t, n = "") {
if (t.endsWith(Y)) {
const a = Z(t) ? t : K(Q(n), t);
return D.concat(a.replace(Y, ".svg"));
}
return t.startsWith(it) ? at.concat(t) : null;
},
async load(t) {
if (t.startsWith(D)) {
const n = t.replace(D, "");
return `export default '${await O(n)}';`;
}
if (t.startsWith(V)) {
const [, n = "icons"] = t.split("@"), a = await W(n);
return await P(), a;
}
return null;
},
transformIndexHtml() {
if (!(i.isDevMode || !p))
return [{
tag: "svg",
attrs: o,
children: U,
injectTo: "body-prepend"
}];
},
async generateBundle(t, n) {
if (i.isDevMode) return;
const a = yt(n, C), d = await k(a), l = X(d), y = G(l), v = ft(r.replace("[hash]", y)), w = p ? "" : A(i.base, i.assets, v), g = new RegExp(C, "g");
vt(n, g, w), p ? U = d : this.emitFile({
type: "asset",
fileName: A(i.assets, v),
source: l
});
},
api: {
async generateSymbolsTypes() {
const t = Object.keys(i.aliases).map((n) => n.replace("@", "")).filter(Boolean);
return await Promise.all(t.map(W)), P();
}
}
};
};
export {
St as default,
bt as defaultInheritAttrs,
wt as defaultInjectAttrs
};