Preface 🍊

There are a variety of UI component libraries on the market, and their power is undeniable. But sometimes it is necessary to develop a custom component library for your own team. Sometimes the original components can’t meet our various needs, so we need to modify the original components.

So the purpose of this article is to let readers through this article, small can do a simple plug-in for people to use, big can architecture and maintenance of a component library is not at all. Welcome to give directions, you can also welcome the comments below.

Technology stack 🍊

  • Vue-cli3 basic creation and packaging tips
  • The basic syntax of VUE
  • The release of the NPM

Complete project directory structure 🍊

Vase - UI ├ ─. Eslintrc. Js ├ ─. Gitignore ├ ─. Npmignore ├ ─ Babel. Config. Js ├ ─ the deploy. Sh ├ ─ docs / / vuepress development │ ├ ─ │ ├─ ├─ ├─ ├─ ├─ vs-home. Vue │ ├─ config.js // Vurepess entrance configuration changes, including the sidebar on the left, upper right nav navigation menu │ │ └ ─ dist/pack/vuepress │ │ ├ ─ 404 HTML │ │ ├ ─ assets │ │ │ ├ ─ CSS │ │ │ ├ ─ │ ├─ ├─ ├─ 0 ├─ ├─ 0, ├─ 0, ├─ 0 Markdown │ ├─ ├─ ├─ basic │ │ ├─ ├─ │ ├─ get-started The HTML │ ├ ─ README. Md │ └ ─ views │ ├ ─ components │ │ └ ─ basic │ │ └ ─ README. Md │ └ ─ guide │ ├ ─ the get - started. Md │ └ ─ Install.md ├─ Package-Lock. json ├─ Package. json // │ ├─ ├─ SRC │ ├─ ├─ ├─ ├─ vue │ ├─ fonts │ ├─ font ├─ ├─ element.txt // ├─ element.txt // ├─ element.txt // Keep-alive │ ├─ ├─ public // │ ├─ ├─ class │ ├─ favicon │ ├─ favicon.ico │ ├─ class │ ├─ vue │ ├─ class │ ├─ favicon │ ├─ favicon ├ ─ button. Which s │ ├ ─ component, which s │ ├ ─ index, which s │ └ ─ vase - UI. Which s ├ ─ vue. Config. Js └ ─ yarn. The lockCopy the code

Project Planning 🍊

Create a default project using commands in the specified directory, or choose your own.

Create a project

Just name the project whatever makes sense to you

$ vue create vase-ui

Note: Since we are developing a third-party dependency library, we chose Manually select Features.

Select which features need to be installed in the project

 (*) Babel
 ( ) TypeScript
 ( ) Progressive Web App (PWA) Support
 ( ) Router
 ( ) Vuex
 (*) CSS Pre-processors
 (*) Linter / Formatter
 ( ) Unit Testing
 ( ) E2E Testing
Copy the code

The copied code system includes the basic Babel + ESLint setting by default, we just need to choose the CSS configuration. Move the up and down keys on the keyboard to select a feature and press the space bar

Which CSS preprocessor language to install

  Sass/SCSS (with dart-sass)
  Sass/SCSS (with node-sass)
  Less
  Stylus
Copy the code

Because the style in Element’S UI is Sass, we can just choose the first option. Why not choose the second option? Dart-sass is easier to download than Node-sass

Selecting a code Style

  ESLint with error prevention only
  ESLint + Airbnb config
  ESLint + Standard config
  ESLint + Prettier
Copy the code

Generally, ESLint + Prettier is used

That way to do code formatting

 (*) Lint on save
 ( ) Lint and fix on commit
Copy the code

Copy code select Ctrl+S to check the code format when saving

Configuration file generation mode

  In dedicated config files
  In package.json
Copy the code

Depending on personal preference, I’ll take the first option

Whether to save the preconfiguration

Save this as a preset for future projects? (y/N)
Copy the code

