Vite+Vue3+Ts搭建私有组件库

Vite+Vue3+Ts搭建私有组件库

目前前端技术的持续发展,社区生态出现了一批比较适用的组件库,例如:ElementUI,Vant,AntDesign等,但这些组件库的组件不一定能满足我们一些项目的特定业务需求,那么我们的团队就应该建立起我们自己的组件库,方便于我们自己的业务开发,那么我们这篇文章就来了解一下我们开发组件库的大体技术架构体系。

搭建组件库的技术要点:

  • 如何使用pnpm搭建出一个menorepo环境
  • 如何使用vite搭建一个基本的Vue3脚手架项目
  • 如何开发调试一个自己的UI组件库
  • 如何使用vite打包并发布自己的UI组件库

一、menorepo环境的搭建

首先我们要了解什么是menorepo及它是如何搭建 (单仓库 多项目)

就是指在一个大的项目仓库中,管理多个模块/包(package),这种类型的项目大都在项目根目录下有一个packages文件夹,分多个项目管理。大概结构如下:

-- packages
  -- pkg1
    --package.json
  -- pkg2
    --package.json
--package.json

目前很多我们熟知的项目都是采用这种模式,如Vant,ElementUI,Vue3等。打造一个menorepo环境的工具有很多,如:lerna、pnpm、yarn等,这里我们将使用pnpm来开发我们的UI组件库

1.使用pnpm

npm install pnpm -g //安装pnpm
pnpm init //初始化package.json

新建配置文件 .npmrc

shamefully-hoist = true

这里简单说下为什么要配置shamefully-hoist。

如果某些工具仅在根目录的node_modules时才有效,可以将其设置为true来提升那些不在根目录的node_modules,就是将你安装的依赖包的依赖包的依赖包的…都放到同一级别(扁平化)。说白了就是不设置为true有些包就有可能会出问题。

安装对应依赖

pnpm i vue@next typescript less -D -w //开发环境中的依赖一般全部安装在整个项目根目录下,方便下面我们每个包都可以引用,所以在安装的时候需要加个 -w

如果我们是在根目录下运行命令,那么就不用加 -w 了,要不然会提示

--workspace-root may only be used inside a workspace

配置tsconfit.json

npx tsc --init //初始化
//tsconfit.json配置
{
  "compilerOptions": {
    "baseUrl": ".",
    "jsx": "preserve",
    "strict": true,
    "target": "ES2015",
    "module": "ESNext",
    "skipLibCheck": true,
    "esModuleInterop": true,
    "moduleResolution": "Node",
    "lib": ["esnext", "dom"]
  }
}

2.monorepo的实现

各个项目之间能够互相引用我们要新建一个pnpm-workspace.yaml文件将我们的包关联起来

packages:
    - 'packages/**'
    - 'examples'

这样就能将我们项目下的packages目录和examples目录关联起来了,当然如果你想关联更多目录你只需要往里面添加即可。根据上面的目录结构很显然你在根目录下新建packages和examples文件夹,packages文件夹存放我们开发的包,examples用来调试我们的组件。

examples文件夹就是接下来我们要使用vite搭建一个基本的Vue3脚手架项目的地方。

3.手动搭建一个基于vite的vue3项目

进入examples文件夹,执行

pnpm init //初始化仓库
pnpm install vite @vitejs/plugin-vue -D -w //安装vite和@vitejs/plugin-vue

配置vite.config.ts

新建vite.config.ts文件

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

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

新建html文件

@vitejs/plugin-vue 会默认加载examples下的index.html

新建index.html

<!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>

注意:
vite 是基于esmodule的 所以type=”module”

新建app.vue模板

<template>
    <div>
        启动测试
    </div>
</template>

新建main.ts

import {createApp} from 'vue'
import App from './app.vue'

const app = createApp(App)

app.mount('#app')

此时会发现编译器会提示个错误:找不到模块“./app.vue”或其相应的类型声明

因为直接引入.vue文件 TS会找不到对应的类型声明;所以需要新建typings(命名没有明确规定,TS会自动寻找.d.ts文件)文件夹来专门放这些声明文件。

