如何从零开始搭建一个 Vue3 组件库?
最近有一些小伙伴问雨声:想要本人做一个组件库,但不晓得如何开始?看到他人那些组件库各种各样、形形色色的配置文件和文件构造,基本不晓得该从何下手。
别慌,其实再简单的构造与配置,也是从一开始非常简单的我的项目,在开发过程中依据须要,经验一直的迭代、重构与欠缺后才逐步形成的。
咱们想要开始的时候,其实最重要的第一步就是口头起来,先开一个最简略的我的项目,写一个最简略的组件,实现一次最简略的打包。
开始☕!
创立我的项目
首先是包管理工具,举荐大家选用 pnpm
,相较于其余包治理,它更快、更节约空间,还有一个很重要的劣势就是能十分不便地创立和治理 monorepo,装置可自行查阅 官网。
而后就是脚手架的抉择,既然是 Vue3 组件库,那 Vite 必然是不二之选了。首先它简直能够说是 Vue3 的官配,其次在做库我的项目方面,Vite 在打包时没 Webpack 这么麻烦,在开发时也比 Rollup 更容易搭建开发服务。
咱们间接应用 Vite 官网提供的 vue-ts
模版来疾速创立一个原始我的项目:
pnpm create vite demo-ui --template vue-ts
之后咱们把 Git 初始化一下:
git init
接着把 package.json
中 vue-tsc
的内容去掉,因为类型谬误的查看咱们能够间接借助编辑器的 Volar 插件实时进行。
package.json
{
"name": "demo-ui",
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {"vue": "^3.2.25"},
"devDependencies": {
"@vitejs/plugin-vue": "^2.3.3",
"typescript": "^4.5.4",
"vite": "^2.9.9"
}
}
最初装置一下依赖,我的项目创立就 OK 了。
pnpm i
第一个组件
有了我的项目之后,咱们不论其余乌七八糟的,先来个最简略的组件。
咱们先对目录构造做简略的革新,使其适应 Lib 型我的项目。
这是咱们原始创立后的构造:
咱们把 public
、src/assets
、src/components/HelloWorld.vue
都删掉,增加 src/index.ts
、src/components/button.vue
:
其中的 src/App.vue
和 src/main.ts
留着用于开发组件的时候随时看成果。
接着咱们快快地写一个按钮组件,不论其余的,页面上能看到就行。
src/components/button.vue
<template>
<button class="d-button">
<slot></slot>
</button>
</template>
<script lang="ts">
import {defineComponent} from 'vue'
export default defineComponent({name: 'DButton'})
</script>
src/App.vue
<template>
<DButton>
一个按钮
</DButton>
</template>
<script setup lang="ts">
import DButton from './components/button.vue'
</script>
而后咱们启动开发服务:
pnpm run dev
关上浏览器:
平平无奇,没有任何款式的按钮组件进去了。
不过通常的组件库应该是以 app.use(DemoUI)
的模式来装置的,咱们再来改装一下,导出一个具备 install
办法的对象。
src/index.ts
import DButton from './components/button.vue'
import type {App} from 'vue'
const components = [DButton]
export function install(app: App) {
components.forEach(component => {app.component(component.name, component)
})
}
export default {install}
export {DButton}
src/main.ts
import {createApp} from 'vue'
import App from './App.vue'
import DemoUI from './index'
createApp(App).use(DemoUI).mount('#app')
src/App.vue
<template>
<DButton>
一个按钮
</DButton>
</template>
<script setup lang="ts">
// import DButton from './components/button.vue'
</script>
打包组件
下一步咱们就来实现咱们的组件打包。
借助 vite
的 build.lib
配置疾速实现库打包,留神打包的时候要排除 Vue 自身。
vite.config.ts
import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
build: {
lib: {
entry: 'src/index.ts',
formats: ['cjs', 'es']
},
rollupOptions: {external: ['vue']
}
},
plugins: [vue()]
})
而后咱们执行打包命令:
pnpm run build
最初咱们为 package.json
增加上 main
和 module
字段,那么一个可公布的组件库雏形就进去了(留神把 private
去掉或者改为 false
)。
package.json
{
"name": "demo-ui",
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"main": "dist/demo-ui.cjs.js",
"module": "dist/demo-ui.es.js",
"dependencies": {"vue": "^3.2.25"},
"devDependencies": {
"@vitejs/plugin-vue": "^2.3.3",
"typescript": "^4.5.4",
"vite": "^2.9.9"
}
}
这个时候如果你执行 pnpm publish
那它就真的能够公布进来了(版本号要更新),不过它还有点薄弱,还是先不要焦急。
增加款式
通过下面的步骤,咱们失去一个最简略的组件库,不过是光溜溜的,咱们还须要为这个组件增加款式。
这里咱们选用 sass
作为 css
的预处理语言,当然你也能够抉择你喜爱的计划,不过雨声认为 sass
与 css
的过渡足够平滑,而且性能也足够弱小,所以比拟喜爱用。
pnpm i -D sass
而后咱们在 src
下创立一个 style
目录,用来专门寄存款式文件,并增加 index.scss
和 button.scss
:
接着咱们给 button.scss
写上一些按钮的款式,并在 index.scss
中导出。
src/style/button.scss
.d-button {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
height: 32px;
padding: 0 14px;
color: #fff;
background-color: #339af0;
border: 1px solid #339af0;
border-radius: 4px;
outline: 0;
}
src/style/index.scss
@use './button.scss';
随后咱们在 src/index.ts
中将款式也引入进来。
src/index.ts
import './style/index.scss'
import DButton from './components/button.vue'
// 省略上面
这时咱们就失去一个具备自定义款式的按钮组件了:
再执行 pnpm run build
就能够看到打包后的文件有了 style.css
的款式文件。
类型申明
作为一个 Vue3 + TypeScript 的我的项目,天然不能少了主动生成类型申明文件。
这里咱们借助 vite-plugin-dts
插件来实现打包时主动生成类型申明文件。
pnpm i -D vite-plugin-dts
而后在 Vite 配置文件中增加插件:
vite.config.ts
import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'
import dts from 'vite-plugin-dts'
export default defineConfig({
build: {
lib: {
entry: 'src/index.ts',
formats: ['cjs', 'es']
},
rollupOptions: {external: ['vue']
}
},
plugins: [vue(),
dts({insertTypesEntry: true, copyDtsFiles: false})
]
})
咱们再一次执行 pnpm run build
进行打包,能够看到打包后的文件也蕴含类型申明文件了:
最初在 package.json
中增加 types
字段:
package.json
{
// ...
"main": "dist/demo-ui.cjs.js",
"module": "dist/demo-ui.es.js",
"types": "dist/index.d.ts",
// ...
}
Lint 工具
就和惯例我的项目一样,组件库的我的项目也须要像是 ESLint 和 Stylelint 这样的来标准对立代码格调的工具的。
在我的项目的搭建上,雨声感觉到了这一步才是最乏味的,就如同拼乐高一样,有了主体之后,就能够开始往上拼上各种各样的模块,而后看着我的项目变得丰盛起来,十分有意思。
咱们先来装置 Eslint 全家桶,蕴含 Vue 和 TypeScript 相干的:
pnpm i -D eslint eslint-plugin-import eslint-plugin-n eslint-plugin-node eslint-plugin-promise
pnpm i -D eslint-plugin-vue @vue/eslint-config-typescript vue-eslint-parser
这里咱们根底 ESLint 配置采纳 eslint-config-standard
作为根底,你能够抉择你喜爱的:
pnpm i -D eslint-config-standard
接着在根目录创立 .eslintrc.js
和 .eslintignore
两个文件,配置大部分来自继承,在这根底上将一小部分不太适宜组件库状况的做一些调整。
.eslintrc.js
module.exports = {
root: true,
env: {node: true},
parser: 'vue-eslint-parser',
extends: [
'plugin:vue/vue3-essential',
'plugin:vue/vue3-strongly-recommended',
'plugin:vue/vue3-recommended',
'standard',
'@vue/typescript/recommended'
],
parserOptions: {ecmaVersion: 'latest'},
rules: {
'no-console':
process.env.NODE_ENV === 'production'
? [
'error',
{allow: ['warn', 'error']
}
]
: 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'space-before-function-paren': [
'error',
{
anonymous: 'always',
named: 'never',
asyncArrow: 'always'
}
],
'vue/match-component-file-name': 'off',
'vue/html-self-closing': 'off'
}
}
.eslintignore
dist/
node_modules/
.*rc.js
*.config.js
*.css
*.pcss
*.scss
*.json
同时记得在编辑器上装置 ESLint 相干的插件,而后回过头看看先前的代码有没有报错,有的话就调整一下。
下一个是 Stylelint 全家桶~
当然了,如果你喜爱 Free Style 的话,齐全能够不装 Stylelint 系列。
pnpm i -D postcss postcss-html postcss-preset-env
pnpm i -D stylelint stylelint-config-html stylelint-config-recess-order stylelint-config-recommended-vue
pnpm i -D stylelint-config-standard stylelint-config-standard-scss stylelint-order
在根目录创立 .stylelintrc.js
和 .stylelintignore
两个文件,Stylelint 为了适应 sass
的书写习惯,这里做了比拟多的定制化,有趣味深刻理解的能够自行拓展浏览。
.stylelintrc.js
module.exports = {
defaultSeverity: 'error',
extends: [
'stylelint-config-standard',
'stylelint-config-standard-scss',
'stylelint-config-html',
'stylelint-config-recommended-vue',
'stylelint-config-recess-order'
],
plugins: ['stylelint-order'],
rules: {
'no-empty-source': process.env.NODE_ENV === 'production' ? true : null,
'block-no-empty': process.env.NODE_ENV === 'production' ? true : null,
'string-quotes': 'single',
'at-rule-no-unknown': null,
'at-rule-no-vendor-prefix': true,
'declaration-property-value-disallowed-list': {'/^transition/': ['/all/'],
'/^background/': ['http:', 'https:'],
'/^border/': ['none'],
'/.+/': ['initial']
},
'media-feature-name-no-vendor-prefix': true,
'property-no-vendor-prefix': true,
'selector-no-vendor-prefix': true,
'value-no-vendor-prefix': true,
'at-rule-empty-line-before': [
'always',
{except: ['first-nested'],
ignore: [
'after-comment',
'blockless-after-same-name-blockless',
'blockless-after-blockless'
],
ignoreAtRules: ['else']
}
],
'no-descending-specificity': null,
'custom-property-empty-line-before': null,
'selector-class-pattern': ['^([#a-z][$#{}a-z0-9]*)((-{1,2}|_{2})[$#{}a-z0-9]+)*$',
{message: 'Expected class selector to be kebab-case'}
],
'keyframes-name-pattern': ['^([#a-z][$#{}a-z0-9]*)((-{1,2}|_{2})[$#{}a-z0-9]+)*$',
{message: 'Expected keyframe name to be kebab-case'}
],
'color-function-notation': null,
'scss/at-import-partial-extension': 'always',
'function-no-unknown': null,
'alpha-value-notation': 'percentage',
'scss/dollar-variable-empty-line-before': null,
'scss/operator-no-newline-after': null
},
ignoreFiles: [/* see .stylelintignore */]
}
.stylelintignore
dist/
node_modules/
*.js
*.ts
*.tsx
*.svg
*.gif
*.md
一样别忘了在编辑器上装置 Stylelint 相干的插件,以取得编辑器的谬误提醒,之前的款式如果有报错那也跟着调整一下。
至此,咱们就把最要害的两个 Lint 工具初始化了。
提交
提交 Message 的规范化和提交时的主动格式化代码,咱们采纳 Commitlint、lint-staged 和 husky 配合实现。
尽管 Commitlint 也属于 Lint 工具,不过因为波及到 Git 提交,就专门放到这个章节一起讲。
老样子,咱们先来装置相干的依赖:
pnpm i -D @commitlint/cli @commitlint/config-conventional
pnpm i -D husky is-ci lint-staged && pnpm dlx husky install
# 在 windows 零碎下只须要把 && 改为 ; 即可拼接多条命令
pnpm i -D husky is-ci lint-staged; pnpm dlx husky install
其中 pnpm dlx husky install
会帮忙咱们主动实现 husky 的初始化。
之后咱们在 package.json
的 scripts
下找到 prepare
这个命令,并做一些小调整:
package.json
{
"scripts": {"prepare": "is-ci || husky install"}
}
装置实现后,咱们开始配置 Commitlint,在根目录创立 .commitlintrc.js
,看到咱们下面装置 Commitlint 的时候还多装置了一个配置,咱们就继承这个配置再做一些拓展:
.commitlintrc.js
module.exports = {extends: ['@commitlint/config-conventional'],
rules: {'body-leading-blank': [2, 'always'],
'footer-leading-blank': [1, 'always'],
'header-max-length': [2, 'always', 108],
'subject-empty': [2, 'never'],
'type-empty': [2, 'never'],
'type-enum': [
2,
'always',
[
'feat',
'fix',
'perf',
'style',
'docs',
'test',
'refactor',
'build',
'ci',
'chore',
'revert',
'wip',
'workflow',
'types',
'release'
]
]
}
}
这里能够略微锁一下 type-enum
外面的内容,能够看到有很多的提交类型,这些类型来自于 Augular 提交标准,并在其根底上做了一些拓展,是目前开源社区里比拟风行的计划,感兴趣的小伙伴能够自行拓展浏览。
接着咱们开始配置 husky,它能够让咱们不便地创立一些 Git 钩子脚本,使的咱们能够在提交过程的各个阶段执行一些命令,其中就包含调用各种 Lint 工具来查看代码和提交 Message。
先前咱们执行了 pnpm dlx husky install
这个命令后,能够看到根目录下多了一个 .husky
的文件夹,这外面就是搁置相干配置文件的中央。
咱们应用 husky 提供的命令创立 common
、commit-msg
和 pre-commit
三个脚本文件,或者间接在 .husky
目录下手动创立也能够:
pnpm dlx husky add .husky/common
pnpm dlx husky add .husky/commit-msg
pnpm dlx husky add .husky/pre-commit
接着别离调整外面的内容:
.husky/common
command_exists () {command -v "$1" >/dev/null 2>&1}
# Workaround for Windows 10, Git Bash and Yarn
if command_exists winpty && test -t 1; then
exec < /dev/tty
fi
.husky/commit-msg
#!/bin/sh
. "$(dirname"$0")/_/husky.sh"
. "$(dirname"$0")/common"
npx --no-install commitlint --edit "$1"
.husky/pre-commit
#!/bin/sh
. "$(dirname"$0")/_/husky.sh"
. "$(dirname"$0")/common"
[-n "$CI"] && exit 0
pnpm run precommit
husky 这样就算是配置好了,除去 common
是一个补丁脚本外,另外两个脚本会别离在咱们进行提交 Message 解决时,以及行将开始提交时执行,咱们也别离放入了调用 Commitlint 查看提交 Message 和一条 pnpm run precommit
的命令,不过这条命令在咱们的我的项目里临时还没有。
下一步,咱们来配置 lint-staged 的同时把这条命令补全。
lint-staged 的配置文件为 .lintstagedrc
,雨声习惯会把它一起放入 .husky
便于管理,你也能够放到本人喜爱的地位,比方根目录下。
.husky/.lintstagedrc
{"*.{js,jsx,ts,tsx}": ["eslint --fix"],
"*.{css,scss,html}": ["stylelint --fix"],
"*.vue": [
"eslint --fix",
"stylelint --fix"
]
}
而后咱们在 package.json
中增加 precommit
命令:
{
"scripts": {"precommit": "lint-staged -c ./.husky/.lintstagedrc -q"}
}
这样一来,在每次 pre-commit
脚本执行时,就会调用 lint-staged 对提交的文件执行 Lint 相干的命令,并且咱们想要手动查看要提交的文件时也能够间接执行这条命令。
事不宜迟,咱们马上来进行咱们的第一次提交~
git add .
为了验证一下 Commitlint 是否能失常工作,咱们先来一个不符合规范的提交 Message:
git commit -m "init"
能够看到控制台输入了一些错误信息,并且提交失败了:
确认了 Commitlint 能够失常工作后咱们就来一次符合规范的提交:
git commit -m "chore: init"
提交胜利,功败垂成。
当初,咱们的我的项目构造长这个样子:
告一段落
本篇介绍了从零开始搭建一个 Vue3 组件库的第一步,实现了一个最简略的组件库我的项目,并且可能打包。
同时还介绍了三个 Lint 工具的拆卸,以及如何实现提交规范化及主动格式化代码。
只管咱们没有真的公布到 npm 上,不过咱们如果想试试的话,能够应用 link 协定在本地的其余我的项目上试一试。
同样用 pnpm create vite
创立一个我的项目,在 package.json
的 dependencies
上手动增加 link 协定的依赖:
{
"dependencies": {
"demo-ui": "link: 到 demo-ui 的根目录的物理门路"
// 如 "link:D:/ui-library/demo-ui"
}
}
执行 pnpm i
后就能够像应用其余库一样,来应用本地的 demo-ui
了。
未完待续
🚧 组件库我的项目的搭建还远不止这些,随着深刻咱们还会遇到诸如:
- 组件的开发工作流
- 组件库的公布流程设计
- 组件的单元测试
- 组件的款式的治理
- css 变量设计与主题
- 实现间接的按需加载
- 组件库文档的构建
- 组件库 Playground 的实现
- 应用 monorepo 治理组件库的多个模块
- 等等 …
👀 下一篇 ,咱们持续围绕着从零开始搭建一个 Vue3 组件库会遇到的问题,持续讲讲如何欠缺组件库我的项目的配套设施。
如果有不同的见解或倡议,欢送在评论区感性探讨,或者如果下面列举的点中有哪些更想晓得的也能够留言哦。
最初,举荐一下雨声的开源组件库 Vexip-UI – GitHub(小伙伴们赏一个🌟)
现正招募小伙伴来尝试应用或者保护与倒退这个我的项目。
如果你对写组件感兴趣的话,十分欢送来试一试,还有许多组件的性能等着你来开发与欠缺,你能够随时回复感兴趣的 issue 参加探讨或发动一个新的 issue。
与大家共勉,独特学习提高🎉~