element-plus-icons图标库

element-plus的图标库(element-plus-icons)。作为一个独立库存在。

因此在使用图标的时候,需要我们通过npm i @element-plus/icons-vue

这里学习下该图标库的搭建

目录结构

该库采用pnpm monorep方式开发

text 复制代码
├── packages
│   ├── svg
│   └── vue
├── play
└── tsconfig.json

子包 @element-plus/icons-svg

直接放了各个icon的svg图标,使用到svgo,能将svg图标进行压缩。

不需要有主入口导出之类的,因为这个包不会被引用到,只是用来存放svg图标的。

在包 package/vue 中会通过node读取这个包的svg,自动生成vue组件。

1.脚本说明

json 复制代码
{
  "optimize": "svgo -f ./icon -o ./icon" // -f源目录 -o目标目录
}
  • pnpm optimize: 将指定目录的svg图标进行压缩,这里压缩后还是存到源目录,就是把源文件覆盖掉的意思

2.注意事项

有新svg放进该库之前,需要将svg代码中的 fill="red" 之类配置颜色改为 fill="currentColor"

如果有width/height等属性,需要删除

子包 @element-plus/icons-vue

这个库是提供给外界使用的库,根据 packages/svg/icon 库的svg图标自动生成vue组件和入口components.ts

1.目录结构

text 复制代码
packages/vue
  ├── build             // 各种node脚本
  │   ├── build.ts      // 执行构建的node脚本
  │   ├── generate.ts   // 根据 `packages/svg/icon` 自动生成vue组件的node脚本
  │   ├── paths.ts      // 路径配置
  │   └── utils.ts      // 脚本公共函数
  ├── src
  │   ├── icons         // 自动生成的,各种vue图标组件
  │   ├── components.ts // 自动生成的,引入所有vue图标组件
  │   ├── global.ts     // 导出所有vue图标组件,以及app.use的注册
  │   └── index.ts      // 导出所有vue图标组件
  ├── tsconfig.build.json
  ├── tsconfig.json
  └── package.json

2.脚本说明

json 复制代码
{
  "build": "pnpm build:generate && pnpm clear:dist && run-p build:build build:types",
  "clear:dist": "rimraf dist",
  "build:generate": "tsx build/generate.ts",
  "build:build": "tsx build/build.ts",
  "build:types": "vue-tsc --declaration --emitDeclarationOnly",
  "deploy": "npm publish --registry=https://registry.npmjs.org/"
}
  • build: 多条命令组合
  • clear:dist: 删除dist目录
  • build:generate: 自动生成vue组件和components.ts
  • build:build: 构建出dist目录
  • build:types: 生成ts类型文件

2.1 pnpm run build:generate命令

执行的是tsx build/generate.ts。核心逻辑如下

ts 复制代码
import { findWorkspaceDir } from '@pnpm/find-workspace-dir';
import { findWorkspacePackages } from '@pnpm/find-workspace-packages';

function getSvgFiles () {
    const projectRoot = await findWorkspaceDir(process.cwd());
    const pkgs = await findWorkspacePackages(projectRoot!); // 找到pnpmWorkspace的各个子包返回[对象]
    const pkg = pkgs.find((pkg) => pkg.manifest.name === '@element-plus/icons-svg')!; // 找到`@element-plus/icons-svg`包

    // 匹配所有svg后缀名的文件,返回路径['E:/xxx']
    // {absolute: true}返回的路径都是绝对路径,false:返回的只是文件名数组
    return glob('*.svg', { cwd: path.resolve(pkg.dir, 'icon'), absolute: true });
}

其中使用 findWorkspaceDir/findWorkspacePackages 找到上面存在svg图标的子库,接着用 fast-glob 匹配出所有.svg结尾的图标

经过上面得到了一个数组,格式如 ['E:/xxxx/yy.svg']

接着函数transformToVueComponent对数组的每个路径进行处理

ts 复制代码
function transformToVueComponent(file) {
    const content = await readFile(file, 'utf-8'); // 得到了svg的代码
    const { filename, componentName } = getName(file); // 这里getName作用是得到文件名并且是大驼峰格式
    
    // formatCode() 这里是封装了prettier美化代码并且告诉了prettier这里是vue代码
    const vue = await formatCode(
        `
        <template>
            ${content}
        </template>

        <script lang="ts">
            import  { defineComponent } from 'vue'
            export default defineComponent({
                name: "${componentName}",
            });
        </script>`,
        'vue'
    );
    writeFile(path.resolve(pathIcons, `${filename}.vue`), vue, 'utf-8');
}

上面其实就是读取svg的代码出来,然后放到一个vue组件中,形成下面一个普通的vue组件

vue 复制代码
<template>
    <svg>....</svg>
</template>

<script lang="ts">
import  { defineComponent } from 'vue'
export default defineComponent({
    name: "${componentName}",
});
</script>

并将上面一个个vue组件放到了/packages/vue/src/icons/xxxx.vue里面

接着做最后一步,生成/packages/vue/src/components.ts作为组件的引入入口

