前端web开发过程中,往往需要开发UI组件库,今天这篇文章就是分享一下如何开发一个UI组件库,并且发布到npm仓库,并且为UI组件库编写开发文档。
B站视频:
开发vue3 UI组件库,并且发布到NPM,并且编写指导文档_哔哩哔哩_bilibili
一、效果图
查看代码:
二、创建VUE3项目
npm create vite
三、创建package
在src目录下创建package文件夹,作为组件库的主目录。在package文件夹下创建components文件夹,作为UI组件的主目录。 在components文件夹下创建button文件夹,并在在button文件夹下创建index.ts和index.vue文件,index.vue文件用来编写button组件,index.ts文件用来导出button组件,方便将来按需引入组件,以下是各文件分别的内容。
package/components/index.vue
<template>
<button class="dd-button" :class="`dd-button-${size} dd-button-${type} `">
<slot></slot>
</button>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
name: "dd-button",
props: {
size: {
type: String,
default: "middle",
},
type: {
type: String,
default: "normal",
},
},
setup() {},
});
</script>
<style lang="less" scoped>
.dd-button {
font-size: 14px;
min-width: 97px;
padding: 4px;
box-sizing: border-box;
}
.dd-button-large {
height: 40px;
}
.dd-button-middle {
height: 36px;
}
.dd-button-small {
height: 32px;
}
.dd-button-primary {
background-color: #4033da;
color: #ffffff;
border: 1px solid #4033da;
}
.dd-button-normal {
background-color: #ffffff;
color: #333333;
border: 1px solid #eee9e9;
}
.dd-button-error {
background-color: #570a20;
color: #ffffff;
}
</style>
package/components/index.ts
import { App } from 'vue';
import ddButton from './index.vue';
ddButton.install = (app: App) => {
app.component(ddButton.name, ddButton);
return app;
}
export default ddButton;
接下来在package下创建一个index.ts文件,用来导出所有的组件。
package/index.ts
import { App } from "vue";
import ddButton from "./components/button";
const components = [ddButton];
const install = (app: App) => {
for (const item of components){
app.component(item.name, item);
}
return app;
}
export default { install };
然后在package下创建package.json文件,用来编写组件库的配置。
package/package.json
{
"name": "@wdddev/dd-ui",
"version": "0.0.1",
"description": "dd 组件库",
"author": {
"name": "wdddev"
},
"private": false,
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org/"
},
"dependencies": {}
}
到这里基本的组件库就已经写好了,可以发布了。
四、发布到NPM仓库
发布之前需要创建一个npm账号。可以访问npm | Sign Up进行注册。然后打开cmd, 执行npm adduser,然后根据提示登陆用户。登录成功之后,就可以在package目录下执行 npm publish 进行发布。
发布之后就可以执行 npm install @wdddev/dd-ui进行安装,并且在main.ts中引入使用。
五、编写组件文档
编写组件文档,主要做到两个功能,第一个是需要展示demo组件的内容和demo中的编写代码。另外一个就是表格文档。用来展示属性、插槽、方法、事件等。所以需要写两个组件,一个是代码的预览组件,一个是表格文档组件。
document.vue 表格文档组件
<template>
<h3>{{ title }}</h3>
<table class="document-table">
<thead>
<tr>
<th v-for="(item, indnx) in tableheaders" :key="indnx">{{ item }}</th>
</tr>
</thead>
<tbody>
<tr v-for="(itemRow, indexRow) in body" :key="indexRow">
<td v-for="(itemCol, indexCol) in itemRow" :key="indexCol">
{{ itemCol }}
</td>
</tr>
</tbody>
</table>
</template>
<script setup lang="ts">
import { computed } from "vue";
const props = defineProps({
title: {
type: String,
default: "",
},
type: {
type: String,
default: "prop",
values: ["prop", "event", "slot", "methods"],
},
body: {
type: Array,
default: [],
},
headers: {
type: Array,
default: [],
},
});
const headerByType: any = {
prop: ["属性", "说明", "数据类型", "可选值", "默认值"],
slot: ["插槽名", "说明"],
event: ["事件名", "说明", "回调参数"],
methods: ["方法名", "说明", "入参"],
};
const tableheaders = computed(() => {
if (props.headers.length === 0) {
const type = props.type as string;
return headerByType[type];
}
return props.headers;
});
</script>
<style lang="less" scoped>
.document-table {
width: 100%;
margin: 20px 0;
th, td {
padding: 0.5rem 1rem;
border: 1px solid #777474;
white-space: normal;
text-align: left;
}
}
</style>
priview.vue 用来预览和展示代码的组件
<template>
<div class="priview-button-contain">
<dd-button type="error" @click="hideCode" v-if="codeVisibel" >隐藏代码</dd-button>
<dd-button type="primary" @click="showCode" v-else >查看代码</dd-button>
</div>
<div class="priview-component-contain">
<component :is="component" />
</div>
<div v-if="codeVisibel" class="priview-code-contain">
<pre v-html="codeHtml"></pre>
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';
import 'prismjs';
import 'prismjs/themes/prism.css';
const Prism = (window as any).Prism;
const props = defineProps({
component: Object,
componentName: String,
demoName: String
})
const codeVisibel = ref(false);
function hideCode() {
codeVisibel.value = false;
}
function showCode() {
codeVisibel.value = true;
}
const codeResourc = ref('');
const isDev = import.meta.env.MODE;
const getCode = async () => {
if (isDev === 'development') {
codeResourc.value = (await import(/* @vite-ignore */ `../views/document/${props.componentName}/${props.demoName}.vue?raw`)).default;
} else {
codeResourc.value = await fetch(`src/views/document/${props.componentName}/${props.demoName}.vue`).then((res) => res.text())
}
}
onMounted(() => {
getCode();
})
const codeHtml = computed(() => {
return Prism.highlight(codeResourc.value, Prism.languages.html, 'html')
})
</script>
<style lang="less" scoped>
.priview-button-contain {
width: 300px;
height: 40px;
margin: 20px 0 ;
}
.priview-component-contain {
width: 100%;
border: 1px solid #c3bcbc;
margin-bottom: 20px;
padding: 10px;
box-sizing: border-box;
}
.priview-code-contain {
width: 100%;
border: 1px solid #c3bcbc;
margin-bottom: 20px;
padding: 10px;
box-sizing: border-box;
font-size: 16px;
}
</style>
接下来写一个文档的主页面,分别是左侧菜单和右边的内容区域。在src下创建views文件夹,并且创建doc.vue文件,作为主页面。
src/views/doc.vue
<template>
<div class="doc-contain">
<div class="doc-contain-menu">
<div
v-for="menu in menuList"
:key="menu.path"
@click="changeView(menu.path)"
class="doc-contain-menu-item"
:class="{ 'doc-contain-menu-item-active': $route.path == menu.path }"
>
{{ menu.title }}
</div>
</div>
<router-view class="doc-contain-content" />
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import MenuList from "./menu";
import { useRouter } from "vue-router";
export default defineComponent({
setup() {
return {
menuList: MenuList,
router: useRouter(),
};
},
methods: {
changeView(path: any) {
this.router.push(path);
},
},
});
</script>
<style lang="less" scoped>
.doc-contain {
width: calc(100vw - 40px);
height: calc(100vw - 40px);
display: flex;
.doc-contain-menu {
height: 100%;
width: 220px;
overflow-y: auto;
padding: 20px 10px;
box-sizing: border-box;
border: 1px solid #e6e0e0;
.doc-contain-menu-item {
width: 100%;
height: 40px;
padding: 2px 4px;
box-sizing: border-box;
border: 1px solid #eae4e4;
display: flex;
align-items: center;
}
.doc-contain-menu-item-active {
border: 1px solid #3c09a3;
}
}
.doc-contain-content {
width: calc(100% - 240px);
height: 100%;
overflow-y: auto;
margin-left: 20px;
}
}
</style>
接下来在views下创建document文件夹,然后创建button文件夹,作为按钮组件demo的主目录。然后创建index.vue文件和demo.vue文件。index.vue文件用来作为文档的主要页面,demo.vue用来示范如何使用button组件。
src/views/document/index.vue
<template>
<div class="document-contain">
<h1>dd-button</h1>
<h3>按钮</h3>
<div class="priview-contain">
<priview :component="buttonPriview" componentName="button" demoName="demo"></priview>
<document title="属性" type="prop" :body="propBody"></document>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, reactive } from 'vue'
import priview from '../../../components/priview.vue';
import document from '../../../components/document.vue';
import buttonDemo from './demo.vue'
export default defineComponent({
components: {
priview,
document
},
setup() {
return {
buttonPriview: buttonDemo,
propBody: reactive([
['size', "尺寸", 'String', 'samll large middle', 'middle'],
['type', "类型", 'String', 'primary error normal', 'normal'],
])
}
},
})
</script>
<style lang="less" scoped>
.document-contain {
width: 100%;
.priview-contain {
width: 100%;
}
}
</style>
src/views/document/demo.vue
<template>
<div class="btn-demo">
<dd-button class="button-position" type="primary">主要按钮</dd-button>
<dd-button class="button-position">普通按钮</dd-button>
<dd-button class="button-position" type="error">错误类型</dd-button>
</div>
<div class="btn-demo">
<dd-button class="button-position" size="large">大尺寸</dd-button>
<dd-button class="button-position" size="middle">中等尺寸</dd-button>
<dd-button class="button-position" size="small">小尺寸</dd-button>
</div>
</template>
<style lang="less" scoped>
.btn-demo {
display: flex;
align-items: center;
margin-top: 20px;
.button-position {
margin-right: 20px;
}
}
</style>
最后就是配置路由,通过页面跳转就可以了。就可以看到界面内容了。