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
  • runyarn buildCreate the latest Dist
  • runhs distTest 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