preface

Recently, we started the reconstruction of Vue3 background in the development of our company. As a result, the removal of the Vue component library started, which is a secondary package based on vue3.x + Element-Plus to encapsulate common and common components. In the Vue component library technology research more methods in the selection, the final selection does not represent is the best, but must be at this stage I think the most suitable the scenario of technology selection, actually writing component library is not complicated, the whole process of technology research and technology selection, a method of various methods and their advantages and disadvantages have a broad understanding of a little, The purpose of this paper is to record the whole process from the selection to the final product. The basic configuration is not described, the article only records the general development framework and packaging and single test, for reference only. I don’t want to look at the article but I want to look at the code and I want to look at the interaction document

purpose

Why do component libraries? This problem is a commonplace, PC side background XX management system such a scenario, the general things are easy to abstract out, so that there is no need to open a new project in the project to write a set of basic components, can directly use the basis of the development can save a lot of time, This time, only several NPM packages with high integration and generality are extracted to realize common general functions at first. Subsequent extensions can add functions without changing the original architecture. These components include a common layout component, a common table component, a common form component, a common file image upload component, and so on.

Especially form and form components, used directly in the page will appear a lot of duplicate code, write code redundancy, so the general project will encapsulate these basic components to realize the general, want to achieve purpose is just through the different configuration can be universal in the page, generally these are really the same, This is also the original intention of developing such a base component library. It is not necessary to write a set of components in each project, but directly dump the component library into THE NPM management for subsequent upgrades. In this way, the company can slowly precipitate its own unique base and business component library for subsequent new projects out of the box.

Prior to the start

Before you start, in addition to settling the technology stack Vue 3.x + ElementPlus, you need to define the development principles of the component library: simplicity, efficiency, flexibility, and extensibility. First, have a readable document library with examples to interact with; Secondly, the repetitive work that can be automated will never be manually copied, and the document library will be automatically deployed. Finally, it is best to have component testing to ensure that components are correct and complete.

The project structure

│ ├─ ├─ config.ts /* │ ├─ ├─ config.ts /* │ ├─ ├─ config.ts /* Vuepress configuration file * / │ index. The md / * * / document └ ─ ─ packages / * * / │ ├ ─ ─ layout / * * / layout components │ │ ├ ─ ─ the SRC / * * / │ vue component │ ├ ─ ─ Package. json / / │ │ ├─ Form / / │ │ ├─ Table / │ │ ├─ Templates /* │ │ ├─ Form /* │ │ ├─ Table /* │ │ ├─ Templates /* Plop Config Clone Template folder */ ├─ typings /* Declaration folder */ ├─.eslintrc.js /* EsLint Config */ ├─.gitignore /* Gitignore Config */ ├── .prettierrc /* Prettier configuration */ ├─.stylelintrc /* Stylelint configuration */ ├─ babel.config.js /* Babel configuration */ ├─ jest.config.js /* ├─ package.json /* LICENSE */ ├─ package.json /* LICENSE */ ├─ package.json */ ├─ plopfile.js /* plop ├─ Tsconfig. Json / * * / ts configuration ├ ─ ─ a rollup. Config. * / js / * rollup packaging configuration └ ─ ─ the README. Md / * * / documentation filesCopy the code

Package management mode

Because it is a component library, multiple component packages have common dependencies. In order to reduce repeated code, Lerna + YARN workspace is used for package management, which is also the choice of most component libraries today.

Component packaging

Rollup is selected for component packaging, because the components of this time are packaged for several common scenarios, and we intend to manage them separately. Rollup packaging can package packages of various modes, such as ESM, CJS, UMD, etc., and ESM comes with tree-shaking, the semantics of the printed packages are clear. It is also easier to debug. The rollup package configuration file is placed in the outermost layer to uniformly configure the packaging of components

The following configuration has several key points:

  1. Multi-entry, each component is packaged separately, and packaged separately into umD format index.js file and ESM format index.module.js file
  2. Babel configuration requires manual extension of.ts and.vue to properly compile ts and vue files
  3. Json in each package declares main Module and Typings. By default, index.module.js is loaded when esM mode is supported, and index.js is loaded otherwise
  4. During the configuration, add peerDependencies to the External configuration item and do not pack the package of peerDependencies to reduce the package volume and improve the packaging efficiency
  5. Esm supports tree-shaking, so CSS is not packaged separately, so using ESM directly loads on demand without plug-ins

rollup.config.js

/* eslint-disable @typescript-eslint/no-var-requires */ import fs from 'fs' import path from 'path' import json from '@rollup/plugin-json' import postcss from 'rollup-plugin-postcss' import vue from '@vitejs/plugin-vue' import { terser }  from 'rollup-plugin-terser' import { nodeResolve } from '@rollup/plugin-node-resolve' import typescript from '@rollup/plugin-typescript' import babel from '@rollup/plugin-babel' import commonjs from '@rollup/plugin-commonjs' import { DEFAULT_EXTENSIONS } from '@babel/core' const isDev = process.env.NODE_ENV ! == 'production' // packages folder path const root = path.resolve(__dirname, Const getPlugins = () => {return [vue(), typescript({tsconfig: './tsconfig.json' }), nodeResolve({ mainField: ['jsnext:main', 'browser', 'module', 'main'], browser: }), commonjs(), json(), postcss({plugins: [require('autoprefixer')], // Insert CSS into style inject: // Extract: unitary // unitary // unitary // unitary // !isDev, // Enable sourceMap. sourceMap: isDev, // This plugin will process files ending with these extensions and the extensions supported by custom loaders. extensions: ['.sass', '.less', '.scss', '.css'] }), babel({ exclude: 'node_modules/**', babelHelpers: 'Runtime ', // Babel does not support ts by default. [...DEFAULT_EXTENSIONS, '.ts', '.tsx', '.vue']}), // If not the development environment, enable compression! IsDev &&terser ({toplevel: Module. Exports = fs. readdirSync(root) // filter, Filter (item => fs.statsync (path.resolve(root, Map (item => {const PKG = require(path.resolve(root, item)).isdirectory ()) // Create a configuration for each folder. 'package.json')) return { input: path.resolve(root, item, 'src/main.ts'), output: [ { name: 'index', file: path.resolve(root, item, pkg.main), format: 'umd', sourcemap: isDev, globals: { vue: 'vue', 'element-plus': 'element-plus' } }, { name: 'index.module', file: path.join(root, item, pkg.module), format: 'es', sourcemap: isDev, globals: { vue: 'vue', 'element-plus': 'element-plus' } } ], onwarn: function (warning) { if (warning.code === 'THIS_IS_UNDEFINED' || warning.code === 'CIRCULAR_DEPENDENCY') { return } console.error(`(!) ${warning.message}`) }, plugins: getPlugins(), external: Object.keys(require(path.join(root, item, 'package.json'))? .peerDependencies || {}) } })Copy the code

