如何使用Vue3.2+Vite2.7從0快速打造一個UI組件庫
1. 前言
最近自己學習寫了一個基于Vue3的組件庫,感覺有點意思,這篇文章來記錄一下我是怎么從0快速打造一個UI組件庫的
2. 使用Vite搭建官網(wǎng)
Vite是尤雨溪開發(fā)的一種新型前端構建工具,具體介紹可以查看官方文檔
2.1 創(chuàng)建項目
2.1.1. 全局安裝vite(這里我裝的時候是2.7.2)
$ yarn create vite@2.7.2
2.1.2. 構建一個vue模板(項目名可以改成自己的名字)
yarn create vite jw-ui --template vue
2.1.3. 裝好之后按照提示逐步執(zhí)行命令
cd jw-ui yarn yarn dev
可以看到界面

ps: 推薦的IDE和設置:VSCode + Volar
2.2 基本完成官網(wǎng)的搭建
2.2.1. 下載vue-router
yarn add vue-router@4
2.2.2. 創(chuàng)建home首頁與doc文檔頁 以及頂部導航欄
/* /views/home/index.vue 首頁*/
<template>
<div>
Home
</div>
</template>/* /views/doc/index.vue 文檔頁面 */
<template>
<div>
Doc
</div>
</template>/* /components/Topnav.vue 頂部導航欄組件 */
<template>
<div class="topnav">
<router-link to="/home">首頁</router-link>
<router-link to="/doc">文檔</router-link>
</div>
</template>2.2.3. 配置路由
創(chuàng)建路由配置文件
// router/index.ts
import { createRouter, createWebHashHistory } from "vue-router";
const history = createWebHashHistory();
const router = createRouter({
history,
routes: [
{ path: "/", redirect: "" },
],
});
export default router;在main.ts里導入,使得整個應用支持路由。
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
const app = createApp(App);
app.use(router);
app.mount("#app");修改App.vue
<template> <Topnav /> <router-view /> </template> <script setup> import Topnav from "./components/Topnav.vue"; </script>
到目前為止的效果

裝飾一下頂部導航欄后的效果

這里首頁按照自己喜歡的來寫CSS就好了,接下來講一下文檔頁面。
文檔頁需要一個側(cè)邊欄來切換不同組件的文檔,這里我就舉例做一個Button組件
// doc/index.vue
<template>
<div class="layout">
<div class="content">
<aside>
<router-link class="menu-item text-overflow" to="/doc/button"
>Button 組件</router-link
>
</aside>
<main style="padding-left: 302px">
<router-view />
</main>
</div>
</template>// router/index.ts 添加一個展示的button頁面
import { createRouter, createWebHashHistory } from "vue-router";
import Home from "../views/home/index.vue";
import Doc from "../views/doc/index.vue";
import ButtonDoc from "../views/doc/button/index.vue";
const history = createWebHashHistory();
const router = createRouter({
history,
routes: [
{ path: "/", redirect: "/home" },
{ path: "/home", component: Home },
{
path: "/doc",
component: Doc,
children: [{ path: "button", component: ButtonDoc }],
},
],
});
export default router;// /views/doc/button/index <template> <Button /> </template> <script setup> import Button from '../../../lib/button/index.vue' </script> <style lang="scss" scoped> </style>
展示效果

