vue3 构建属于自己的组件库dxui

Posted 可缺不可滥

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue3 构建属于自己的组件库dxui相关的知识,希望对你有一定的参考价值。

文章目录

前言

为了我自己的组件库,我从去年下半年开始,到现在一直在运用vue3框架,写各种前端web常用的组件,从Button开始到Select,将来还会继续完善。

第一步,通过vue-cli搭建vue3框架

当然你用vite也可以,但后面将组建库打包到npm的方式,可能略有不同。

这篇文章介绍了如何快速搭建vue3项目 https://dengxi.blog.csdn.net/article/details/124837593

第二步,构建一个入口,将所有的组件统一管理

// src/components/dxui/index.ts
import Button from '../button/Button.vue'
import Card from '../card/Card.vue'
import BreadCrumb from '../breadCrumb/Breadcrumb.vue'
import CardGroup from '../cardGroup/CardGroup.vue'
import Checkbox from '../checkbox/Checkbox.vue'
import CheckboxGroup from '../checkboxGroup/CheckboxGroup.vue'
import Dialog from '../dialog/Dialog.vue'
import Slider from '../slider/Slider.vue'
import Switch from '../switch/Switch.vue'
import Tag from '../tag/Tag.vue'
import Tooltip from '../tooltip/Tooltip.vue'
import AnimationIcon from '../animationIcon/AnimationIcon.vue'

// 分别导出,让别人可以按需引入
export 
  Button,
  Card,
  CardGroup,
  BreadCrumb,
  Checkbox,
  CheckboxGroup,
  Dialog,
  Slider,
  Switch,
  Tag,
  Tooltip,
  AnimationIcon


const component = [
  Button,
  Card,
  CardGroup,
  BreadCrumb,
  Checkbox,
  CheckboxGroup,
  Dialog,
  Slider,
  Switch,
  Tag,
  Tooltip,
  AnimationIcon
]

const Dxui = 
  install(App: any) 
    component.forEach((item: any) => 
      App.component(item.name, Button)
    )
  


export default Dxui

第三步 修改package.json ,对组件进行单独打包

// package.json

  "name": "dxui",
  "version": "0.1.0",
  "private": true,
  "scripts": 
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint",
    "build-dxui": "vue-cli-service build --target lib ./src/components/dxui/index.ts --name dxui --dest dxui"
  ,
  "dependencies": 
    "@codemirror/lang-html": "^6.1.3",
    "@codemirror/lang-javascript": "^6.1.1",
    "@codemirror/theme-one-dark": "^6.1.0",
    "core-js": "^3.6.5",
    "lottie-web": "^5.9.6",
    "vue": "^3.0.0",
    "vue-class-component": "^8.0.0-0",
    "vue-codemirror": "^6.1.1",
    "vue-router": "^4.0.0-0",
    "vuex": "^4.0.0-0"
  ,
  "devDependencies": 
    "@typescript-eslint/eslint-plugin": "^4.18.0",
    "@typescript-eslint/parser": "^4.18.0",
    "@vue/cli-plugin-babel": "~4.5.11",
    "@vue/cli-plugin-eslint": "~4.5.11",
    "@vue/cli-plugin-router": "~4.5.11",
    "@vue/cli-plugin-typescript": "~4.5.11",
    "@vue/cli-plugin-vuex": "~4.5.11",
    "@vue/cli-service": "~4.5.11",
    "@vue/compiler-sfc": "^3.0.0",
    "@vue/eslint-config-standard": "^5.1.2",
    "@vue/eslint-config-typescript": "^7.0.0",
    "eslint": "^6.7.2",
    "eslint-plugin-import": "^2.20.2",
    "eslint-plugin-node": "^11.1.0",
    "eslint-plugin-promise": "^4.2.1",
    "eslint-plugin-standard": "^4.0.0",
    "eslint-plugin-vue": "^7.0.0",
    "sass": "^1.26.5",
    "sass-loader": "^8.0.2",
    "typescript": "~4.1.5",
    "vue3-dxui": "^1.0.1"
  ,
  "eslintConfig": 
    "root": true,
    "env": 
      "node": true
    ,
    "extends": [
      "plugin:vue/vue3-essential",
      "@vue/standard",
      "@vue/typescript/recommended"
    ],
    "parserOptions": 
      "ecmaVersion": 2020
    ,
    "rules": 
      "space-before-function-paren": [
        0,
        "always"
      ],
      "@typescript-eslint/explicit-module-boundary-types": "off",
      "no-unused-expressions": "off",
      "@typescript-eslint/no-this-alias": "off",
      "@typescript-eslint/no-inferrable-types": "off"
    
  ,
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not dead"
  ]