ts 复制代码
const code = await formatCode(
    files
        .map((file) => {
            const { filename, componentName } = getName(file);
            return `export { default as ${componentName} } from './${ICONS_NAME}/${filename}.vue'`;
        })
        .join('\n'),
);
await writeFile(path.resolve(pathSrc, 'components.ts'), code, 'utf-8');

经过上面生成的 /packages/vue/src/components.ts 如下:

ts 复制代码
export { default as AddLocation } from './icons/add-location.vue';
export { default as Aim } from './icons/aim.vue';
// ...

并且在主入口/packages/vue/src/index.ts中引入

ts 复制代码
export * from './components';

除了/packages/vue/src/index.ts,还有一个global.ts,代码如下:

ts 复制代码
import * as icons from './components';

export default (app: App, { prefix = 'EsIcon' } = {}) => {
    for (const [key, component] of Object.entries(icons)) {
        app.component(prefix + key, component);
    }
};

上面这种写法是可以让外界可以用下面方式全量导入:

ts 复制代码
import EsIcons from '@element-plus/icons-vue/global';
app.use(EsIcons, { prefix: '自定义前缀' });

接着还有

ts 复制代码
export { icons };

外界可以用下面方式全量导入

ts 复制代码
import { icons } from '@codenatsu/icons-vue/global';

至此,完成了svg图标到vue组件的转变,以及入口的生成

2.2 pnpm run build:build命令

执行的是tsx build/build.ts,这里主要是打包的配置,这里主要用esbuild打包

前面已经讲了主要入口有2个/packages/vue/src/index.ts/packages/vue/src/global.ts

esBuild打包配置如下

ts 复制代码
import { build } from 'esbuild';
import vue from 'unplugin-vue/esbuild';

build({
    entryPoints: [path.resolve(pathSrc, 'index.ts'), path.resolve(pathSrc, 'global.ts')], // 2个入口
    target: 'es2018',
    platform: 'neutral',
    plugins: [
        vue({
            isProduction: true,
            sourceMap: false,
        }),
    ],
    bundle: true, // true:构建成一个文件,false:只会构建入口文件其他文件不处理
    format: 'esm', // 构建规范 esm:es6规范, cjs:commonjs规范, iife:浏览器规范
    minifySyntax: true, // 只简化js语法,当minify=true的时候才起作用
    banner: {
        js: `/*! Esdesign Icons Vue v${version} */\n`, // js文件头部注释
    },
    outdir: pathOutput, // 输出目录
    entryNames: `[name]${minify ? '.min' : ''}`,
    outExtension: { '.js': '.cjs' }, // commonjs规范的,多这个配置,输出文件后缀名cjs
    external: ['vue'],
    minify: <boolean> // 是否要压缩代码
})

因此,打包出了iife、esm、commonjs 3种规范的包,每种都有压缩和未压缩

2.3 pnpm run build:types命令

这条就是打包出d.ts类型文件,输出到/packages/vue/dist/types目录

至此,需要的文件都打包好了并发布npm上

package.json的配置如下

json 复制代码
{
    "sideEffects": false,
    "files": [
        "dist"
    ],
    "main": "./dist/index.cjs",
    "module": "./dist/index.js",
    "types": "./dist/types/index.d.ts",
    "unpkg": "dist/index.iife.min.js",
    "jsdelivr": "dist/index.iife.min.js",
    "exports": {
        ".": {
            "types": "./dist/types/index.d.ts",
            "require": "./dist/index.cjs",
            "import": "./dist/index.js"
        },
        "./global": {
            "types": "./dist/types/global.d.ts",
            "require": "./dist/global.cjs",
            "import": "./dist/global.js"
        }
    },
    "typesVersions": {
        "*": {
            "*": [
                "./*",
                "./dist/types/*"
            ]
        }
    },
    "peerDependencies": {
        "vue": "^3.3.4"
    }
}
相关推荐
wordbaby8 分钟前
搞不懂 px、dpi 和 dp?看这一篇就够了:图解 RN 屏幕适配逻辑
前端
程序员爱钓鱼10 分钟前
使用 Node.js 批量导入多语言标签到 Strapi
前端·node.js·trae
鱼樱前端11 分钟前
uni-app开发app之前提须知(IOS/安卓)
前端·uni-app
V***u45312 分钟前
【学术会议论文投稿】Spring Boot实战:零基础打造你的Web应用新纪元
前端·spring boot·后端
i听风逝夜1 小时前
Web 3D地球实时统计访问来源
前端·后端
iMonster1 小时前
React 组件的组合模式之道 (Composition Pattern)
前端
呐呐呐呐呢1 小时前
antd渐变色边框按钮
前端
元直数字电路验证1 小时前
Jakarta EE Web 聊天室技术梳理
前端
wadesir1 小时前
Nginx配置文件CPU优化(从零开始提升Web服务器性能)
服务器·前端·nginx
牧码岛1 小时前
Web前端之canvas实现图片融合与清晰度介绍、合并
前端·javascript·css·html·web·canvas·web前端