0-1搭建业务组件库一

在完成了一个项目,去做另一个项目时,会用到重复的组件,我们可以去打开上一个工程,把组件代码拷过来,再重新调整数据,这样会很麻烦,随着项目的越来越多,也不好定位哪个项目中使用了可复用的组件,所以业务组件库就发挥了它的优势,直接安装好后,按需引入,不用再手搬代码。本篇文章会讲述如何从 0-1 搭建自己的组件库。

技术栈:

  • Vue3
  • TypeScript
  • pnpm

1 环境准备

在 cmd 终端安装 pnpm

1
npm install pnpm -g

创建工程目录:vue3-components。

用 vscode 打开工程目录,在 vscode 终端执行:

1
pnpm init

根目录新建.npmrc 文件,写入下面的语句,作用是与项目不直接相关的不会安装在 node modules 下:

1
shamefully-hoist = true

在根目录安装依赖:

1
pnpm install vue@next typescript -D

配置 tsconfig.json:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
"compilerOptions": {
"module": "esnext",
"declaration": false,
"noImplicitAny": false,
"removeComments":true,
"esModuleInterop": true,
"moduleResolution": "node",
"jsx": "preserve",
"noLib": false,
"target": "es2018",
"sourceMap": true,
"lib": ["esnext", "DOM"],
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"strict": true,
"skipLibCheck": true,
},
"exclude": [
"node_modules",
"dist/**",
"**/__tests__",
]
}

根目录新建 pnpm-workspace.yaml 文件,写入:

1
2
3
packages:
- "packages/**" # 存放编写的组件
- play # 使用组件的地方

2 项目创建

在根目录新建 paly 目录,进入 play,执行:pnpm init
修改 package.json 文件配置:

1
2
3
4
5
6
7
8
9
10
{
"name": "@vue3-components/play",
"private": true,
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "liuhp",
"license": "ISC"
}

在 play 目录下安装依赖:

1
pnpm install vite @vitejs/plugin-vue -D

在 play 目录下新建 vite.config.ts, 写入:

1
2
3
4
5
6
import { defineConfig } from "vite"
import vue from "@vitejs/plugin-vue"

export default defineConfig({
plugins: [vue()],
})

新建 index.html,写入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>

<body>
<div id="app"></div>
<script src="/main.ts" type="module"></script>
</body>
</html>

新建 app.vue, 写入:

1
2
3
<template>
<div>测试</div>
</template>

新建 main.js, 写入:

1
2
3
4
5
import { createApp } from "vue"
import App from "./app.vue"

const app = createApp(App)
app.mount("#app")

main.ts 此时会提示:

image.png

所以我们在 vue3-components 根目录下新建typings文件夹,在 typings 下新建vue-shim.d.ts文件,写入:

1
2
3
4
5
declare module "*.vue" {
import type { DefineComponent } from "vue"
const component: DefineComponent<{}, {}, any>
export default component
}

此时,main.ts 就不会报错了。
修改 package.json 里 script 的命令:

1
2
3
"scripts": {
"dev": "vite"
},

然后就可以在 play 目录下运行npm run dev, 为了能够在 vue3-components 根目录下直接运行 play 项目,需要修改 vue3-components 根目录下的 package.json 里 script 的命令:

1
2
3
"scripts": {
"dev": "pnpm -C play dev"
},

就可以在 vue3-components 根目录下直接运行npm run dev了。

3 封装组件

3.1 创建三个文件夹

packages 下建立 3 个文件夹:

  • components:存放封装的组件;
  • utils: 存放公共方法;
  • theme-chalk: 存放公共样式;

这三个文件夹下都要执行pnpm init,为 package.json 中的 name 起好名字(后面安装需要用这个名字),都是独立包,可以独立发布。

然后在 vue3-components 根目录下安装这三个包,例如:pnpm install @vue3-components/components -w -w 是安装在根目录下,安装好后根目录下的 package.json 如下:

image.png

3.2 封装组件:

在 packages\components\下新建组件文件夹,这里以 v-form 为例:

image.png
v-form.vuev-form.ts中封装好组件,也可以先随便写点,方便测试,README.md中,写好参数说明。index.ts 中:

1
2
3
4
5
6
import vform from "./src/v-form.vue"
import { withInstall } from "@vue3-components/utils/with-install"

const VForm = withInstall(vform)

export default VForm

packages\utils\with-install.ts:

1
2
3
4
5
6
7
8
9
10
11
import type { App, Plugin } from "vue" // 只导入类型,不是值

export type SFCWithInstall<T> = T & Plugin

export const withInstall = <T>(comp: T) => {
(comp as SFCWithInstall<T>).install = function (app: App) {
app.component((comp as any).name, comp)
}
return comp as SFCWithInstall<T>
}

验证封装组件是否生效

play\main.ts 全局引入组件:

1
2
3
4
5
6
7
8
9
10
11
import { createApp } from "vue"
import App from "./app.vue"
import ElementPlus from "element-plus"
import "element-plus/dist/index.css"

import VForm from "@vue3-components/components/v-form"

const app = createApp(App)
app.use(ElementPlus)
app.use(VForm)
app.mount("#app")

play\app.vue 中使用组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<template>
<div>测试</div>
<v-form :elements="forms" @search="search"></v-form>
</template>
<script lang="ts" setup>
const forms = [
{
label: "角色标识",
prop: "roleCode",
placeholder: "角色标识",
},
{
label: "角色名称",
prop: "roleName",
placeholder: "分组名称",
},
{
label: "产品",
prop: "productCode",
placeholder: "产品",
type: "select",
optionGroup: true,
options: [],
style: "width:100%;",
},
{
label: "创建时间",
prop: "create_at_start,create_at_end",
type: "datetimerange",
defaultValue: [],
startPlaceholder: "选择开始时间",
endPlaceholder: "选择结束时间",
valueFormat: "yyyy-MM-dd HH:mm:ss",
layout: {
lg: {
span: 8,
},
md: {
span: 12,
},
sm: {
span: 24,
},
xs: {
span: 24,
},
},
},
]

const search = (params) => {
console.log(params)
}
</script>

至此,组件封装完成,接下来需要解决的问题是如何抽离公共样式,随着组件库中组件越来越多,一些重复的样式需要抽离出来,抽离完公共样式后,再解决打包和发布问题,发布后才能在其他项目中安装使用。

-------------本文结束&感谢您的阅读-------------