1
0
mirror of https://github.com/tabler/tabler-icons.git synced 2025-09-08 21:30:40 +02:00

Generate dynamic imports for icons-react (#1081)

Co-authored-by: Paweł Kuna <1282324+codecalm@users.noreply.github.com>
This commit is contained in:
Tim Heerwagen
2024-09-27 22:09:40 +02:00
committed by GitHub
parent 1fbb588c69
commit 7d1f101f00
7 changed files with 318 additions and 261 deletions

View File

@@ -1,7 +1,7 @@
import fs from 'fs-extra' import fs from 'fs-extra';
import path from 'path' import path from 'path';
import { PACKAGES_DIR, getAliases, toPascalCase, getAllIcons } from './helpers.mjs' import { PACKAGES_DIR, getAliases, toPascalCase, getAllIcons } from './helpers.mjs';
import { stringify } from 'svgson' import { stringify } from 'svgson';
/** /**
* Build icons * Build icons
@@ -22,40 +22,39 @@ export const buildJsIcons = ({
key = true, key = true,
pascalCase = false, pascalCase = false,
pascalName = true, pascalName = true,
indexFile = 'icons.ts' indexFile = 'icons.ts',
}) => { }) => {
const DIST_DIR = path.resolve(PACKAGES_DIR, name); const DIST_DIR = path.resolve(PACKAGES_DIR, name);
const aliases = getAliases(), const aliases = getAliases(),
allIcons = getAllIcons(false, true) allIcons = getAllIcons(false, true);
let index = [] let index = [];
Object.entries(allIcons).forEach(([type, icons]) => { Object.entries(allIcons).forEach(([type, icons]) => {
icons.forEach((icon, i) => { icons.forEach((icon, i) => {
process.stdout.write(`Building \`${name}\` ${type} ${i}/${icons.length}: ${icon.name.padEnd(42)}\r`) process.stdout.write(
`Building \`${name}\` ${type} ${i}/${icons.length}: ${icon.name.padEnd(42)}\r`,
);
const children = icon.obj.children const children = icon.obj.children
.map(({ .map(({ name, attributes }, i) => {
name,
attributes
}, i) => {
if (key) { if (key) {
attributes.key = `svg-${i}` attributes.key = `svg-${i}`;
} }
if (pascalCase) { if (pascalCase) {
attributes.strokeWidth = attributes['stroke-width'] attributes.strokeWidth = attributes['stroke-width'];
delete attributes['stroke-width'] delete attributes['stroke-width'];
} }
return [name, attributes] return [name, attributes];
}) })
.filter((i) => { .filter((i) => {
const [name, attributes] = i const [name, attributes] = i;
return !attributes.d || attributes.d !== 'M0 0h24v24H0z' return !attributes.d || attributes.d !== 'M0 0h24v24H0z';
}) });
const iconName = `${icon.name}${type !== 'outline' ? `-${type}` : ''}`, const iconName = `${icon.name}${type !== 'outline' ? `-${type}` : ''}`,
iconNamePascal = `${icon.namePascal}${type !== 'outline' ? toPascalCase(type) : ''}` iconNamePascal = `${icon.namePascal}${type !== 'outline' ? toPascalCase(type) : ''}`;
let component = componentTemplate({ let component = componentTemplate({
type, type,
@@ -63,21 +62,27 @@ export const buildJsIcons = ({
namePascal: iconNamePascal, namePascal: iconNamePascal,
children, children,
stringify, stringify,
svg: icon.content svg: icon.content,
}) });
let filePath = path.resolve(DIST_DIR, 'src/icons', `${pascalName ? iconNamePascal : iconName}.${extension}`) let filePath = path.resolve(
fs.writeFileSync(filePath, component, 'utf-8') DIST_DIR,
'src/icons',
`${pascalName ? iconNamePascal : iconName}.${extension}`,
);
fs.writeFileSync(filePath, component, 'utf-8');
index.push(indexItemTemplate({ index.push(
indexItemTemplate({
type, type,
name: iconName, name: iconName,
namePascal: iconNamePascal namePascal: iconNamePascal,
})) }),
}) );
}) });
});
fs.writeFileSync(path.resolve(DIST_DIR, `src/icons/${indexFile}`), index.join('\n'), 'utf-8') fs.writeFileSync(path.resolve(DIST_DIR, `src/icons/${indexFile}`), index.join('\n'), 'utf-8');
// Write aliases // Write aliases
let aliasesStr = ''; let aliasesStr = '';
@@ -87,28 +92,61 @@ export const buildJsIcons = ({
from, from,
to, to,
fromPascal: toPascalCase(from), fromPascal: toPascalCase(from),
toPascal: toPascalCase(to) toPascal: toPascalCase(to),
}) });
}) });
} }
fs.writeFileSync(path.resolve(DIST_DIR, `./src/aliases.ts`), aliasesStr || `export {};`, 'utf-8') fs.writeFileSync(path.resolve(DIST_DIR, `./src/aliases.ts`), aliasesStr || `export {};`, 'utf-8');
} };
export const buildIconsList = (name) => { export const buildIconsList = (name) => {
const DIST_DIR = path.resolve(PACKAGES_DIR, name); const DIST_DIR = path.resolve(PACKAGES_DIR, name);
const allIcons = getAllIcons(false, true) const allIcons = getAllIcons(false, true);
let index = [] let index = [];
Object.entries(allIcons).forEach(([type, icons]) => { Object.entries(allIcons).forEach(([type, icons]) => {
icons.forEach((icon, i) => { icons.forEach((icon, i) => {
process.stdout.write(`Building \`${name}\` ${type} ${i}/${icons.length}: ${icon.name.padEnd(42)}\r`) process.stdout.write(
`Building \`${name}\` ${type} ${i}/${icons.length}: ${icon.name.padEnd(42)}\r`,
);
const iconName = `${icon.name}${type !== 'outline' ? `-${type}` : ''}` const iconName = `${icon.name}${type !== 'outline' ? `-${type}` : ''}`;
index.push(iconName) index.push(iconName);
}) });
}) });
fs.writeFileSync(path.resolve(DIST_DIR, `./src/icons-list.ts`), `export default ${JSON.stringify(index, null, 2)};`, 'utf-8') fs.writeFileSync(
} path.resolve(DIST_DIR, `./src/icons-list.ts`),
`export default ${JSON.stringify(index, null, 2)};`,
'utf-8',
);
};
export const buildIconsDynamicImport = (name) => {
const DIST_DIR = path.resolve(PACKAGES_DIR, name);
const allIcons = getAllIcons(false, true);
let dynamicImportString = 'export default {';
Object.entries(allIcons).forEach(([type, icons]) => {
icons.forEach((icon, i) => {
process.stdout.write(
`Building \`${name}\` ${type} ${i}/${icons.length}: ${icon.name.padEnd(42)}\r`,
);
const iconName = `${icon.name}${type !== 'outline' ? `-${type}` : ''}`,
iconNamePascal = `${icon.namePascal}${type !== 'outline' ? toPascalCase(type) : ''}`;
dynamicImportString += ` '${iconName}': () => import('./icons/${iconNamePascal}'),\n`;
});
});
dynamicImportString += '};\n';
fs.writeFileSync(
path.resolve(DIST_DIR, `./src/dynamic-imports.ts`),
dynamicImportString,
'utf-8',
);
};

View File

@@ -1,19 +1,22 @@
import fs from 'fs' import fs from 'fs';
import path, { resolve, basename } from 'path' import path, { resolve, basename } from 'path';
import { fileURLToPath } from 'url' import { fileURLToPath } from 'url';
import svgParse from 'parse-svg-path' import svgParse from 'parse-svg-path';
import svgpath from 'svgpath' import svgpath from 'svgpath';
import cheerio from 'cheerio'; import cheerio from 'cheerio';
import { minify } from 'html-minifier'; import { minify } from 'html-minifier';
import { parseSync } from 'svgson' import { parseSync } from 'svgson';
import { optimize } from 'svgo' import { optimize } from 'svgo';
import cp from 'child_process' import cp from 'child_process';
import minimist from 'minimist' import minimist from 'minimist';
import matter from 'gray-matter' import matter from 'gray-matter';
import { globSync } from 'glob' import { globSync } from 'glob';
import { exec } from 'child_process' import { exec } from 'child_process';
import slash from 'slash';
export const iconTemplate = (type) => type === 'outline' ? `<svg export const iconTemplate = (type) =>
type === 'outline'
? `<svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="24" width="24"
height="24" height="24"
@@ -23,53 +26,54 @@ export const iconTemplate = (type) => type === 'outline' ? `<svg
stroke-width="2" stroke-width="2"
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
>` : `<svg >`
: `<svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="24" width="24"
height="24" height="24"
viewBox="0 0 24 24" viewBox="0 0 24 24"
fill="currentColor" fill="currentColor"
>` >`;
export const blankSquare = '<path stroke="none" d="M0 0h24v24H0z" fill="none"/>' export const blankSquare = '<path stroke="none" d="M0 0h24v24H0z" fill="none"/>';
export const types = ['outline', 'filled'] export const types = ['outline', 'filled'];
export const getCurrentDirPath = () => { export const getCurrentDirPath = () => {
return path.dirname(fileURLToPath(import.meta.url)); return path.dirname(fileURLToPath(import.meta.url));
} };
export const HOME_DIR = resolve(getCurrentDirPath(), '..') export const HOME_DIR = resolve(getCurrentDirPath(), '..');
export const ICONS_SRC_DIR = resolve(HOME_DIR, 'icons') export const ICONS_SRC_DIR = resolve(HOME_DIR, 'icons');
export const PACKAGES_DIR = resolve(HOME_DIR, 'packages') export const PACKAGES_DIR = resolve(HOME_DIR, 'packages');
export const GITHUB_DIR = resolve(HOME_DIR, '.github') export const GITHUB_DIR = resolve(HOME_DIR, '.github');
export const parseMatter = (icon) => { export const parseMatter = (icon) => {
const { data, content } = matter.read(icon, { delims: ['<!--', '-->'] }) const { data, content } = matter.read(icon, { delims: ['<!--', '-->'] });
return { data, content } return { data, content };
} };
const getSvgContent = (svg, type, name) => { const getSvgContent = (svg, type, name) => {
return svg return svg
.replace(/<svg([^>]+)>/, (m, m1) => { .replace(/<svg([^>]+)>/, (m, m1) => {
return `<svg${m1} class="icon icon-tabler icons-tabler-${type} icon-tabler-${name}"\n>\n ${blankSquare}` return `<svg${m1} class="icon icon-tabler icons-tabler-${type} icon-tabler-${name}"\n>\n ${blankSquare}`;
}) })
.trim() .trim();
} };
export const getAllIcons = (withContent = false, withObject = false) => { export const getAllIcons = (withContent = false, withObject = false) => {
let icons = {} let icons = {};
const limit = process.env['ICONS_LIMIT'] || Infinity; const limit = process.env['ICONS_LIMIT'] || Infinity;
types.forEach(type => { types.forEach((type) => {
icons[type] = globSync(path.join(ICONS_SRC_DIR, `${type}/*.svg`)) icons[type] = globSync(slash(path.join(ICONS_SRC_DIR, `${type}/*.svg`)))
.slice(0, limit) .slice(0, limit)
.sort() .sort()
.map(i => { .map((i) => {
const { data, content } = parseMatter(i), const { data, content } = parseMatter(i),
name = basename(i, '.svg') name = basename(i, '.svg');
return { return {
name, name,
@@ -80,20 +84,20 @@ export const getAllIcons = (withContent = false, withObject = false) => {
version: data.version || '', version: data.version || '',
unicode: data.unicode || '', unicode: data.unicode || '',
...(withContent ? { content: getSvgContent(content, type, name) } : {}), ...(withContent ? { content: getSvgContent(content, type, name) } : {}),
...(withObject ? { obj: parseSync(content.replace(blankSquare, '')) } : {}) ...(withObject ? { obj: parseSync(content.replace(blankSquare, '')) } : {}),
} };
})
.sort()
}) })
.sort();
});
return icons return icons;
} };
export const getAllIconsMerged = (withContent = false, withObject = false) => { export const getAllIconsMerged = (withContent = false, withObject = false) => {
const allIcons = getAllIcons(true) const allIcons = getAllIcons(true);
const icons = {}; const icons = {};
allIcons.outline.forEach(icon => { allIcons.outline.forEach((icon) => {
icons[icon.name] = { icons[icon.name] = {
name: icon.name, name: icon.name,
category: icon.category || '', category: icon.category || '',
@@ -103,42 +107,41 @@ export const getAllIconsMerged = (withContent = false, withObject = false) => {
version: icon.version || '', version: icon.version || '',
unicode: icon.unicode || '', unicode: icon.unicode || '',
...(withContent ? { content: icon.content } : {}), ...(withContent ? { content: icon.content } : {}),
...(withObject ? { obj: icon.obj } : {}) ...(withObject ? { obj: icon.obj } : {}),
} },
} },
} };
}) });
allIcons.filled.forEach(icon => { allIcons.filled.forEach((icon) => {
if (icons[icon.name]) { if (icons[icon.name]) {
icons[icon.name].styles.filled = { icons[icon.name].styles.filled = {
version: icon.version || '', version: icon.version || '',
unicode: icon.unicode || '', unicode: icon.unicode || '',
...(withContent ? { content: icon.content } : {}), ...(withContent ? { content: icon.content } : {}),
...(withObject ? { obj: icon.obj } : {}) ...(withObject ? { obj: icon.obj } : {}),
};
} }
} });
})
return icons; return icons;
} };
export const getArgvs = () => { export const getArgvs = () => {
return minimist(process.argv.slice(2)) return minimist(process.argv.slice(2));
} };
export const getPackageDir = (packageName) => { export const getPackageDir = (packageName) => {
return `${PACKAGES_DIR}/${packageName}` return `${PACKAGES_DIR}/${packageName}`;
} };
/** /**
* Return project package.json * Return project package.json
* @returns {any} * @returns {any}
*/ */
export const getPackageJson = () => { export const getPackageJson = () => {
return JSON.parse(fs.readFileSync(resolve(HOME_DIR, 'package.json'), 'utf-8')) return JSON.parse(fs.readFileSync(resolve(HOME_DIR, 'package.json'), 'utf-8'));
} };
/** /**
* Reads SVGs from directory * Reads SVGs from directory
@@ -147,17 +150,17 @@ export const getPackageJson = () => {
* @returns {string[]} * @returns {string[]}
*/ */
export const readSvgDirectory = (directory) => { export const readSvgDirectory = (directory) => {
return fs.readdirSync(directory).filter((file) => path.extname(file) === '.svg') return fs.readdirSync(directory).filter((file) => path.extname(file) === '.svg');
} };
export const getAliases = (groupped = false) => { export const getAliases = (groupped = false) => {
const allAliases = JSON.parse(fs.readFileSync(resolve(HOME_DIR, 'aliases.json'), 'utf-8')); const allAliases = JSON.parse(fs.readFileSync(resolve(HOME_DIR, 'aliases.json'), 'utf-8'));
const allIcons = getAllIcons() const allIcons = getAllIcons();
if (groupped) { if (groupped) {
let aliases = []; let aliases = [];
types.forEach(type => { types.forEach((type) => {
const icons = allIcons[type].map(i => i.name); const icons = allIcons[type].map((i) => i.name);
aliases[type] = {}; aliases[type] = {};
@@ -168,22 +171,23 @@ export const getAliases = (groupped = false) => {
} }
}); });
return aliases return aliases;
} else { } else {
let aliases = []; let aliases = [];
types.forEach(type => { types.forEach((type) => {
const icons = allIcons[type].map(i => i.name); const icons = allIcons[type].map((i) => i.name);
for (const [key, value] of Object.entries(allAliases[type])) { for (const [key, value] of Object.entries(allAliases[type])) {
if (icons.includes(value)) { if (icons.includes(value)) {
aliases[`${key}${type !== 'outline' ? `-${type}` : ''}`] = `${value}${type !== 'outline' ? `-${type}` : ''}`; aliases[`${key}${type !== 'outline' ? `-${type}` : ''}`] =
`${value}${type !== 'outline' ? `-${type}` : ''}`;
} }
} }
}); });
return aliases return aliases;
}
} }
};
/** /**
* Read SVG * Read SVG
@@ -193,8 +197,8 @@ export const getAliases = (groupped = false) => {
* @returns {string} * @returns {string}
*/ */
export const readSvg = (fileName, directory) => { export const readSvg = (fileName, directory) => {
return fs.readFileSync(path.join(directory, fileName), 'utf-8') return fs.readFileSync(path.join(directory, fileName), 'utf-8');
} };
/** /**
* Create directory if not exists * Create directory if not exists
@@ -212,8 +216,8 @@ export const createDirectory = (dir) => {
* @returns {string} * @returns {string}
*/ */
export const getSvgName = (fileName) => { export const getSvgName = (fileName) => {
return path.basename(fileName, '.svg') return path.basename(fileName, '.svg');
} };
/** /**
* Convert string to CamelCase * Convert string to CamelCase
@@ -221,48 +225,53 @@ export const getSvgName = (fileName) => {
* @returns {*} * @returns {*}
*/ */
export const toCamelCase = (string) => { export const toCamelCase = (string) => {
return string.replace(/^([A-Z])|[\s-_]+(\w)/g, (match, p1, p2) => p2 ? p2.toUpperCase() : p1.toLowerCase()) return string.replace(/^([A-Z])|[\s-_]+(\w)/g, (match, p1, p2) =>
} p2 ? p2.toUpperCase() : p1.toLowerCase(),
);
};
export const toPascalCase = (string) => { export const toPascalCase = (string) => {
const camelCase = toCamelCase(string); const camelCase = toCamelCase(string);
return camelCase.charAt(0).toUpperCase() + camelCase.slice(1); return camelCase.charAt(0).toUpperCase() + camelCase.slice(1);
} };
export const addFloats = function (n1, n2) { export const addFloats = function (n1, n2) {
return Math.round((parseFloat(n1) + parseFloat(n2)) * 1000) / 1000 return Math.round((parseFloat(n1) + parseFloat(n2)) * 1000) / 1000;
} };
export const optimizePath = function (path) { export const optimizePath = function (path) {
let transformed = svgpath(path).rel().round(3).toString() let transformed = svgpath(path).rel().round(3).toString();
return svgParse(transformed).map(function (a) { return svgParse(transformed)
return a.join(' ') .map(function (a) {
}).join('') return a.join(' ');
} })
.join('');
};
export const optimizeSVG = (data) => { export const optimizeSVG = (data) => {
return optimize(data, { return optimize(data, {
js2svg: { js2svg: {
indent: 2, indent: 2,
pretty: true pretty: true,
}, },
plugins: [ plugins: [
{ {
name: 'preset-default', name: 'preset-default',
params: { params: {
overrides: { overrides: {
mergePaths: false mergePaths: false,
} },
} },
}] },
}).data ],
} }).data;
};
export function buildIconsObject(svgFiles, getSvg) { export function buildIconsObject(svgFiles, getSvg) {
return svgFiles return svgFiles
.map(svgFile => { .map((svgFile) => {
const name = path.basename(svgFile, '.svg'); const name = path.basename(svgFile, '.svg');
const svg = getSvg(svgFile); const svg = getSvg(svgFile);
const contents = getSvgContents(svg); const contents = getSvgContents(svg);
@@ -281,251 +290,255 @@ function getSvgContents(svg) {
export const asyncForEach = async (array, callback) => { export const asyncForEach = async (array, callback) => {
for (let index = 0; index < array.length; index++) { for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array) await callback(array[index], index, array);
}
} }
};
export const createScreenshot = (filePath, retina = true) => { export const createScreenshot = (filePath, retina = true) => {
cp.execSync(`rsvg-convert -x 2 -y 2 ${filePath} > ${filePath.replace('.svg', '.png')}`) cp.execSync(`rsvg-convert -x 2 -y 2 ${filePath} > ${filePath.replace('.svg', '.png')}`);
if (retina) { if (retina) {
cp.execSync(`rsvg-convert -x 4 -y 4 ${filePath} > ${filePath.replace('.svg', '@2x.png')}`) cp.execSync(`rsvg-convert -x 4 -y 4 ${filePath} > ${filePath.replace('.svg', '@2x.png')}`);
}
} }
};
export const createSvgSymbol = (svg, name, stroke) => { export const createSvgSymbol = (svg, name, stroke) => {
return svg.replace('<svg', `<symbol id="${name}"`) return svg
.replace('<svg', `<symbol id="${name}"`)
.replace(' width="24" height="24"', '') .replace(' width="24" height="24"', '')
.replace(' stroke-width="2"', ` stroke-width="${stroke}"`) .replace(' stroke-width="2"', ` stroke-width="${stroke}"`)
.replace('</svg>', '</symbol>') .replace('</svg>', '</symbol>')
.replace(/\n\s+/g, ' ') .replace(/\n\s+/g, ' ')
.replace(/<!--(.*?)-->/gis, '') .replace(/<!--(.*?)-->/gis, '')
.trim() .trim();
} };
export const generateIconsPreview = async function (files, destFile, { export const generateIconsPreview = async function (
files,
destFile,
{
columnsCount = 19, columnsCount = 19,
paddingOuter = 7, paddingOuter = 7,
color = '#354052', color = '#354052',
background = '#fff', background = '#fff',
png = true, png = true,
stroke = 2, stroke = 2,
retina = true retina = true,
} = {}) { } = {},
) {
const padding = 20, const padding = 20,
iconSize = 24 iconSize = 24;
const iconsCount = files.length, const iconsCount = files.length,
rowsCount = Math.ceil(iconsCount / columnsCount), rowsCount = Math.ceil(iconsCount / columnsCount),
width = columnsCount * (iconSize + padding) + 2 * paddingOuter - padding, width = columnsCount * (iconSize + padding) + 2 * paddingOuter - padding,
height = rowsCount * (iconSize + padding) + 2 * paddingOuter - padding height = rowsCount * (iconSize + padding) + 2 * paddingOuter - padding;
let svgContentSymbols = '', let svgContentSymbols = '',
svgContentIcons = '', svgContentIcons = '',
x = paddingOuter, x = paddingOuter,
y = paddingOuter y = paddingOuter;
files.forEach(function (file, i) { files.forEach(function (file, i) {
const name = file.replace(/^(.*)\/([^\/]+)\/([^.]+).svg$/g, '$2-$3'); const name = file.replace(/^(.*)\/([^\/]+)\/([^.]+).svg$/g, '$2-$3');
let svgFile = fs.readFileSync(file), let svgFile = fs.readFileSync(file),
svgFileContent = svgFile.toString() svgFileContent = svgFile.toString();
svgFileContent = createSvgSymbol(svgFileContent, name, stroke) svgFileContent = createSvgSymbol(svgFileContent, name, stroke);
svgContentSymbols += `\t${svgFileContent}\n` svgContentSymbols += `\t${svgFileContent}\n`;
svgContentIcons += `\t<use xlink:href="#${name}" x="${x}" y="${y}" width="${iconSize}" height="${iconSize}" />\n` svgContentIcons += `\t<use xlink:href="#${name}" x="${x}" y="${y}" width="${iconSize}" height="${iconSize}" />\n`;
x += padding + iconSize x += padding + iconSize;
if (i % columnsCount === columnsCount - 1) { if (i % columnsCount === columnsCount - 1) {
x = paddingOuter x = paddingOuter;
y += padding + iconSize y += padding + iconSize;
} }
}) });
const svgContent = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 ${width} ${height}" width="${width}" height="${height}" style="color: ${color}"><rect x="0" y="0" width="${width}" height="${height}" fill="${background}"></rect>\n${svgContentSymbols}\n${svgContentIcons}\n</svg>` const svgContent = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 ${width} ${height}" width="${width}" height="${height}" style="color: ${color}"><rect x="0" y="0" width="${width}" height="${height}" fill="${background}"></rect>\n${svgContentSymbols}\n${svgContentIcons}\n</svg>`;
console.log(destFile) console.log(destFile);
fs.writeFileSync(destFile, svgContent) fs.writeFileSync(destFile, svgContent);
if (png) { if (png) {
await createScreenshot(destFile, retina) await createScreenshot(destFile, retina);
} }
} };
export const printChangelog = function (newIcons, modifiedIcons, renamedIcons, pretty = false) { export const printChangelog = function (newIcons, modifiedIcons, renamedIcons, pretty = false) {
if (newIcons.length > 0) { if (newIcons.length > 0) {
if (pretty) { if (pretty) {
console.log(`### ${newIcons.length} new icon${newIcons.length > 1 ? 's' : ''}:\n`) console.log(`### ${newIcons.length} new icon${newIcons.length > 1 ? 's' : ''}:\n`);
newIcons.forEach(function (icon, i) { newIcons.forEach(function (icon, i) {
console.log(`- \`${icon}\``) console.log(`- \`${icon}\``);
}) });
} else { } else {
let str = '' let str = '';
str += `${newIcons.length} new icon${newIcons.length > 1 ? 's' : ''}: ` str += `${newIcons.length} new icon${newIcons.length > 1 ? 's' : ''}: `;
newIcons.forEach(function (icon, i) { newIcons.forEach(function (icon, i) {
str += `\`${icon}\`` str += `\`${icon}\``;
if ((i + 1) <= newIcons.length - 1) { if (i + 1 <= newIcons.length - 1) {
str += ', ' str += ', ';
} }
}) });
console.log(str) console.log(str);
} }
console.log('') console.log('');
} }
if (modifiedIcons.length > 0) { if (modifiedIcons.length > 0) {
let str = '' let str = '';
str += `Fixed icon${modifiedIcons.length > 1 ? 's' : ''}: ` str += `Fixed icon${modifiedIcons.length > 1 ? 's' : ''}: `;
modifiedIcons.forEach(function (icon, i) { modifiedIcons.forEach(function (icon, i) {
str += `\`${icon}\`` str += `\`${icon}\``;
if ((i + 1) <= modifiedIcons.length - 1) { if (i + 1 <= modifiedIcons.length - 1) {
str += ', ' str += ', ';
} }
}) });
console.log(str) console.log(str);
console.log('') console.log('');
} }
if (renamedIcons.length > 0) { if (renamedIcons.length > 0) {
console.log(`Renamed icons: `) console.log(`Renamed icons: `);
renamedIcons.forEach(function (icon, i) { renamedIcons.forEach(function (icon, i) {
console.log(`- \`${icon[0]}\` renamed to \`${icon[1]}\``) console.log(`- \`${icon[0]}\` renamed to \`${icon[1]}\``);
}) });
} }
} };
export const getCompileOptions = () => { export const getCompileOptions = () => {
const compileOptions = { const compileOptions = {
includeIcons: [], includeIcons: [],
strokeWidth: null, strokeWidth: null,
fontForge: 'fontforge' fontForge: 'fontforge',
} };
if (fs.existsSync('../compile-options.json')) { if (fs.existsSync('../compile-options.json')) {
try { try {
const tempOptions = JSON.parse(fs.readFileSync('../compile-options.json').toString()) const tempOptions = JSON.parse(fs.readFileSync('../compile-options.json').toString());
if (typeof tempOptions !== 'object') { if (typeof tempOptions !== 'object') {
throw 'Compile options file does not contain an json object' throw 'Compile options file does not contain an json object';
} }
if (typeof tempOptions.includeIcons !== 'undefined') { if (typeof tempOptions.includeIcons !== 'undefined') {
if (!Array.isArray(tempOptions.includeIcons)) { if (!Array.isArray(tempOptions.includeIcons)) {
throw 'property inludeIcons is not an array' throw 'property inludeIcons is not an array';
} }
compileOptions.includeIcons = tempOptions.includeIcons compileOptions.includeIcons = tempOptions.includeIcons;
} }
if (typeof tempOptions.includeCategories !== 'undefined') { if (typeof tempOptions.includeCategories !== 'undefined') {
if (typeof tempOptions.includeCategories === 'string') { if (typeof tempOptions.includeCategories === 'string') {
tempOptions.includeCategories = tempOptions.includeCategories.split(' ') tempOptions.includeCategories = tempOptions.includeCategories.split(' ');
} }
if (!Array.isArray(tempOptions.includeCategories)) { if (!Array.isArray(tempOptions.includeCategories)) {
throw 'property includeCategories is not an array or string' throw 'property includeCategories is not an array or string';
} }
const tags = Object.entries(require('./tags.json')) const tags = Object.entries(require('./tags.json'));
tempOptions.includeCategories.forEach(function (category) { tempOptions.includeCategories.forEach(function (category) {
category = category.charAt(0).toUpperCase() + category.slice(1) category = category.charAt(0).toUpperCase() + category.slice(1);
for (const [icon, data] of tags) { for (const [icon, data] of tags) {
if (data.category === category && compileOptions.includeIcons.indexOf(icon) === -1) { if (data.category === category && compileOptions.includeIcons.indexOf(icon) === -1) {
compileOptions.includeIcons.push(icon) compileOptions.includeIcons.push(icon);
} }
} }
}) });
} }
if (typeof tempOptions.excludeIcons !== 'undefined') { if (typeof tempOptions.excludeIcons !== 'undefined') {
if (!Array.isArray(tempOptions.excludeIcons)) { if (!Array.isArray(tempOptions.excludeIcons)) {
throw 'property excludeIcons is not an array' throw 'property excludeIcons is not an array';
} }
compileOptions.includeIcons = compileOptions.includeIcons.filter(function (icon) { compileOptions.includeIcons = compileOptions.includeIcons.filter(function (icon) {
return tempOptions.excludeIcons.indexOf(icon) === -1 return tempOptions.excludeIcons.indexOf(icon) === -1;
}) });
} }
if (typeof tempOptions.excludeOffIcons !== 'undefined' && tempOptions.excludeOffIcons) { if (typeof tempOptions.excludeOffIcons !== 'undefined' && tempOptions.excludeOffIcons) {
// Exclude `*-off` icons // Exclude `*-off` icons
compileOptions.includeIcons = compileOptions.includeIcons.filter(function (icon) { compileOptions.includeIcons = compileOptions.includeIcons.filter(function (icon) {
return !icon.endsWith('-off') return !icon.endsWith('-off');
}) });
} }
if (typeof tempOptions.strokeWidth !== 'undefined') { if (typeof tempOptions.strokeWidth !== 'undefined') {
if (typeof tempOptions.strokeWidth !== 'string' && typeof tempOptions.strokeWidth !== 'number') { if (
throw 'property strokeWidth is not a string or number' typeof tempOptions.strokeWidth !== 'string' &&
typeof tempOptions.strokeWidth !== 'number'
) {
throw 'property strokeWidth is not a string or number';
} }
compileOptions.strokeWidth = tempOptions.strokeWidth.toString() compileOptions.strokeWidth = tempOptions.strokeWidth.toString();
} }
if (typeof tempOptions.fontForge !== 'undefined') { if (typeof tempOptions.fontForge !== 'undefined') {
if (typeof tempOptions.fontForge !== 'string') { if (typeof tempOptions.fontForge !== 'string') {
throw 'property fontForge is not a string' throw 'property fontForge is not a string';
} }
compileOptions.fontForge = tempOptions.fontForge compileOptions.fontForge = tempOptions.fontForge;
} }
} catch (error) { } catch (error) {
throw `Error reading compile-options.json: ${error}` throw `Error reading compile-options.json: ${error}`;
} }
} }
return compileOptions return compileOptions;
} };
export const convertIconsToImages = async (dir, extension, size = 240) => { export const convertIconsToImages = async (dir, extension, size = 240) => {
const icons = getAllIcons() const icons = getAllIcons();
await asyncForEach(Object.entries(icons), async function ([type, svgFiles]) { await asyncForEach(Object.entries(icons), async function ([type, svgFiles]) {
fs.mkdirSync(path.join(dir, `./${type}`), { recursive: true }) fs.mkdirSync(path.join(dir, `./${type}`), { recursive: true });
await asyncForEach(svgFiles, async function (file, i) { await asyncForEach(svgFiles, async function (file, i) {
const distPath = path.join(dir, `./${type}/${file.name}.${extension}`) const distPath = path.join(dir, `./${type}/${file.name}.${extension}`);
process.stdout.write(`Building \`icons/${extension}\` ${type} ${i}/${svgFiles.length}: ${file.name.padEnd(42)}\r`) process.stdout.write(
`Building \`icons/${extension}\` ${type} ${i}/${svgFiles.length}: ${file.name.padEnd(42)}\r`,
);
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
exec(`rsvg-convert -f ${extension} -h ${size} ${file.path} > ${distPath}`, (error) => { exec(`rsvg-convert -f ${extension} -h ${size} ${file.path} > ${distPath}`, (error) => {
error ? reject() : resolve() error ? reject() : resolve();
}) });
}) });
}) });
}) });
} };
export const getMaxUnicode = () => { export const getMaxUnicode = () => {
const files = globSync(path.join(ICONS_SRC_DIR, '**/*.svg')) const files = globSync(path.join(ICONS_SRC_DIR, '**/*.svg'));
let maxUnicode = 0 let maxUnicode = 0;
files.forEach(function (file) { files.forEach(function (file) {
const svgFile = fs.readFileSync(file).toString() const svgFile = fs.readFileSync(file).toString();
svgFile.replace(/unicode: "([a-f0-9.]+)"/i, function (m, unicode) { svgFile.replace(/unicode: "([a-f0-9.]+)"/i, function (m, unicode) {
const newUnicode = parseInt(unicode, 16) const newUnicode = parseInt(unicode, 16);
if (newUnicode) { if (newUnicode) {
maxUnicode = Math.max(maxUnicode, newUnicode) maxUnicode = Math.max(maxUnicode, newUnicode);
} }
}) });
}) });
console.log(`Max unicode: ${maxUnicode}`) console.log(`Max unicode: ${maxUnicode}`);
return maxUnicode return maxUnicode;
} };

