UNPKG

vite-svg-sprite-wrapper

Version:
192 lines (191 loc) 5.5 kB
// 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 };