typings/vue-shim.d.ts

TypeScriptTS默认只认ES 模块。如果你要导入.vue文件就要declare module把他们声明出来。

declare module '*.vue' {
    import type { DefineComponent } from "vue";
    const component:DefineComponent<{},{},any>
}

配置脚本启动项目

最后在package.json文件中配置scripts脚本

...
"scripts": {
    "dev": "vite"
  },
...

然后终端输入我们熟悉的命令:pnpm run dev

如果出现了cannot find module ‘node:path’这样的错误提示,那可能是node版本不兼容,升级一下node版本就可以了

我的node 版本是 v14.17.0 的版本,出现了不兼容的问题,于是升级到了稳定版 v16.16.0 。

再运行vite就没问题了

二、本地调试

1.新建包文件

往packages文件夹冲填充内容

一般packages要有utils包来存放我们公共方法,工具函数等

既然它是一个包,所以我们新建utils目录后就需要初始化它,让它变成一个包;终端进入utils文件夹执行:pnpm init 然后会生成一个package.json文件;这里需要改一下包名,我这里将name改成@alanmf/utils表示这个utils包是属于alanmf这个组织下的。所以记住发布之前要登录npm新建一个组织;例如alanmf

因为我们使用ts写的,所以需要将入口文件index.js改为index.ts,并新建index.ts文件:(先导出一个简单的加法函数)

components是我们用来存放各种UI组件的包

新建components文件夹并执行 pnpm init 生成package.json

  • 组件库包 这里命名为alanmf
{
  "name": "@alanmf/utils",
  "version": "1.0.0",
  "description": "",
  "main": "index.ts",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

新建index.ts入口文件并引入utils包

//@kitty-ui/utils
import {testfun} from '@alanmf/utils'

const result = testfun (1,1)

console.log(result)

由于组件库是基于ts的,所以需要安装esno来执行ts文件便于测试组件之间的引入情况

控制台输入esno xxx.ts即可执行ts文件

npm i esno -g //安装esno
eson index.ts //执行index.ts文件

2.包之间本地调试

进入components文件夹执行

pnpm install @alanmf/utils

你会发现pnpm会自动创建个软链接直接指向我们的utils包;此时components下的packages:

{
  "name": "alanmf",
  "version": "1.0.0",
  "description": "",
  "main": "index.ts",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@alanmf/utils": "workspace:^1.0.0"
  }
}

你会发现它的依赖@alanmf/utils对应的版本为:workspace:^1.0.0;因为pnpm是由workspace管理的,所以有一个前缀workspace可以指向utils下的工作空间从而方便本地调试各个包直接的关联引用。

到这里基本开发方法我们已经知道啦;接下来就要进入正题了,开发一个button组件

三、开发一个button组件

1.创建一个测试组件

在components文件夹下新建src,同时在src下新建button组件目录和icon组件目录(新建icon为了便于调试);此时components文件目录如下

-- components
  -- src
    -- button
    -- icon
    -- index.ts
-- package.json

让我们先测试一下我们的button组件能否在我们搭建的examples下的vue3项目本引用~

首先在button下新建一个简单的button.vue

<template>
    <button>测试按钮</button>
</template>

然后在button/index.ts将其导出

import Button from './button.vue'

export default Button

因为我们开发组件库的时候不可能只有button,所以我们需要一个components/index.ts将我们开发的组件一个个的集中导出

import Button from './button'

export {
    Button
}

好了,一个组件的大体目录差不多就是这样了,接下来请进入我们的examples来看看能否引入我们的button组件

2.测试项目引入

上面已经说过执行在workspace执行 pnpm i xxx的时候pnpm会自动创建个软链接直接指向我们的xxx包。

所以这里我们直接在examples执行:pnpm i alanmf

此时你就会发现packages.json的依赖多了个

"alanmf": "workspace:^1.0.0"

这时候我们就能直接在我们的测试项目下引入我们本地的components组件库了,启动我们的测试项目,来到我们的 examples/app.vue 直接引入Button

