preface
Recently, I learned Vue 3 systematically, and wanted to make some wheels to improve the efficiency of my own development, so I came up with the idea of making an official website of UI component library. Currently, there are Input, Switch, Dialog, Button, and Tabs components
Preview link (requires scientific Access) Source code link (requires scientific access)
Environment construction and preparation
The installation
create-vite-app
, can be used directlyyarn dev
Open the page for debugging;vue-router
, which is used to configure routes for different pages.
- There are other
github-markdown-css
,marked
,sass
和prismjs
.
The specific version number can be found in package.json:
{
"name": "sky-ui-dev"."version": "0.0.0"."scripts": {
"dev": "vite"."build": "vite build"
},
"dependencies": {
"github-markdown-css": "4.0.0"."prismjs": "^ 1.21.0"."vue": "^ 3.1.3"."vue-router": "Three 4.0.0 - beta."
},
"devDependencies": {
"@vue/compiler-sfc": "^ 3.1.3"."marked": "^ 2.1.1"."sass": "^ 1.32.11"."vite": 1 "" ^ 1.0.0 - rc.}}Copy the code
CSS minimum impact principles for UI libraries
(1) Scoped is not recommended
XXX in the HTML tag data-v-xxx is a random number every time we run, we need to output stable class selector, convenient for users to cover.
(2) The class prefix needs personalization
Sky-theme-button, for example, is not easily overridden by the user. Prefixes such as.button and.theme-link are more generic and can easily be overwritten by users.
(3) Store the common style of UI library
We can create a new sky.scss to hold the common styles of the UI library, and use the write name prefix only for that UI library.
[class^="sky-"].[class*=" sky-"] {
margin: 0;
padding: 0;
box-sizing: border-box;
}
Copy the code
[class^=”sky-“] =
[class*=” sky-“] means a class starting with space +tree-, equivalent to a tag like
Project directory structure
Plugins: The plugins that are stored in the markdown documentation that describes the project;
Assets: official website logo;
Components: Vue files for different pages;
Lib: component file;
Markdown: website introduction and start documentation;
Views: page files on the official website.
Vue Router configuration
Create a router-. ts file in the project root directory. We use hash mode to configure routes here, and do not need to set up a background server.
/ / hash pattern
const history = createWebHashHistory();
export const router = createRouter({
history: history,
// Link to different files
routes: [{path: '/'.component: Home},
{
path: '/doc'.component: Doc,
children: [{path: ' '.redirect: '/doc/intro'},
{path: 'intro'.component: md(intro)},
{path: 'get-started'.component: md(getStarted)},
{path: 'switch'.component: SwitchDemo},
{path: 'button'.component: ButtonDemo},
{path: 'dialog'.component: DialogDemo},
{path: 'tabs'.component: TabsDemo},
{path: 'radio'.component: RadioDemo},
{path: 'input'.component: InputDemo}
]
}
]
});
router.afterEach(() = > {});
Copy the code
Add
to app.vue
<template>
<router-view/>
</template>
<script lang="ts">
import {router} from './router';
export default {
name: 'App'.setup(){}}</script>
Copy the code
In main.ts add:
app.use(router)
Copy the code
At this point, the preliminary work of a project has been basically prepared, and then we begin to develop our UI library.
Component development
More details in the development process, here to pick a few representative out to talk about.
How do I get a part of a component to fire an event?
inButton
Component, external onediv
Red box, click on thisdiv
It triggers an event, but all we have to do is clickbutton
Triggers an event. How can this be done?
Step 1: Make divs in components do not inherit properties
Set inheritAttrs: false and clicking on div will not trigger an event. Inherit means inheritance, attrs is short for attributes, which means attributes. However, clicking on a button at this point does not trigger an event, because this statement masks all inherited events by default. Next we need to do another setup.
<script lang="ts">
export default {
inheritAttrs: false
}
</script>
Copy the code
Step 2: Bind $attrs to buttons in the div
$attrs allows an element of a button to inherit all attributes bound to a button. Add v-bind=”$attrs” to the button.
<button v-bind="$attrs">
Copy the code
Click button at this point to see the @click/@focus/@mouseover event results appear in the console.
How do I load events for different components on demand?
Let’s say I have a component that wants the click and Focus events, and another component that only needs the click event. How do I implement this? The main idea is to declare setup inside the component, pass in parameters, and return the obtained parameters.
<template>
<div :size="size"> <! -- Step 3: Define the event or attribute on the tag, where the size is specified -->
<button v-bind="$attrs">
<slot/>
</button>
</div>
</template>
<script lang="ts">
export default {
inheritAttrs: false.setup(props, context){
// Step 1: Destructor syntax to get an external event or attribute
const {size, onClick, onMouseOver} = context.attrs;
// Step 2: Return the size event or property
return {size}
}
}
</script>
Copy the code
You can also simplify by using ES6’s latest remaining operators. To get to… After REST, use it in the template.
<script lang="ts">
export default {
inheritAttrs: false.setup(props, context){
/ / use... Rest extension operator that gets other properties or events other than size
const{size, ... rest} = context.attrs;return {size, rest}
}
}
</script>
Copy the code
Named slot in the popover
We created a Dialog that lets the user customize its title and content, using a named slot called V-slot. Vue 3 uses a named slot differently from Vue 2.
Step 1: Define the slot name using v-slot: XXX:
<template v-slot:content>
<div>hello</div>
</template>
Copy the code
Step 2: Introduce slots where needed, using
<main>
<slot name="content"/>
</main>
Copy the code
The results are as follows:
Use Teleport to “deliver” components
So how do we prevent the Dialog from being blocked? This is where teleport comes in. Teleport in Chinese it means “send”, we can use it to put the Dialog “send” to go out, it will not be affected by other elements.
Use
to wrap the component that needs to be “sent” and add to=”body” to specify the destination to be sent, moving directly under the body tag.
Upon success, we can see that the Dialog has been successfully “delivered” in the developer tools.
Dynamically mount components
If we don’t want to declare a variable in the component and want to change the value of the variable, we can use the h of createApp to do so. Let’s take the one click to open the Dialog component as an example.
Step 1: Create a Button, add @click=”showDialog” event, and click to open the Dialog;
Step 2: Call openDialog from the showDialog function. The user can pass title, content, OK, cancel from the contents of the defined Dialog component.
<script>
const showDialog = () = >{
openDialog({
title:'title'.content:'hello'.ok(){console.log('ok'); },cancel(){console.log('cancel'); }})}return {showDialog}
}
}
</script>
Copy the code
Step 3: Create an OpenDialog. ts component that retrieves external properties and creates a div directly in the body. Here you need to use h() to construct the new Dialog.
import Dialog from './Dialog.vue';
import {createApp, h} from 'vue';
export const openDialog = (options) = > {
const {title, content, ok, cancel} = options;
const div = document.createElement('div');
document.body.appendChild(div);
const close = () = > {
app.unmount(div);
div.remove();
};
const app = createApp({
render() {
// Use h to construct the Dialog
return h(Dialog, {
visible: true.'onUpdate:visible': (newVisible) = > {
if (newVisible === false) { close(); } }, ok, cancel }, { title, content }); }});// Mount the new div
app.mount(div);
};
Copy the code
useTemplate Refs
Dynamic settingdiv
The width of the
After making the Tab bottom navigation alert, I set the width to 100px at first, but the width is more than the width of the text. For component libraries, how do you dynamically set the width of the navigation bar based on the user’s text? We need to use Template Refs in Vue3.
As described in Vue 3, we bind a ref to the navigation text div and use the value in the ref to get the width of the div when mounting the component.
<template>
<div :ref="element => { if(element) navItems[index] = element }"></div><! If element exists, then make the element in navItems equal to element--><div class="tree-tabs-nav-indicator"></div></template> <script lang="ts"> import {onMounted, ref} from 'vue'; export default { setup(props, context) { const navItems = ref<HTMLDivElement[]>([]); const indicator = ref<HTMLDivElement>(null); onMounted(() => { const divs = navItems.value; const result = divs.filter(div=>div.classList.contains('selected'))[0] const {width} = result.getBoundingClientRect() indicator.value.style.width = width + 'px' }); return {navItems, indicator}; }}; </script>Copy the code
After running the code successfully, we can get indicator width in real time from the console.
Component source code beautification
usecustom block
Show source code
The Custom Block plug-in is used when you want to display the source code from a component on the page.
Step 1: Add the following code to vite. Config. ts, which is used to parse the code in the component.
import {md} from './plugins/md'
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()
const parsed = baseParse(file).children.find(n= > n.tag === 'demo')
const title = parsed.children[0].content
const main = file.split(parsed.loc.source).join(' ').trim()
return `export default function (Component) {
Component.__sourceCode = The ${JSON.stringify(main)
}
Component.__sourceCodeTitle = The ${JSON.stringify(title)}} `.trim()
}
}
}
Copy the code
Step 2: In the component that you want to show the code to, add the
tag:
</demo><template>
<Switch v-model:value="bool"/>
</template>
Copy the code
Step 3: Display code in the parent component:
<div class="demo-code">
<pre>{{Switch1Demo.__sourceCode}}</pre>
</div>
Copy the code
useprismjs
和 v-html
Highlight source code
Prismjs is a library for highlighting code that needs to be introduced and is suitable for displaying code in our UI library.
To install prismjs, run yarn add prismjs
Introduced in a component and exported using setup() for ease of use:
import 'prismjs';
import 'prismjs/themes/prism-okaidia.css';
// In the themes file, you can try a few more and use the one you like better
const Prism = (window as any).Prism;
export default {
setup() {
return{Prism}; }};Copy the code
This is shown in the template:
<template>
<div class="demo-code">
<pre class="language-html" v-html="Prism.highlight(Switch1Demo.__sourceCode, Prism.languages.html,'html')"/>
</div>
</template>
Copy the code
Highlighting the code looks like this:
Compatible with TypeScript and MarkDown
Sometimes libraries do not have TypeScript declarations. You can run the following code after installing the library, with XXX as the library name:
yarn add --dev @types/xxx
Copy the code
Ides do not recognize markdown files, so you can declare this in shims.d.ts to avoid errors:
declare module '*.md'{
const str: string
export default str
}
Copy the code
Automate project packaging and deployment
Run the following command:
yarn build
Copy the code
This will generate a dist folder in the project root directory. You just need to upload this folder to Github.
Typically, it takes a lot of command lines to go from packaging to deploying github, so we can automate the deployment using a single deploy.sh.
Create a deploy.sh file in the project root directory and run the following command (add && to confirm the running status of the command. For example, if a command fails to run, the system stops the operation) :
rm -rf dist &&
yarn build &&
cd dist &&
git init &&
git add . &&
git commit -m "update"&& git branch -m main && // Enter your github repository address git remote add origin [email protected]: XXXXXX && git push -f -u origin main &&cd.Copy the code
After that, you only need to enter sh deploy.sh in the terminal to implement one-line code deployment.
If this post has helped you, please follow, like, or comment