UNPKG

vite-plugin-icons-spritesheet

Version:

Vite plugin that generates a spritesheet and types out of your icons folder.

274 lines (272 loc) 10 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; 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 index_exports = {}; __export(index_exports, { iconsSpritesheet: () => iconsSpritesheet }); module.exports = __toCommonJS(index_exports); var import_node_fs = require("fs"); var import_promises = require("fs/promises"); var import_node_path = __toESM(require("path")); var import_node_process = require("process"); var import_node_stream = require("stream"); var import_chalk = __toESM(require("chalk")); var import_glob = require("glob"); var import_node_html_parser = require("node-html-parser"); var import_tinyexec = require("tinyexec"); var import_vite = require("vite"); var generateIcons = async ({ withTypes = false, inputDir, outputDir, typesOutputFile = `${outputDir}/types.ts`, cwd, formatter, fileName = "sprite.svg", iconNameTransformer }) => { const cwdToUse = cwd ?? process.cwd(); const inputDirRelative = import_node_path.default.relative(cwdToUse, inputDir); const outputDirRelative = import_node_path.default.relative(cwdToUse, outputDir); const files = import_glob.glob.sync("**/*.svg", { cwd: inputDir }); if (files.length === 0) { console.log(`\u26A0\uFE0F No SVG files found in ${import_chalk.default.red(inputDirRelative)}`); return; } await (0, import_promises.mkdir)(outputDirRelative, { recursive: true }); await generateSvgSprite({ files, inputDir, outputPath: import_node_path.default.join(outputDir, fileName), outputDirRelative, iconNameTransformer, formatter }); if (withTypes) { const typesOutputDir = import_node_path.default.dirname(typesOutputFile); const typesFile = import_node_path.default.basename(typesOutputFile); const typesOutputDirRelative = import_node_path.default.relative(cwdToUse, typesOutputDir); await (0, import_promises.mkdir)(typesOutputDirRelative, { recursive: true }); await generateTypes({ names: files.map((file) => transformIconName(file, iconNameTransformer ?? fileNameToCamelCase)), outputPath: import_node_path.default.join(typesOutputDir, typesFile), formatter }); } }; var transformIconName = (fileName, transformer) => { const iconName = fileName.replace(/\.svg$/, ""); return transformer(iconName); }; function fileNameToCamelCase(fileName) { const words = fileName.split("-"); const capitalizedWords = words.map((word) => word.charAt(0).toUpperCase() + word.slice(1)); return capitalizedWords.join(""); } async function generateSvgSprite({ files, inputDir, outputPath, outputDirRelative, iconNameTransformer, formatter }) { const symbols = await Promise.all( files.map(async (file) => { const fileName = transformIconName(file, iconNameTransformer ?? fileNameToCamelCase); const input = await import_node_fs.promises.readFile(import_node_path.default.join(inputDir, file), "utf8"); const root = (0, import_node_html_parser.parse)(input); const svg = root.querySelector("svg"); if (!svg) { console.log(`\u26A0\uFE0F No SVG tag found in ${file}`); return; } svg.tagName = "symbol"; svg.setAttribute("id", fileName); svg.removeAttribute("xmlns"); svg.removeAttribute("xmlns:xlink"); svg.removeAttribute("version"); svg.removeAttribute("width"); svg.removeAttribute("height"); return svg.toString().trim(); }) ); const output = [ '<?xml version="1.0" encoding="UTF-8"?>', '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="0" height="0">', "<defs>", // for semantics: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/defs ...symbols.filter(Boolean), "</defs>", "</svg>" ].join("\n"); const formattedOutput = await lintFileContent(output, formatter, "svg"); return writeIfChanged( outputPath, formattedOutput, `\u{1F5BC}\uFE0F Generated SVG spritesheet in ${import_chalk.default.green(outputDirRelative)}` ); } async function lintFileContent(fileContent, formatter, typeOfFile) { if (!formatter) { return fileContent; } if (formatter === "biome" && typeOfFile === "svg") { return fileContent; } const prettierOptions = ["--parser", typeOfFile === "ts" ? "typescript" : "html"]; const biomeOptions = ["format", "--stdin-file-path", `file.${typeOfFile}`]; const options = formatter === "biome" ? biomeOptions : prettierOptions; const stdinStream = new import_node_stream.Readable(); stdinStream.push(fileContent); stdinStream.push(null); const { process: process2 } = (0, import_tinyexec.exec)(formatter, options, {}); if (!process2?.stdin) { return fileContent; } stdinStream.pipe(process2.stdin); process2.stderr?.pipe(import_node_process.stderr); process2.on("error", (err) => { }); let formattedContent = ""; process2.stdout?.on("data", (data) => { formattedContent = formattedContent + data.toString(); }); return new Promise((resolve) => { process2.on("exit", (code) => { if (code === 0) { resolve(formattedContent); } else { resolve(fileContent); } }); }); } async function generateTypes({ names, outputPath, formatter }) { const output = [ "// This file is generated by icon spritesheet generator", "", "export const iconNames = [", ...names.map((name) => ` "${name}",`), "] as const", "", "export type IconName = typeof iconNames[number]", "" ].join("\n"); const formattedOutput = await lintFileContent(output, formatter, "ts"); const file = await writeIfChanged( outputPath, formattedOutput, `${import_chalk.default.blueBright("TS")} Generated icon types in ${import_chalk.default.green(outputPath)}` ); return file; } async function writeIfChanged(filepath, newContent, message) { try { const currentContent = await import_node_fs.promises.readFile(filepath, "utf8"); if (currentContent !== newContent) { await import_node_fs.promises.writeFile(filepath, newContent, "utf8"); console.log(message); } } catch (e) { await import_node_fs.promises.writeFile(filepath, newContent, "utf8"); console.log(message); } } var iconsSpritesheet = (maybeConfigs) => { const configs = Array.isArray(maybeConfigs) ? maybeConfigs : [maybeConfigs]; const allSpriteSheetNames = configs.map((config) => config.fileName ?? "sprite.svg"); return configs.map((config, i) => { const { withTypes, inputDir, outputDir, typesOutputFile, fileName, cwd, iconNameTransformer, formatter } = config; const iconGenerator = async () => generateIcons({ withTypes, inputDir, outputDir, typesOutputFile, fileName, iconNameTransformer, formatter }); const workDir = cwd ?? process.cwd(); return { name: `icon-spritesheet-generator${i > 0 ? i.toString() : ""}`, async buildStart() { await iconGenerator(); }, async watchChange(file, type) { const inputPath = (0, import_vite.normalizePath)(import_node_path.default.join(workDir, inputDir)); if (file.includes(inputPath) && file.endsWith(".svg") && ["create", "delete"].includes(type.event)) { await iconGenerator(); } }, async handleHotUpdate({ file }) { const inputPath = (0, import_vite.normalizePath)(import_node_path.default.join(workDir, inputDir)); if (file.includes(inputPath) && file.endsWith(".svg")) { await iconGenerator(); } }, async config(config2) { if (i > 0) { return; } config2.build = config2.build ?? {}; const subFunc = typeof config2.build.assetsInlineLimit === "function" ? config2.build.assetsInlineLimit : void 0; const limit = typeof config2.build.assetsInlineLimit === "number" ? config2.build.assetsInlineLimit : void 0; const assetsInlineLimitFunction = (name, content) => { const isSpriteSheet = allSpriteSheetNames.some((spriteSheetName) => { return name.endsWith((0, import_vite.normalizePath)(`${outputDir}/${spriteSheetName}`)); }); if (isSpriteSheet) { return false; } if (limit) { const size = content.byteLength; return size <= limit; } if (typeof subFunc === "function") { return subFunc(name, content); } return void 0; }; config2.build.assetsInlineLimit = assetsInlineLimitFunction; } }; }); }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { iconsSpritesheet });