<template>
    <div>
        <Button />
    </div>
</template>
<script lang="ts" setup>
import { Button } from 'alanmf/src'
</script>

不出意外的话你的页面就会展示我们刚刚写的button组件了

接下来的工作就是专注于组件的开发了;让我们回到我们的button组件目录下(测试页面不用关,此时我们已经可以边开发边调试边看效果了)

3.types.ts文件规范组件属性

我们的button组件是需要接收很多属性的,如type、size等等,所以我们要新建个types.ts文件来规范这些属性

在button目录下新建types.ts


import { ExtractPropTypes } from 'vue'


export const ButtonType = ['default', 'primary', 'success', 'warning', 'danger']

export const ButtonSize = ['large', 'normal', 'small', 'mini'];


export const buttonProps = {
  type: {
    type: String,
    values: ButtonType
  },
  size: {
    type: String,
    values: ButtonSize
  }
}

export type ButtonProps = ExtractPropTypes<typeof buttonProps>

import type 表示只导入类型;ExtractPropTypes是vue3中内置的类型声明,它的作用是接收一个类型,然后把对应的vue3所接收的props类型提供出来,后面有需要可以直接使用

很多时候我们在vue中使用一个组件会用的app.use 将组件挂载到全局。要使用app.use函数的话我们需要让我们的每个组件都提供一个install方法,app.use()的时候就会调用这个方法;

我们将button/index.ts调整为

import button from './button.vue'
import type {App,Plugin} from "vue"
type SFCWithInstall<T> = T&Plugin
const withInstall = <T>(comp:T) => {
    (comp as SFCWithInstall<T>).install = (app:App)=>{
        //注册组件
        app.component((comp as any).name,comp)
    }
    return comp as SFCWithInstall<T>
}
const Button = withInstall(button)
export default Button

此时我们就可以使用app.use来挂载我们的组件啦

其实withInstall方法可以做个公共方法放到工具库里,因为后续每个组件都会用到,这里等后面开发组件的时候再调整

到这里组件开发的基本配置已经完成,最后我们对我们的组件库以及工具库进行打包,打包之前如果要发公共包的话记得将我们的各个包的协议改为MIT开源协议

...
"license": "MIT",
...

四、vite打包

1.配置文件

打包们这里选择vite,它有一个库模式专门为我们来打包这种库组件的。

前面已经安装过vite了,所以这里直接在components下直接新建vite.config.ts(配置参数文件中已经注释):

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue"
export default defineConfig(
    {
        build: {
            target: 'modules',
            //打包文件目录
            outDir: "es",
            //压缩
            minify: false,
            //css分离
            //cssCodeSplit: true,
            rollupOptions: {
                //忽略打包vue文件
                external: ['vue'],
                input: ['src/index.ts'],
                output: [
                    {
                        format: 'es',
                        //不用打包成.es.js,这里我们想把它打包成.js
                        entryFileNames: '[name].js',
                        //让打包目录和我们目录对应
                        preserveModules: true,
                        //配置打包根目录
                        dir: 'es',
                        preserveModulesRoot: 'src'
                    },
                    {
                        format: 'cjs',
                        entryFileNames: '[name].js',
                        //让打包目录和我们目录对应
                        preserveModules: true,
                        //配置打包根目录
                        dir: 'lib',
                        preserveModulesRoot: 'src'
                    }
                ]
            },
            lib: {
                entry: './index.ts',
                formats: ['es', 'cjs']
            }
        },
        plugins: [
            vue()
        ]
    }
)

这里我们选择打包cjs(CommonJS)和esm(ESModule)两种形式,cjs模式主要用于服务端引用(ssr),而esm就是我们现在经常使用的方式,它本身自带treeShaking而不需要额外配置按需引入(前提是你将模块分别导出)

2.vite打包

到这里就已经可以直接打包了;components下执行: pnpm run build你就会发现打包了es和lib两个目录

参考链接:

[私有仓库搭建源码]: “https://github.com/mengfeng/alanmf_ui.git

Donate
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
  • Copyrights © 2022-2023 alan_mf
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信