# auto-register-components
**Repository Path**: wangzhaoyv/auto-register-components
## Basic Information
- **Project Name**: auto-register-components
- **Description**: 自动注册全局组件
- **Primary Language**: TypeScript
- **License**: Apache-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2023-07-22
- **Last Updated**: 2023-07-29
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# Vue3资源文件加载与自动导入组件
来实现自动导入公共组件,以及公共组件的参数提示,资源加载能力主要是使用[`import.meta.glob`](https://cn.vitejs.dev/guide/features.html#glob-import)这个`api`
这里为完整代码:[【非指令版参考项目地址】](https://gitee.com/wangzhaoyv/auto-register-components)
## 目录介绍
```bash
|-- auto-register-components
|-- src
| |-- App.vue
| |-- main.ts
| |-- style.css
| |-- vite-env.d.ts
| |-- assets
| | |-- vue.svg
| | |-- iconfont // 字体图标资源
| | | |-- iconfont.css // 普通版本,当前使用的
| | | |-- index.scss // 自定义前缀版本
| | |-- images // 图片资源
| | |-- index.ts
| | |-- icons
| | | |-- language.svg
| | | |-- moon.svg
| | | |-- sun.svg
| | |-- logo
| | |-- index.svg
| |-- constant // 常量文件
| | |-- images.ts
| |-- globalComponents // 需要全局注册的组件
| |-- index.ts // 全局注册的主要文件
| |-- FontIcon // 字体图标组件
| | |-- index.vue
| | |-- README.md
| |-- ImgIcon // 图片组件
| |-- index.vue
| |-- README.md
|-- typings // 其他的ts类型,主要用做全局组件类型提示
|-- shims-vue.d.ts
|-- .gitignore
|-- index.html
|-- package.json
|-- README.md
|-- tsconfig.json
|-- tsconfig.node.json
|-- vite.config.ts
```
> + 全局组件需要统一注册,所以单独新增【globalComponents】文件夹放公共组件
> + FontIcon: 字体图标组件
> + ImgIcon:图片组件
> + index.ts: 自动加载组件相关逻辑
> + typings: 全局组件没有参数提示,所以需要使用这个来进行添加
## 图片资源加载
**整体思路:**
+ 制定规则:
+ 范围:托管到的图片资源为`src/assets/images`文件夹内所有相关资源
+ 重名规则:出现重名,并标识没有后缀,默认顺序为`@/constant/images`中的`imageTypes`决定
+ 使用`import.meta.glob`获取指定位置的图片资源
+ 将所有文件转为地址存入对象中
+ 导出方法获取对象中的地址并返回
+ 新建ImgIcon组件在模板中使用
### 获取指定位置资源
**src\assets\images\index.ts**
```typescript
const getIconListMap = () => {
const files = import.meta.glob("./**/*");
const svgs: { [key: string]: { [key: string]: any } } = {};
for (const key in files) {
const iconInfo = key.split("/");
iconInfo.splice(0, 1);
const fileName = iconInfo.pop() || "undefined";
const fileFolder = iconInfo.join("/");
if (!fileFolder) {
continue;
}
if (!svgs[fileFolder]) {
svgs[fileFolder] = {};
}
// https://cn.vitejs.dev/guide/assets.html
svgs[fileFolder][fileName] = new URL(
`./${fileFolder}/${fileName}`,
import.meta.url
).href;
}
return svgs;
};
// 所有的图片资源
const svgs = getIconListMap();
```
> 注意点:
>
> + 这个位置的`new URL`不能直接使用 变量key,而需要手动组装,否则打包后图片资源地址出现问题, 具体可以查看[官方文档](https://cn.vitejs.dev/guide/assets.html)
> + 注意这个文件的位置,文件位置与`import.meta.glob("./**/*")`是相关联的
>
>
>
> 最终`svgs`对象的数据结构大概:
>
> ```json
> {
> 文件夹名称:{
> 文件名称.后缀:地址
> }
> }
> ```
### 导出获取资源相关内容
#### src\assets\images\index.ts 添加并导出方法 getIconUrl
```typescript
/**
* 获取图片地址
* @param folder 文件夹名称
* @param name 图片名称
* @param hasSuffix 是否有后缀
* @returns 图片地址
*/
export const getIconUrl = (folder: string, name: string = "index", hasSuffix: boolean = false) => {
let iconUrl = "";
if (hasSuffix) {
iconUrl = svgs[folder][name];
} else {
imageTypes.some((suffix: string) => {
let iconName = name + suffix;
iconUrl = svgs[folder][iconName];
return iconUrl;
});
}
return iconUrl;
};
```
+ 如果有后缀,就直接获取路径
+ 如果没有后缀,则使用`imageTypes`作为后缀排序搜索路径
+ 实在没有就直接返回空字符串
> 这里并没有做容错,因为按道理来说,资源文件应该是存在的,如果没有就应该在开发环境进行提示,但是也可以做容错,然后`console.error`出来
#### 添加组件ImgIcon
```vue
```
+ folder:可以设置一个常用的文件夹,减少组件的相关参数
+ icon:也可以设置一个默认的文件
+ hasSuffix:默认不要后缀,这样更方便。但是也可以默认设置为true,减少查找过程
### 编写说明文档,完善组件
#### ImgIcon 使用说明
##### 1. 图片加载
在 [`src/assets/images`](/src/assets/images) 文件夹中找是否有对应的分类,如果没有想要的分类文件夹,那么就手动添加一个文件夹
> 这个很重要,直接放在 `images` 文件夹下的图片资源文件是不读取的,加载工作在[`images/index.ts`](/src/assets/images/index.ts)文件中完成
##### 2.组件方式使用图片
提供了 `ImgIcon` 组件 来访问该图片
```html
```
- folder: 文件夹名称,如果是二级目录中的图片,可以使用`logo/icons`
- icon: 图标名称,不带后缀,支持的后缀在 [`constant\images.ts`](/src/constant/images.ts) 的 `imageTypes` 数组中配置
> 已使用 `globalComponents` 注册为全局组件了,所以可以直接使用,而不需要再注册
```html
```
##### 3.调用方法方式获取图片地址
提供了 getIconUrl 方法来访问图片地址,参数同 ImgIcon 组件
```typescript
getIconUrl("logo", "index");
```
##### 4.最后
`getIconUrl` 在 `src/assets/images/index.ts` 中导出
`IconImg` 在 `src/globalComponents/IconImg`中
##### 5.svg 下载地址
- [icones](https://icones.js.org/)
- [iconify](https://icon-sets.iconify.design/)
- [iconfont](https://www.iconfont.cn/home/index?spm=a313x.7781069.1998910419.2)
## 字体图标资源处理的两种方式
### 1、字体图标加载
字体图标使用的是阿里的[【IconFont字体图标库】](https://www.iconfont.cn/), 只需要添加图标到项目,然后下载并替换大部分`src/assets/iconfont`中的文件即可
#### 1.1 需要自定义图标前缀可以使用这种模式,一般在组件库中使用,防止图标库冲突使用
+ 【IconFont字体图标库】网站中,需要配置`FontClass/Symbol 前缀`, 配置内容与`iconfont.scss`的`[class^='my-icon-']`的`my-icon-`相同, 又与`组件FontIcon`的默认前缀相同
+ 除`index.scss`外整体替换
+ `index.scss`:
+ 文件替换方式:将`iconfont.css` 除了`class` 外的所有内容复制过来,到此替换就算完成了
+ 链接替换方式:`@import "IconFont网站提供的资源连接";`
> 具体可以查看`src\assets\iconfont\index.scss`文件. 因为涉及到组件,并且最终代码以下面的方式为主,所以这里提供一下组件相关代码
**FontIcon/index.vue**
```html
```
#### 1.1 普通方式加载图标
直接替换`src\assets\iconfont文件`内容,不需要设置前缀,设置了就需要自己在组件中添加
### 2、组件
#### 编写
这个其实很简单,因为所有的图标都需要一个iconfont样式,所以我们添加一个组件,`class`提前添加好`iconfont样式`即可
#### 使用
提供了 `FontIcon` 组件 来访问该字体图标库
```html
```
- icon: 图标名称,来自【IconFont字体图标库】
> 已使用 `globalComponents` 注册为全局组件了,所以可以直接使用,而不需要再注册
```html
```
## 自动注册全局组件
### 思路介绍
+ 规定什么类型文件为需要动态加载的组件【部分可能只是公共组件的一部分内容】
+ 使用资源加载所有globalComponents下规定的组件
+ 使用app.use方式对组件进行注册
### 思路实现
**src\globalComponents\main.ts**
```typescript
import type { App } from "vue";
const getComponents = () => {
const components = import.meta.glob("./*/index.vue");
return Object.values(components);
}
const install = (app: App) => {
const componentList = getComponents();
componentList.forEach(async (element: any) => {
const { default: component } = await element();
console.log(component)
if (component.name) {
app.component(component.name, component);
}
});
}
export default { install }
```
> + 通过`getComponents`获取组件
> + 返回一个带有install属性的对象,当使用app.use时,就会调用该方法,并传入app实例
> + 我们通过install方法进行注册组件
**入口文件main.ts**
```typescript
import { createApp } from "vue";
import App from "./App.vue";
import GlobalComponents from "./globalComponents/main";
import "@/assets/iconfont/iconfont.css";
const app = createApp(App);
// 注册全局组件
app.use(GlobalComponents);
// 挂载
app.mount("#app");
```
**问题:**界面上没有展示出来全局组件,并提示没有注册该组件
**猜想:**因为`element方法是异步`,所以导致组件注册慢于app的挂载,所以在挂载前没有注册成功
**证明猜想:**当你把组件放在`v-if`内,添加切换操作,就发现组件后面是能正常渲染的
**src\App.vue**
```vue
```
### 解决问题
既然挂载比组件注册快了一点,那么是否可以让挂载等待组件注册后再进行挂载呢?为了这个目的,将改造组件注册,对外提供一个函数,返回一个Promise的插件对象【`Promise<{install: (app: App) => void}>`】
**src\globalComponents\index.ts**
```typescript
/** 处理异步问题 */
import { Component, ComputedOptions, MethodOptions, type App } from "vue";
export default async function () {
const components = import.meta.glob("./*/index.vue");
const componentList: any = [];
for (const key in components) {
const element = components[key];
const component: any = await element();
componentList.push(component.default);
}
return {
install: (app: App) => {
componentList.forEach((element: Component) => {
if (element.name) {
app.component(element.name, element);
}
});
}
}
}
```
> 在导出的函数中先使用`async await`获取到所有的组件对象,然后通过`install方法`进行组件注册
**修改入口文件main.ts**
```typescript
import { createApp } from "vue";
import App from "./App.vue";
import GlobalComponents from "./globalComponents";
// import GlobalComponents from "./globalComponents/main";
// import "@/assets/iconfont/index.scss";
import "@/assets/iconfont/iconfont.css";
async function init() {
const app = createApp(App);
const commonComponents = await GlobalComponents();
// 注册全局组件
app.use(commonComponents);
// 挂载
app.mount("#app");
}
init();
```
> 入口文件支持`async await`,就完成了
### 添加全局组件参数提示
**typings/shims-vue.d.ts**
```typescript
/**
* 全局组件类型
*/
import FontIcon from "@/globalComponents/FontIcon/index.vue"
import ImgIcon from "@/globalComponents/ImgIcon/index.vue"
declare module 'vue' {
interface GlobalComponents {
FontIcon: typeof FontIcon;
ImgIcon: typeof ImgIcon;
}
}
export { }
```
+ `export { }`: 这个是必要的,不然会报错
+ 引入组件,然后拓充 `GlobalComponents` 接口
+ 该配置文件需要包含在`tsconfig.json`中的`include`范围内
## 最后
到此,自动导入组件就算完成了,后期只需要在`globalComponents`下新建组件即可自动注册,主要是需要改造的内容有点多,并且也没有办法自动添加组件参数提示。