好了到這里官網(wǎng)總算是基本搭建完了,我們終于就可以愉快的在src/lib/button/index.vue文件里封裝組件啦。(封裝的組件都放在lib文件夾里,以后打包用)
3. 封裝一個Button組件
下面附上我寫的一個Button組件以及使用效果
PS: 需要注意的一點是封裝的樣式一定要加自己獨特的前綴我這里是 jw 以避免在項目中產(chǎn)生樣式重疊
<template>
<button class="jw-button" :class="classes">
<span v-if="loading" class="jw-loadingIndicator"></span>
<slot> {{ theme }} </slot>
</button>
</template>
<script setup lang="ts">
import { computed } from "vue";
const props = defineProps({
theme: {
type: String,
default: "default",
},
dashed: {
type: Boolean,
default: false,
},
size: {
type: String,
default: "default",
},
round: {
type: Boolean,
default: false,
},
disabled: {
type: Boolean,
default: false,
},
loading: {
type: Boolean,
default: false,
},
});
const { theme, dashed, size, round, disabled } = props;
const classes = computed(() => {
return {
[`jw-theme-${theme}`]: theme,
[`jw-theme-dashed`]: dashed,
[`jw-size-${size}`]: size,
[`is-round`]: round,
[`is-disabled`]: disabled,
};
});
</script>
<script lang="ts">
export default {
name: "JwButton",
};
</script>
<style lang="scss" scoped>
$h-default: 32px;
$h-small: 20px;
$h-large: 48px;
$white: #fff;
$default-color: #333;
$primary-color: #36ad6a;
$info-color: #4098fc;
$success-color: #85ce61;
$warning-color: #f0a020;
$error-color: #d03050;
$grey: grey;
$default-border-color: #d9d9d9;
$radius: 3px;
$green: #18a058;
.jw-button {
box-sizing: border-box;
height: $h-default;
background-color: #fff;
padding: 0 12px;
cursor: pointer;
display: inline-flex;
justify-content: center;
align-items: center;
white-space: nowrap;
border-radius: $radius;
box-shadow: 0 1px 0 fade-out(black, 0.95);
transition: all 250ms;
color: $default-color;
border: 1px solid $default-border-color;
user-select: none;
&:focus {
outline: none;
}
&::-moz-focus-inner {
border: 0;
}
&.jw-size-large {
font-size: 24px;
height: $h-large;
padding: 0 16px;
}
&.jw-size-small {
font-size: 12px;
height: $h-small;
padding: 0 8px;
}
&.is-round.jw-size-default {
border-radius: calc($h-default / 2);
}
&.is-round.jw-size-large {
border-radius: calc($h-large / 2);
}
&.is-round.jw-size-small {
border-radius: calc($h-small / 2);
}
&.jw-theme-default {
&:hover {
color: $green;
border-color: $green;
> .jw-loadingIndicator {
border-style: dashed;
border-color: $green $green $green transparent;
}
}
&:active {
color: darken($green, 20%);
border-color: darken($green, 20%);
> .jw-loadingIndicator {
border-style: dashed;
border-color: darken($green, 20%) darken($green, 20%)
darken($green, 20%) transparent;
}
}
&.jw-theme-dashed {
border-style: dashed;
}
> .jw-loadingIndicator {
border-style: dashed;
border-color: $default-color $default-color $default-color transparent;
}
}
&.jw-theme-primary {
background-color: $primary-color;
border-color: $primary-color;
color: $white;
&:hover {
background: lighten($primary-color, 20%);
border-color: lighten($primary-color, 20%);
}
&:active {
background-color: darken($primary-color, 20%);
border-color: darken($primary-color, 20%);
}
&.is-disabled {
cursor: not-allowed;
background: lighten($primary-color, 20%);
border-color: lighten($primary-color, 20%);
&:hover {
background: lighten($primary-color, 20%);
border-color: lighten($primary-color, 20%);
}
}
&.jw-theme-dashed {
border-style: dashed;
background-color: $white !important;
color: $primary-color;
> .jw-loadingIndicator {
border-style: dashed;
border-color: $primary-color $primary-color $primary-color transparent;
}
}
}
&.jw-theme-info {
background-color: $info-color;
border-color: $info-color;
color: $white;
&:hover {
background: lighten($info-color, 20%);
border-color: lighten($info-color, 20%);
}
&:active {
background-color: darken($info-color, 20%);
border-color: darken($info-color, 20%);
}
&.is-disabled {
cursor: not-allowed;
background: lighten($info-color, 20%);
border-color: lighten($info-color, 20%);
&:hover {
background: lighten($info-color, 20%);
border-color: lighten($info-color, 20%);
}
}
&.jw-theme-dashed {
border-style: dashed;
background-color: $white !important;
color: $info-color;
> .jw-loadingIndicator {
border-style: dashed;
border-color: $info-color $info-color $info-color transparent;
}
}
}
&.jw-theme-success {
background-color: $success-color;
border-color: $success-color;
color: $white;
&:hover {
background: lighten($success-color, 20%);
border-color: lighten($success-color, 20%);
}
&:active {
background-color: darken($success-color, 20%);
border-color: darken($success-color, 20%);
}
&.is-disabled {
cursor: not-allowed;
background: lighten($success-color, 20%);
border-color: lighten($success-color, 20%);
&:hover {
background: lighten($success-color, 20%);
border-color: lighten($success-color, 20%);
}
}
&.jw-theme-dashed {
border-style: dashed;
background-color: $white !important;
color: $success-color;
> .jw-loadingIndicator {
border-style: dashed;
border-color: $success-color $success-color $success-color transparent;
}
}
}
&.jw-theme-warning {
background-color: $warning-color;
border-color: $warning-color;
color: $white;
&:hover {
background: lighten($warning-color, 20%);
border-color: lighten($warning-color, 20%);
}
&:active {
background-color: darken($warning-color, 20%);
border-color: darken($warning-color, 20%);
}
&.is-disabled {
cursor: not-allowed;
background: lighten($warning-color, 20%);
border-color: lighten($warning-color, 20%);
&:hover {
background: lighten($warning-color, 20%);
border-color: lighten($warning-color, 20%);
}
}
&.jw-theme-dashed {
border-style: dashed;
background-color: $white !important;
color: $warning-color;
> .jw-loadingIndicator {
border-style: dashed;
border-color: $warning-color $warning-color $warning-color transparent;
}
}
}
&.jw-theme-error {
background-color: $error-color;
border-color: $error-color;
color: $white;
&:hover {
background: lighten($error-color, 20%);
border-color: lighten($error-color, 20%);
}
&:active {
background-color: darken($error-color, 20%);
border-color: darken($error-color, 20%);
}
&.is-disabled {
cursor: not-allowed;
background: lighten($error-color, 20%);
border-color: lighten($error-color, 20%);
&:hover {
background: lighten($error-color, 20%);
border-color: lighten($error-color, 20%);
}
}
&.jw-theme-dashed {
border-style: dashed;
background-color: $white !important;
color: $error-color;
> .jw-loadingIndicator {
border-style: dashed;
border-color: $error-color $error-color $error-color transparent;
}
}
}
> .jw-loadingIndicator {
width: 14px;
height: 14px;
display: inline-block;
margin-right: 4px;
border-radius: 8px;
border-color: $white $white $white transparent;
border-style: solid;
border-width: 2px;
animation: jw-spin 1s infinite linear;
}
}
@keyframes jw-spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
雖然有不完美,但差不多就這個意思吧??
4. 封裝Markdown組件介紹文檔
4.1. 下載
vite-plugin-markdown:一個插件可以讓你導入Markdown文件作為各種格式的vite項目。
github-markdown-css:復制GitHub Markdown風格
yarn add github-markdown-css vite-plugin-markdown
4.2. main.ts中引入
import "github-markdown-css";
4.3. vite.config.js中配置vite-plugin-markdown插件
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
const md = require("vite-plugin-markdown");
export default defineConfig({
plugins: [vue(),
md.plugin({
mode: ["html", "vue"],
}),]
})4.4. 封裝Markdown組件
// /components/Markdown.vue
<template>
<article class="markdown-body" v-html="content"></article>
</template>
<script setup lang="ts">
// 傳入的md文件
const props = defineProps({
content: {
type: String,
required: true,
},
});
</script>4.5. 創(chuàng)建介紹頁面路由
import { h } from "vue";
import { createRouter, createWebHashHistory } from "vue-router";
import Home from "../views/home/index.vue";
import Doc from "../views/doc/index.vue";
import ButtonDoc from "../views/doc/button/index.vue";
const history = createWebHashHistory();
import Markdown from "../components/Markdown.vue";
const md = (string) => h(Markdown, { content: string, key: string });
import { html as Intro } from "../../markdown/intro.md";
const IntroDoc = md(Intro);
const router = createRouter({
history,
routes: [
{ path: "/", redirect: "/home" },
{ path: "/home", component: Home },
{
path: "/doc",
component: Doc,
children: [
{ path: "intro", component: IntroDoc },
{ path: "button", component: ButtonDoc },
],
},
],
});
export default router;可以看到,最終md就能導入,并且生成了github上md的樣式了??