Copy the code and see what the project needs, so I’m going to choose N. When we press Enter, the system will automatically integrate the selected configuration into the template for us, and then generate a complete project.

Adjust the directory

Here we refer to element’s directory structure

Delete the SRC and assets directories and create one in the root directorypackagesThe directory is for the UI components we are developing; Create a test directory in the root directory to test our own UI components (no longer needed after the introduction of Vuepress).

| - packages / / change the original SRC directory to packages for writing storage componentCopy the code

Added packages directory that was not added to webpack compilation

Note: Cli3 provides an optional vue.config.js configuration file. If this file exists, it will be automatically loaded, and all the configuration for the project and webpack will be in this file.

Note: Vue’s package folder uses Monorepo: Monorepo is a code management mode that puts multiple packages in a single REPO, rather than the traditional multiple packages and multiple repOs. About monorepo can understand in this article https://juejin.cn/post/6844903961896435720

Webpack configuration modified

Packages is a new directory that is not handled by WebPack by default, so you need to add configuration support for this directory.

ChainWebpack is a function that takes an instance of a ChainableConfig based on webpack-chain. Allows for more fine-grained changes to the internal WebPack configuration. Create the vue.config.js file in the root directory and do the following:

// Change SRC to test
const path = require("path");
module.exports = {
  pages: {
    index: {
 entry: "test/main.js". template: "public/index.html". filename: "index.html"  }  },  // Extend webpack configuration to include Packages in compilation  chainWebpack: config= > {  config.module  .rule("js")  .include.add(path.resolve(__dirname, "packages"))  .end()  .use("babel")  .loader("babel-loader")  .tap(options= > {  return options;  });  } }; Copy the code

Component writing 🍊

The button component

  1. inpackagesDirectory, where all individual components are stored as folders, so create a directory herebutton
  2. inbuttonDirectory creationsrcDirectory stores component source code
  3. inbuttonDirectory creationindex.jsThe file provides references to components externally.
  4. createfontsFolder function is to storeelementSome basic styles of
  • /packages/button/src/button.vueThe core component code is as follows (the style code is not posted here, but can be found in the code repository) :
<template>
  <button
    class="vs-button"
 :disabled="disabled"  @click="handleClick"  :class="[  type ? `vs-button--${type}` : '',  buttonSize ? `vs-button--${buttonSize}` : '',  {  'is-plain': plain,  'is-round': round,  'is-circle': circle,  'is-disabled': disabled  } ]. ""  >  <i :class="icon" v-if="icon"></i>  <! -- if no incoming slot is displayed -->  <span v-if="$slots.default"><slot></slot></span>  </button> </template> <script> export default {  name: "VsButton". props: {  size: String. type: {  type: String. default: "default"  },  plain: {  type: Boolean. default: false  },  round: {  type: Boolean. default: false  },  circle: {  type: Boolean. default: false  },  disabled: {  type: Boolean. default: false  },  icon: {  type: String. default: ""  }  },  methods: {  handleClick(e) {  this.$emit("click", e);  }  } }; </script> <style lang="scss"> </style> Copy the code
  • Modify the/packages/button/index.jsDocuments for external reference:
import VsButton from "./src/button.vue";
// Provide the install installation method for components to be imported on demand
VsButton.install = function(Vue) {
  Vue.component(VsButton.name, VsButton);
};
export default VsButton; Copy the code

Keep alive – components

Introduction: This component is a simple stack implementation of Vue forward refresh backward without refresh

  1. inpackagesDirectory creationkeep-alive
  2. inkeep-aliveDirectory creationindex.jsThe file uses the functional component in it and provides references to the component externally.
  • The complete code is as follows:
import Vue from "vue";

let cacheKey = "cacheTo";
let $router = { beforeEach: (a)= >{}};// The vue. observable handle makes the component's store pluggable
const state = Vue.observable({  caches: [] }); const clearCache = (a)= > {  if (state.caches.length > 0) {  state.caches = [];  } }; const addCache = name= > state.caches.push(name);  const beforeEach = (a)= > {  $router.beforeEach((to, from, next) = > {  // 1. None of these are class list pages  // Clear the cache  // 2. Both are class list pages  // If 'to' is not in 'from' configuration, clear the cache and add 'to' cache  // Keep the cache of 'from' and add the cache of 'to'  // 3. The new route is the class list page  // If 'from' is not in 'to' configuration, clear the cache and add 'to' cache  // Otherwise, no action is required  // 4. Old routes are class list pages  // If 'to' is not in 'from' configuration, clear the cache   const toName = to.name;  const toCacheTo = (to.meta || {})[cacheKey];  const isToPageLikeList = toCacheTo && toCacheTo.length > 0;  const fromName = from.name;  const fromCacheTo = (from.meta || {})[cacheKey];  const isFromPageLikeList = fromCacheTo && fromCacheTo.length > 0;   if(! isToPageLikeList && ! isFromPageLikeList) { clearCache();  } else if (isToPageLikeList && isFromPageLikeList) {  if (fromCacheTo.indexOf(toName) === - 1) {  clearCache();  }  addCache(toName);  } else if (isToPageLikeList) {  if (toCacheTo.indexOf(fromName) === - 1) {  clearCache();  addCache(toName);  }  } else if (isFromPageLikeList) {  if (fromCacheTo.indexOf(toName) === - 1) {  clearCache();  }  }  next();  }); }; const VsKeepAlive = {  install(Vue, options = { key: "".router: ""{}) const { key = "cacheTo", router } = options;   if (key) {  cacheKey = key;  $router = router;  beforeEach();  }   const component = {  name: "VsKeepAlive". functional: true. render(h, { children }) {  return h("keep-alive", { props: { include: state.caches } }, children);  }  };   Vue.component("VsKeepAlive", component);  } }; export default VsKeepAlive; Copy the code

Precautions for use:

  1. The route configuration must include the name attribute, and the name must be the same as the component name
  2. The cacheTo priority is smaller than keepAlive, so pages that handle this need do not set keepAlive
  3. It is possible to set up two pages before caching only when switching between them, but I haven’t found a scenario that works

Export components

  • Integrate all components, export, that is, a complete component library changes/packages/index.jsFile to export the entire component library:
import Button from "./button";
import KeepAlive from "./keep-alive";

import "./fonts/font.scss";
// Store a list of components
const components = [Button];  // Define the install method that accepts Vue as an argument. If the plug-in is registered with use, all components will be registered const install = function(Vue, options = { key: "", router: {} }) {  const { key = "cacheTo", router } = options;  // Iterate over registered global components  components.forEach(function(item) {  if (item.install) {  Vue.use(item);  } else if (item.name) {  Vue.component(item.name, item);  }  });  Vue.use(KeepAlive, { key, router }); };  // Check whether the file is imported directly if (typeof window! = ="undefined" && window.Vue) {  install(window.Vue); } export { Button, KeepAlive }; export default {  // Exported objects must have install to be installed by vue.use ()  version: "0.3.4". install }; Copy the code

Note: If we want to use our keep-alive component, we need to pass in the Router parameter during registration.

Here we can see how Element defines this block:


NPM release 🍊

  • package.jsonIn thescriptAdd a new command to compile to the library, and then use itnpm run libCommand to pack
"scripts": {
    "lib": "vue-cli-service build --target lib --name vase-ui --dest lib packages/index.js"
  },
Copy the code

Note:

  1. --target: Build target, default to application mode. Here change to lib to enable library mode.
  2. dest: Output directory, default dist. So let’s change this to lib
  3. [entry]: The last parameter is the entry file, which defaults to SRC/app.vue. Here we specify compile packages/ component library directory
  • Execute the compile library command

npm run lib


  • configurationpackage.jsonFile published tonpmThe field of
  1. Name: package name, which is unique. Search for the name on the NPM website, or change the name if it exists.
  2. Version: indicates the version number. The version number must be changed each time you release data to NPM. The version number cannot be the same as the historical version number.
  3. Description: description.
  4. Main: entry file, this field should point to our final compiled package file.
  5. Keyword: Separates the final word that you want the user to search for with Spaces.
  6. Author: the author
  7. Private: indicates whether to publish to NPM. Change the value to false
  8. License: Open source agreement
  9. You want to package the generation file of the library
  10. Browserslist: Specifies the scope of the target browser for the project
  11. Repository: Specifies the location of the code. This is very helpful for people who want to contribute. If your Git repo is on GitHub, the NPM docs command will be able to find you.

Here are my reference Settings

{
 "name": "vase-ui".  "version": "0.3.4".  "description": "A Component Library for Vue.js.".  "private": false. "main": "lib/vase-ui.common.js". "files": [  "lib". "src". "packages". "types" ]. "repository": {  "type": "git". "url": "[email protected]:JohnYu588/vase-ui.git"  },  "author": "yzx". "license": "MIT". "browserslist": [  "1%" >. "last 2 versions"  ]  } Copy the code
  • Add the.npmignore file to ignore the publishing file
# ignore directorytest/
packages/
public/
docs/
node_modules/  # ignore the specified filevue.config.js babel.config.js *.map .editorconfig.js Copy the code
  • Release NPM
  1. Go to the NPM website and register an account
  2. performnpm loginEnter your account and password to log in
  3. performnpm publishupload
  4. Wait a few minutes after the successful releaseNPM's official websiteTo search. The following is just submitted

Pay attention to

  1. Be sure to add main to the scripts of package.json so that others can find the packaged files when downloading them
  2. When uploading to NPM, change the private property value in package.json to false
  3. Be sure to change the project version number when you publish modified source code to NPM

Official website production 🍊

Using the vue press

  • Used in the original project
# install dependencies
npm install -D vuepress
Create a docs directory
mkdir docs
Copy the code
  • inpackage.jsonTo configure the script
  "scripts": {
    "docs:dev": "vuepress dev docs".    "docs:build": "vuepress build docs"
  },
Copy the code
  • Simple configuration indocs/.vuepressNew fileconfig.js
module.exports = {
  base: '/vase-ui/'.  title: 'Vase UI'.  description: 'Inspiration from heian vase'.  head: [['link', { rel: 'icon'.href: '/favicon.ico' }]],
 themeConfig: {  nav: [  { text: 'Home'.link: '/' },  { text: 'Github'.link: 'https://github.com/JohnYu588/vase-ui/' }, ]. sidebar: [  {  title: 'Development Guide'. collapsable: true. children: ['views/guide/install.md'.'views/guide/get-started.md']. },  {  title: 'components'. collapsable: true. children: ['views/components/basic/']. }, ]. }, }; Copy the code
  • Use vUE components

Vue files found in.vuepress/ Components are automatically registered as global asynchronous components. Can be referenced in the markdown, we can write here to show case the vue file code in the highlight what I use be a vue – highlightjs: in the/docs /. Vuepress/components/create button vs. – botton. Vue file code is as follows:

<template>
  <div>
    <h3>Basic usage</h3>
    <vs-button>default</vs-button>
 <vs-button type="primary">primary</vs-button>  <vs-button type="info">info</vs-button>  <vs-button type="success">success</vs-button>  <vs-button type="warning">warning</vs-button>  <vs-button type="danger">danger</vs-button>  <pre v-highlightjs><code class="vue">{{code1}}</code></pre>  </div> </template>  <script> import btn from '.. /.. /.. /packages/button/src/button';import Vue from 'vue' import VueHighlightJS from 'vue-highlightjs'; Vue.use(VueHighlightJS);  export default {data() {  return {  code1: `  <vs-button>default</vs-button>  <vs-button type="primary">primary</vs-button>  `  .replace(/^\s*/gm, '')  .trim(),  code2: `  <s-button disabled type="primary">disabled</s-button>  `  .replace(/^\s*/gm, '')  .trim(),  code3: `  <s-button icon="home" type="primary">home</s-button>  <s-button icon="phone-fill" type="primary" icon-position="right">call</s-button>  <s-button icon="visible" type="primary">show password</s-button>  `  .replace(/^\s*/gm, '')  .trim(),  code4: `  <s-button loading icon="download" type="primary">In the load</s-button>  `  .replace(/^\s*/gm, '')  .trim(),  code5: `  <s-button-group>  <s-button icon="left" icon-position="left">prev</s-button>  <s-button>middle</s-button>  <s-button icon="right" icon-position="right">next</s-button>  </s-button-group>  `  .replace(/^ {8}/gm, '')  .trim(),  };  },  components: {  'vs-button': btn,  }, }; </script> // Styles are not posted here <style lang="scss" scoped></style> Copy the code
  • Written document

Since all pages need to be rendered by the Node.js server to generate static HTML, for components that are less SSR friendly (such as those that contain custom instructions), you can wrap them in the built-in ClientOnly component. Also note that because it is SSR, the component internal beforeCreate, created lifecycle hook functions do not access the browser/DOM API and can only be called in beforeMount and Mounted.

/ docs/views/components/basic created under the README, md:

---
title: 'the Basic basis'
sidebarDepth: 2
---

# # Button Button <ClientOnly>  <vs-button/> <font size=5>Attributes</font> | parameters | | | | | alternatives, a default value| : -- -- -- -- -- - | -- - | -- - | -- -- -- -- -- - | -- - || type | | button type string | primary, info, success, warning, danger | - || disabled button if disable | | Boolean | - |false | | icon name | | button icon string | - | - || icon - position | | icon around the button's position string | left and right | - || loading | displayed loading icon | Boolean | - |false |  </ClientOnly> Copy the code

Note: Refer to the documentation above for creating the installation help and start page in the Guide directory (see git for details).

  • Deployment to making

It’s very clear on the website. Click here. Sh is added in the root directory of the project and run the./deploy.sh command in Windows to publish the project to Github Pages.

deploy.sh:

#! /usr/bin/env sh
Make sure the script throws any errors it encounters
set -e

Generate static files npm run docs:build  Go to the generated folder cd docs/.vuepress/dist  # if publish to custom domain name # echo 'www.example.com' > CNAME  git init git add -A git commit -m 'deploy'  # if posted to https://
                        
                         .github
                         # git push -f [email protected]:<USERNAME>/<USERNAME>.github.io.git master  Github. IO /
                            git push -f [email protected]:JohnYu588/vase-ui.git master:gh-pages  cd - Copy the code
  • Preview the executionyarn docs:devCommand preview official website:

After the upload at https://johnyu588.github.io/vase-ui/ can see directly

Use the newly released component library 🍊

npm install vase-ui -S
Copy the code
  • References can be introduced in a project’s main.js in two ways
  1. Global registration
import VaseUI from "vase-ui";
Vue.use(VaseUI, { router });
Copy the code
  1. According to the need to introduce
import { Button, KeepAlive } from "vase-ui";
Vue.use(Button)
.use(KeepAlive, { router });
Copy the code
  • use
  <vs-button icon="vs-icon-check" circle plain type="primary">test</vs-button>
Copy the code

Use of keep-alive reference Vue forward refresh backward no refresh, simple page stack implementation [1]

Git address: https://github.com/JohnYu588/vase-ui

Reference 🍊

Girl wind vUE component library production complete walkthrough ~~

Build your own UI framework from scratch – publish to NPM

Vue forward refresh backward not refresh, simple page stack implementation

The resources

[1]

Vue forward refresh, refresh back do not stack to achieve simple page: https://juejin.cn/post/6844904002526642184.