preface

Vue is a set of progressive frameworks for building user interfaces that more and more developers are learning and using. Component libraries help us save development effort by not having to do everything from scratch, but by piecing together pieces to get the final page we want. If there are no specific business requirements in daily development, the use of component library development is undoubtedly more convenient and efficient, and the quality is relatively higher.

This paper describes how to build a UI component library step by step based on VUE.

Component Library official website

Making the address

NPM address

I. Technology stack

Let’s take a quick look at what stacks are involved in building a UI component library. Here are my picks:

  1. Vue-cli: officially supported CLI scaffolding that provides a modern build setup with zero configuration;
  2. Vue: Progressive JavaScript library;
  3. Jest: JavaScript testing framework for unit testing component libraries;

Second, component development

Project initialization

To get started, we need to create an empty VUE project from which we can start writing the next component library!

NPM I -g vue-cli // yarn add global vue-cli vue init webpack heaven- UI //(heaven- UI) You can change the name to CD heaven- UI NPM run devCopy the code

Vue-cli3 will automatically show us a default page after we have installed the dependencies and entered the project startup service. Here I used sass to style the UI components.

The directory structure

Here is my directory structure and explanation

| | - build / # webpack packaging configuration - lib / # packaged generated files here | | - here in SRC / # write the code - the components / # components, Each component is a subdirectory | - mixins / # reuse mixin | - # utils directory tools | - App. Vue # | - index. The development of the local operation preview js # packaged entrance, Components of export | - main. Js # # run locally run | - static/deposit additional resource files, Pictures # test folder | - | - test/specs / # for all test cases | - jest. Conf., js / # jest unit test configuration | - npmignore | - gitignore | - babelrc | - README.md |- package.jsonCopy the code

Component library structure

Expose an entry for all components, with the component detail code showing only the Button section

src/components/index.js

import Alert from './components/alert/index.js' import Button from './components/button/index.js' import ButtonGroup from './components/button-group/index.js' import Checkbox from './components/checkbox/index.js' import CheckboxGroup from './components/checkbox-group/index.js' import DatePicker from './components/date-picker/index.js' import Form from './components/form/index.js' import FormItem from './components/form-item/index.js' import Icon from './components/icon/index.js' import Input from './components/input/index.js' import Option from './components/option/index.js' import Pagination from './components/pagination/index.js' import Radio from './components/radio/index.js' import RadioGroup from './components/radio-group/index.js' import Rate from './components/rate/index.js' import Select from './components/select/index.js' import Switch from './components/switch/index.js' import Table from './components/table/index.js' import HTableColumn from './components/table-column/index.js' import Tag from './components/tag/index.js' const components = [ Button, ButtonGroup, Checkbox, CheckboxGroup, DatePicker, Form, FormItem, Icon, Input, Option, Pagination, Radio, RadioGroup, Rate, Select, Switch, Table, HTableColumn, Tag, ] const install = function(Vue, opts = {}) { components.map(component => { Vue.component(component.name, component); }) Vue.prototype.$alert = Alert; } /* Support the use of tags to import */ if (typeof window! == 'undefined' && window.Vue) { install(window.Vue); } export default { install, Alert, Button, ButtonGroup, Checkbox, CheckboxGroup, DatePicker, Form, FormItem, Icon, Input, Option, Pagination, Radio, RadioGroup, Rate, Select, Switch, Table, HTableColumn, Tag, }Copy the code

src/components/button/index.js

import HButton from './src/button';

HButton.install = function(Vue) {
  Vue.component(HButton.name, HButton);
};

export default HButton;
Copy the code

The component part looks something like this

Packaging configuration

Directory built, it is time to fill the flesh, to package a component library project, must be configured first our Webpack, or write the source code can not run. So let’s navigate to the build directory first

Webpack. Base. Js. Store some basic rules configuration

Webpack. Prod. Js. Packaging configuration of the entire component library

build/webpack.base.conf.js

