前言:本文介绍如何做一个简单的 Vue UI 组件库 DEMO 项目。简单地介绍利用脚手架搭建目录框架结构,到开发组件,到测试组件(暂无),再到发布到 npm 的一整个 流程。
撇开组件库中可能用到的复杂技术不谈,做一个简单的玩具项目还是很简单的(既不需要适配,也不考虑兼容性,更不考虑性能),不是一座能直接压死人的大山。
参考 Vue CLI 官方文档 进行安装
npm install -g @vue/cli
# OR
yarn global add @vue/cli
将 src 目录重命名为 examples,此文件夹主要用于日后组件库的文档展示以及测试页面;新建 packages 目录,用于存放所有组件文件,在目录下新建若干个文件夹,单 个组件放在一个文件夹内,每个文件夹内都包含一个 index.ts 文件用于单组件注册,在 packages 目录下包含一个 index.ts 用于全局注册,注册所有组件库的组件;新 建 typings 文件夹,用于存放所有 ts 定义文件,将原存放于根目录下的两个 shims.d.ts 文件移入该文件夹;在项目根目录下新建 vue.config.js 文件,修改相关配置 以让修改目录结构后的项目跑起来。
修改后目录结构如下:
vue.config.js 的配置内容,配置 index 中的入口地址,指向 examples 文件夹下的 main.ts 文件;添加 chainWebpack 的配置,将新建的 packages 目录加入编译。
// vue.config.js
const path = require('path')
module.exports = {
pages: {
index: {
entry: 'examples/main.ts',
template: 'public/index.html',
filename: 'index.html'
}
},
chainWebpack: config => {
config.module.rule('js').include.add(path.resolve(__dirname, 'packages')).end().use('babel').loader('babel-loader')
}
}
dist 是打包后的文件夹,也是发布到 npm 的文件夹(现在暂时打包不起来),examples 是文档与测试的文件夹,packages 是组件的文件夹,typings 是存放定义的文件 夹。
至此,运行 yarn && yarn dev 应该是可以把项目跑起来了。
由于真正的组件库开发这一块内容非常多而且非常复杂,所以这里就只写一个简单的 Tag 组件,意思到位,具体想玩什么样的逻辑与样式,可以自行拓展。
新建 Tag 文件夹,Tag 文件夹下新建 src 目录与 index.ts 文件,src 文件夹下创建 Tag.vue。
具体代码如下:
// packages/Tag/src/Tag.vue
<template>
<span :class="['wkb-tag', type]"><slot /></span>
</template>
<script>
export default {
name: 'Tag',
props: {
type: {
type: String,
default: 'primary'
}
}
}
</script>
<style scoped lang="scss">
.wkb-tag {
font-weight: 500;
font-size: 18px;
line-height: 24px;
text-transform: uppercase;
padding: 2px 10px;
border-radius: 10.5px;
&.primary {
color: #387dff;
background-color: rgba(#387dff, 0.2);
}
&.success {
color: #23b899;
background-color: rgba(#23b899, 0.2);
}
&.danger {
color: #fe7c4b;
background-color: rgba(#fe7c4b, 0.2);
}
}
</style>
// packages/Tag/index.ts
import { PluginObject } from 'vue'
import WkbTag from './src/Tag.vue'
const injectInstallObject: PluginObject<null> = {
install(Vue) {
Vue.component('wkb-tag', WkbTag)
},
pluginName: 'wkb-tag'
}
const WkbComponent = Object.assign(WkbTag, injectInstallObject)
if (typeof window !== 'undefined' && window.Vue) {
injectInstallObject.install(window.Vue as any)
}
export default WkbComponent
// packages/index.ts
import { PluginFunction } from 'vue'
import WkbTag from './Tag'
const WkbComponentList = [WkbTag]
const install: PluginFunction<null> = function(Vue) {
WkbComponentList.forEach(component => {
Vue.component(component.pluginName, component)
})
}
if (typeof window !== 'undefined' && window.Vue) {
install(window.Vue as any)
}
export default { install }
组件开发完成后可以在 examples 文件夹下撰写使用文档及做一些基础测试,确保组件的功能正常。
方便起见,直接使用全局注册的方式注册组件,代码如下:
// examples/App.vue
<template>
<div id="app">
<div class="wrapper">
<h1>WKB-UI</h1>
<div class="tag-group">
<h2>Tag 标签</h2>
<wkb-tag type="primary">堆溢出</wkb-tag>
<wkb-tag type="success">堆溢出</wkb-tag>
<wkb-tag type="danger">堆溢出</wkb-tag>
</div>
</div>
</div>
</template>
<script lang="ts">
import Vue from 'vue'
export default Vue.extend({})
</script>
// examples/main.ts
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import WkbUI from '../packages'
Vue.use(WkbUI)
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App)
}).$mount('#app')
成功调用的话,可以在浏览器看到如下内容:
至此,一个 Tag.vue 组件就开发完了,既可以但组件注册,也可以全局统一注册。
在根目录新建一个 .npmignore 文件,配置一下不上传到 npm 的内容,我的配置如下:
// .npmignore
.DS_Store
examples/
packages/
public/
vue.config.js
babel.config.js
.prettierrc
.eslintrc.js
.editorconfig
.browserslistrc
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
配置完了之后还差最后一步,记得 yarn build 打包出来一个 dist 文件夹,最终发布到 npm 上的其实只要这个 dist 文件夹(typings)就够了。
我们还需要最后配置一下 yarn build 的命令,因为默认是将这个项目编译成一个应用而不是一个插件,我们要修改配置使其变为一个插件。
参考 vue 官网相关介绍 进行如下配置,将 build 的入口指向 packages 下的 index.ts:
{
"scripts": {
"dev": "vue-cli-service serve",
"build": "vue-cli-service build --target lib packages/index.ts",
"lint": "vue-cli-service lint"
}
}
自行操作,顺便完善下项目根目录下的 README,介绍一下组件库的使用方式。
很简单其实也就两部操作,如果没有 npm 账号的话首先先去 npm 官网 注册一个。
先登录:
npm login
Username: 你的用户名
password: 你的密码
email: 填你自己的邮箱就行
登陆完了,发布:
npm publish --access=public
自行想办法用代理解决该问题。
使用 js 开发时,通常会使用下面的方法来注册组件。
import Button from './src/Button.vue'
Button.install = function (Vue, config) {
// 处理 config 的相关逻辑
Vue.component(Button.name, Button)
}
诚然,使用 js 开发时并不会出现什么问题,一切都在正常的运行。但是当使用 ts 时,坑就出现了:
首先 ts 的单页组件一般会用 Vue.extend()
来导出,这就导致导出的对象是拿不到 name 属性的,也就是以上代码的 Button.name
会出错,会错误的显示为构造
函数上的名称 VueComponent
。
其次,Button 的类型被自动推断为VueConstructor<Button>
,这也就意味着如果想直接在该属性上添加 install 方法会直接报
错Property 'install' does not exist on type 'ExtendedVue<Vue, unknown, unknown, unknown, Record<never, any>>'.
。
甚至如果想全局注册组件,Vue.use()
方法会报出重载错误
Property 'install' is missing in type 'VueConstructor<Record<never, any> & Vue>' but required in type 'PluginObject<any>'.
,这是因为导出的模块被
错误的推导为 VueConstructor<Button>
,而其本身是不具备 install 方法的。
解决方法如下:
// Button.vue
import Vue from 'vue'
export default Vue.extend({
name: 'WbkButton'
})
// index.ts
import { PluginObject } from 'vue'
import WkbButton from './src/Button.vue'
// 此处的泛型为 install 第二个参数的类型,本组件用不到所以设置为了 null,可以更具自己的需求做响应的更改
const injectInstallObject: PluginObject<null> = {
install(Vue) {
Vue.component('wkb-button', WkbButton)
}
}
const WkbComponent = Object.assign(WkbButton, injectInstallObject)
export default WkbComponent
// main.ts 全局注册
import WkbButton from '../packages/Button/index'
Vue.use(WkbButton) // Vue.use<T> 的泛型同样也是定义的 use 方法的第二个参数,即传入 install 方法的第二个参数
// App.vue 局部注册
import Vue from 'vue'
import WkbButton from '../packages/Button'
export default Vue.extend({
components: {
'wkb-button': WkbButton
}
})
如果你使用了淘宝镜像或者 nrm 来管理你的 npm 源,请切换到 npm 本身来。
检查 packages.json 中,name 在 npm 上是唯一且不存在已经发布了的包的;private 属性一定得是 false,私有的包不付费是发布不上去的。对于第一种情况,建议使 用私人名作为前缀,如 @weekbin/package-name,使用这种情况发布的包,在 npm publish 时要特别声明我的包时公开的,npm publish --access=public。