iconoir/bin/build/targets/react/index.js
2023-10-29 00:33:17 +02:00

249 lines
6.2 KiB
JavaScript

import * as svgr from '@svgr/core';
import * as esbuild from 'esbuild';
import fs from 'node:fs/promises';
import path from 'node:path';
import {
generateExport,
generateImport,
toImportPath,
} from '../../lib/import-export.js';
import { getDts } from '../../lib/ts.js';
import iconoirContextTemplate, {
exports as iconoirContextExports,
} from './resources/context-template.js';
import { getTemplate as getIconTemplate } from './resources/icon-template.js';
import { nativeSvgrOptions, svgrOptions } from './resources/svgr-options.js';
const outDir = 'dist';
const jsTargets = [
{
format: 'cjs',
module: 'commonjs',
dir: '.',
ext: 'js',
dtsExt: 'd.ts',
},
{
format: 'esm',
module: 'esnext',
dir: 'esm',
ext: 'mjs',
dtsExt: 'd.mts',
},
];
/** @type {import('esbuild').TransformOptions} */
const defaultEsbuildOptions = { target: 'es6', minify: true };
/** @type {import('typescript').CompilerOptions} */
const defaultTsOptions = {
jsx: 'react',
declaration: true,
emitDeclarationOnly: true,
target: 'es6',
strict: true,
esModuleInterop: true,
skipLibCheck: true,
};
export default async (ctx, target) => {
const localJsTargets = jsTargets.map((jsTarget) => ({
...jsTarget,
iconTemplate: getIconTemplate(target.native, jsTarget.ext),
}));
const promises = [];
const outPath = path.join(target.path, outDir);
// Preparation
// (needs to run in a separate loop, otherwise leads to uncaught exceptions in case of errors in main loop)
for (const jsTarget of localJsTargets) {
jsTarget.path = path.join(outPath, jsTarget.dir);
await fs.mkdir(jsTarget.path, { recursive: true });
const iconoirContext = iconoirContextTemplate(target.native);
jsTarget.iconoirContextPath = path.join(
jsTarget.path,
`IconoirContext.${jsTarget.ext}`,
);
await generateJs(
jsTarget.iconoirContextPath,
iconoirContext,
jsTarget.format,
);
const iconoirContextTsxPath = path.join(
jsTarget.path,
'IconoirContext.tsx',
);
const iconoirContextDtsPath = path.join(
jsTarget.path,
`IconoirContext.${jsTarget.dtsExt}`,
);
await generateDts(
iconoirContextTsxPath,
iconoirContextDtsPath,
iconoirContext,
jsTarget.module,
);
for (const variant of Object.keys(ctx.icons)) {
jsTarget.path = path.join(outPath, jsTarget.dir);
await fs.mkdir(path.join(jsTarget.path, variant), { recursive: true });
}
}
for (const jsTarget of localJsTargets) {
const mainIndex = prepareIndex(jsTarget);
for (const [variant, icons] of Object.entries(ctx.icons)) {
const variantIndex = prepareIndex(jsTarget, variant);
for (const icon of icons) {
const mainIndexComponentName =
variant === ctx.global.defaultVariant
? icon.pascalName
: icon.pascalNameVariant;
const jsPath = path.join(
jsTarget.path,
variant,
`${icon.pascalName}.${jsTarget.ext}`,
);
mainIndex.add(mainIndexComponentName, jsPath);
variantIndex.add(icon.pascalName, jsPath);
const reactComponent = getReactComponent(
icon.path,
target.native,
jsTarget.iconTemplate,
);
// Only run for first icon, type is same and can be reused for all the others
if (!jsTarget.iconDts) {
jsTarget.iconDts = true;
// Virtual input path
const tsxPath = path.join(jsTarget.path, variant, 'icon.tsx');
const dtsPath = path.join(jsTarget.path, `icon.${jsTarget.dtsExt}`);
const iconDts = generateDts(
tsxPath,
dtsPath,
reactComponent,
jsTarget.module,
);
promises.push(iconDts);
}
const iconJs = generateJs(jsPath, reactComponent, jsTarget.format);
promises.push(iconJs);
}
promises.push(variantIndex.generate());
}
promises.push(mainIndex.generate());
}
return Promise.all(promises);
};
async function getReactComponent(iconPath, native, template) {
const iconContent = await fs.readFile(iconPath, 'utf8');
const options = native ? nativeSvgrOptions : svgrOptions;
options.template = template;
return svgr.transform(iconContent, options);
}
async function generateDts(inputPath, outputPath, input, module) {
const dts = getDts(inputPath, await input, {
...defaultTsOptions,
module,
});
return fs.writeFile(outputPath, dts);
}
async function generateJs(outputPath, input, format) {
const { code } = await esbuild.transform(await input, {
...defaultEsbuildOptions,
loader: 'tsx',
format,
});
return fs.writeFile(outputPath, code);
}
function prepareIndex(jsTarget, variant) {
const outputPath = path.join(jsTarget.path, variant ?? '');
const iconoirContextPath = toImportPath(
path.relative(outputPath, jsTarget.iconoirContextPath),
);
const iconoirContext = generateExport(
iconoirContextExports,
iconoirContextPath,
);
const content = [iconoirContext];
const iconJsPath = toImportPath(
path.relative(outputPath, path.join(jsTarget.path, `icon.${jsTarget.ext}`)),
);
const iconDtsImport = generateImport('Icon', iconJsPath);
const dtsContent = [iconoirContext, iconDtsImport, 'type I = typeof Icon;'];
function add(name, iconPath) {
const iconImportPath = toImportPath(path.relative(outputPath, iconPath));
content.push(generateExport(`default as ${name}`, iconImportPath));
dtsContent.push(`export declare const ${name}: I;`);
}
function generate() {
const indexJs = generateIndexJs(
outputPath,
content,
jsTarget.format,
jsTarget.ext,
);
const indexDts = generateIndexDts(outputPath, dtsContent, jsTarget.dtsExt);
return Promise.all([indexJs, indexDts]);
}
return { add, generate };
}
async function generateIndexJs(outputDir, content, format, ext) {
const { code } = await esbuild.transform(content.join(''), {
minify: true,
format,
});
return fs.writeFile(path.join(outputDir, `index.${ext}`), code);
}
async function generateIndexDts(outputDir, content, dtsExt) {
return fs.writeFile(path.join(outputDir, `index.${dtsExt}`), content);
}