Recently, I want to learn the knowledge of VUE3, and the best way to learn something is to learn in use, so that I can quickly master the necessary knowledge. So I want to learn by developing a component library, and have the following article.
Here’s what you can learn from this article:
- Vue3 project construction
- Component development process
- Component source code display
- Deployment and publication of component libraries
1. Use Vite to build the official website
1.1 Initializing the Project
Install create-viet-app globally
yarn global add create-vite-app@1.18. 0
/ / or
npm i -g create-vite-app@1.18. 0
Copy the code
Create a project directory
cva gulu-ui-1
/ / or
create-vite-app gulu-ui-1
// Gulu-uI-1 can be changed to any name
Copy the code
Install the vue – the router
// Run the command to view all versions of vue-router
npm info vue-router versions
// Install dependency, current latest version is 4.0.6
yarn add vue-router@4.06.
Copy the code
Install the sass
yarn add sass -D
Copy the code
1.2 Setting up the official website
Our official website mainly has two pages, one is the home page, one is the document page.
Initialize the vue – the router
Create two pages, a Home page home.vue and a document page doc.vue. Both pages have a common component at the top of the navigation topnav.vue. Next, let’s create these components.
views/Home.vue
<template> <div> <div class="topnavAndBanner"> <TopNav /> <div class="banner"> < H1 > Wheels UI</h1> <h2> A UI framework for learning </h2> <p Class = "actions" > < a href = "https://github.com/liuzb30/gulu-ui" > making < / a >, < the router - link to = "/ doc" > start < / router - the link > < / p > < / div > < / div > < div class = "features" > < ul > < li > < SVG > < use xlink: href = "# icon - vue3" > < / use > < / SVG > < h3 > based on Vue 3 < / h3 > < p > use Vue 3 Composition API</p> </li> <li> < SVG >< use xlink:href="#icon-ts"></use> </ SVG >< h3> Based on TypeScript</h3> <p> source code TypeScript written < / p > < / li > < li > < SVG > < use xlink: href = "# icon - code" > < / use > < / SVG > < h3 > code easy to read < / h3 > < p > each component's source code is very concise < / p > < / li > </ul> </div> </div> </template> <script lang="ts"> import TopNav from ".. /components/TopNav.vue"; export default { components: { TopNav, }, }; </script> <style lang="scss" scoped> $green: #02bcb0; $border-radius: 4px; $color: #007974; .topnavAndBanner { background: linear-gradient( 145deg, rgba(227, 255, 253, 1) 0%, rgba(183, 233, 230, 1) 100% ); clip-path: ellipse(80% 60% at 50% 40%); } .features { margin: 64px auto; width: 100%; @media (min-width: 800px) { width: 800px; } @media (min-width: 1200px) { width: 1200px; } @media (max-width: 500px) { ul { padding-left: 10px; } } > ul { display: flex; flex-wrap: wrap; > li { width: 400px; margin: 16px 0; display: grid; justify-content: start; align-content: space-between; grid-template-areas: "icon title" "icon text"; grid-template-columns: 80px auto; grid-template-rows: 1fr auto; > svg { grid-area: icon; width: 64px; height: 64px; } > h3 { grid-area: title; font-size: 28px; } > p { grid-area: text; } } } } .banner { color: $color; padding: 100px 0; display: flex; justify-content: center; align-items: center; flex-direction: column; > .actions { padding: 8px 0; a { margin: 0 8px; background: $green; color: white; display: inline-block; padding: 8px 24px; border-radius: $border-radius; &:hover { text-decoration: none; } } } } </style>Copy the code
views/Doc.vue
<template> <div class="layout"> <TopNav class="nav" toggleMenuButtonVisible /> <div class="content"> <aside V-if ="menuVisible"> <h2> document </h2> < OL > <li> <router-link to="/doc/intro"> Introduction </router-link> </li> <li> <router-link Installed to = "/ doc/install" > < / router - the link > < / li > < li > < the router - link to = "/ doc/get started -" > start using < / router - the link > < / li > < / ol > < H2 > Component list </h2> < OL > <li> <router-link to="/doc/switch">Switch component </router-link> </li> <li> <router-link To ="/doc/button">Button component </router-link> </li> <li> <router-link to="/doc/dialog"> </li> <li> < the router - link to = "/ doc/tabs" > tabs component < / router - the link > < / li > < / ol > < value > < main id = "main" > < the router - view / > < / main > < / div > </div> </template> <script lang="ts"> import TopNav from ".. /components/TopNav.vue"; import { inject, Ref } from "vue"; export default { components: { TopNav }, setup() { const menuVisible = inject<Ref<boolean>>("menuVisible"); // get return { menuVisible }; }}; </script> <style lang="scss" scoped> $lightgreen: #bceeeb; .layout { display: flex; flex-direction: column; height: 100vh; > .nav { flex-shrink: 0; } > .content { flex-grow: 1; padding-top: 60px; padding-left: 156px; @media (max-width: 500px) { padding-left: 0; } } } .content { display: flex; > aside { flex-shrink: 0; } > main { flex-grow: 1; padding: 16px; background: white; } } aside { background: $lightgreen; width: 150px; padding: 16px 0; position: fixed; top: 0; left: 0; padding-top: 70px; height: 100%; z-index: 10; > h2 { font-size: 22px; margin-bottom: 4px; padding: 0 16px; } > ol { > li { > a { display: block; padding: 8px 16px; text-decoration: none; } .router-link-active { background: white; } } } } main { overflow: auto; } </style>Copy the code
Top navigation Components/Topnav.vue
<template> <div class="topnav"> <router-link class="logo" to="/"> <svg class="icon"> <use Xlink: href = "# icon - wheel" > < / use > < / SVG > < / router - the link > < ul class = "menu" > < li > < the router - link to = "/ doc" > document < / router - the link > < / li > </ul> <svg v-if="toggleMenuButtonVisible" class="toggleAside" @click="toggleMenu"> <use xlink:href="#icon-menu"></use> </svg> </div> </template> <script lang="ts"> import { inject, Ref } from "vue"; export default { props: { toggleMenuButtonVisible: { type: Boolean, default: false, }, }, setup() { const menuVisible = inject<Ref<boolean>>("menuVisible"); // get const toggleMenu = () => { menuVisible.value = ! menuVisible.value; }; return { toggleMenu }; }}; </script> <style lang="scss" scoped> $color: #007974; .topnav { color: $color; display: flex; padding: 16px; position: fixed; top: 0; left: 0; width: 100%; z-index: 11; justify-content: center; align-items: center; background-color: white; > .logo { max-width: 6em; margin-right: auto; > svg { width: 32px; height: 32px; } } > .menu { display: flex; white-space: nowrap; flex-wrap: nowrap; > li { margin: 0 1em; } } > .toggleAside { width: 32px; height: 32px; position: absolute; left: 16px; top: 50%; transform: translateY(-50%); / / background: fade - out (black, 0.9); display: none; } @media (max-width: 500px) { > .menu { display: none; } > .logo { margin: 0 auto; } > .toggleAside { display: inline-block; } } } </style>Copy the code
Next, modify the entry files main.js and app.vue.
Since we are going to use ts for development, we will change main.js to main.ts.
main.ts
import { createApp } from 'vue'
import App from './App.vue'
import './index.scss'
import './assets/svg.js'
import router from './router'
const app = createApp(App)
app.use(router)
app.mount('#app')
Copy the code
SVG. Js is used to display ICONS. This file can be downloaded at Github.
Routing configuration we put in a separate file
router.ts
import {createWebHashHistory, createRouter} from 'vue-router'
import Home from './views/Home.vue'
import Doc from './views/Doc.vue'
const history = createWebHashHistory()
const router = createRouter({
history: history,
routes: [{path: '/'.component: Home},
{path: '/doc'.component: Doc}
]
})
export default router
Copy the code
Modify the contents of app.vue
<template> <router-view /> </template> <script lang="ts"> import { ref, provide } from "vue"; import router from "./router"; export default { name: "App", setup() { const width = document.documentElement.clientWidth; Const menuVisible = ref(width <= 500? false : true); provide("menuVisible", menuVisible); // set router.afterEach(() => { if (width <= 500) { menuVisible.value = false; }}); }}; </script>Copy the code
Change the global style index.css to index.scss
index.scss
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
ul.ol {
list-style: none;
}
a {
text-decoration: none;
color: inherit;
&:hover {
border-bottom: 1px solid;
cursor: pointer; }}h1.h2.h3.h4.h5.h6 {
font-weight: normal;
}
body {
font-size: 16px;
Font-family: arial, sans-serif
/ / the answer to see https://github.com/zenozeng/fonts.css/
font-family: -apple-system, "Noto Sans"."Helvetica Neue", Helvetica,
"Nimbus Sans L", Arial, "Liberation Sans"."PingFang SC"."Hiragino Sans GB"."Noto Sans CJK SC"."Source Han Sans SC"."Source Han Sans CN"."Microsoft YaHei"."Wenquanyi Micro Hei"."WenQuanYi Zen Hei"."ST Heiti",
SimHei, "WenQuanYi Zen Hei Sharp", sans-serif;
}
Copy the code
shime-vue.d.ts
This file is used to solve the problem where the module xxx.vue could not be found.
declare module '*.vue'{
import {ComponentOptions} from 'vue'
const componentOptions:ComponentOptions
export default componentOptions
}
Copy the code
Here, our official website framework is basically built.
2. Switch component development
Component development generally consists of three steps:
- Demand analysis
- API design
- Write the code
2.1 Requirement Analysis
With reference to antD, Element and other component libraries, our Switch component looks something like this, and its function is to Switch on and off.
2.2 API design
Next we’ll design the component API, which is how others will use our component. Our Switch component accepts a value attribute, which can be either a string or a Boolean value.
<Switch value="true" /> // value is the string "true"
<Switch value="false" /> // value is the string "false"
<Switch :value=" true " /> // value is a Boolean value true
<Switch :value=" false " /> // value is a Boolean value false
Copy the code
2.3 write code
Next, we began to write code, because our component development, need a page to show, so, we need to create two components, one is the Switch component, one is the SwitchDemo component.
lib/Switch.vue
The lib directory is the component directory that houses our component library.
<template> <button @click="toggle" :class="{ checked: value }"><span></span></button> </template> <script> export default { props: { value: Boolean | String, }, setup(props, context) { const toggle = () => { context.emit("update:value", ! props.value); }; return { toggle }; }}; </script> <style lang="scss" scoped> $h: 22px; $h2: $h - 4px; button { height: $h; width: $h * 2; border: none; background: #bfbfbf; border-radius: $h/2; position: relative; > span { position: absolute; top: 2px; left: 2px; height: $h2; width: $h2; background: white; border-radius: $h2 / 2; transition: all 250ms; } &.checked { background: #1890ff; > span { left: calc(100% - #{$h2} - 2px); } } &:focus { outline: none; } &:active { > span { width: $h2 + 4px; } } &.checked:active { > span { width: $h2 + 4px; margin-left: -4px; } } } </style>Copy the code
components/SwitchDemo.vue
<template> <div> <Switch v-model:value="bool" /> </div> </template> <script lang="ts"> import { ref } from "vue"; import Switch from ".. /lib/Switch.vue"; export default { components: { Switch }, setup() { const bool = ref(false); return { bool }; }}; </script>Copy the code
With both components developed, we now need to configure the route to access the SwitchDemo page
router.ts
.import SwitchDemo from './components/SwitchDemo.vue'
const history = createWebHashHistory()
const router = createRouter({
...
{
path: '/doc'.component: Doc,
redirect:'/doc/switch'.children:[
{path:'switch'.component:SwitchDemo},
]
}
]
})
export default router
Copy the code
Open the documentation page and test whether the component we developed works properly.
Click the Switch button and it does work, but our demo looks pretty ugly and there’s no source code to look at. Let’s develop a Demo component to show the components.
3. Demo component development
The idea is to use a switch.demo1.vue component to demonstrate the component, while the Demo component displays the incoming component and displays the source code for the component.
The new switch. Not. Vue
<demo> general usage </demo> <template> <Switch V-model :value="bool" /> </template> <script lang="ts"> import {ref} from "vue"; import Switch from ".. /lib/Switch.vue"; export default { components: { Switch }, setup() { const bool = ref(false); return { bool }; }}; </script>Copy the code
The demo tag is an identifier that distinguishes components from other components. How do we get the source code of the component
Demo.vue
<template> <div class="demo"> <h2>{{ component.__sourceCodeTitle }}</h2> <div class="demo-component"> <component :is="component" /> </div> <div class="demo-actions"> <div @click="hideCode" v-if="codeVisible"> hideCode </div> <div </div> <div class="demo-code" v-if="codeVisible"> <pre class="language-html" v-html=" Prism.highlight(component.__sourceCode, Prism.languages.html, 'html') " /> </div> </div> </template> <script lang="ts"> import "prismjs"; import "prismjs/themes/prism.css"; import { ref } from "vue"; const Prism = (window as any).Prism; export default { props: { component: { type: Object, require: true, }, }, setup() { const codeVisible = ref(false); const showCode = () => (codeVisible.value = true); const hideCode = () => (codeVisible.value = false); return { Prism, codeVisible, showCode, hideCode }; }}; </script> <style lang="scss"> $border-color: #d9d9d9; .demo { border: 1px solid $border-color; border-radius: 6px; margin: 16px 0 32px; > h2 { font-size: 18px; padding: 8px 16px; border-bottom: 1px solid $border-color; } &-component { padding: 16px; } &-actions { padding: 8px 16px; border-top: 1px dashed $border-color; } &-code { padding: 8px 16px; border-top: 1px dashed $border-color; > < span style = "max-width: 100%; clear: both; font-family: Consolas, "Courier New", Courier, monospace; margin: 0; } } } </style>Copy the code
Prism, a plug-in, is used to highlight code.
The installation relies on PrismJS
yarn add prismjs
Copy the code
Let’s focus on this code
Prism.highlight(component.__sourceCode, Prism.languages.html, 'html')
Copy the code
You may wonder where the component.__sourcecode attribute came from, which was transforms vite’s vueCustomBlockTransforms.
Let’s create a new vite. Config. ts to configure vite.
vite.config.ts
// @ts-nocheck
import fs from 'fs'
import {baseParse} from '@vue/compiler-core'
export default {
vueCustomBlockTransforms: {
demo: (options) = > {
const { code, path } = options
const file = fs.readFileSync(path).toString()
// Parse the file to find any tags with demo tags
const parsed = baseParse(file).children.find(n= > n.tag === 'demo')
// Get the contents of the Demo label
const title = parsed.children[0].content
// Get the source code
const main = file.split(parsed.loc.source).join(' ').trim()
// Add a __sourceCode and __sourceCodeTitle attribute to the Component when exporting
return `export default function (Component) {
Component.__sourceCode = The ${JSON.stringify(main)
}
Component.__sourceCodeTitle = The ${JSON.stringify(title)}} `.trim()
}
}
};
Copy the code
This is where the __sourceCode attribute is added.
At this point, our Demo component has been developed.
4. Package and deploy
Next we need to publish our component library to NPM, and we need to do two things
- Deploy the official website to get the official website online, there is documentation so that people will use your wheels
- Release gulu-UI so that other developers can install the source code using NPM install gulu-UI
4.1 Deploying the Official Website
Upload the dist directory to the web. Set the build path when yarn build
Steps to launch the official website
- Delete the dist directory if there is one
- Add a line of /dist/ to.gitignore and commit the code
- run
yarn build
Create the latest Dist - run
hs dist
Test your site locally to see if it works - Deployment to making
- Run the command
- Enable the Pages function of gulu-website
Here we use github Pages function, need to create a repository on Github, and then open the Pages function, there are a lot of information on the Internet, I will not go into details.
Let’s talk about one-click deployment. Basically, we use bash scripts to do our deployment for us.
Create the deploy. Bash
cd dist
git init
git add .
git commit -m "first commit"Git branch -m master git remote add origin Git push -f -u origin masterCopy the code
4.2 Packaging library Files
Vite does not support this functionality, so you need to configure rollup
Step 1: Create lib/index.ts
Export everything that needs to be exported
export {default as Switch } from './Switch.vue'
Copy the code
Step 2: rollup.config.js
Tell rollup how to package, need to install the corresponding dependency, preferably the version number is the same as mine, otherwise there may be various problems.
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"
export default {
input: 'src/lib/index.ts'.output: [{
globals: {
vue: 'Vue'
},
name: 'Gulu'.file: 'dist/lib/gulu.js'.format: 'umd'.plugins: [terser()]
}, {
name: 'Gulu'.file: 'dist/lib/gulu.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$/,}})]Copy the code
Step 3: Run rollup -c
Please install rollup globally first (or locally)
yarn global add rollup
npm i -g rollup
Copy the code
Step 4: Publish the dist/lib/ directory
In fact, it is uploaded to the NPM server
Modify package.json to add files and main
{
"name": "gulu-ui-1"."version": "0.0.1"."files": ["dist/lib/*"]."main": "dist/lib/gulu.js"."module": "dist/lib/gulu.esm.js"."scripts": {
"dev": "vite"."build": "vite build"
},
"resolutions": {
"node-sass": "NPM: [email protected]"
},
"dependencies": {
"github-markdown-css": "4.0.0"."marked": 1.1.1 ""."prismjs": "1.21.0"."vue": "3.0.0"."vue-router": "Three 4.0.0 - beta."
},
"devDependencies": {
"@vue/compiler-sfc": "3.0.0"."rollup-plugin-esbuild": "2.5.0"."rollup-plugin-scss": "Server"."rollup-plugin-terser": "7.0.2"."rollup-plugin-vue": "10 6.0.0 - beta."."sass": "1.32.11"."vite": "1.0.0 - rc. 1"}}Copy the code
Published to the NPM
npm login
npm publish
Copy the code
To ensure that you are not using taobao source, please use the official source NPM config get registry NPM config set registry registry.npmjs.org/
5. Making the address
Finally, the github address is attached. Interested students can download and use it or study it. If there is any problem or bad improvement in the demo, they can also discuss with each other. If there is a harvest, welcome to start, you can also leave a message feedback at any time