UNPKG

@ikona/cli

Version:
293 lines (285 loc) 9.64 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/icons/build.ts var build_exports = {}; __export(build_exports, { generateSprite: () => generateSprite }); module.exports = __toCommonJS(build_exports); var import_crypto = __toESM(require("crypto")); var import_fs_extra2 = __toESM(require("fs-extra")); var import_glob = require("glob"); var import_node_html_parser = require("node-html-parser"); var path = __toESM(require("path")); // src/utils/validations.ts var import_fs_extra = __toESM(require("fs-extra")); var import_node_path = require("path"); function clear(folderPath) { import_fs_extra.default.readdir(folderPath, (err, files) => { if (err) { console.error("Error reading folder:", err); return; } const svgFiles = files.filter( (file) => (0, import_node_path.extname)(file).toLowerCase() === ".svg" && file.startsWith("sprite") ); svgFiles.forEach((svgFile) => { const filePath = (0, import_node_path.join)(folderPath, svgFile); import_fs_extra.default.unlink(filePath, (err2) => { if (err2) { console.error(`Error removing file ${filePath}:`, err2); } else { console.log(`Removed file: ${filePath}`); } }); }); }); } async function writeIfChanged({ filepath, newContent, hash, force }) { let _filepath = filepath; if (hash) { _filepath = filepath.replace(/\.svg$/, `.${hash}.svg`); } const currentContent = await import_fs_extra.default.readFile(_filepath, "utf8").catch(() => ""); const shouldSkip = currentContent === newContent && force !== true; if (shouldSkip) return false; if (hash) { const folder = filepath.replace(/sprite\.svg$/, ``); clear(folder); } await import_fs_extra.default.writeFile(_filepath, newContent, "utf8"); return true; } // src/utils/config.ts var import_bundle_n_require = require("bundle-n-require"); var import_sync = __toESM(require("escalade/sync")); var defaultConfig = { outputDir: ".ikona" }; var configs = [".ts", ".js", ".mts", ".mjs", ".cts", ".cjs"]; var configRegex = new RegExp(`ikona.config(${configs.join("|")})$`); // src/icons/build.ts var import_svgo = require("svgo"); // src/utils/file.ts function calculateFileSizeInKB(str) { const buffer = Buffer.from(str, "utf-8"); const fileSizeInKB = buffer.length / 1024; return fileSizeInKB; } // src/icons/build.ts async function generateIconFiles({ files, inputDir, outputDir, spriteOutputDir, shouldOptimize, shouldHash, force }) { const spriteFilepath = path.join(spriteOutputDir, "sprite.svg"); const typeOutputFilepath = path.join(outputDir, "types", "icon-name.d.ts"); const currentSprite = await import_fs_extra2.default.readFile(spriteFilepath, "utf8").catch(() => ""); const currentTypes = await import_fs_extra2.default.readFile(typeOutputFilepath, "utf8").catch(() => ""); const iconNames = files.map((file) => iconName(file)); const spriteUpToDate = iconNames.every( (name) => currentSprite.includes(`id=${name}`) ); const typesUpToDate = iconNames.every( (name) => currentTypes.includes(`"${name}"`) ); if (spriteUpToDate && typesUpToDate) { console.log(`Icons are up to date`); return; } let output = await generateSvgSprite({ files, inputDir }); if (shouldOptimize) { const config = await (0, import_svgo.loadConfig)() || void 0; output = (0, import_svgo.optimize)(output, config).data; } let hash; if (shouldHash) { hash = import_crypto.default.createHash("md5").update(output).digest("hex"); } const spriteChanged = await writeIfChanged({ filepath: spriteFilepath, newContent: output, hash, force }); if (spriteChanged) { console.log(`Generating sprite for ${inputDir}`); for (const file of files) { console.log("\u2705", file); } console.log(`File size: ${calculateFileSizeInKB(output)} KB`); if (shouldHash) { console.log(`Generated sprite with hash ${hash}`); console.log( `Saved to ${path.relative( process.cwd(), spriteFilepath.replace(/\.svg$/, `.${hash}.svg`) )}` ); } else { console.log(`Saved to ${path.relative(process.cwd(), spriteFilepath)}`); } } const stringifiedIconNames = iconNames.map((name) => JSON.stringify(name)); const typeOutputContent = `export type IconName = | ${stringifiedIconNames.join("\n | ").replace(/"/g, "'")}; `; const typesChanged = await writeIfChanged({ filepath: typeOutputFilepath, newContent: typeOutputContent, force }); if (typesChanged) { console.log( `Types saved to ${path.relative(process.cwd(), typeOutputFilepath)}` ); } const iconsOutputFilepath = path.join(outputDir, "icons.ts"); const iconsOutputContent = `import { IconName } from './types/icon-name'; export const icons = [ ${stringifiedIconNames.join(",\n ")}, ] satisfies Array<IconName>; `; const iconsChanged = await writeIfChanged({ filepath: iconsOutputFilepath, newContent: iconsOutputContent, force }); if (iconsChanged) { console.log( `Icons names saved to ${path.relative( process.cwd(), iconsOutputFilepath )}` ); } if (shouldHash) { const hashOutputFilepath = path.join(outputDir, "hash.ts"); const hashFileContent = `export const hash = '${hash}'; `; const hashFileChanged = await writeIfChanged({ filepath: hashOutputFilepath, newContent: hashFileContent, force }); if (hashFileChanged) { console.log( `Hash file saved to ${path.relative(process.cwd(), hashOutputFilepath)}` ); } } if (spriteChanged || typesChanged || iconsChanged) { console.log(`Generated ${files.length} icons`); } else { console.log(`Icons are up to date`); } } function iconName(file) { return file.replace(/\.svg$/, "").replace(/\\/g, "/"); } async function generateSvgSprite({ files, inputDir }) { const symbols = await Promise.all( files.map(async (file) => { const input = await import_fs_extra2.default.readFile(path.join(inputDir, file), "utf8"); const root = (0, import_node_html_parser.parse)(input); const svg = root.querySelector("svg"); if (!svg) throw new Error("No SVG element found"); svg.tagName = "symbol"; svg.setAttribute("id", iconName(file)); svg.removeAttribute("xmlns"); svg.removeAttribute("xmlns:xlink"); svg.removeAttribute("version"); svg.removeAttribute("width"); svg.removeAttribute("height"); return svg.toString().trim(); }) ); return [ `<?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, `</defs>`, `</svg>`, "" // trailing newline ].join("\n"); } async function generateSprite(cliConfig, config) { const outputDir = cliConfig["out-dir"] || config.outputDir || defaultConfig.outputDir; const { icons, force } = config; const { inputDir, spriteOutputDir, optimize: optimize2, hash } = icons; const cwd = process.cwd(); const inputDirRelative = path.relative(cwd, inputDir); const outputDirRelative = path.join(cwd, outputDir); const spriteOutputDirRelative = path.join(cwd, spriteOutputDir); await Promise.all([ import_fs_extra2.default.ensureDir(inputDirRelative), import_fs_extra2.default.ensureDir(outputDirRelative), import_fs_extra2.default.ensureDir(spriteOutputDirRelative) ]); const files = import_glob.glob.sync("**/*.svg", { cwd: inputDirRelative }).sort((a, b) => a.localeCompare(b)); if (files.length === 0) { console.log(`No SVG files found in ${inputDirRelative}`); } else { await generateIconFiles({ files, inputDir: inputDirRelative, outputDir: outputDirRelative, spriteOutputDir: spriteOutputDirRelative, shouldOptimize: cliConfig.optimize || optimize2, shouldHash: cliConfig.hash || hash, force: cliConfig.force || force }); } } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { generateSprite });