"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __defProps = Object.defineProperties; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropDescs = Object.getOwnPropertyDescriptors; var __getOwnPropNames = Object.getOwnPropertyNames; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { default: () => unplugin_default }); module.exports = __toCommonJS(src_exports); // src/core/unplugin.ts var import_unplugin = require("unplugin"); var import_pathe3 = __toESM(require("pathe"), 1); var import_chokidar = __toESM(require("chokidar"), 1); // src/core/log.ts var import_consola = __toESM(require("consola"), 1); // src/core/constants.ts var PLUGIN_NAME = "unplugin-svg-sprite"; var PLUGIN_ABBR = "uss"; var OUTPUT_DIR = "svg-sprite"; var SpriteMode = /* @__PURE__ */ ((SpriteMode2) => { SpriteMode2["Symbol"] = "symbol"; SpriteMode2["Stack"] = "stack"; return SpriteMode2; })(SpriteMode || {}); var SVG_SPRITE_PREFIX = `~svg-sprite/`; var SVG_SPRITE_SYMBOL = `${SVG_SPRITE_PREFIX}symbol`; var IS_DEV = process.env.NODE_ENV === "development"; // src/core/log.ts var logger = import_consola.default.withTag(PLUGIN_ABBR).withDefaults({ level: 3 }); // src/core/helpers/symbols.ts var import_pathe = __toESM(require("pathe"), 1); async function transformSymbolSprite(data, context) { const { userOptions, pathname } = context; if (!data.shapes.length) { logger.warn(`There is no dynamic SVG shapes,`); logger.warn(`You do not have to use ${SVG_SPRITE_SYMBOL} component.`); } const spriteGeneratorPath = `file:///${import_pathe.default.normalize( userOptions.runtime.spriteGenerator )}`; const _spriteGenerator = await import(spriteGeneratorPath); const spriteGenerator = typeof _spriteGenerator === "function" ? _spriteGenerator : _spriteGenerator.default; if (typeof spriteGenerator !== "function") { throw new TypeError("Please export valid sprite generator function"); } const defaultDomStr = ` ${data.shapes.map((item) => item.svg).join("")} `.replace(/\n/g, ""); const spriteProps = data.shapes.length ? userOptions.runtime.transformSpriteData ? userOptions.runtime.transformSpriteData(pathname, defaultDomStr, data) : { pathname } : {}; const cwd = import_pathe.default.normalize(process.cwd()); const transformedCode = spriteGenerator(__spreadProps(__spreadValues({}, spriteProps), { cwd })); return { code: transformedCode, map: null }; } async function transformSymbolItem(svgAbsolutePath, context) { const { compiledResult, userOptions, transformData, staticPathname } = context; const data = transformData.type === "static" ? compiledResult.static.data.symbol : compiledResult.dynamic.data.symbol; const shapes = data.shapes; const target = shapes.find((item) => item.name === transformData.runtimeId); if (!target) { throw new Error(`target shape of [${svgAbsolutePath}] not found`); } const getHref = () => { if (transformData.type === "dynamic") { return `#${target.name}`; } return `${staticPathname}#${target.name}`; }; const result = { href: getHref(), width: target.width, height: target.height }; const itemGeneratorPath = `file:///${import_pathe.default.normalize( userOptions.runtime.itemGenerator )}`; const _itemGenerator = await import(itemGeneratorPath); const itemGenerator = typeof _itemGenerator === "function" ? _itemGenerator : _itemGenerator.default; if (typeof itemGenerator !== "function") { throw new TypeError("Please export valid item generator function"); } const cwd = import_pathe.default.normalize(process.cwd()); const transformedCode = itemGenerator({ item: result, cwd }); return { code: transformedCode, map: null }; } // src/core/ctx.ts var import_node_crypto = __toESM(require("crypto"), 1); var import_lodash = __toESM(require("lodash"), 1); var import_pathe2 = __toESM(require("pathe"), 1); var import_svg_sprite = __toESM(require("svg-sprite"), 1); var import_fs_extra = __toESM(require("fs-extra"), 1); var import_minimatch = require("minimatch"); function createContext(options) { var _a; const { content = ["**/*.svg"], publicDir = "public", outputDir = OUTPUT_DIR, spriterConfig, sprites = {}, debug = false, silent = false } = options || {}; if (debug) { logger.level = 4; } if (silent) { logger.level = 1; } const mergedSpriterConfig = __spreadProps(__spreadValues({}, spriterConfig), { shape: __spreadValues({ transform: [ { // svg-sprite internal logic: https://github.com/svg-sprite/svg-sprite/blob/main/lib/svg-sprite/transform/svgo.js#L48 // ref: https://github.com/svg/svgo#configuration svgo: { plugins: [ "preset-default", { name: "removeViewBox", active: false } ] } } ] }, spriterConfig == null ? void 0 : spriterConfig.shape), svg: __spreadValues({ xmlDeclaration: false, doctypeDeclaration: false, dimensionAttributes: false }, spriterConfig == null ? void 0 : spriterConfig.svg), log: debug ? "debug" : void 0 }); if (!Object.keys(sprites).length) { throw new Error( `Pick a sprite mode required, supported [${Object.values(SpriteMode).join( ", " )}]` ); } const absolutePublicPath = import_pathe2.default.join(process.cwd(), publicDir); const absoluteOutputPath = import_pathe2.default.join(absolutePublicPath, outputDir); const absoluteOutputStaticPath = import_pathe2.default.join(absoluteOutputPath, "static"); const absoluteOutputDynamicPath = import_pathe2.default.join(absoluteOutputPath, "dynamic"); const userModes = Object.keys(sprites); const useSymbolMode = "symbol" in sprites; const useSymbolResourceQuery = "symbol" in sprites && !!((_a = sprites.symbol) == null ? void 0 : _a.runtime.resourceQuery); const isDynamicSvg = (svgStr) => { var _a2; return [ "linearGradient", "radialGradient", "filter", "clipPath", ...((_a2 = options.sprites.symbol) == null ? void 0 : _a2.runtime.dynamicSvgNodes) || [] ].some((item) => { return svgStr.includes(`<${item}`); }); }; const contentPatterns = [ ...content, `!${publicDir}/${outputDir}`, `!node_modules` ]; const store = { /** All SVG cache map */ transformMap: /* @__PURE__ */ new Map(), /** hash, uniq path */ duplicatedHashes: {}, svgSpriteCompiledResult: null, /** * In Next.js env, after optimized result, still call `transform` hook, * So use extra memo to store optimized result to keep svg static path immutable. */ svgSpriteOptimizedCompiledResult: null, optimized: false }; const compile = async (options2 = { optimization: false }) => { const { optimization = false } = options2; if (store.svgSpriteCompiledResult && !optimization) { logger.debug( "Has compiled result, no need to re-compile with un-optimization" ); return; } if (optimization) { logger.debug("Spriter compile with optimization start..."); } else { logger.debug("Spriter compile start..."); } const spriterMode = userModes.reduce((prev, current) => { const userCurrentConfig = import_lodash.default.get(sprites, [current]); const mergedConfig = import_lodash.default.isPlainObject(userCurrentConfig) ? userCurrentConfig : {}; return __spreadProps(__spreadValues({}, prev), { [current]: __spreadValues({ example: !optimization && debug, /** For better HMR, SVG sprite file name without hash in DEV env */ bust: !IS_DEV }, mergedConfig) }); }, {}); const staticSpriter = new import_svg_sprite.default(__spreadProps(__spreadValues({}, mergedSpriterConfig), { dest: absoluteOutputStaticPath, mode: spriterMode })); const dynamicSpriter = new import_svg_sprite.default(__spreadProps(__spreadValues({}, mergedSpriterConfig), { dest: absoluteOutputDynamicPath, mode: spriterMode })); let staticCount = 0; let dynamicCount = 0; const addedDuplicatedHashes = []; store.transformMap.forEach((item, key) => { if (optimization && !item.used) { logger.debug(`Never been used svg: ${key}`); return; } if (optimization && store.duplicatedHashes[item.hash]) { if (addedDuplicatedHashes.includes(item.hash)) { logger.debug(`Duplicated svg [${item.hash}]: ${key}`); return; } else { addedDuplicatedHashes.push(item.hash); } } if (item.type === "static") { staticCount += 1; staticSpriter.add(item.svgHashPath, null, item.svgStr); } else { dynamicCount += 1; dynamicSpriter.add(item.svgHashPath, null, item.svgStr); } }); if (optimization) { let originStaticSize = 0; let originDynamicSize = 0; store.transformMap.forEach((item) => { if (item.type === "static") { originStaticSize += 1; } else { originDynamicSize += 1; } }); if (originStaticSize !== staticCount) { logger.warn( `Static svg sprite size optimized: ${originStaticSize} => ${staticCount}` ); } if (originDynamicSize !== dynamicCount) { logger.warn( `Dynamic svg sprite size optimized: ${originDynamicSize} => ${dynamicCount}` ); } } else { logger.log(`SVG sprite static transform size: ${staticCount}`); logger.log(`SVG sprite dynamic transform size: ${dynamicCount}`); } const [staticResult, dynamicResult] = await Promise.all([ staticSpriter.compileAsync(), dynamicSpriter.compileAsync() ]); if (optimization) { store.svgSpriteOptimizedCompiledResult = { static: staticResult, dynamic: dynamicResult }; } else { store.svgSpriteCompiledResult = { static: staticResult, dynamic: dynamicResult }; } if (debug) { if (optimization) { logger.debug("Spriter compile with optimization end"); } else { logger.debug("Spriter compile end"); } } if (optimization) { logger.debug("Write optimized sprite files start..."); } else { logger.debug("Write sprite files start..."); } const { globbySync } = await import("globby"); const generatedSvgSprites = globbySync( ["static/**/sprite.*.svg", "dynamic/**/sprite.*.svg"], { cwd: absoluteOutputPath } ); import_fs_extra.default.emptyDirSync(absoluteOutputPath); async function writeStaticFiles() { const compileResult = optimization ? store.svgSpriteOptimizedCompiledResult : store.svgSpriteCompiledResult; for (const [mode, modeResult] of Object.entries( compileResult.static.result )) { for (const resource of Object.values(modeResult)) { await import_fs_extra.default.ensureDir(import_pathe2.default.dirname(resource.path)); if (optimization) { if (resource.path.endsWith(".svg")) { const targetGeneratedPath = generatedSvgSprites.find((item) => { return import_pathe2.default.normalize(item).startsWith(import_pathe2.default.join("static", mode)); }); if (!targetGeneratedPath) { throw new Error("targetGeneratedPath not found"); } const optimizedFilePath = import_pathe2.default.join( absoluteOutputPath, targetGeneratedPath ); await import_fs_extra.default.writeFile(optimizedFilePath, resource.contents); logger.log( `Output optimized static svg sprite: ${optimizedFilePath}` ); } return; } await import_fs_extra.default.writeFile(resource.path, resource.contents); if (resource.path.endsWith(".svg")) { logger.debug(`Output static svg sprite: ${resource.path}`); } } } } async function writeDynamicFiles() { const compileResult = optimization ? store.svgSpriteOptimizedCompiledResult : store.svgSpriteCompiledResult; for (const [mode, modeResult] of Object.entries( compileResult.dynamic.result )) { for (const resource of Object.values(modeResult)) { await import_fs_extra.default.ensureDir(import_pathe2.default.dirname(resource.path)); if (optimization) { if (resource.path.endsWith(".svg")) { const targetGeneratedPath = generatedSvgSprites.find((item) => { return import_pathe2.default.normalize(item).startsWith(import_pathe2.default.join("dynamic", mode)); }); if (!targetGeneratedPath) { throw new Error("targetGeneratedPath not found"); } const optimizedFilePath = import_pathe2.default.join( absoluteOutputPath, targetGeneratedPath ); await import_fs_extra.default.writeFile(optimizedFilePath, resource.contents); logger.log( `Output optimized dynamic svg sprite: ${optimizedFilePath}` ); } return; } await import_fs_extra.default.writeFile(resource.path, resource.contents); if (resource.path.endsWith(".svg")) { logger.debug(`Output dynamic svg sprite: ${resource.path}`); } } } } function stat() { return Array.from(store.transformMap).reduce( (prev, [key, value]) => { const grouped = Object.keys(prev).find((item) => { return value.hash === item; }); if (grouped) { prev[grouped] = [...prev[grouped], key]; return prev; } return __spreadProps(__spreadValues({}, prev), { [value.hash]: [key] }); }, // a hash with same hash files {} ); } function printStat() { const result = import_lodash.default.omitBy(stat(), (value) => value.length <= 1); if (Object.keys(result).length) { const format = Object.keys(result).reduce( (prev, current, index, array) => { prev += `\u{1F916} ${import_lodash.default.padStart( `${index + 1}`, String(array.length).length, "0" )}.${current} `; prev += ` - ${result[current].join("\n - ")} `; return prev; }, "\n\n" ); logger.log( `There are some SVGs have same file hash (after [ \\n\\t] removed):${format}` ); } else { logger.debug("No duplicate svg files"); } } await Promise.all([writeStaticFiles(), writeDynamicFiles()]); if (optimization) { logger.debug("Write optimized sprite files end"); store.optimized = true; return; } else { logger.debug("Write sprite files end"); } logger.debug("Svg stat start"); printStat(); logger.debug("Svg stat end"); }; const upsertSvg = (path, _map, watch = false) => { const svgStr = import_fs_extra.default.readFileSync(path, { encoding: "utf-8" }); const hash = import_node_crypto.default.createHash("md5").update(svgStr.replace(/[ \n\t]/g, ""), "utf8").digest("hex").slice(0, 8); const svgId = `${import_pathe2.default.parse(path).name}-${hash}`; const type = isDynamicSvg(svgStr) ? "dynamic" : "static"; const svgHashPath = import_pathe2.default.join(import_pathe2.default.dirname(path), `${svgId}.svg`); if (_map.get(path)) { logger.debug(`Update ${type} svg`, path); } else { if (watch) { logger.debug(`Add ${type} svg`, path); } else { logger.debug(`Add ${type} svg`, path); } } _map.set(path, { type, svgStr, hash, svgHashPath, runtimeId: svgId, used: false }); }; const scanDirs = async () => { const { globbySync } = await import("globby"); const svgFiles = globbySync(contentPatterns); logger.debug("Match files:", svgFiles.length); const _transformMap = /* @__PURE__ */ new Map(); svgFiles.filter((item) => item.endsWith(".svg")).map((item) => { return import_pathe2.default.join(process.cwd(), item); }).forEach((item) => { upsertSvg(item, _transformMap); }); const hashed = {}; _transformMap.forEach((item, key) => { if (hashed[item.hash]) { if (!store.duplicatedHashes[item.hash]) { store.duplicatedHashes[item.hash] = hashed[item.hash]; } const originData = _transformMap.get(hashed[item.hash]); _transformMap.set(key, originData); } else { hashed[item.hash] = key; } }); store.transformMap = _transformMap; await compile(); }; function waitSpriteCompiled() { return new Promise((resolve, reject) => { if (store.svgSpriteCompiledResult) { resolve(); } let count = 0; function check() { setTimeout(() => { count += 1; if (store.svgSpriteCompiledResult) { resolve(); return; } if (count >= 100) { reject(new Error(`Compile by svg-sprite timeout of ${count}s`)); } check(); }, 1e3); } check(); }); } const timer = {}; const handleSvgUpsert = (path) => { const relativePath = import_pathe2.default.relative(process.cwd(), path); if (contentPatterns.map((item) => (0, import_minimatch.minimatch)(relativePath, item)).every((item) => item === true)) { clearTimeout(timer[path]); timer[path] = setTimeout(() => { upsertSvg(path, store.transformMap, true); compile(); delete timer[path]; }); } }; const handleSvgUnlink = (path) => { var _a2; const relativePath = import_pathe2.default.relative(process.cwd(), path); const type = (_a2 = store.transformMap.get(path)) == null ? void 0 : _a2.type; if (contentPatterns.map((item) => (0, import_minimatch.minimatch)(relativePath, item)).every((item) => item === true) && type) { store.transformMap.delete(path); logger.debug(`Delete ${type} svg`, path); compile(); } }; return { sprites, useSymbolMode, useSymbolResourceQuery, contentPatterns, store, path: { publicDir, outputDir, absolutePublicPath, absoluteOutputPath }, api: { scanDirs, compile, isDynamicSvg, waitSpriteCompiled, handleSvgUpsert, handleSvgUnlink } }; } var ctx; function getContext(options) { if (ctx) { logger.debug("Reuse ctx"); return ctx; } logger.debug("Create new ctx"); return ctx = createContext(options); } // src/core/unplugin.ts var initialized = false; var isWatched = false; var unplugin_default = (0, import_unplugin.createUnplugin)((options) => { const ctx2 = getContext(options); return { name: PLUGIN_NAME, async buildStart() { if (!initialized) { await ctx2.api.scanDirs(); } initialized = true; }, resolveId(id) { if (ctx2.useSymbolMode && id.startsWith(SVG_SPRITE_PREFIX)) { logger.debug("Got resolveId", id); return id; } }, loadInclude(id) { if (ctx2.useSymbolMode) { return id === SVG_SPRITE_SYMBOL; } }, async load(id) { const normalizeId = import_pathe3.default.normalize(id); logger.debug("Got load id", normalizeId); await ctx2.api.waitSpriteCompiled(); const { data } = ctx2.store.svgSpriteCompiledResult.dynamic; return transformSymbolSprite(data.symbol, { userOptions: ctx2.sprites.symbol, pathname: import_pathe3.default.join( ctx2.path.absoluteOutputPath.replace(ctx2.path.absolutePublicPath, ""), "dynamic", "symbol" /* Symbol */, data.symbol.sprite ) }); }, transformInclude(id) { if (!ctx2.useSymbolMode) { return false; } return ctx2.useSymbolResourceQuery ? id.endsWith(".svg?symbol") : id.endsWith(".svg"); }, async transform(_2, id) { const normalizeId = import_pathe3.default.normalize(id); logger.debug("Got transform id", normalizeId); const parsedId = ctx2.useSymbolResourceQuery ? import_pathe3.default.normalize(id.replace(".svg?symbol", ".svg")) : normalizeId; await ctx2.api.waitSpriteCompiled(); const target = ctx2.store.transformMap.get(parsedId); if (!target) { throw new Error(`svg sprite [${parsedId}] not found`); } ctx2.store.transformMap.set(parsedId, __spreadProps(__spreadValues({}, target), { used: true })); const originUniqId = ctx2.store.duplicatedHashes[target.hash]; if (originUniqId) { logger.debug(`Duplicated id, corresponding uniq id: ${originUniqId}`); } const readId = originUniqId || parsedId; const compiledResult = ctx2.store.svgSpriteCompiledResult; const staticPathname = import_pathe3.default.join( ctx2.path.absoluteOutputPath.replace(ctx2.path.absolutePublicPath, ""), "static", "symbol" /* Symbol */, compiledResult.static.data.symbol.sprite ); return transformSymbolItem(readId, { compiledResult, userOptions: ctx2.sprites.symbol, transformData: target, staticPathname }); }, async writeBundle() { var _a; if (((_a = ctx2.sprites.symbol) == null ? void 0 : _a.compileOptimization) && process.env.NODE_ENV !== "development" && !ctx2.store.optimized) { await ctx2.api.compile({ optimization: true }); } }, vite: { configureServer(server) { const { watcher } = server; if (!isWatched) { watcher.on("add", (path) => { if (initialized) { ctx2.api.handleSvgUpsert(path); } }).on("change", (path) => { if (initialized) { ctx2.api.handleSvgUpsert(path); } }).on("unlink", (path) => { if (initialized) { ctx2.api.handleSvgUnlink(path); } }); } isWatched = true; } }, webpack: (compiler) => { if (isWatched) { return; } compiler.hooks.make.tap.bind( compiler.hooks.make, `${PLUGIN_NAME}_watcher` )(() => { const watcher = import_chokidar.default.watch(`${process.cwd()}/**/*.svg`, { ignored: [ "**/.git/**", "**/node_modules/**", `**/${ctx2.path.publicDir}/${ctx2.path.outputDir}/**`, `${import_pathe3.default.normalize(process.cwd())}/.*/**` ], ignoreInitial: true, ignorePermissionErrors: true }); watcher.on("add", (path) => { if (initialized) { ctx2.api.handleSvgUpsert(path); } }).on("change", (path) => { if (initialized) { ctx2.api.handleSvgUpsert(path); } }).on("unlink", (path) => { if (initialized) { ctx2.api.handleSvgUnlink(path); } }); }); isWatched = true; } }; }); exports.default = module.exports;