Component library documentation

One of the reasons for choosing Vuepress is that the page is simple and flexible. The plug-in can not only configure the component interaction description, but also configure other instruction guidance documents. Vue-styleguidst was considered before. However, it has strong limitations. It can only configure component interaction documents and the page style is not as simple and good-looking as Vuepress. I also investigated VitePress, but because VitePress has been in WIP and removed plugins and other configurations in Vuepress, This would have been sufficient for pure documentation, but we needed to have component interaction documentation, so we ended up with vu3-supported vuepress@next.

Vuepress packaging

In addition to webpack, vuepress@next also adds a vite development package that can be configured under.vuepress/config.ts

The following configuration has several key points:

  1. Read the folder name under the Packges folder and add an alias to the referenced package
  2. The @vitejs/ plugin-vuE-jsx plugin was added because the JSX syntax was supported in the component
  3. Bundler’s configuration (@vuepress/webpack / @vuepress/vite) defaults to Webpack if it is not set, and vite packs if vuepress-vite is installed
  4. Add vuepress plugin vuepress-plugin-demoblock-plus, which uses Element-Plus’s document rendering implementation to render interactive components
  5. Because GitHub Actions are used to automate the deployment of documents to GitHub Pages, the base option needs to be configured to be consistent with the GitHub project name because the static resource path is loaded under that folder

.vuepress/config.js

