前面有介绍使用资源加载的方式自动注册组件,这次我们来介绍一下使用创建指令来添加组件。
这里为完整代码:
相关内容:
目录介绍
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 // 需要全局注册的组件
| |-- FontIcon // 字体图标组件
| | |-- index.vue
| | |-- README.md
| |-- ImgIcon // 图片组件
| |-- index.vue
| |-- README.md
| |-- importComponent.ts // 引入全局组件【*】
| |-- index.ts // 全局注册的主要文件
|-- tools // 指令实现部分
| |-- component.js // 指令主体
| |-- template // 文件模板内容
| | |-- components.js
| |-- utils // 工具类
| |-- file.js
| |-- index.js
| |-- util.js
|-- typings // 其他的ts类型,主要用做全局组件类型提示
| |-- components.d.ts // 全局组件类型文件【*】
|-- .gitignore
|-- index.html
|-- package.json
|-- README.md
|-- tsconfig.json
|-- tsconfig.node.json
|-- vite.config.ts
- 以上【*】的文件都需要使用node修改文件
- 全局组件需要统一注册,所以单独新增【globalComponents】文件夹放公共组件
- 因为要使用node修改文件,所以尽量抽出不需要改动的逻辑为一个文件
- 添加一个
importComponent.ts
作为所有的公共组件引入,后面使用指令添加的时候就自动为该文件添加一行引入代码即可- 抽出一个
index.ts
,作为注册使用。这个文件导入importComponent.ts
导出的组件,然后进行注册- typings: 全局组件没有参数提示,所以需要使用这个来进行添加
- 上文中我们使用
shims-vue.d.ts
文件进行注册全局组件,但是因为这个可能全局使用,后面修改有所影响,所以我们换了一个components.d.ts
作为自动导入全局组件的类型注册。
思路介绍
- 添加指令。运行
components.js
文件。- 在
globalComponents
目录下创建组件 - 在
importComponent.ts
文件中导入组件【这里写的越简单,对文件的修改就越简单】 - 修改
typings/components.d.ts
文件,添加组件类型
- 在
思路实现
恢复主要文件 + 添加指令 + 补充工具函数
src\globalComponents\index.ts
typescript
import { App } from "vue";
import * as components from "./importComponent";
const install = (app: App) => {
// 转一下类型
let componentList: {[key:string]: any} = components;
for (const key in componentList) {
// 标记:1
const element = componentList[key].default;
if (element.name) {
app.component(element.name, element);
}
}
}
export default { install }
标记1:后面有对这里说明
入口文件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");
package.json
json
{
// ....
"scripts": {
// ....
"component": "ts-node ./tools/component.js"
},
}
会发现,这两个文件又回到了以前的样子,那是因为我们不在需要异步处理了,所以恢复了以前的方式,接下来主要看看我们的主体
tools\component.js
该指令为:
npm run component 组件名称
tools\utils
javascript
import fs from 'fs';
import path from "path";
// 递归创建目录 同步方法
export function mkdirSync(dirname) {
if (!fs.existsSync(dirname)) {
if (mkdirSync(path.dirname(dirname))) {
fs.mkdirSync(dirname);
return true;
}
}
}
/**
* 创建输出目录
* @param outputPath 输出目录
*/
export function mkdirFileDirectory(outputPath) {
if (!fs.existsSync(outputPath)) {
fs.mkdirSync(outputPath);
}
}
实现指令
第一步:创建组件
添加模板 tools\template\components.js
javascript
export const componentTemplateFunc = (name) => {
const componentTemplate = `
<template>
<div>${name}</div>
</template>
<script setup lang="ts">
defineOptions({ name: "${name}" });
// 逻辑代码部分
</script>
`
return { componentTemplate };
}
组件名称这个参数后面所有导入,类型等都有相关,所以我们这里导出一个函数,接受一个组件名称,然后返回模板内容
创建 组件文件 tools\component.js
javascript
import path from "path";
import fs from "fs";
import { mkdirFileDirectory } from "./utils/index.js";
import { componentTemplateFunc } from "./template/components.js";
// 获取组件名称
const componentName = process.argv[2];
// 获取组件文件夹根目录
const componentRoot = path.resolve(process.cwd(), "./src/globalComponents");
// 获取组件模板
const { componentTemplate } = componentTemplateFunc(componentName);
function createComponents() {
const componentPath = path.join(componentRoot, componentName);
// 获取组件目录
const componentFilePath = path.join(componentPath, "./index.vue");
// 创建组件
console.log("开始创建组件");
mkdirFileDirectory(componentPath);
fs.writeFileSync(componentFilePath, componentTemplate);
console.log("组件创建完成");
}
function beginCreateComponent() {
createComponents();
}
beginCreateComponent();
- 使用
process.argv[2]
获取组件名称 - 获取组件存放位置目录地址
- 使用
mkdirFileDirectory
方法进行递归创建文件夹 - 创建文件,并写入内容
注意:这里文件一定要带后缀,不然可能会找不到模块
第二步:引入组件
上面有提到,我们引入组件越简单,那么对这个文件的编辑也就越简单,最好能一行搞定,不然可能有多处修改,所以导出的方法,我采用的是以下格式
javascript
export * as 组件名称 from "./组件名称/index.vue";
这里在使用的时候要加上
default
才能访问到组件对象,也就是为什么标记1
要加上default
添加模板 tools\template\components.js
javascript
export const componentTemplateFunc = (name) => {
// 略
const componentExportTemplate = `\nexport * as ${name} from "./${name}/index.vue";`
return { componentTemplate, componentExportTemplate };
}
这里
\n
是为了换行
引入组件文件添加组件 tools\component.js
javascript
// 略
// 获取组件模板
const { componentTemplate, componentExportTemplate } = componentTemplateFunc(componentName);
// 略createComponents
function importComponent() {
// 获取组件导入的文件
const fileImportFilePath = path.join(componentRoot, "./importComponent.ts");
// 组件导入
console.log("开始组件导入");
// 查找是否有该导出文件
try {
fs.appendFileSync(fileImportFilePath, componentExportTemplate);
} catch (error) {
fs.writeFileSync(fileImportFilePath, componentExportTemplate);
}
console.log("组件导入完成")
}
function beginCreateComponent() {
createComponents();
importComponent();
}
beginCreateComponent();
- 获取到导入组件的文件
- 直接进行文件内容追加,没有文件就会报错,报错就进行创建并写入
到这里运行完成指令,组件其实就可以使用了,但是并没有添加类型
第三步:添加类型
这一步稍微难一点,前面引入都在一个点,可以直接尾部添加。但是类型文件修改点两处,还有在文件中间的内容,所以需要准确定位到位置。
看以往的文件,我们发现主要有两个位置,一个是引入组件,一个是添加类型。所以我们使用标记,标记哪里是需要添加类型,哪里需要引入文件,然后进行文件替换即可
添加模板 tools\template\components.js
javascript
export const componentTemplateFunc = (name) => {
// 略
const addTypeTemplate = `\nimport ${name} from "@/globalComponents/${name}/index.vue";\n`;
const registerTypeTemplate = `\n ${name}: typeof ${name};\n `;
const importComponentFlag = "/** 组件导入位置 **/";
const registerComponentFlag = "/** 类型申明添加位置 **/";
const typeReplaceFlagList = [{
flagText: importComponentFlag,
replaceText: addTypeTemplate + importComponentFlag
}, {
flagText: registerComponentFlag,
replaceText: registerTypeTemplate + registerComponentFlag
}];
const initTypeTemplate = `
/**
* 全局组件类型
*/
${addTypeTemplate}
${importComponentFlag}
declare module 'vue' {
interface GlobalComponents {
${registerTypeTemplate}
${registerComponentFlag}
}
}
export { }
`
return { componentTemplate, componentExportTemplate, typeReplaceFlagList, initTypeTemplate }
}
-
这里返回一个数组【
typeReplaceFlagList
】,然后数组里记录了标记文案和替换文案
,后面循环将文档进行内容替换即可 -
并且还有一个初始化模板,当发现没有该文件的时候可以直接使用
-
一定要记得替换文案一定要带上标识符,否则只能创建一个组件,后面要是再创建组件文件中就没有标记了,那么就没有办法添加相关说明了
引入组件文件添加组件 tools\component.js
javascript
// 略
// 获取组件模板
const { componentTemplate, componentExportTemplate, initTypeTemplate, typeReplaceFlagList } = componentTemplateFunc(componentName);
// 略createComponents importComponent
function addComponentType() {
const typePath = path.resolve(process.cwd(), "./typings/components.d.ts");
// 添加类型
console.log("开始添加组件类型");
try {
let value = fs.readFileSync(typePath, "utf-8");
let resultText = typeReplaceFlagList.reduce((pre, { replaceText, flagText }) => pre.replace(flagText, replaceText), value);
fs.writeFileSync(typePath, resultText);
} catch (error) {
fs.writeFileSync(typePath, initTypeTemplate);
}
console.log("组件类型添加完成")
}
function beginCreateComponent() {
createComponents();
importComponent();
addComponentType();
}
beginCreateComponent();
- 获取类型文件
- 读取文件,然后使用
typeReplaceFlagList
数组进行替换,最后写回文件中 - 如果发现没有该文件,那么就写入初始化模板到文件里去
最后
到此,组件导入相关内容就完全结束了,后续还可以优化一下内容,比如说:
- 添加同名组件提示错误
- 添加指令移除组件
- 添加指令自动引入未注册的全局组件