如何使用Vue3.2+Vite2.7從0快速打造一個UI組件庫
1. 前言
最近自己學(xué)習(xí)寫了一個基于Vue3的組件庫,感覺有點(diǎn)意思,這篇文章來記錄一下我是怎么從0快速打造一個UI組件庫的
2. 使用Vite搭建官網(wǎng)
Vite是尤雨溪開發(fā)的一種新型前端構(gòu)建工具,具體介紹可以查看官方文檔
2.1 創(chuàng)建項(xiàng)目
2.1.1. 全局安裝vite(這里我裝的時候是2.7.2)
$ yarn create vite@2.7.2
2.1.2. 構(gòu)建一個vue模板(項(xiàng)目名可以改成自己的名字)
yarn create vite jw-ui --template vue
2.1.3. 裝好之后按照提示逐步執(zhí)行命令
cd jw-ui yarn yarn dev
可以看到界面
ps: 推薦的IDE和設(shè)置:VSCode + Volar
2.2 基本完成官網(wǎng)的搭建
2.2.1. 下載vue-router
yarn add vue-router@4
2.2.2. 創(chuàng)建home首頁與doc文檔頁 以及頂部導(dǎo)航欄
/* /views/home/index.vue 首頁*/ <template> <div> Home </div> </template>
/* /views/doc/index.vue 文檔頁面 */ <template> <div> Doc </div> </template>
/* /components/Topnav.vue 頂部導(dǎo)航欄組件 */ <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里導(dǎo)入,使得整個應(yīng)用支持路由。
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>
到目前為止的效果
裝飾一下頂部導(dǎo)航欄后的效果
這里首頁按照自己喜歡的來寫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: 需要注意的一點(diǎn)是封裝的樣式一定要加自己獨(dú)特的前綴我這里是 jw 以避免在項(xiàng)目中產(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
:一個插件可以讓你導(dǎo)入Markdown文件作為各種格式的vite項(xiàng)目。
github-markdown-css
:復(fù)制GitHub Markdown風(fēng)格
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就能導(dǎo)入,并且生成了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>基礎(chǔ)示例</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. 去掉示例中的文件導(dǎo)入
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中導(dǎo)入注冊
import JwUi from "./lib/index"; app.use(JwUi);
6.3. 這樣在示例中就可以直接用了/src/views/doc/button/Button1.preview
<preview>基礎(chǔ)示例</preview> <template> <jw-button /> </template>
6.4. 效果
7. 部署到github官網(wǎng)
7.1. 打包
yarn build
7.2. 上傳至github
github創(chuàng)建一個新的倉庫 將dist上傳只倉庫
7.3. 進(jìn)入倉庫Settings最底層
7.4. 找到GitHub Pages
7.5. 選擇master分支 點(diǎn)擊保存 鏈接就生成了
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配置文件
// 為了保證版本一致,請復(fù)制我的 package.json 到你的項(xiàng)目,并把 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: '@', // 別名名稱,作為依賴項(xiàng)目需要使用項(xiàng)目名 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. 最后
到此這篇關(guān)于如何使用Vue3.2+Vite2.7從0快速打造一個UI組件庫的文章就介紹到這了,更多相關(guān)Vue3.2+Vite2.7打造UI組件庫內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
在Vue里如何把網(wǎng)頁的數(shù)據(jù)導(dǎo)出到Excel的方法
這篇文章主要介紹了在Vue里如何把網(wǎng)頁的數(shù)據(jù)導(dǎo)出到Excel,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09深入理解Vue.js3中Reactive的實(shí)現(xiàn)
reactive是Vue 3的Composition API中的一個函數(shù),它允許你創(chuàng)建一個響應(yīng)式的數(shù)據(jù)對象,本文主要介紹了深入理解Vue.js3中Reactive的實(shí)現(xiàn),感興趣的可以了解一下2024-01-01v-for中動態(tài)校驗(yàn)el-form表單項(xiàng)的實(shí)踐
在項(xiàng)目開發(fā)中,我們經(jīng)常會遇到表單保存的功能,本文主要介紹了v-for中動態(tài)校驗(yàn)el-form表單項(xiàng)的實(shí)踐,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧<BR>2022-05-05詳解vue-cli+element-ui樹形表格(多級表格折騰小計)
這篇文章主要介紹了vue-cli + element-ui 樹形表格(多級表格折騰小計),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-04-04vue實(shí)現(xiàn)右鍵點(diǎn)擊彈框信息功能
這篇文章主要介紹了vue實(shí)現(xiàn)右鍵點(diǎn)擊彈框信息功能方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-12-12element中el-autocomplete的常見用法示例
這篇文章主要給大家介紹了關(guān)于element中el-autocomplete的常見用法的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用element具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2023-03-03