vite-svg-sprite-wrapper
Version:
Creating one sprite file on the fly
192 lines (191 loc) • 5.5 kB
JavaScript
// src/index.ts
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { basename, dirname, resolve } from "node:path";
import { cwd } from "node:process";
import FastGlob from "fast-glob";
import colors from "picocolors";
import picomatch from "picomatch";
import SVGSpriter from "svg-sprite";
import { normalizePath } from "vite";
var defaultOptions = {
icons: "src/assets/images/svg/*.svg",
outputDir: "src/public/images",
sprite: {},
generateType: false,
typeName: "SvgIcons",
typeFileName: "svg-icons",
typeOutputDir: ""
};
function resolveOptions(options) {
return Object.assign({}, defaultOptions, options);
}
var root = cwd();
var isSvg = /\.svg$/;
function normalizePaths(root2, path) {
return (Array.isArray(path) ? path : [path]).map((path2) => resolve(root2, path2)).map(normalizePath);
}
function generateConfig(outputDir, options) {
return {
dest: normalizePath(resolve(root, outputDir)),
mode: {
symbol: {
sprite: "../sprite.svg"
}
},
svg: {
xmlDeclaration: false
},
shape: {
transform: [
{
svgo: {
// @ts-expect-error [need to fix type for plugins property]
plugins: [
{ name: "preset-default" },
{
name: "removeAttrs",
params: {
attrs: ["*:(data-*|style|fill):*"]
}
},
{
name: "addAttributesToSVGElement",
params: {
attributes: [
{ fill: "currentColor" }
]
}
},
"removeXMLNS"
]
}
}
]
},
...options.sprite
};
}
async function generateSvgSprite(options) {
const {
icons,
outputDir,
generateType,
typeName,
typeFileName
} = options;
const spriter = new SVGSpriter(generateConfig(outputDir, options));
const rootDir = icons.replace(/(\/(\*+))+\.(.+)/g, "");
const entries = await FastGlob([icons]);
for (const entry of entries) {
if (isSvg.test(entry)) {
const relativePath = entry.replace(`${rootDir}/`, "");
spriter.add(
entry,
relativePath,
readFileSync(entry, { encoding: "utf-8" })
);
}
}
const { result, data } = await spriter.compileAsync();
if (generateType) {
const shapeIds = data.symbol.shapes.map(({ base }) => {
return `'${base}'`;
});
const fileComment = "/* this file was generated by vite-svg-sprite-wrapper */\n";
const fileContent = `export type ${typeName} = ${shapeIds.join(" | ")}
`;
const typeOutputDir = options.typeOutputDir || dirname(icons);
if (!existsSync(typeOutputDir))
mkdirSync(typeOutputDir, { recursive: true });
writeFileSync(
normalizePath(`${resolve(root, typeOutputDir)}/${typeFileName}.d.ts`),
fileComment + fileContent
);
}
if (!existsSync(outputDir))
mkdirSync(outputDir, { recursive: true });
const sprite = result.symbol.sprite;
writeFileSync(
sprite.path,
sprite.contents.toString()
);
return sprite.path.replace(root, "");
}
function ViteSvgSpriteWrapper(options = {}) {
const resolved = resolveOptions(options);
const { icons } = resolved;
let timer;
let config;
function clear() {
clearTimeout(timer);
}
function schedule(fn) {
clear();
timer = setTimeout(fn, 200);
}
const formatConsole = (msg) => `${colors.cyan("[vite-plugin-svg-sprite]")} ${msg}`;
const successGeneration = (res) => {
config.logger.info(formatConsole(`Sprite generated ${colors.green(res)}`));
};
const failGeneration = (err) => {
config.logger.info(formatConsole(`${colors.red("Sprite error")} ${colors.dim(err)}`));
};
return [
{
name: "vite-plugin-svg-sprite:build",
apply: "build",
configResolved(_config) {
config = _config;
},
enforce: "pre",
async buildStart() {
await generateSvgSprite(resolved).then((path) => {
const name = basename(path);
const source = readFileSync(`${root}${path}`);
this.emitFile({
name,
source,
type: "asset"
});
successGeneration(path);
}).catch(failGeneration);
}
},
{
name: "vite-plugin-svg-sprite:serve",
apply: "serve",
configResolved(_config) {
config = _config;
},
enforce: "pre",
async buildStart() {
await generateSvgSprite(resolved).then(successGeneration).catch(failGeneration);
},
config: () => ({ server: { watch: { disableGlobbing: false } } }),
configureServer({ watcher, hot }) {
const iconsPath = normalizePaths(root, icons);
const shouldReload = picomatch(iconsPath);
const checkReload = (path) => {
if (shouldReload(path)) {
schedule(() => {
generateSvgSprite(resolved).then((res) => {
hot.send({ type: "full-reload", path: "*" });
successGeneration(res);
}).catch(failGeneration);
});
}
};
watcher.add(iconsPath);
watcher.on("add", checkReload);
watcher.on("change", checkReload);
watcher.on("unlink", checkReload);
}
}
];
}
var src_default = ViteSvgSpriteWrapper;
export {
src_default as default,
defaultOptions,
resolveOptions
};