UNPKG

fantasticon

Version:
671 lines (639 loc) 21.8 kB
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, { ASSET_TYPES: () => ASSET_TYPES, FontAssetType: () => FontAssetType, OtherAssetType: () => OtherAssetType, default: () => generateFonts, generateFonts: () => generateFonts }); module.exports = __toCommonJS(index_exports); // src/types/misc.ts var FontAssetType = /* @__PURE__ */ ((FontAssetType2) => { FontAssetType2["EOT"] = "eot"; FontAssetType2["WOFF2"] = "woff2"; FontAssetType2["WOFF"] = "woff"; FontAssetType2["TTF"] = "ttf"; FontAssetType2["SVG"] = "svg"; return FontAssetType2; })(FontAssetType || {}); var OtherAssetType = /* @__PURE__ */ ((OtherAssetType3) => { OtherAssetType3["CSS"] = "css"; OtherAssetType3["SCSS"] = "scss"; OtherAssetType3["SASS"] = "sass"; OtherAssetType3["HTML"] = "html"; OtherAssetType3["JSON"] = "json"; OtherAssetType3["TS"] = "ts"; return OtherAssetType3; })(OtherAssetType || {}); var ASSET_TYPES_WITH_TEMPLATE = [ "css" /* CSS */, "html" /* HTML */, "scss" /* SCSS */, "sass" /* SASS */ ]; var ASSET_TYPES = { ...FontAssetType, ...OtherAssetType }; // src/constants.ts var import_path3 = require("path"); // src/utils/path.ts var import_fs = require("fs"); var import_path = require("path"); var import_url = require("url"); var import_meta = {}; var MAX_LOOKUP_DEPTH = 5; var getFileName = () => typeof __filename !== "undefined" ? __filename : (0, import_url.fileURLToPath)(import_meta.url); var getDirName = () => (0, import_path.dirname)(getFileName()); var removeExtension = (path) => path.includes(".") ? path.split(".").slice(0, -1).join(".") : path; var splitSegments = (path) => (0, import_path.normalize)(path).split(/\/|\\/).filter((part) => part && part !== "."); var getRoot = () => { let current = getDirName(); for (let i = 0; i < MAX_LOOKUP_DEPTH; i++) { const pkg = (0, import_path.join)(current, "package.json"); if ((0, import_fs.existsSync)(pkg)) return (0, import_path.resolve)(current); const parent = (0, import_path.dirname)(current); current = parent; } throw new Error("Module root not found"); }; // src/utils/icon-id.ts var import_slugify = __toESM(require("slugify"), 1); var getIconId = ({ relativeFilePath }) => (0, import_slugify.default)(removeExtension(relativeFilePath).replace(/(\/|\\|\.)+/g, "-"), { replacement: "-", remove: /['"`]/g }); // src/constants.ts var TEMPLATES_DIR = (0, import_path3.resolve)(getRoot(), "templates"); var DEFAULT_OPTIONS = { name: "icons", fontTypes: ["eot" /* EOT */, "woff2" /* WOFF2 */, "woff" /* WOFF */], assetTypes: [ "css" /* CSS */, "html" /* HTML */, "json" /* JSON */, "ts" /* TS */ ], formatOptions: { json: { indent: 4 } }, pathOptions: {}, templates: {}, codepoints: {}, round: void 0, fontHeight: 300, descent: void 0, normalize: void 0, selector: null, tag: "i", prefix: "icon", fontsUrl: void 0, getIconId }; var DEFAULT_START_CODEPOINT = 61697; // src/utils/assets.ts var import_glob = require("glob"); var import_path5 = require("path"); // src/utils/fs-async.ts var import_util = require("util"); var fs = __toESM(require("fs"), 1); var readFile2 = (0, import_util.promisify)(fs.readFile); var writeFile2 = (0, import_util.promisify)(fs.writeFile); var stat2 = (0, import_util.promisify)(fs.stat); var checkPath = async (filepath, type) => { try { const result = await stat2(filepath); if (type) { return type === "directory" ? result.isDirectory() : result.isFile(); } return true; } catch (err) { return false; } }; // src/utils/assets.ts var ASSETS_EXTENSION = "svg"; var loadPaths = async (dir) => { const globPath = (0, import_path5.join)(dir, `**/*.${ASSETS_EXTENSION}`); const files = await (0, import_glob.glob)(globPath, {}); if (!files.length) { throw new Error(`No SVGs found in ${dir}`); } return files; }; var failForConflictingId = ({ relativePath: pathA, id }, { relativePath: pathB }) => { throw new Error( `Conflicting result from 'getIconId': '${id}' - conflicting input files: ` + [pathA, pathB].map((fpath) => ` - ${fpath}`).join("\n") ); }; var loadAssets = async ({ inputDir, getIconId: getIconId2 }) => { const paths = await loadPaths(inputDir); const out = {}; let index = 0; for (const path of paths) { const relativePath = (0, import_path5.relative)((0, import_path5.resolve)(inputDir), (0, import_path5.resolve)(path)); const parts = splitSegments(relativePath); const basename = removeExtension(parts.pop()); const absolutePath = (0, import_path5.resolve)(path); const iconId = getIconId2({ basename, relativeDirPath: (0, import_path5.join)(...parts), absoluteFilePath: absolutePath, relativeFilePath: relativePath, index }); const result = { id: iconId, relativePath, absolutePath }; if (out[iconId]) { failForConflictingId(out[iconId], result); } out[iconId] = result; index++; } return out; }; var writeAssets = async (assets, { name, pathOptions = {}, outputDir }) => { const results = []; for (const ext of Object.keys(assets)) { const filename = [name, ext].join("."); const writePath = pathOptions[ext] || (0, import_path5.join)(outputDir, filename); results.push({ content: assets[ext], writePath }); await writeFile2(writePath, assets[ext]); } return results; }; // src/generators/generator-options.ts var import_path7 = require("path"); // src/utils/codepoints.ts var getCodepoints = (assets, predefined = {}, start = DEFAULT_START_CODEPOINT) => { const out = {}; const used = Object.values(predefined); let current = start; const getNextCodepoint = () => { while (used.includes(current)) { current++; } const res = current; current++; return res; }; for (const id of Object.keys(assets)) { if (!predefined[id]) { out[id] = getNextCodepoint(); } } return { ...predefined, ...out }; }; var getHexCodepoint = (decimalCodepoint) => decimalCodepoint.toString(16); // src/generators/generator-options.ts var getGeneratorOptions = (options, assets) => ({ ...options, codepoints: getCodepoints(assets, options.codepoints), formatOptions: prefillOptions( Object.values(ASSET_TYPES), options.formatOptions, (assetType) => DEFAULT_OPTIONS.formatOptions[assetType] || {} ), templates: prefillOptions( ASSET_TYPES_WITH_TEMPLATE, options.templates, (assetType) => (0, import_path7.join)(TEMPLATES_DIR, `${assetType}.hbs`) ), assets }); var prefillOptions = (keys, userOptions = {}, getDefault) => keys.reduce( (cur = {}, type) => ({ ...cur, [type]: mergeOptions(userOptions[type], getDefault(type)) }), {} ); var mergeOptions = (input, defaultVal) => { if (typeof defaultVal === "object") { return { ...defaultVal, ...input }; } return input || defaultVal; }; // src/utils/validation.ts var parseNumeric = (value) => { const out = Number(value); if (typeof value !== "number" && typeof value !== "string" || Number.isNaN(out)) { throw new Error(`${value} is not a valid number`); } return out; }; var parseString = (value) => { if (typeof value !== "string") { throw new Error(`${value} is not a string`); } return value; }; var parseFunction = (value) => { if (typeof value !== "function") { throw new Error(`${value} is not a function`); } return value; }; var listMembersParser = (allowedValues) => (values) => { for (const value of values) { if (!allowedValues.includes(value)) { throw new Error( [ `${value} is not valid`, `accepted values are: ${allowedValues.join(", ")}` ].join(" - ") ); } } return values; }; var parseBoolean = (val) => { if (typeof val === "string" && ["1", "0", "true", "false"].includes(val)) { return val === "true" || val === "1"; } else if (typeof val === "number" && [0, 1].includes(val)) { return val === 1; } else if (typeof val === "boolean") { return val; } throw new Error(`must be a boolean value`); }; var skipIfMatching = (match) => (fn) => (val) => val === match ? match : fn(val); var parseDir = async (dirname2) => { if (await checkPath(dirname2, "directory") === false) { throw new Error(`${dirname2} is not a directory`); } return dirname2; }; var nullable = skipIfMatching(null); var optional = skipIfMatching(void 0); // src/core/config-parser.ts var CONFIG_VALIDATORS = { inputDir: [optional(parseString), optional(parseDir)], outputDir: [optional(parseString), optional(parseDir)], name: [optional(parseString)], fontTypes: [listMembersParser(Object.values(FontAssetType))], assetTypes: [listMembersParser(Object.values(OtherAssetType))], formatOptions: [], pathOptions: [], templates: [], codepoints: [], fontHeight: [optional(parseNumeric)], descent: [optional(parseNumeric)], normalize: [optional(parseBoolean)], round: [optional(parseNumeric)], selector: [nullable(parseString)], tag: [parseString], prefix: [parseString], fontsUrl: [optional(parseString)], getIconId: [optional(parseFunction)] }; var parseConfig = async (input = {}) => { const options = { ...DEFAULT_OPTIONS, ...input }; const out = {}; const allkeys = [ .../* @__PURE__ */ new Set([...Object.keys(options), ...Object.keys(CONFIG_VALIDATORS)]) ]; for (const key of allkeys) { const validators = CONFIG_VALIDATORS[key]; if (!validators) { throw new Error(`The option '${key}' is not recognised`); } let val = options[key]; try { for (const fn of validators) { val = await fn(val, val); } } catch (err) { throw new Error(`Invalid option ${key}: ${err.message}`); } out[key] = val; } return out; }; // src/generators/asset-types/svg.ts var import_fs2 = require("fs"); var import_svgicons2svgfont = require("svgicons2svgfont"); var generator = { generate: ({ name: fontName, fontHeight, descent, normalize: normalize2, assets, codepoints, formatOptions: { svg } = {} }) => new Promise((resolve5) => { let font = Buffer.alloc(0); const fontStream = new import_svgicons2svgfont.SVGIcons2SVGFontStream({ fontName, fontHeight, descent, normalize: normalize2, // log: () => null, ...svg }).on("data", (data) => font = Buffer.concat([font, Buffer.from(data)])).on("end", () => resolve5(font.toString())); for (const { id, absolutePath } of Object.values(assets)) { const glyph = (0, import_fs2.createReadStream)(absolutePath); const unicode = String.fromCharCode(codepoints[id]); glyph.metadata = { name: id, unicode: [unicode] }; fontStream.write(glyph); } fontStream.end(); }) }; var svg_default = generator; // src/generators/asset-types/ttf.ts var import_svg2ttf = __toESM(require("svg2ttf"), 1); var generator2 = { dependsOn: "svg" /* SVG */, async generate({ formatOptions }, svg) { const font = (0, import_svg2ttf.default)(svg, { ts: 0, ...formatOptions?.ttf || {} }); return Buffer.from(font.buffer); } }; var ttf_default = generator2; // src/generators/asset-types/woff.ts var import_ttf2woff = __toESM(require("ttf2woff"), 1); var generator3 = { dependsOn: "ttf" /* TTF */, async generate({ formatOptions }, ttf) { const font = (0, import_ttf2woff.default)(new Uint8Array(ttf), formatOptions?.woff); return Buffer.from(font.buffer); } }; var woff_default = generator3; // src/generators/asset-types/woff2.ts var generator4 = { dependsOn: "ttf" /* TTF */, async generate(_options, ttf) { const font = (await import("ttf2woff2")).default(ttf); return Buffer.from(font.buffer); } }; var woff2_default = generator4; // src/generators/asset-types/eot.ts var import_ttf2eot = __toESM(require("ttf2eot"), 1); var generator5 = { dependsOn: "ttf" /* TTF */, async generate(_options, ttf) { const font = (0, import_ttf2eot.default)(new Uint8Array(ttf)); return Buffer.from(font.buffer); } }; var eot_default = generator5; // src/utils/template.ts var import_handlebars = __toESM(require("handlebars"), 1); var import_path8 = require("path"); var TEMPLATE_HELPERS = { codepoint: getHexCodepoint }; var renderTemplate = async (templatePath, context, options = {}) => { const absoluteTemplatePath = (0, import_path8.isAbsolute)(templatePath) ? templatePath : (0, import_path8.resolve)(process.cwd(), templatePath); const template = await readFile2(absoluteTemplatePath, "utf8"); return import_handlebars.default.compile(template)(context, { ...options, helpers: { ...TEMPLATE_HELPERS, ...options.helpers || {} } }); }; // src/utils/hash.ts var import_crypto = __toESM(require("crypto"), 1); var getHash = (...contents) => { const hash = import_crypto.default.createHash("md5"); for (const content of contents) { hash.update(content); } return hash.digest("hex"); }; // src/utils/css.ts var renderSrcOptions = { ["eot" /* EOT */]: { formatValue: "embedded-opentype", getSuffix: () => "#iefix" }, ["woff2" /* WOFF2 */]: { formatValue: "woff2" }, ["woff" /* WOFF */]: { formatValue: "woff" }, ["ttf" /* TTF */]: { formatValue: "truetype" }, ["svg" /* SVG */]: { formatValue: "svg", getSuffix: (name) => `#${name}` } }; var renderSrcAttribute = ({ name, fontTypes, fontsUrl }, font, { inline = false } = {}) => fontTypes.map((fontType) => { const { formatValue, getSuffix } = renderSrcOptions[fontType]; const hash = getHash(font.toString("utf8")); const suffix = getSuffix ? getSuffix(name) : ""; return `url("${fontsUrl || "."}/${name}.${fontType}?${hash}${suffix}") format("${formatValue}")`; }).join(inline ? ", " : ",\n"); // src/generators/asset-types/css.ts var generator6 = { dependsOn: "svg" /* SVG */, generate: (options, svg) => renderTemplate(options.templates.css, { ...options, fontSrc: renderSrcAttribute(options, svg) }) }; var css_default = generator6; // src/generators/asset-types/html.ts var generator7 = { generate: (options) => renderTemplate(options.templates.html, options) }; var html_default = generator7; // src/generators/asset-types/json.ts var generator8 = { generate: async ({ formatOptions: { json } = {}, codepoints }) => JSON.stringify(codepoints, null, json?.indent) }; var json_default = generator8; // src/generators/asset-types/ts.ts var import_case = __toESM(require("case"), 1); var generateEnumKeys = (assetKeys) => assetKeys.map((name) => { const enumName = import_case.default.pascal(name); const prefix = enumName.match(/^\d/) ? "i" : ""; return { [name]: `${prefix}${enumName}` }; }).reduce((prev, curr) => Object.assign(prev, curr), {}); var generateEnums = (enumName, enumKeys, quote = '"') => [ `export enum ${enumName} {`, ...Object.entries(enumKeys).map( ([enumValue, enumKey]) => ` ${enumKey} = ${quote}${enumValue}${quote},` ), "}\n" ].join("\n"); var generateConstant = ({ constantName, enumName, literalIdName, literalKeyName, enumKeys, codepoints, quote = '"', kind = {} }) => { let varType = ": Record<string, string>"; if (kind.enum) { varType = `: { [key in ${enumName}]: string }`; } else if (kind.literalId) { varType = `: { [key in ${literalIdName}]: string }`; } else if (kind.literalKey) { varType = `: { [key in ${literalKeyName}]: string }`; } return [ `export const ${constantName}${varType} = {`, Object.entries(enumKeys).map(([enumValue, enumKey]) => { const key = kind.enum ? `[${enumName}.${enumKey}]` : `${quote}${enumValue}${quote}`; return ` ${key}: ${quote}${codepoints[enumValue]}${quote},`; }).join("\n"), "};\n" ].join("\n"); }; var generateStringLiterals = (typeName, literals, quote = '"') => [ `export type ${typeName} =`, `${literals.map((key) => ` | ${quote}${key}${quote}`).join("\n")}; ` ].join("\n"); var generator9 = { generate: async ({ name, codepoints, assets, formatOptions: { ts } = {} }) => { const quote = Boolean(ts?.singleQuotes) ? "'" : '"'; const generateKind = (Boolean(ts?.types?.length) ? ts.types : ["enum", "constant", "literalId", "literalKey"]).map((kind) => ({ [kind]: true })).reduce((prev, curr) => Object.assign(prev, curr), {}); const enumName = ts?.enumName || import_case.default.pascal(name); const constantName = ts?.constantName || `${import_case.default.constant(name)}_CODEPOINTS`; const literalIdName = ts?.literalIdName || `${import_case.default.pascal(name)}Id`; const literalKeyName = ts?.literalKeyName || `${import_case.default.pascal(name)}Key`; const names = { enumName, constantName, literalIdName, literalKeyName }; const enumKeys = generateEnumKeys(Object.keys(assets)); const stringLiteralId = generateKind.literalId ? generateStringLiterals(literalIdName, Object.keys(enumKeys), quote) : null; const stringLiteralKey = generateKind.literalKey ? generateStringLiterals(literalKeyName, Object.values(enumKeys), quote) : null; const enums = generateKind.enum ? generateEnums(enumName, enumKeys, quote) : null; const constant = generateKind.constant ? generateConstant({ ...names, enumKeys, codepoints, quote, kind: generateKind }) : null; return [stringLiteralId, stringLiteralKey, enums, constant].filter(Boolean).join("\n"); } }; var ts_default = generator9; // src/generators/asset-types/sass.ts var generator10 = { dependsOn: "svg" /* SVG */, generate: (options, svg) => renderTemplate(options.templates.sass, { ...options, fontSrc: renderSrcAttribute(options, svg, { inline: true }) }) }; var sass_default = generator10; // src/generators/asset-types/scss.ts var generator11 = { dependsOn: "svg" /* SVG */, generate: (options, svg) => renderTemplate( options.templates.scss, { ...options, fontSrc: renderSrcAttribute(options, svg) }, { helpers: { codepoint: (str) => str.toString(16) } } ) }; var scss_default = generator11; // src/generators/asset-types/index.ts var generators = { ["svg" /* SVG */]: svg_default, ["ttf" /* TTF */]: ttf_default, ["woff" /* WOFF */]: woff_default, ["woff2" /* WOFF2 */]: woff2_default, ["eot" /* EOT */]: eot_default, ["css" /* CSS */]: css_default, ["html" /* HTML */]: html_default, ["json" /* JSON */]: json_default, ["ts" /* TS */]: ts_default, ["sass" /* SASS */]: sass_default, ["scss" /* SCSS */]: scss_default }; var asset_types_default = generators; // src/generators/generate-assets.ts var generateAssets = async (options) => { const generated = {}; const generateTypes = [...options.fontTypes, ...options.assetTypes]; const generateAsset = async (type) => { if (generated[type]) { return generated[type]; } const generator12 = asset_types_default[type]; const dependsOn = "dependsOn" in generator12 && generator12.dependsOn; if (dependsOn && !generated[dependsOn]) { generated[dependsOn] = await generateAsset(dependsOn); } return generated[type] = await generator12.generate( options, dependsOn ? generated[dependsOn] : null ); }; for (const type of generateTypes) { await generateAsset(type); } return generateTypes.reduce( (out, type) => ({ ...out, [type]: generated[type] }), {} ); }; // src/core/runner.ts var sanitiseOptions = (userOptions) => parseConfig({ ...DEFAULT_OPTIONS, ...userOptions }); var generateFonts = async (userOptions, mustWrite = false) => { const options = await sanitiseOptions(userOptions); const { outputDir, inputDir } = options; if (!inputDir) { throw new Error("You must specify an input directory"); } if (mustWrite && !outputDir) { throw new Error("You must specify an output directory"); } const assetsIn = await loadAssets(options); const generatorOptions = getGeneratorOptions(options, assetsIn); const assetsOut = await generateAssets(generatorOptions); const writeResults = outputDir ? await writeAssets(assetsOut, options) : []; const { codepoints } = generatorOptions; return { options, assetsIn, assetsOut, writeResults, codepoints }; }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { ASSET_TYPES, FontAssetType, OtherAssetType, generateFonts });