数通智联化工云平台
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

222 lines
6.8 KiB

2 years ago
import fg from 'fast-glob';
import getEtag from 'etag';
import cors from 'cors';
import fs from 'fs-extra';
import path from 'pathe';
import Debug from 'debug';
import SVGCompiler from 'svg-baker';
import { optimize } from 'svgo';
import { normalizePath } from 'vite';
const SVG_ICONS_REGISTER_NAME = "virtual:svg-icons-register";
const SVG_ICONS_CLIENT = "virtual:svg-icons-names";
const SVG_DOM_ID = "__svg__icons__dom__";
const XMLNS = "http://www.w3.org/2000/svg";
const XMLNS_LINK = "http://www.w3.org/1999/xlink";
const debug = Debug.debug("vite-plugin-svg-icons");
function createSvgIconsPlugin(opt) {
const cache = /* @__PURE__ */ new Map();
let isBuild = false;
const options = {
svgoOptions: true,
symbolId: "icon-[dir]-[name]",
inject: "body-last",
customDomId: SVG_DOM_ID,
...opt
};
let { svgoOptions } = options;
const { symbolId } = options;
if (!symbolId.includes("[name]")) {
throw new Error("SymbolId must contain [name] string!");
}
if (svgoOptions) {
svgoOptions = typeof svgoOptions === "boolean" ? {} : svgoOptions;
}
debug("plugin options:", options);
return {
name: "vite:svg-icons",
configResolved(resolvedConfig) {
isBuild = resolvedConfig.command === "build";
debug("resolvedConfig:", resolvedConfig);
},
resolveId(id) {
if ([SVG_ICONS_REGISTER_NAME, SVG_ICONS_CLIENT].includes(id)) {
return id;
}
return null;
},
async load(id, ssr) {
if (!isBuild && !ssr)
return null;
const isRegister = id.endsWith(SVG_ICONS_REGISTER_NAME);
const isClient = id.endsWith(SVG_ICONS_CLIENT);
if (ssr && !isBuild && (isRegister || isClient)) {
return `export default {}`;
}
const { code, idSet } = await createModuleCode(cache, svgoOptions, options);
if (isRegister) {
return code;
}
if (isClient) {
return idSet;
}
},
configureServer: ({ middlewares }) => {
middlewares.use(cors({ origin: "*" }));
middlewares.use(async (req, res, next) => {
const url = normalizePath(req.url);
const registerId = `/@id/${SVG_ICONS_REGISTER_NAME}`;
const clientId = `/@id/${SVG_ICONS_CLIENT}`;
if ([clientId, registerId].some((item) => url.endsWith(item))) {
res.setHeader("Content-Type", "application/javascript");
res.setHeader("Cache-Control", "no-cache");
const { code, idSet } = await createModuleCode(cache, svgoOptions, options);
const content = url.endsWith(registerId) ? code : idSet;
res.setHeader("Etag", getEtag(content, { weak: true }));
res.statusCode = 200;
res.end(content);
} else {
next();
}
});
}
};
}
async function createModuleCode(cache, svgoOptions, options) {
const { insertHtml, idSet } = await compilerIcons(cache, svgoOptions, options);
const xmlns = `xmlns="${XMLNS}"`;
const xmlnsLink = `xmlns:xlink="${XMLNS_LINK}"`;
const html = insertHtml.replace(new RegExp(xmlns, "g"), "").replace(new RegExp(xmlnsLink, "g"), "");
const code = `
if (typeof window !== 'undefined') {
function loadSvg() {
var body = document.body;
var svgDom = document.getElementById('${options.customDomId}');
if(!svgDom) {
svgDom = document.createElementNS('${XMLNS}', 'svg');
svgDom.style.position = 'absolute';
svgDom.style.width = '0';
svgDom.style.height = '0';
svgDom.id = '${options.customDomId}';
svgDom.setAttribute('xmlns','${XMLNS}');
svgDom.setAttribute('xmlns:link','${XMLNS_LINK}');
}
svgDom.innerHTML = ${JSON.stringify(html)};
${domInject(options.inject)}
}
if(document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', loadSvg);
} else {
loadSvg()
}
}
`;
return {
code: `${code}
export default {}`,
idSet: `export default ${JSON.stringify(Array.from(idSet))}`
};
}
function domInject(inject = "body-last") {
switch (inject) {
case "body-first":
return "body.insertBefore(svgDom, body.firstChild);";
default:
return "body.insertBefore(svgDom, body.lastChild);";
}
}
async function compilerIcons(cache, svgOptions, options) {
const { iconDirs } = options;
let insertHtml = "";
const idSet = /* @__PURE__ */ new Set();
for (const dir of iconDirs) {
const svgFilsStats = fg.sync("**/*.svg", {
cwd: dir,
stats: true,
absolute: true
});
for (const entry of svgFilsStats) {
const { path: path2, stats: { mtimeMs } = {} } = entry;
const cacheStat = cache.get(path2);
let svgSymbol;
let symbolId;
let relativeName = "";
const getSymbol = async () => {
relativeName = normalizePath(path2).replace(normalizePath(dir + "/"), "");
symbolId = createSymbolId(relativeName, options);
svgSymbol = await compilerIcon(path2, symbolId, svgOptions);
idSet.add(symbolId);
};
if (cacheStat) {
if (cacheStat.mtimeMs !== mtimeMs) {
await getSymbol();
} else {
svgSymbol = cacheStat.code;
symbolId = cacheStat.symbolId;
symbolId && idSet.add(symbolId);
}
} else {
await getSymbol();
}
svgSymbol && cache.set(path2, {
mtimeMs,
relativeName,
code: svgSymbol,
symbolId
});
insertHtml += `${svgSymbol || ""}`;
}
}
return { insertHtml, idSet };
}
async function compilerIcon(file, symbolId, svgOptions) {
if (!file) {
return null;
}
let content = fs.readFileSync(file, "utf-8");
if (svgOptions) {
const { data } = await optimize(content, svgOptions);
content = data || content;
}
content = content.replace(/stroke="[a-zA-Z#0-9]*"/, 'stroke="currentColor"');
const svgSymbol = await new SVGCompiler().addSymbol({
id: symbolId,
content,
path: file
});
return svgSymbol.render();
}
function createSymbolId(name, options) {
const { symbolId } = options;
if (!symbolId) {
return name;
}
let id = symbolId;
let fName = name;
const { fileName = "", dirName } = discreteDir(name);
if (symbolId.includes("[dir]")) {
id = id.replace(/\[dir\]/g, dirName);
if (!dirName) {
id = id.replace("--", "-");
}
fName = fileName;
}
id = id.replace(/\[name\]/g, fName);
return id.replace(path.extname(id), "");
}
function discreteDir(name) {
if (!normalizePath(name).includes("/")) {
return {
fileName: name,
dirName: ""
};
}
const strList = name.split("/");
const fileName = strList.pop();
const dirName = strList.join("-");
return { fileName, dirName };
}
export { compilerIcon, compilerIcons, createModuleCode, createSvgIconsPlugin, createSymbolId, discreteDir };