vite-plugin-icons-spritesheet
Version:
Vite plugin that generates a spritesheet and types out of your icons folder.
274 lines (272 loc) • 10 kB
JavaScript
;
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
});