5. 自定義代碼塊獲取組件展示源代碼
5.1. 自定義插件vue-custom-blocks-plugin
import path from "path";
import fs from "fs";
import { baseParse } from "@vue/compiler-core";
const vitePluginVue = {
name: "preview",
transform(code, id) {
if (
!/\/src\/views\/doc\/.*\.preview\.vue/.test(id) ||
!/vue&type=preview/.test(id)
) {
return;
}
let path = `.${id.match(/\/src\/views\/doc\/.*\.preview\.vue/)[0]}`;
const file = fs.readFileSync(path).toString();
const parsed = baseParse(file).children.find((n) => n.tag === "preview");
const title = parsed.children[0].content;
const main = file.split(parsed.loc.source).join("").trim();
return `export default function (Component) {
Component.__sourceCode = ${JSON.stringify(main)}
Component.__sourceCodeTitle = ${JSON.stringify(title)}
}`.trim();
},
};
export default vitePluginVue;5.2. 在vite.config.ts中配置
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
const md = require("vite-plugin-markdown");
import vitePluginVue from "./plugins/vue-custom-blocks-plugin";
export default defineConfig({
plugins: [vue(),
md.plugin({
mode: ["html", "vue"],
}),
vitePluginVue]
})5.3. 封裝Preview組件展示
<template>
<div class="pre">
<h2>
{{ component.__sourceCodeTitle }}
<Button @click="hideCode" v-if="codeVisible">隱藏代碼</Button>
<Button @click="showCode" v-else>查看代碼</Button>
</h2>
<div class="pre-component">
<component :is="component" />
</div>
<div class="pre-code" v-if="codeVisible">
<pre class="language-html">{{ component__sourceCOde }}</pre>
</div>
</div>
</template>
<script setup lang="ts">
import Button from "../lib/button/index.vue";
import { computed, ref } from "vue";
const props = defineProps({
component: Object,
});
const showCode = () => (codeVisible.value = true);
const hideCode = () => (codeVisible.value = false);
const codeVisible = ref(false);
</script>
<style lang="scss" scoped>
$border-color: #d9d9d9;
.pre {
border: 1px solid $border-color;
margin: 16px 0px 32px;
max-width: 700px;
min-width: 300px;
> h2 {
font-size: 20px;
padding: 8px 16px;
border-bottom: 1px solid $border-color;
display: flex;
justify-content: space-between;
}
&-component {
padding: 16px;
}
&-actions {
padding: 8px 16px;
border-top: 1px dashed $border-color;
}
&-code {
padding: 8px 16px;
border-top: 1px dashed $border-color;
> pre {
line-height: 1.1;
font-family: Consolas, "Courier New", Courier, monospace;
margin: 0;
background-color: #fff;
}
}
}
</style>5.4. 使用Preview組件
views/doc/button/index.vue
<template>
<div>
<Preview :component="Button1" />
</div>
</template>
<script setup>
import Button1 from "./Button1.preview.vue";
import Preview from "../../../components/Preview.vue";
</script>
<style lang="scss">
.jw-button + .jw-button {
margin-left: 20px;
}
</style>/views/doc/button/Button1.preview.vue
<preview>基礎示例</preview> <template> <Button /> </template> <script setup lang="ts"> import Button from "../../../lib/button/index.vue"; </script>
現(xiàn)在,只要編寫上面的以.preview.vue后綴的文件就行了。
- 效果:

