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"
    }
}
相关推荐
用户4081281200381几秒前
JWT 和 token 区别
前端
盏茶作酒291 分钟前
打造自己的组件库(三)打包及发布
前端·vue.js
单休好_好就好在比双休少一天2 分钟前
Vite打包从12.17M -> 7.95M,速度提升≈51.85%
前端·javascript
yinke小琪2 分钟前
JavaScript DOM内容操作常用方法和XSS注入攻击
前端·javascript
归于尽2 分钟前
从 TodoList 看自定义 Hook 的设计思想
前端
G等你下课2 分钟前
如何优雅地组织业务逻辑?自定义 Hook 全解析
前端·react.js
刘坤153 分钟前
封装axios二方包
前端·http
饮茶三千4 分钟前
几个在开发中起大作用的CSS新特性!
前端·css
mrsk4 分钟前
React useContext 实战指南:打造主题切换功能
前端·react.js·面试
然我4 分钟前
闭包在类封装中的神技:实现真正安全的私有属性,面试必懂的封装技巧
前端·javascript·面试