Add support for aliases (#899)

* extract workflow

* Add aliases build

* Setup types building for aliases

* Add types generation for aliases

* Finish React Aliases

* Finish aliases for lucide-react

* setup aliases preact

* Fix aliases in preact

* Add aliases preact

* Add aliases lucide-react-native

* Fix solid js build

* update lock file

* Improve solid for solid start

* update import

* update import

* lucide solid fix types generation

* Fix lucide sold

* Fix svelte aliases

* update lockfile

* Fix imports

* Fix solid js issues

* Add aliases to the vue packages

* Fix lucide react native

* Test alpha versions lucide-vue, lucide-vue-next, lucide-svelte
This commit is contained in:
Eric Fennis
2023-01-17 08:04:34 +01:00
committed by GitHub
parent 621cf6b290
commit 8fd8007c96
70 changed files with 7181 additions and 501 deletions

View File

@@ -0,0 +1,48 @@
import path from 'path';
import { toPascalCase, resetFile, appendFile } from '../../../scripts/helpers.mjs';
const getImportString = (componentName, iconName, aliasImportFileExtension = '') =>
`export { default as ${componentName} } from './icons/${iconName}${aliasImportFileExtension}';\n`;
export default function generateAliasesFile({
iconNodes,
outputDirectory,
fileExtension,
aliases,
aliasImportFileExtension,
showLog = true,
}) {
const fileName = path.basename(`aliases${fileExtension}`);
const icons = Object.keys(iconNodes);
// Reset file
resetFile(fileName, outputDirectory);
// Generate Import for Icon VNodes
icons.forEach((iconName) => {
const componentName = toPascalCase(iconName);
const iconAliases = aliases[iconName]?.aliases;
let importString = `// ${componentName} aliases\n`;
importString += getImportString(`${componentName}Icon`, iconName, aliasImportFileExtension);
importString += getImportString(`Lucide${componentName}`, iconName, aliasImportFileExtension);
if (iconAliases != null && Array.isArray(iconAliases)) {
iconAliases.forEach((alias) => {
const componentNameAlias = toPascalCase(alias);
importString += getImportString(componentNameAlias, iconName, aliasImportFileExtension);
});
}
importString += '\n';
appendFile(importString, fileName, outputDirectory);
});
appendFile('\n', fileName, outputDirectory);
if (showLog) {
console.log(`Successfully generated ${fileName} file`);
}
}

View File

@@ -0,0 +1,23 @@
import path from 'path';
import { toPascalCase, resetFile, appendFile } from '../../../scripts/helpers.mjs';
export default (inputEntry, outputDirectory, iconNodes, iconFileExtension = '') => {
const fileName = path.basename(inputEntry);
// Reset file
resetFile(fileName, outputDirectory);
const icons = Object.keys(iconNodes);
// Generate Import for Icon VNodes
icons.forEach((iconName) => {
const componentName = toPascalCase(iconName);
const importString = `export { default as ${componentName} } from './${iconName}${iconFileExtension}';\n`;
appendFile(importString, fileName, outputDirectory);
});
appendFile('\n', fileName, outputDirectory);
console.log(`Successfully generated ${fileName} file`);
};

View File

@@ -0,0 +1,49 @@
import fs from 'fs';
import path from 'path';
import prettier from 'prettier';
import { toPascalCase } from '../../../scripts/helpers.mjs';
export default ({
iconNodes,
outputDirectory,
template,
showLog = true,
iconFileExtension = '.js',
pretty = true,
}) => {
const icons = Object.keys(iconNodes);
const iconsDistDirectory = path.join(outputDirectory, `icons`);
if (!fs.existsSync(iconsDistDirectory)) {
fs.mkdirSync(iconsDistDirectory);
}
const writeIconFiles = icons.map(async (iconName) => {
const location = path.join(iconsDistDirectory, `${iconName}${iconFileExtension}`);
const componentName = toPascalCase(iconName);
let { children } = iconNodes[iconName];
children = children.map(({ name, attributes }) => [name, attributes]);
const elementTemplate = template({ componentName, iconName, children });
const output = pretty
? prettier.format(elementTemplate, {
singleQuote: true,
trailingComma: 'all',
parser: 'babel',
})
: elementTemplate;
await fs.promises.writeFile(location, output, 'utf-8');
});
Promise.all(writeIconFiles)
.then(() => {
if (showLog) {
console.log('Successfully built', icons.length, 'icons.');
}
})
.catch((error) => {
throw new Error(`Something went wrong generating icon files,\n ${error}`);
});
};

View File

@@ -0,0 +1,2 @@
// eslint-disable-next-line import/prefer-default-export
export { default as getAliases } from './utils/getAliases.mjs';

85
tools/build-icons/main.mjs Executable file
View File

@@ -0,0 +1,85 @@
#!/usr/bin/env node
import fs from 'fs';
import path from 'path';
import getArgumentOptions from 'minimist';
import renderIconsObject from './render/renderIconsObject.mjs';
import generateIconFiles from './building/generateIconFiles.mjs';
import generateExportsFile from './building/generateExportsFile.mjs';
import { readSvgDirectory, getCurrentDirPath } from '../../scripts/helpers.mjs';
import generateAliasesFile from './building/generateAliasesFile.mjs';
import getAliases from './utils/getAliases.mjs';
const cliArguments = getArgumentOptions(process.argv.slice(2));
const currentDir = getCurrentDirPath(import.meta.url);
const ICONS_DIR = path.resolve(currentDir, '../../icons');
const OUTPUT_DIR = path.resolve(process.cwd(), cliArguments.output || '../build');
if (!fs.existsSync(OUTPUT_DIR)) {
fs.mkdirSync(OUTPUT_DIR);
}
const {
renderUniqueKey = false,
templateSrc,
silent = false,
iconFileExtension = '.js',
importImportFileExtension = '',
exportFileName = 'index.js',
withAliases = false,
aliasesFileExtension = '.js',
aliasImportFileExtension = '',
pretty = true,
} = cliArguments;
async function buildIcons() {
if (templateSrc == null) {
throw new Error('No `templateSrc` argument given.');
}
const svgFiles = readSvgDirectory(ICONS_DIR);
const icons = renderIconsObject(svgFiles, ICONS_DIR, renderUniqueKey);
const { default: iconFileTemplate } = await import(path.resolve(process.cwd(), templateSrc));
// Generates iconsNodes files for each icon
generateIconFiles({
iconNodes: icons,
outputDirectory: OUTPUT_DIR,
template: iconFileTemplate,
showLog: !silent,
iconFileExtension,
pretty: JSON.parse(pretty),
});
if (withAliases) {
const aliases = await getAliases(ICONS_DIR);
generateAliasesFile({
iconNodes: icons,
aliases,
outputDirectory: OUTPUT_DIR,
fileExtension: aliasesFileExtension,
aliasImportFileExtension,
showLog: !silent,
});
}
// Generates entry files for the compiler filled with icons exports
generateExportsFile(
path.join(OUTPUT_DIR, 'icons', exportFileName),
path.join(OUTPUT_DIR, 'icons'),
icons,
importImportFileExtension,
);
}
try {
buildIcons();
} catch (error) {
console.error(error);
}

View File

@@ -0,0 +1,27 @@
{
"name": "@lucide/build-icons",
"private": true,
"version": "1.0.0",
"description": "",
"main": "index.mjs",
"type":"module",
"scripts": {
"start": "node ./main.mjs"
},
"bin": {
"build-icons": "./main.mjs"
},
"engines": {
"node": ">= 16"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"minimist": "^1.2.7",
"node-fetch": "^3.2.10",
"prettier": "2.7.1",
"svgo": "^3.0.0",
"svgson": "^5.2.1"
}
}

View File

@@ -0,0 +1,11 @@
{
"xmlns": "http://www.w3.org/2000/svg",
"width": 24,
"height": 24,
"viewBox": "0 0 24 24",
"fill": "none",
"stroke": "currentColor",
"stroke-width": 2,
"stroke-linecap": "round",
"stroke-linejoin": "round"
}

View File

@@ -0,0 +1,39 @@
import { basename } from 'path';
import { parseSync } from 'svgson';
import { generateHashedKey, readSvg, hasDuplicatedChildren } from '../../../scripts/helpers.mjs';
/**
* Build an object in the format: `{ <name>: <contents> }`.
* @param {string[]} svgFiles - A list of filenames.
* @param {Function} getSvg - A function that returns the contents of an SVG file given a filename.
* @returns {Object}
*/
export default (svgFiles, iconsDirectory, renderUniqueKey = false) =>
svgFiles
.map((svgFile) => {
const name = basename(svgFile, '.svg');
const svg = readSvg(svgFile, iconsDirectory);
const contents = parseSync(svg);
if (!(contents.children && contents.children.length)) {
throw new Error(`${name}.svg has no children!`);
}
if (hasDuplicatedChildren(contents.children)) {
throw new Error(`Duplicated children in ${name}.svg`);
}
if (renderUniqueKey) {
contents.children = contents.children.map((child) => {
child.attributes.key = generateHashedKey(child);
return child;
});
}
return { name, contents };
})
.reduce((icons, icon) => {
icons[icon.name] = icon.contents;
return icons;
}, {});

View File

@@ -0,0 +1,16 @@
import path from 'path';
import { readSvgDirectory } from '../../../scripts/helpers.mjs';
async function getAliases(iconDirectory) {
const iconJsons = readSvgDirectory(iconDirectory, '.json');
const aliasesEntries = await Promise.all(
iconJsons.map(async (jsonFile) => {
const file = await import( path.join(iconDirectory, jsonFile), { assert: { type: 'json' } });
return [path.basename(jsonFile, '.json'), file.default]
})
)
return Object.fromEntries(aliasesEntries);
}
export default getAliases

View File

@@ -10,6 +10,7 @@
"type": "module",
"dependencies": {
"@atomico/rollup-plugin-sizes": "^1.1.4",
"@rollup/plugin-replace": "^5.0.1",
"esbuild": "^0.15.16",
"rollup": "^3.5.1",
"rollup-plugin-esbuild": "^4.10.2",

View File

@@ -1,13 +1,15 @@
/* eslint-disable import/no-extraneous-dependencies */
import bundleSize from '@atomico/rollup-plugin-sizes';
import { visualizer } from 'rollup-plugin-visualizer';
import bundleSize from '@atomico/rollup-plugin-sizes';
import replace from '@rollup/plugin-replace';
import license from 'rollup-plugin-license';
import esbuild from 'rollup-plugin-esbuild';
const plugins = (pkg, minify) =>
const plugins = (pkg, minify, esbuildOptions = {}) =>
[
esbuild({
minify,
...esbuildOptions,
}),
license({
banner: `${pkg.name} v${pkg.version} - ${pkg.license}`,
@@ -19,4 +21,6 @@ const plugins = (pkg, minify) =>
}),
].filter(Boolean);
export { bundleSize, license, visualizer, replace };
export default plugins;