新增了一条命令行

"build-dxui": "vue-cli-service build --target lib ./src/components/dxui/index.ts --name dxui --dest dxui"

打包命令解释:

–target lib 关键字 指定打包的目录 也就是 src/components/dxui/index.ts 作为打包的入口

–name 打包后的文件名字 dxui

–dest 打包后的文件夹的名称 dxui

第四步输入命令行开始打包

npm run build-dxui

第五步,修改package.json文件,为npm 发布做准备

//dxui/package.json

  "name": "vue3-dxui",
  "version": "1.0.9",
  "files": [
    "dxui"
  ],
  "main": "./dxui/dxui.common.js",
  "browser": "./dxui/dxui.umd.js",
  "types": "./dxui/components/dxui/index.d.ts",
  "scripts": 
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint",
    "build-dxui": "vue-cli-service build --target lib ./src/components/dxui/index.ts --name dxui --dest dxui"
  ,
  "dependencies": 
    "@codemirror/lang-html": "^6.1.3",
    "@codemirror/lang-javascript": "^6.1.1",
    "@codemirror/theme-one-dark": "^6.1.0",
    "core-js": "^3.6.5",
    "lottie-web": "^5.9.6",
    "vue": "^3.0.0",
    "vue-class-component": "^8.0.0-0",
    "vue-codemirror": "^6.1.1",
    "vue-router": "^4.0.0-0",
    "vuex": "^4.0.0-0"
  ,
  "devDependencies": 
    "@typescript-eslint/eslint-plugin": "^4.18.0",
    "@typescript-eslint/parser": "^4.18.0",
    "@vue/cli-plugin-babel": "~4.5.11",
    "@vue/cli-plugin-eslint": "~4.5.11",
    "@vue/cli-plugin-router": "~4.5.11",
    "@vue/cli-plugin-typescript": "~4.5.11",
    "@vue/cli-plugin-vuex": "~4.5.11",
    "@vue/cli-service": "~4.5.11",
    "@vue/compiler-sfc": "^3.0.0",
    "@vue/eslint-config-standard": "^5.1.2",
    "@vue/eslint-config-typescript": "^7.0.0",
    "eslint": "^6.7.2",
    "eslint-plugin-import": "^2.20.2",
    "eslint-plugin-node": "^11.1.0",
    "eslint-plugin-promise": "^4.2.1",
    "eslint-plugin-standard": "^4.0.0",
    "eslint-plugin-vue": "^7.0.0",
    "sass": "^1.26.5",
    "sass-loader": "^8.0.2",
    "typescript": "~4.1.5",
    "vue3-dxui": "^1.0.9"
  ,
  "eslintConfig": 
    "root": true,
    "env": 
      "node": true
    ,
    "extends": [
      "plugin:vue/vue3-essential",
      "@vue/standard",
      "@vue/typescript/recommended"
    ],
    "parserOptions": 
      "ecmaVersion": 2020
    ,
    "rules": 
      "space-before-function-paren": [
        0,
        "always"
      ],
      "@typescript-eslint/explicit-module-boundary-types": "off",
      "no-unused-expressions": "off",
      "@typescript-eslint/no-this-alias": "off",
      "@typescript-eslint/no-inferrable-types": "off"
    
  ,
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not dead"
  ]


name 包名称,包名称得独一无二
verison 版本号,新的版本号得大于之前的版本号1.0.9
files 你真正想要上传供大家使用的文件,我直接将打包好的上传就好 dxui
main 别人下载时,引用后的主文件,这里是打包好的 ./dxui/dxui.common.js
browser 供html使用的文件 ./dxui/dxui.umd.js
types,用来提供类型声明的,因为类型声明没写好,现在暂不可用
删掉了 private,否则无法上传到npm,

