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