# 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`下新建组件即可自动注册,主要是需要改造的内容有点多,并且也没有办法自动添加组件参数提示。