UNPKG

fantasticon

Version:
654 lines (623 loc) 20.2 kB
var __getOwnPropNames = Object.getOwnPropertyNames; var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { get: (a, b) => (typeof require !== "undefined" ? require : a)[b] }) : x)(function(x) { if (typeof require !== "undefined") return require.apply(this, arguments); throw Error('Dynamic require of "' + x + '" is not supported'); }); var __commonJS = (cb, mod) => function __require2() { return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.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 import { resolve as resolve2 } from "path"; // src/utils/path.ts import { existsSync } from "fs"; import { normalize, resolve, dirname, join } from "path"; import { fileURLToPath } from "url"; var MAX_LOOKUP_DEPTH = 5; var getFileName = () => typeof __filename !== "undefined" ? __filename : fileURLToPath(import.meta.url); var getDirName = () => dirname(getFileName()); var removeExtension = (path) => path.includes(".") ? path.split(".").slice(0, -1).join(".") : path; var splitSegments = (path) => normalize(path).split(/\/|\\/).filter((part) => part && part !== "."); var getRoot = () => { let current = getDirName(); for (let i = 0; i < MAX_LOOKUP_DEPTH; i++) { const pkg = join(current, "package.json"); if (existsSync(pkg)) return resolve(current); const parent = dirname(current); current = parent; } throw new Error("Module root not found"); }; // src/utils/icon-id.ts import slug from "slugify"; var getIconId = ({ relativeFilePath }) => slug(removeExtension(relativeFilePath).replace(/(\/|\\|\.)+/g, "-"), { replacement: "-", remove: /['"`]/g }); // src/constants.ts var TEMPLATES_DIR = resolve2(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/fs-async.ts import { promisify } from "util"; import * as fs from "fs"; var readFile2 = promisify(fs.readFile); var writeFile2 = promisify(fs.writeFile); var stat2 = 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/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 removeUndefined = (object) => { for (const key of Object.keys(object)) { if (typeof object[key] === "undefined") { delete object[key]; } } return object; }; 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/utils/assets.ts import { glob } from "glob"; import { resolve as resolve3, relative, join as join2 } from "path"; var ASSETS_EXTENSION = "svg"; var loadPaths = async (dir) => { const globPath = join2(dir, `**/*.${ASSETS_EXTENSION}`); const files = await 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 = relative(resolve3(inputDir), resolve3(path)); const parts = splitSegments(relativePath); const basename = removeExtension(parts.pop()); const absolutePath = resolve3(path); const iconId = getIconId2({ basename, relativeDirPath: join2(...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] || join2(outputDir, filename); results.push({ content: assets[ext], writePath }); await writeFile2(writePath, assets[ext]); } return results; }; // src/generators/generator-options.ts import { join as join3 } from "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) => join3(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/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 import { createReadStream } from "fs"; import { SVGIcons2SVGFontStream } from "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 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 = 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 import svg2ttf from "svg2ttf"; var generator2 = { dependsOn: "svg" /* SVG */, async generate({ formatOptions }, svg) { const font = svg2ttf(svg, { ts: 0, ...formatOptions?.ttf || {} }); return Buffer.from(font.buffer); } }; var ttf_default = generator2; // src/generators/asset-types/woff.ts import ttf2woff from "ttf2woff"; var generator3 = { dependsOn: "ttf" /* TTF */, async generate({ formatOptions }, ttf) { const font = ttf2woff(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 import ttf2eot from "ttf2eot"; var generator5 = { dependsOn: "ttf" /* TTF */, async generate(_options, ttf) { const font = ttf2eot(new Uint8Array(ttf)); return Buffer.from(font.buffer); } }; var eot_default = generator5; // src/utils/template.ts import Handlebars from "handlebars"; import { resolve as resolve4, isAbsolute } from "path"; var TEMPLATE_HELPERS = { codepoint: getHexCodepoint }; var renderTemplate = async (templatePath, context, options = {}) => { const absoluteTemplatePath = isAbsolute(templatePath) ? templatePath : resolve4(process.cwd(), templatePath); const template = await readFile2(absoluteTemplatePath, "utf8"); return Handlebars.compile(template)(context, { ...options, helpers: { ...TEMPLATE_HELPERS, ...options.helpers || {} } }); }; // src/utils/hash.ts import crypto from "crypto"; var getHash = (...contents) => { const hash = crypto.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 import { default as caseUtil } from "case"; var generateEnumKeys = (assetKeys) => assetKeys.map((name) => { const enumName = caseUtil.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 || caseUtil.pascal(name); const constantName = ts?.constantName || `${caseUtil.constant(name)}_CODEPOINTS`; const literalIdName = ts?.literalIdName || `${caseUtil.pascal(name)}Id`; const literalKeyName = ts?.literalKeyName || `${caseUtil.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 }; }; export { __require, __commonJS, FontAssetType, OtherAssetType, ASSET_TYPES, DEFAULT_OPTIONS, readFile2 as readFile, checkPath, removeUndefined, generateFonts };