第六步修改README.md

这个markdown文件介绍了组件如何使用,和相关其它内容。

第七步 npm登录后,直接发布

npm login
// 当然要输入账号密码,甚至验证码之类的才能登录成功,这些内容就不展示了
npm publish


上图表示一个公开的包已经成功发布了
npm地址 https://www.npmjs.com/package/vue3-dxui

感兴趣的同学可以按照上述流程自己试试,或者将包拉下来,试试组件好不好用,如果有组件有bug,可以在npm地址或者本篇博客下留言,我会回复或者修复。

使用Vite和TypeScript带你从零打造一个属于自己的Vue3组件库



前言

随着前端技术的发展,业界涌现出了许多的UI组件库。例如我们熟知的ElementUI,Vant,AntDesign等等。但是作为一个前端开发者,你知道一个UI组件库是如何被打造出来的吗?

读完这篇文章你将学会:

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

作为一个前端拥有一个属于自己的UI组件库是一件非常酷的事情。它不仅能让我们对组件的原理有更深的理解,还能在找工作的时候为自己增色不少。试问有哪个前端不想拥有一套属于自己的UI组件库呢?

本文将使用Vue3和TypeScript来编写一个组件库,使用Vite+Vue3来对这个组件库中的组件进行调试,最后使用vite来对组件库进行打包并且发布到npm上。最终的产物是一个名为kitty-ui的组件库。

话不多说~ 接下来让我们开始搭建属于我们自己的UI组件库吧

Monorepo环境

首先我们要了解什么是Monorepo及它是如何搭建的吧

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

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

简单来说就是单仓库 多项目

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

为什么要使用pnpm?

因为它简单高效,它没有太多杂乱的配置,它相比于lerna操作起来方便太多

好了,下面我们就开始用pnpm来进行我们的组件库搭建吧

使用pnpm

安装

npm install pnpm -g

初始化package.json

pnpm init

新建配置文件 .npmrc

shamefully-hoist = true

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

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

monorepo的实现

接下就是pnpm如何实现monorepo的了。

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