const { readdirSync } = require('fs') const { join } = require('path') const chalk = require('chalk') const headPkgList = []; // const pkgList = readdirSync(join(__dirname, '.. /.. /packages')).filter( (pkg) => pkg.charAt(0) ! = = '&&! headPkgList.includes(pkg), ); const alias = pkgList.reduce((pre, pkg) => { pre[`@sum-ui/${pkg}`] = join(__dirname, '.. /.. /packages', pkg, 'src/Index.vue'); return { ... pre, }; }, {}); Console. The log (` 🌼 alias list \ n ${chalk. Blue (Object. The keys (alias). The join () '\ n')} `); Module. exports = {title: "sum-ui", // top left title description: 'Vue3 + ElementPlus library ', base: '/sum-ui/', bundler: '@vuepress/vite', bundlerConfig: { viteOptions: { plugins: [ vueJsx() ] } }, alias, head: [// Set description and keywords ["meta", {name: "keywords", Content: "Vue3 UI Component library "},]], themeConfig: {sidebar: {// sidebar "/": [{text: "introduction ", children: [{text:" install", link: "/guide/install"}, {text: "quick start ", link: "/guide/start"},],}, {text: "component ", children: [{text: "Layout ", link: "/components/ Layout"}, {text: "component ", children: [{text: "Layout ", link: "/components/ Layout"}, {text: "component ", children: [{text: "Layout ", link: "/components/ Layout"}, {text: [// 查 看 全 文, link: "/components/ Table "}],},],}, nav: [// 查 看 全 文, link: "/", activeMatch: "^ / $| ^ / guide/"}, {the text:" component ", link: "/ components/layout. HTML," activeMatch: "^ / $| ^ / components/"}], / / page meta editLinkText: 'on the lot to edit this page, lastUpdatedText:' last updated, contributorsText: }, plugins: ['demoblock-plus'] // vuepress-plugin-demoblock-plus is used to display interactive documents and code expansion};Copy the code

.vuepress/clientAppEnhance.ts

In addition to the config.ts configuration, the global component registration is required to take effect, and clientappenhance. ts is required for configuration

Import {defineClientAppEnhance} from '@vuepress/client' import 'elemental-plus /theme-chalk/ SRC /index.scss' // fully import the style file scss TODO: If the viet-plugin-element-plus plugin is introduced on demand, as described in the element-Plus documentation, Import SumTable from '@sum-ui/table' import SumLayout from '@sum-ui/layout'  export default defineClientAppEnhance(({ app }) => { app.component('SumTable', SumTable) app.component('SumLayout', SumLayout) })Copy the code

Component Development Preview

Once the interactive document library is configured, you can develop the component library and see what the components look like

Yarn docs:dev // Vuepress Document library development mode Yarn docs:build // Vuepress document library is packaged as a static resource fileCopy the code

The generated resource files can be automatically deployed on Github Pages using Github Actions to address the sum-UI component library document

Component test

Component tests are placed in each component directory. After the component is written, the single test of the unit test vue of the component can be written with @vue/test-utils. In addition, when importing components in the component test, ts and VUE files cannot be directly identified. Ts-jest vue-jest babel-jest is required to do the transformation

Configuration jest. Config. Js

const alias = require('./alias') module.exports = { globals: { // work around: https://github.com/kulshekhar/ts-jest/issues/748#issuecomment-423528659 'ts-jest': { diagnostics: { ignoreCodes: [151001] } } }, testEnvironment: 'jsdom', transform: { '^.+\.vue$': 'vue-jest', '^.+\.(t|j)sx?$': [ 'babel-jest', { presets: [ [ '@babel/preset-env', { targets: { node: true } } ], [ '@babel/preset-typescript', { isTSX: true, allExtensions: true } ] ] } ] }, moduleNameMapper: Alias, // Declare an alias so that the moduleFileExtensions file can be correctly loaded when the import file is loaded in jest: ['ts', 'tsx', 'js', 'json'], // u can change this option to a more specific folder for test single component or util when dev // for example, ['<rootDir>/packages/input'] roots: ['<rootDir>'] }Copy the code

Vue support TSX

Babel’s PRESET configuration isTSX: true, allExtensions: AllExtensions is true to support allExtensions, primarily to support the resolution of.vue files; isTSX is true to support the resolution of JSX syntax

babel.config.js

module.exports = {
    // ATTENTION!!
    // Preset ordering is reversed, so `@babel/typescript` will called first
    // Do not put `@babel/typescript` before `@babel/env`, otherwise will cause a compile error
    // See https://github.com/babel/babel/issues/12066
    presets: [
        '@vue/cli-plugin-babel/preset',
        [
            '@babel/typescript',
            {
                isTSX: true,
                allExtensions: true
            }
        ]
    ],
    plugins: ['@babel/transform-runtime']
}
Copy the code

Thematic color correlation

Because Element-Plus uses CSS variables, you can override the theme color by changing the CSS variables

main.js

import { themeVarsKey } from 'element-plus'
const themeVars = {
  '--el-color-primary': '#29b6b0'
}
const app = createApp(App)
app.provide(themeVarsKey, themeVars)
Copy the code

Any other problems you might encounter

Cannot read property ‘isCE’ of null github.com/vuejs/vue-n…

I encountered this error when I tried to reference the Vue package globally after I packaged the component locally and then referenced it in other projects. It was due to multiple Vue package references. After publishing to NPM, the installation reference from NPM was normal