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"
}
}