5.5. 高亮源代碼
下載prismjs
yarn add prismjs
對Preview組件做修改
<template>
<div class="pre">
<h2>
{{ component.__sourceCodeTitle }}
<Button @click="hideCode" v-if="codeVisible">隱藏代碼</Button>
<Button @click="showCode" v-else>查看代碼</Button>
</h2>
<div class="pre-component">
<component :is="component" />
</div>
<div class="pre-code" v-if="codeVisible">
<pre class="language-html" v-html="html" />
</div>
</div>
</template>
<script setup lang="ts">
import Button from "../lib/button/index.vue";
import { computed, ref } from "vue";
import "prismjs";
import "prismjs/themes/prism.css";
const Prism = (window as any).Prism;
const props = defineProps({
component: Object,
});
console.log(props.component.__sourceCode);
const html = computed(() => {
return Prism.highlight(
props.component.__sourceCode,
Prism.languages.html,
"html"
);
});
const showCode = () => (codeVisible.value = true);
const hideCode = () => (codeVisible.value = false);
const codeVisible = ref(false);
</script>- 效果

6. 去掉示例中的文件導入

6.1. 在lib目錄下創(chuàng)建main.ts 這個也是作為之后打包上傳至npm的入口
import { App } from "vue";
import JwButton from "./button/index.vue";
export { JwButton };
const components = [JwButton];
// 全局注冊主鍵
export function registerJwUi(app: App): void {
for (const component of components) {
app.component(component.name, component);
}
}
export default registerJwUi;6.2. main.ts中導入注冊
import JwUi from "./lib/index"; app.use(JwUi);
6.3. 這樣在示例中就可以直接用了/src/views/doc/button/Button1.preview
<preview>基礎示例</preview> <template> <jw-button /> </template>
6.4. 效果

