【generate】如何维护一套icon组件库,直接输出svg为react component

时间:2024-04-04 21:15:59

https://github.com/ant-design/ant-design-web3/pull/761/files
实现了icon-preview(通过jsdoc, 鼠标放在组件上可以看到icon的样式),因为打包方式、产物以及命名上有一些不同,可能需要稍加改造。

这个同步脚本应该后续也用得上,略加改造同步 svg 可以提高后续添加 svg 的效率直接拖入图片就可以转成组件代码,降低cv和命名心智。
在这里插入图片描述

  1. 在package.json中设置命令可以直接执行ts脚本
"icons:generate": "NODE_OPTIONS='--experimental-specifier-resolution=node' node --loader ts-node/esm ./packages/icons/scripts/generate.ts"
  1. 通过 mport * as allIconDefs from '@ant-design/web3-icons' 获取所有组件,使用camelToKebab函数转换需要符合自己命名需求的组件
    在这里插入图片描述
  2. 通过 template 设置render模版
import pkg from 'lodash';
const { template } = pkg;

全部代码

import * as fs from 'fs';
import * as path from 'path';
import { promisify } from 'util';
import * as allIconDefs from '@ant-design/web3-icons';
import pkg from 'lodash';

const __dirname = new URL(import.meta.url).pathname;
const { template } = pkg;

const writeFile = promisify(fs.writeFile);

interface IconDefinition {
  [key: string]: any;
}
interface IconDefinitionWithIdentifier extends IconDefinition {
  svgIdentifier: string;
  svgBase64: string | null;
}

function camelToKebab(camelCaseString: string) {
  return camelCaseString
    .replace(/([a-z\d])([A-Z][a-z\d])|([A-Z]+(?![a-z\d]))/g, '$1$3-$2')
    .toLowerCase();
}

function detectRealPath(_path: string) {
  try {
    return fs.existsSync(_path) ? _path : null;
  } catch (e) {
    return null;
  }
}

function svg2base64(svgPath: string, size = 50) {
  const svg = fs.readFileSync(svgPath, 'utf-8');
  const svgWithStyle = svg.replace(/<svg/, `<svg width="${size}" height="${size}" fill="#cacaca"`);

  const base64 = Buffer.from(svgWithStyle).toString('base64');
  return `data:image/svg+xml;base64,${base64}`;
}

function walk<T>(fn: (iconDef: IconDefinitionWithIdentifier) => Promise<T>) {
  return Promise.all(
    Object.keys(allIconDefs).map((svgIdentifier) => {
      const iconDef = (allIconDefs as { [id: string]: IconDefinition })[svgIdentifier];
      const svgPathToKebab = camelToKebab(svgIdentifier);
      const realSvgPath = detectRealPath(
        path.resolve(__dirname, `../../src/svgs/${svgPathToKebab}.svg`),
      );

      let svgBase64: string | null = null;

      if (realSvgPath) {
        try {
          svgBase64 = svg2base64(realSvgPath);
        } catch (e) {}
      }
      return fn({ svgIdentifier, svgBase64, svgPathToKebab, ...iconDef });
    }),
  );
}

async function generateIcons() {
  const iconsDir = path.join(__dirname, '../../src/svgs');

  try {
    await promisify(fs.access)(iconsDir);
  } catch (err) {
    await promisify(fs.mkdir)(iconsDir);
  }

  const render = template(
    `
// GENERATE BY ./scripts/generate.ts
// DON NOT EDIT IT MANUALLY
import * as React from 'react';
import AntdIcon from '@ant-design/icons';
import { type IconBaseProps } from '@ant-design/icons/lib/components/Icon';
import { ConfigProvider } from 'antd';
import classnames from 'classnames';

import SVGComponent from '../svgs/<%= svgPathToKebab %>.svg';

<% if (svgBase64) { %> /**![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=%3C%25%3D%20svgBase64%20%25%3E&pos_id=img-c6iaG6Dw-1712112320363) */ <% } %>
export const <%= svgIdentifier %> = React.forwardRef<HTMLSpanElement, IconBaseProps>((props, ref) => {
  const { getPrefixCls } = React.useContext(ConfigProvider.ConfigContext);
  const prefixCls = getPrefixCls('web3-icon-<%= svgPathToKebab %>');

  return (
    <AntdIcon
      {...props}
      className={classnames(prefixCls, props.className)}
      ref={ref}
      component={SVGComponent}
    />
  );
});

<%= svgIdentifier %>.displayName = '<%= svgIdentifier %>';

`.trim(),
  );

  await walk(async (item) => {
    // generate icon file
    const svgPathToKebab = camelToKebab(item.svgIdentifier);

    try {
      await writeFile(
        path.resolve(__dirname, `../../src/components/${svgPathToKebab}.tsx`),
        render(item),
      );
    } catch (error) {}
  });

  // generate icon index
  const entryText = Object.keys(allIconDefs)
    .sort()
    .map((svgIdentifier) => `export * from './components/${camelToKebab(svgIdentifier)}';`)
    .join('\n');

  await promisify(fs.appendFile)(
    path.resolve(__dirname, '../../src/index.ts'),
    `
  // GENERATE BY ./scripts/generate.ts
  // DON NOT EDIT IT MANUALLY

  ${entryText}
      `.trim(),
  );
}

generateIcons();