'use strict' const path = require('path') const utils = require('./utils') const config = require('.. /config') const vueLoaderConfig = require('./vue-loader.conf') function resolve (dir) { return path.join(__dirname, '.. ', dir) } module.exports = { context: path.resolve(__dirname, '.. /'), entry: { app: process.env.NODE_ENV === 'production' ? './src/index.js' : './src/main.js' }, output: { path: config.build.assetsRoot, filename: '[name].js', publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath }, resolve: { extensions: ['.js', '.vue', '.json'], alias: { 'vue$': 'vue/dist/vue.esm.js', '@': resolve('src'), 'untils': resolve('src/untils'), } }, module: { rules: [ { test: /\.vue$/, loader: 'vue-loader', options: vueLoaderConfig }, { test: /\.js$/, loader: 'babel-loader', include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')] }, { test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, loader: 'url-loader', options: { limit: 10000, name: utils.assetsPath('img/[name].[hash:7].[ext]') } }, { test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, loader: 'url-loader', options: { limit: 10000, name: utils.assetsPath('media/[name].[hash:7].[ext]') } }, { test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, loader: 'url-loader', options: { limit: 10000, name: utils.assetsPath('fonts/[name].[hash:7].[ext]') } }, { test: /\.scss$/, loaders:['style','css','sass'] } ] }, node: { // prevent webpack from injecting useless setImmediate polyfill because Vue // source contains it (although only uses it if it's native). setImmediate: false, // prevent webpack from injecting mocks to Node native modules // that does not make sense for the client dgram: 'empty', fs: 'empty', net: 'empty', tls: 'empty', child_process: 'empty' } }Copy the code

build/webpack.prod.conf.js

'use strict'
const path = require('path')
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')
const baseWebpackConfig = require('./webpack.base.conf')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')

const env = require('../config/prod.env')

const webpackConfig = merge(baseWebpackConfig, {
  module: {
    rules: utils.styleLoaders({
      sourceMap: config.build.productionSourceMap,
      extract: true,
      usePostCSS: true
    })
  },
  devtool: config.build.productionSourceMap ? config.build.devtool : false,
  output: {
    path: config.build.assetsRoot,
    filename: 'heaven-ui.min.js',
    library: 'heaven-ui',
    libraryTarget: 'umd'
  },
  plugins: [
    // http://vuejs.github.io/vue-loader/en/workflow/production.html
    new webpack.DefinePlugin({
      'process.env': env
    }),
    new UglifyJsPlugin({
      uglifyOptions: {
        compress: {
          warnings: false
        }
      },
      sourceMap: config.build.productionSourceMap,
      parallel: true
    }),
    // extract css into its own file
    new ExtractTextPlugin({
      filename: 'heaven-ui.min.css',
    }),
    // Compress extracted CSS. We are using this plugin so that possible
    // duplicated CSS from different components can be deduped.
    new OptimizeCSSPlugin()
  ]
})

if (config.build.productionGzip) {
  const CompressionWebpackPlugin = require('compression-webpack-plugin')

  webpackConfig.plugins.push(
    new CompressionWebpackPlugin({
      asset: '[path].gz[query]',
      algorithm: 'gzip',
      test: new RegExp(
        '\\.(' +
        config.build.productionGzipExtensions.join('|') +
        ')$'
      ),
      threshold: 10240,
      minRatio: 0.8
    })
  )
}

if (config.build.bundleAnalyzerReport) {
  const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
  webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}

module.exports = webpackConfig
Copy the code

Here, I configured the output directory as lib, and the result after packaging is as follows:

Unit testing

Unit tests have the following advantages:

1. Hidden feature bugs may be detected

2. Ensure the security of code refactoring.

Every component in the component library is likely to be refactored or updated over time, and if unit test coverage is high, potential problems are more likely to be discovered after code changes. For example, some functions are missing after the version is upgraded.

After component library development and debugging, we need to write unit tests corresponding to each component to achieve 100% coverage as the goal.

Take Button as an example:

test/specs/Button.spec.js

import Vue from 'vue' import Button from '@/components/button' describe('button.vue', () = > {it (' button the existence, () = > {expect (button). To. Be. Ok. })})Copy the code

Release NPM

Before publishing NPM, we have to write our package.json according to NPM’s package distribution rules. Let’s solve the problem of component library packaging first, we need to get scaffolding to compile our component code and export it to the specified directory, we usually export it to lib directory according to the package distribution specification. After the package of the project is completed, we need to compile description and keywords of the package file, which are described as follows:

Description Specifies the description text of the component library

Keywords Keywords of the component library

License Agreement

Repository Git repository address associated with the repository component

Homepage displays the homepage address of the homepage component library

The main entry address of the component library (the address introduced when using the component)

Private Indicates that the component library is private. If you want to publish the component library to the NPM public network, delete this property or set it to false

PublishConfig is used to set the address of the NPM publication. This configuration is critical as an NPM server within the team and can be set up as a private NPM repository

The method of publishing to NPM is also very simple. First, we need to register to the NPM official website to register an account, and then log in to the console. Finally, we execute NPM publish

Yes. The specific process is as follows:

NPM publish --access public if publish fails, run NPM publish --access publicCopy the code

Note: This time the version number of the publish needs to be changed

Five, the summary

We can use vue-CLI or another tool to generate a separate demo project that will introduce our component library. If your package has not been published, it can be published in your

Create a link using the NPM link or YARN link command in the component library project directory

Then use NPM link package_name or YARN Link package_name in your demo directory where package_name belongs to your component library

Package name, then in the entry file of your demo project

Import Vue from Vue import Heaven from 'Heaven - UI' import 'Heaven - UI /dist/ Heaven -ui.min. CSS' Vue.use(Heaven)Copy the code

With this set up, the components we created can be used in the project