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:
- Vue-cli: officially supported CLI scaffolding that provides a modern build setup with zero configuration;
- Vue: Progressive JavaScript library;
- 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