View File

@@ -76,14 +76,15 @@
"rollup-plugin-license": "^3.2.0", "rollup-plugin-license": "^3.2.0",
"rollup-plugin-peer-deps-external": "2.2.4", "rollup-plugin-peer-deps-external": "2.2.4",
"rollup-plugin-visualizer": "^5.12.0", "rollup-plugin-visualizer": "^5.12.0",
"slash": "^5.1.0",
"svg-outline-stroke": "1.3.1", "svg-outline-stroke": "1.3.1",
"svgo": "^3.2.0", "svgo": "^3.2.0",
"svgpath": "^2.6.0", "svgpath": "^2.6.0",
"svgson": "^5.3.1", "svgson": "^5.3.1",
"turbo": "^1.12.4", "turbo": "^1.12.4",
"typescript": "^5.3.3", "typescript": "^5.3.3",
"vitest": "^1.3.1", "vite": "^5.1.4",
"vite": "^5.1.4" "vitest": "^1.3.1"
}, },
"release-it": { "release-it": {
"plugins": { "plugins": {

View File

@@ -1,2 +1,3 @@
src/aliases.ts src/aliases.ts
src/icons-list.ts src/icons-list.ts
src/dynamic-imports.ts

View File

@@ -1,22 +1,20 @@
#!/usr/bin/env node #!/usr/bin/env node
import { buildJsIcons, buildIconsList } from '../../.build/build-icons.mjs' import {
buildJsIcons,
buildIconsList,
buildIconsDynamicImport,
} from '../../.build/build-icons.mjs';
const componentTemplate = ({ const componentTemplate = ({ type, name, namePascal, children }) => `\
type,
name,
namePascal,
children
}) => `\
import createReactComponent from '../createReactComponent'; import createReactComponent from '../createReactComponent';
export default createReactComponent('${type}', '${name}', '${namePascal}', ${JSON.stringify(children)});`; export default createReactComponent('${type}', '${name}', '${namePascal}', ${JSON.stringify(children)});`;
const indexItemTemplate = ({ const indexItemTemplate = ({ name, namePascal }) =>
name, `export { default as ${namePascal} } from './${namePascal}';`;
namePascal
}) => `export { default as ${namePascal} } from './${namePascal}';`
const aliasTemplate = ({ fromPascal, toPascal }) => `export { default as Icon${fromPascal} } from './icons/Icon${toPascal}';\n` const aliasTemplate = ({ fromPascal, toPascal }) =>
`export { default as Icon${fromPascal} } from './icons/Icon${toPascal}';\n`;
buildJsIcons({ buildJsIcons({
name: 'icons-react', name: 'icons-react',
@@ -25,7 +23,9 @@ buildJsIcons({
aliasTemplate, aliasTemplate,
indexFile: 'index.ts', indexFile: 'index.ts',
pascalCase: true, pascalCase: true,
extension: 'ts' extension: 'ts',
}) });
buildIconsList('icons-react') buildIconsList('icons-react');
buildIconsDynamicImport('icons-react');

View File

@@ -1,6 +1,7 @@
export * from './icons/index'; export * from './icons/index';
export * as icons from './icons/index'; export * as icons from './icons/index';
export * as iconsList from './icons-list'; export * as iconsList from './icons-list';
export * as dynamicImports from './dynamic-imports';
export * from './aliases'; export * from './aliases';
export { default as createReactComponent } from './createReactComponent'; export { default as createReactComponent } from './createReactComponent';

3
pnpm-lock.yaml generated
View File

@@ -93,6 +93,9 @@ importers:
rollup-plugin-visualizer: rollup-plugin-visualizer:
specifier: ^5.12.0 specifier: ^5.12.0
version: 5.12.0(rollup@4.12.0) version: 5.12.0(rollup@4.12.0)
slash:
specifier: ^5.1.0
version: 5.1.0
svg-outline-stroke: svg-outline-stroke:
specifier: 1.3.1 specifier: 1.3.1
version: 1.3.1 version: 1.3.1