packages:
- packages/**
- examples

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

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

安装对应依赖

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

pnpm i vue@next typescript less -D -w

因为我们开发的是vue3组件, 所以需要安装vue3,当然ts肯定是必不可少的(当然如果你想要js开发也是可以的,甚至可以省略到很多配置和写法。但是ts可以为我们组件加上类型,并且使我们的组件有代码提示功能,未来ts也将成为主流);less为了我们写样式方便,以及使用它的命名空间(这个暂时这里没用到,后面有时间再补

  • 配置tsconfig.json

这里的配置就不细说了,可以自行搜索都是代表什么意思。或者你可以先直接复制

npx tsc --init

tsconfig.json:


"compilerOptions":
"baseUrl": ".",
"jsx": "preserve",
"strict": true,
"target": "ES2015",
"module": "ESNext",
"skipLibCheck": true,
"esModuleInterop": true,
"moduleResolution": "Node",
"lib": ["esnext", "dom"]

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

其实搭建一个vite+vue3项目是非常容易的,因为vite已经帮我们做了大部分事情

初始化仓库

进入examples文件夹,执行

pnpm init

安装vite和@vitejs/plugin-vue

@vitejs/plugin-vue用来支持.vue文件的转译

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

这里安装的插件都放在根目录下

配置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>

注意:

新建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

vite启动默认端口为3000;在浏览器中打开localhost:3000 就会看我们的“启动测试”页面。

本地调试

新建包文件

本节可能和目前组件的开发关联不大,但是未来组件需要引入一些工具方法的时候会用到

接下来就是要往我们的packages文件夹冲填充内容了。

  • utils包

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

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


"name": "@kitty-ui/utils",
"version": "1.0.0",
"description": "",
"main": "index.ts",
"scripts":
"test": "echo \\"Error: no test specified\\" && exit 1"
,
"keywords": [],
"author": "",
"license": "ISC"

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

export const testfun = (a:number,b:number):number=>
return a + b
  • 组件库包(这里命名为kitty-ui)

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

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


"name": "kitty-ui",
"version": "1.0.0",
"description": "",
"main": "index.ts",
"scripts":
"test": "echo \\"Error: no test specified\\" && exit 1"
,
"keywords": [],
"author": "",
"license": "ISC"

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

import testfun from @kitty-ui/utils

const result = testfun (1,1)

console.log(result)
  • esno

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

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

npm i esno -g

包之间本地调试

进入components文件夹执行

pnpm install @kitty-ui/utils

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


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


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

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

试着开发一个button组件

在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组件

vue3项目使用button

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

所以这里我们直接在examples执行:pnpm i kitty-ui

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

"kitty-ui": "workspace:^1.0.0"

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

<template>
<div>
<Button />
</div>
</template>
<script lang="ts" setup>
import Button from kitty-ui
</script>

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

好了万事具...(其实还差个打包,这个后面再说~);接下来的工作就是专注于组件的开发了;让我们回到我们的button组件目录下(测试页面不用关,此时我们已经可以边开发边调试边看效果了)

因为我们的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>






TIPS

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打包

配置文件

打包们这里选择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而不需要额外配置按需引入(前提是你将模块分别导出),非常好用~

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

使用Vite和TypeScript带你从零打造一个属于自己的Vue3组件库_组件库

到这里其实打包的组件库只能给js项目使用,在ts项目下运行会出现一些错误,而且使用的时候还会失去代码提示功能,这样的话我们就失去了用ts开发组件库的意义了。所以我们需要在打包的库里加入声明文件(.d.ts)。

那么如何向打包后的库里加入声明文件呢? 其实很简单,只需要引入vite-plugin-dts

pnpm i vite-plugin-dts -D -w

然后修改一下我们的vite.config.ts引入这个插件

import  defineConfig  from "vite";
import vue from "@vitejs/plugin-vue"
import dts from vite-plugin-dts

export default defineConfig(

build: ...,
plugins: [
vue(),
dts(
//指定使用的tsconfig.json为我们整个项目根目录下掉,如果不配置,你也可以在components下新建tsconfig.json
tsConfigFilePath: ../../tsconfig.json
),
//因为这个插件默认打包到es下,我们想让lib目录下也生成声明文件需要再配置一个
dts(
outputDir:lib,
tsConfigFilePath: ../../tsconfig.json
)

]

)

因为这个插件默认打包到es下,我们想让lib目录下也生成声明文件需要再配置一个dts插件,暂时没有想到其它更好的处理方法~

然后执行打包命令你就会发现你的es和lib下就有了声明文件

其实后面就可以进行发布了,发布之前更改一下我们components下的package.json如下:


"name": "kitty-ui",
"version": "1.0.0",
"main": "lib/index.js",
"module":"es/index.js",
"scripts":
"build": "vite build"
,
"files": [
"es",
"lib"
],
"keywords": [
"kitty-ui",
"vue3组件库"
],
"author": "小月",
"license": "MIT",
"description": "",
"typings": "lib/index.d.ts"

解释一下里面部分字段

pkg.module

我们组件库默认入口文件是传统的CommonJS模块,但是如果你的环境支持ESModule的话,构建工具会优先使用我们的module入口

pkg.files

files是指我们1需要发布到npm上的目录,因为不可能components下的所有目录都被发布上去

开始发布

如果打包和发布流程你想做到自动化控制,你可以先点个赞,然后直接阅读 ​​从0搭建Vue3组件库:使用gulp自动化处理打包与发布​​ 当然你也可以继续往下阅读,看下本篇文章的处理方式

做了那么多终于到发布的阶段了;其实npm发包是很容易的,就拿我们的组件库kitty-ui举例吧

发布之前记得到​​npm​​官网注册个账户,如果你要发布@xx/xx这种包的话需要在npm新建个组织组织组织名就是@后面的,比如我建的组织就是kitty-ui,注册完之后你就可以发布了

首先要将我们代码提交到git仓库,不然pnpm发布无法通过,后面每次发版记得在对应包下执行 pnpm version patch你就会发现这个包的版本号patch(版本号第三个数) +1 了,同样的 pnpm version major major和 pnpm version minor 分别对应版本号的第一和第二位增加。

如果你发布的是公共包的话,在对应包下执行

pnpm publish --access public

输入你的账户和密码(记得输入密码的时候是不显示的,不要以为卡了)正常情况下应该是发布成功了

注意

发布的时候要将npm的源切换到npm的官方地址(​​https://registry.npmjs.org/​​); 如果你使用了其它镜像源的话

样式问题

引入我们打包后的组件你会发现没有样式,所以你需要在全局引入我们的style.css才行;如 main.ts中需要

import kitty-ui/es/style.css;

很显然这种组件库并不是我们想要的,我们需要的组件库是每个css样式放在每个组件其对应目录下,这样就不需要每次都全量导入我们的css样式。

下面就让我们来看下如何把样式拆分打包

处理less文件

首先我们需要做的是将less打包成css然后放到打包后对应的文件目录下,我们在components下新建build文件夹来存放我们的一些打包工具,然后新建buildLess.ts,首先我们需要先安装一些工具cpy和fast-glob

pnpm i cpy fast-glob -D -w
  • cpy

它可以直接复制我们规定的文件并将我们的文件copy到指定目录,比如buildLess.ts:

import cpy from cpy
import resolve from path

const sourceDir = resolve(__dirname, ../src)
//lib文件
const targetLib = resolve(__dirname, ../lib)
//es文件
const targetEs = resolve(__dirname, ../es)
console.log(sourceDir);
const buildLess = async () =>
await cpy(`$sourceDir/**/*.less`, targetLib)
await cpy(`$sourceDir/**/*.less`, targetEs)

buildLess()

然后在package.json中新增命令

...
"scripts":
"build": "vite build",
"build:less": "esno build/buildLess"
,
...

终端执行 pnpm run build:less 你就会发现lib和es文件对应目录下就出现了less文件.

但是我们最终要的并不是less文件而是css文件,所以我们要将less打包成css,所以我们需要用的less模块.在ts中引入less因为它本身没有声明文件所以会出现类型错误,所以我们要先安装它的 @types/less

pnpm i --save-dev @types/less -D -w

buildLess.ts如下(详细注释都在代码中)

import cpy from cpy
import resolve, dirname from path
import promises as fs from "fs"
import less from "less"
import glob from "fast-glob"
const sourceDir = resolve(__dirname, ../src)
//lib文件目录
const targetLib = resolve(__dirname, ../lib)
//es文件目录
const targetEs = resolve(__dirname, ../es)

//src目录

const srcDir = resolve(__dirname, ../src)

const buildLess = async () =>
//直接将less文件复制到打包后目录
await cpy(`$sourceDir/**/*.less`, targetLib)
await cpy(`$sourceDir/**/*.less`, targetEs)

//获取打包后.less文件目录(lib和es一样)
const lessFils = await glob("**/*.less", cwd: srcDir, onlyFiles: true )

//遍历含有less的目录
for (let path in lessFils)

const filePath = `$srcDir/$lessFils[path]`
//获取less文件字符串
const lessCode = await fs.readFile(filePath, utf-8)
//将less解析成css

const code = await less.render(lessCode,
//指定src下对应less文件的文件夹为目录
paths: [srcDir, dirname(filePath)]
)

//拿到.css后缀path
const cssPath = lessFils[path].replace(.less, .css)


//将css写入对应目录
await fs.writeFile(resolve(targetLib, cssPath), code.css)
await fs.writeFile(resolve(targetEs, cssPath), code.css)





buildLess()



执行打包命令之后你会发现对应文件夹下多了.css文件

使用Vite和TypeScript带你从零打造一个属于自己的Vue3组件库_vite_02

现在我已经将css文件放入对应的目录下了,但是我们的相关组件并没有引入这个css文件;所以我们需要的是每个打包后组件的index.js中出现如:

import "xxx/xxx.css"

之类的代码我们的css才会生效;所以我们需要对vite.config.ts进行相关配置

首先我们先将.less文件忽略

external: [vue, /\\.less/],

这时候打包后的文件中如button/index.js就会出现

import "./style/index.less";

然后我们再将打包后代码的.less换成.css就大功告成了

...
plugins: [
...


name: style,
generateBundle(config, bundle)
//这里可以获取打包后的文件目录以及代码code
const keys = Object.keys(bundle)

for (const key of keys)
const bundler: any = bundle[key as any]
//rollup内置方法,将所有输出文件code中的.less换成.css,因为我们当时没有打包less文件

this.emitFile(
type: asset,
fileName: key,//文件名名不变
source: bundler.code.replace(/\\.less/g, .css)
)



...
]
...

我们最终的vite.config.ts如下

import  defineConfig  from "vite";
import vue from "@vitejs/plugin-vue"
import dts from vite-plugin-dts

export default defineConfig(

build:
target: modules,
//打包文件目录
outDir: "es",
//压缩
minify: false,
//css分离
//cssCodeSplit: true,
rollupOptions:
//忽略打包vue和.less文件
external: [vue, /\\.less/],
input: [src/index.ts],
output: [

format: es,
//不用打包成.es.js,这里我们想把它打包成.js
entryFileNames: [name].js,
//让打包目录和我们目录对应
preserveModules: true,
//配置打包根目录
dir: es,
preserveModulesRoot: src
,

format: cjs,
//不用打包成.mjs
entryFileNames: [name].js,
//让打包目录和我们目录对应
preserveModules: true,
//配置打包根目录
dir: lib,
preserveModulesRoot: src

]
,
lib:
entry: ./index.ts,
formats: [es, cjs]

,




plugins: [
vue(),
dts(
//指定使用的tsconfig.json为我们整个项目根目录下掉,如果不配置,你也可以在components下新建tsconfig.json
tsConfigFilePath: ../../tsconfig.json
),
//因为这个插件默认打包到es下,我们想让lib目录下也生成声明文件需要再配置一个
dts(
outputDir: lib,
tsConfigFilePath: ../../tsconfig.json
),


name: style,
generateBundle(config, bundle)
//这里可以获取打包后的文件目录以及代码code
const keys = Object.keys(bundle)

for (const key of keys)
const bundler: any = bundle[key as any]
//rollup内置方法,将所有输出文件code中的.less换成.css,因为我们当时没有打包less文件

this.emitFile(
type: asset,
fileName: key,//文件名名不变
source: bundler.code.replace(/\\.less/g, .css)
)




]

)

最后我们将打包less与打包组件合成一个命令(package.json):

...
"scripts":
"build": "vite build & esno build/buildLess"
,
...

后续直接执行pnpm run build 即可完成所有打包啦

直接使用

如果你不想一步步的搭建,想直接使用现成的话,你可以直接把项目clone下来->​kittyui第一版​,然后你只需要以下几步便可将其完成

  • 安装pnpm npm i pnpm -g
  • 安装esno npm i esno -g
  • 安装所有依赖 pnpm install
  • 本地测试 进入examples文件夹执行 pnpm run dev 启动vue3项目
  • 打包 pnpm run build

写在最后

由于作者水平有限,难免会存在一些错误或不妥之处,希望各位能够不吝指出,一定及时修改。如果你对这个项目有更好的想法或者建议也欢迎在评论区提出,不胜感激。

后续我会对一些常用组件进行开发,每个组件的开发都会以文章的形式展现出来以供大家参考。也欢迎大家将项目fork下来,提交自己组件或者对kittyui的修改到​​kittyui​

创作不易,你的点赞就是我的动力!如果感觉对自己有帮助的话就请点个赞吧,感谢~


以上是关于vue3 构建属于自己的组件库dxui的主要内容,如果未能解决你的问题,请参考以下文章

猿创征文Vue3 企业级优雅实战 - 组件库框架 - 7 组件库文档的开发和构建

Vue3 企业级优雅实战 - 组件库框架 - 11 组件库的打包构建和发布

使用vite构建Vue3组件库,发布npm包

:Message组件

给自己封装的组件添加v-model(vue3)

:Pagination组件