7. 部署到github官網(wǎng)
7.1. 打包
yarn build
7.2. 上傳至github
github創(chuàng)建一個新的倉庫 將dist上傳只倉庫
7.3. 進入倉庫Settings最底層

7.4. 找到GitHub Pages

7.5. 選擇master分支 點擊保存 鏈接就生成了

7.6 一鍵部署
創(chuàng)建deploy.sh文件
rm -rf dist && yarn build && cd dist && git init && git add . && git commit -m "update" && git branch -M master && git remote add origin git@github.com:coderyjw/jw-ui-website.git && git push -f -u origin master && cd - echo https://coderyjw.github.io/jw-ui-website/
執(zhí)行命令
sh deploy.sh
8. 上傳至npm
8.1. 創(chuàng)建rollup.config.js配置文件
// 為了保證版本一致,請復制我的 package.json 到你的項目,并把 name 改成你的庫名
import esbuild from 'rollup-plugin-esbuild'
import vue from 'rollup-plugin-vue'
import scss from 'rollup-plugin-scss'
import dartSass from 'sass';
import { terser } from "rollup-plugin-terser"
import alias from '@rollup/plugin-alias'
import path from "path";
import resolve from 'rollup-plugin-node-resolve'
export default {
input: 'src/lib/index.ts',
output: [{
globals: {
vue: 'Vue'
},
name: 'Yjw-ui',
file: 'dist/lib/yjw-ui.js',
format: 'umd',
plugins: [terser()]
}, {
name: 'Yjw-ui',
file: 'dist/lib/yjw-ui.esm.js',
format: 'es',
plugins: [terser()]
}],
plugins: [
scss({ include: /\.scss$/, sass: dartSass }),
esbuild({
include: /\.[jt]s$/,
minify: process.env.NODE_ENV === 'production',
target: 'es2015'
}),
vue({
include: /\.vue$/,
}),
alias({
entries: [
{
find: '@', // 別名名稱,作為依賴項目需要使用項目名
replacement: path.resolve(__dirname, 'src'),
customResolver: resolve({
extensions: ['.js', '.jsx', '.vue', '.sass', '.scss']
})
}
]
}),
],
}8.2. 執(zhí)行命令打包
rollup -c
8.3. 效果
可以看到dist文件下有l(wèi)ib文件,就是打包后的文件

8.4. 上傳至npm
需要先注冊npm賬號
npm login // 先登錄 npm publish // 發(fā)布
9. 最后
到此這篇關于如何使用Vue3.2+Vite2.7從0快速打造一個UI組件庫的文章就介紹到這了,更多相關Vue3.2+Vite2.7打造UI組件庫內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
在Vue里如何把網(wǎng)頁的數(shù)據(jù)導出到Excel的方法
這篇文章主要介紹了在Vue里如何把網(wǎng)頁的數(shù)據(jù)導出到Excel,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-09-09
詳解vue-cli+element-ui樹形表格(多級表格折騰小計)
這篇文章主要介紹了vue-cli + element-ui 樹形表格(多級表格折騰小計),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-04-04
element中el-autocomplete的常見用法示例
這篇文章主要給大家介紹了關于element中el-autocomplete的常見用法的相關資料,文中通過實例代碼介紹的非常詳細,對大家學習或者使用element具有一定的參考學習價值,需要的朋友可以參考下2023-03-03


