I usually like to record and summarize some things, including some components. When I accumulate a lot of them, I find that the scattered accumulation is no longer suitable for management.
So I began to think, is there any good way to manage these more scattered things in a relatively standardized manner? If components are managed in the form of component libraries, will it be more suitable for your own accumulation and facilitate future work?
So I began to refer to some excellent UI component libraries on the market, such as element-ui , vux , vant , etc., read the source code to understand the construction of its architecture, and then sorted out a set of my own mobile UI component library vui .
In my spare time, I am active in major technical communities. I often have some friends who have worked for a while or are still preparing to find an internship to ask the author some questions: How to settle yourself and make your own framework, wheel, and library? How to make a component library? Will making a component library yourself become the highlight of your resume? Can you write some articles about component library development? ...
This blog post was born in the mood of answering doubts and sharing questions.
If you have any questions when reading articles, please join the discussion group to discuss (in addition to a group of big guys talking every day, there are also a group of girls~)
Front-end hodgepodge: 731175396
github: https://github.com/xuqiang521
Without further ado, let's go directly to the actual combat chapter~
Here I will only talk about the installation of NODE under Mac and window
If you haven't installed the mac package manager homebrew , the first step is to install it first
/usr/bin/ruby -e " $( curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install ) " Install node using homebrew
brew install node If you want to go to the window official website to download the corresponding version, then click next step to complete the installation.
After the installation is complete, check node and npm versions
node -v
# v9.6.1
npm -v
# 5.6.0 Since then, the node environment on your computer has been built. Next, we need to install the component library to build the dependency scaffolding.
# 全局安装
npm i -g vue-cli
# 查看vue-cli用法
vue -h
# 查看版本
vue -V
# 2.9.3 Initialize a project named personal-components-library using the vue-cli 's init directive
# 项目基于 webpack
vue init webpack personal-components-libraryWhen constructing, the scaffolding will ask you to fill in some descriptions and dependencies of the project. Please refer to the content I selected below to fill in it.
# 项目名称
Project name ? personal-components-library
# 项目描述
Project description ? A Personal Vue.js components Library project
# 项目作者
Author ? qiangdada
# 项目构建 vue 版本(选择默认项)
Vue build ? standalone
# 是否下载 vue-router (后期会用到,这里选 Yes)
Install vue-router ? Yes
# 是否下载 eslint (为了制定合理的开发规范,这个必填)
Use ESLint to lint your code ? Yes
# 安装默认的标准 eslint 规则
Pick an ESLint preset ? Standard
# 构建测试案例
Set up unit tests ? Yes
# 安装 test 依赖 (选择 karma + mocha)
Pick a test runner ? karma
# 构建 e2e 测试案例 (No)
Setup e2e tests with Nightwatch ? No
# 项目初始化完是否安装依赖 (npm)
Should we run ` npm install ` for you after the project has been created ? (recom
mended) npm After you have selected it, you can wait. vue-cli will help you build the project and perform dependency installation.
The initialization project structure is as follows:
├── build webpack打包以及本地服务的文件都在里面
├── config 不同环境的配置都在这里
├── index.html 入口html
├── node_modules npm安装的依赖包都在这里面
├── package.json 项目配置信息
├── README.md 项目介绍
├── src 我们的源代码
│ ├── App.vue vue主入口文件
│ ├── assets 资源存放(如图片)
│ ├── components 可以复用的模块放在这里面
│ ├── main.js 入口js
│ ├── router 路由管理
└── webpack.config.js webpack配置文件
├── static 被copy的静态资源存放地址
├── test 测试文档和案例If you use npm to download dependencies too slowly or some resources are walled, it is recommended to use cnpm to download dependencies
# 全局安装 cnpm
npm install -g cnpm --registry=https://registry.npm.taobao.org
# 使用 cnpm 进行依赖安装
cnpm i You can start your vue project after the dependency installation is completed ~
npm run dev Then visit http://localhost:8080 and you can successfully access the vue project built through vue-cli . At this point, the development environment that your component library depends on has been installed.
First of all, we need to clarify the purpose of this section. We need to modify the directory in order to better develop component libraries.
We have already built the vue project in the previous section, but the initialized project directory cannot meet the subsequent development and maintenance of a component library. Therefore, what we need to do in this chapter is to transform the directory of the initialized vue project and turn it into the directory needed by the component library. Let's take action next.
demo and文档of component librariesmixins , etc. of component registration (for this we need to transform the initialized src directory)OK, start to transform the directory of the projects you initialized.
From the previous example, we know that when we start the local service, the main entry file of the page is index.html . Now our first step is to move the main entrance of the page html and js to the examples directory. The specific examples directory is as follows
├── assets css,图片等资源都在这
├── pages 路由中所有的页面
├── src
│ ├── components demo中可以复用的模块放在这里面
│ ├── index.js 入口js
│ ├── index.tpl 页面入口
│ ├── App.vue vue主入口文件
│ ├── router.config.js 路由jsThe modified code of each file is as follows
index.js
import Vue from 'vue'
import App from './App'
import router from './router.config'
Vue . config . productionTip = false
/* eslint-disable no-new */
new Vue ( {
el : '#app-container' ,
router ,
components : { App } ,
template : '<App/>'
} ) index.tpl
<!DOCTYPE html >
< html lang =" en " >
< head >
< meta charset =" UTF-8 " >
< meta name =" viewport " content =" width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0 " >
< title > My Component Library </ title >
</ head >
< body >
< div id =" app-container " >
< app > </ app >
</ div >
</ body >
</ html > App.vue
< template >
< div id =" app " >
< router-view />
</ div >
</ template >
< script >
export default {
name : 'App'
}
</ script > router.config.js
import Vue from 'vue'
import Router from 'vue-router'
import hello from '../pages/hello' // 请自行去pages下面创建一个hello.vue,以方便之后的测试
Vue . use ( Router )
export default new Router ( {
routes : [
{
path : '/' ,
component : hello
}
]
} ) src directory is mainly used to store the main entry files, tool methods, mixins and other files for the registered component. We can know from the examples directory above that some files in the original src need to be deleted. The modified directory is as follows
├── mixins mixins方法存放在这
├── utils 一些常用辅助方法存放在这
├── index.js 组件注册主入口 Think about it, when you see this, you should also know what we need to do now. That's right, it's just to modify the entry file of the local service. If it is just possible to run, then modify the js entry in entry and the page entry reference of html-webpack-plugin . The code is as follows (only key codes are placed)
entry: {
'vendor' : [ 'vue' , 'vue-router' ] ,
'vui' : './examples/src/index.js'
} ,
// ...
plugins : [
// ...
// 将入口改成examples/src/index.tpl
new HtmlWebpackPlugin ( {
chunks : [ 'vendor' , 'vui' ] ,
template : 'examples/src/index.tpl' ,
filename : 'index.html' ,
inject : true
} )
] OK, modified. Re-execute npm run dev once, and your project can run under the new entry file
In this section, what we need to implement is the service we started locally, which can use the components below packages . Let's develop the simplest hello component to explain it
hello component under packages In order to have a good binding nature, here we restrict: before starting writing a component, a specified directory and file name must be managed uniformly. The files under hello component in packages directory are as follows
├── hello
│ ├── hello.vue hello.vue content is as follows
< template >
< div class =" v-hello " >
hello {{ message }}
</ div >
</ template >
< script >
export default {
name : 'v-hello' ,
props : {
message : String
}
}
</ script > src/index.js sec/index.js file is also mentioned above. It is mainly used to manage the registration of all components in our component library
import Hello from '../packages/hello'
const install = function ( Vue ) {
if ( install . installed ) return
Vue . component ( Hello . name , Hello )
}
if ( typeof window !== 'undefined' && window . Vue ) {
install ( window . Vue )
}
export default {
install ,
Hello
} examples/src/index.js entry js file Next, I need to reference the hello component we wrote in the modified examples in the previous section
import vui from 'src/index.js'
// 完整引用
Vue . use ( vui )
// 独立引用
const { Hello } = vui
Vue . component ( Hello . name , Hello ) examples/pages/hello.vue In examples/pages we need to create a demo file with the same name as the component name and use the component
< v-hello message =" my component library " > </ v-hello >If your running result is the same as the picture above, then congratulations. You have successfully taken another step towards the development of component libraries ~
After seeing this, I need all readers to centrally manage files according to their own preferences (of course, you can also refer to the demo I gave above). Only in this way can the subsequent development work of our component library be smooth.
In the next section, we will optimize the packaged files below build and take you to publish your developed components to npm official website, so that your component library can be used more conveniently!
As usual, before the chapter text begins, we need to know what needs to be done in this chapter and why.
Since the initial project of scaffolding only has one centrally packaged file for build files webpack.prod.conf.js
In order for our component library to be better used in the future, we need to extract all the modules corresponding to the component library into a vui.js file (what you like the name) so that we can refer to our component library in the following way.
import Vue from 'vue'
import vui from 'x-vui'
Vue . use ( vui ) We also need to package and manage the relevant files in examples , because we have to develop the document official website of the component library later, and the relevant entrances of the document official website are in examples
We can see from the initialization project that webpack file in the build file is as follows
├── webpack.base.conf.js 基础配置文件
├── webpack.dev.conf.js 本地服务配置文件
├── webpack.prod.conf.js 打包配置文件
├── webpack.test.conf.js 测试配置文件(这里先不做过多描述) The initialized output output directory is dist . This directory is the output directory after the entire project is packaged, not the directory required by our component library. Since it is not what we want, what do we want to do in the directory we need?
lib/vui.js (component library js main file)lib/vui-css/index.css (The main file of the component library css, we will not describe too much about the css packaging in this chapter, and the following chapters will be explained separately)examples file examples/dist packaged from the examples file (the main entrance of the official website of the later document) Since the goal has been set, the next thing we need to do is to organize the relevant webpack packaging files first, as follows
├── webpack.base.conf.js 基础配置文件(配置方面和webpack.dev.conf.js的配置进行部分整合)
├── webpack.dev.conf.js 本地服务配置文件(将纯配置文件进行对应的删减)
├── webpack.build.js 组件库入口文件打包配置文件(将webpack.prod.conf.js重命名)
├── webpack.build.min.js examples展示文件打包配置文件(新增文件)1. webpack.base.conf.js
Before starting to transform the webpack.base.conf.js file, we need to understand what needs to be done in two packaged files.
webpack.build.js : outputs the lib/vui.js component library js main file, and will use webpack.base.conf.js and webpack.dev.conf.js related configurationswebpack.build.min.js : outputs examples/dist document related files, and uses webpack.base.conf.js and webpack.dev.conf.js related configurations Since both webpack packaging files use webpack.base.conf.js and webpack.dev.conf.js related configurations, why not integrate some of the same files into webpack.base.conf.js files? The goal is clear, let's follow me next
'use strict'
const path = require ( 'path' )
const utils = require ( './utils' )
const config = require ( '../config' )
const vueLoaderConfig = require ( './vue-loader.conf' )
const webpack = require ( 'webpack' )
const CopyWebpackPlugin = require ( 'copy-webpack-plugin' )
const HtmlWebpackPlugin = require ( 'html-webpack-plugin' )
function resolve ( dir ) {
return path . join ( __dirname , '..' , dir )
}
const HOST = process . env . HOST
const PORT = process . env . PORT && Number ( process . env . PORT )
const createLintingRule = ( ) => ( {
test : / .(js|vue)$ / ,
loader : 'eslint-loader' ,
enforce : 'pre' ,
include : [ resolve ( 'src' ) , resolve ( 'test' ) ] ,
options : {
formatter : require ( 'eslint-friendly-formatter' ) ,
emitWarning : ! config . dev . showEslintErrorsInOverlay
}
} )
module . exports = {
context : path . resolve ( __dirname , '../' ) ,
// 文件入口
entry : {
'vendor' : [ 'vue' , 'vue-router' ] ,
'vui' : './examples/src/index.js'
} ,
// 输出目录
output : {
path : path . join ( __dirname , '../examples/dist' ) ,
publicPath : '/' ,
filename : '[name].js'
} ,
resolve : {
extensions : [ '.js' , '.vue' , '.json' ] ,
// 此处新增了一些 alias 别名
alias : {
'vue$' : 'vue/dist/vue.esm.js' ,
'@' : resolve ( 'src' ) ,
'src' : resolve ( 'src' ) ,
'packages' : resolve ( 'packages' ) ,
'lib' : resolve ( 'lib' ) ,
'components' : resolve ( 'examples/src/components' )
}
} ,
// 延用原先的大部分配置
module : {
rules : [
// 原先的配置...
// 整合webpack.dev.conf.js中css相关配置
... utils . styleLoaders ( { sourceMap : config . dev . cssSourceMap , usePostCSS : true } )
]
} ,
// 延用原先的配置
node : {
// ...
} ,
devtool : config . dev . devtool ,
// 整合webpack.dev.conf.js中的devServer选项
devServer : {
clientLogLevel : 'warning' ,
historyApiFallback : {
rewrites : [
{ from : / .* / , to : path . posix . join ( config . dev . assetsPublicPath , 'index.html' ) } ,
] ,
} ,
hot : true ,
contentBase : false , // since we use CopyWebpackPlugin.
compress : true ,
host : HOST || config . dev . host ,
port : PORT || config . dev . port ,
open : config . dev . autoOpenBrowser ,
overlay : config . dev . errorOverlay
? { warnings : false , errors : true }
: false ,
publicPath : config . dev . assetsPublicPath ,
proxy : config . dev . proxyTable ,
quiet : true , // necessary for FriendlyErrorsPlugin
watchOptions : {
poll : config . dev . poll ,
}
} ,
// 整合webpack.dev.conf.js中的plugins选项
plugins : [
new webpack . DefinePlugin ( {
'process.env' : require ( '../config/dev.env' )
} ) ,
new webpack . HotModuleReplacementPlugin ( ) ,
new webpack . NamedModulesPlugin ( ) ,
new webpack . NoEmitOnErrorsPlugin ( ) ,
// 页面主入口
new HtmlWebpackPlugin ( {
chunks : [ 'manifest' , 'vendor' , 'vui' ] ,
template : 'examples/src/index.tpl' ,
filename : 'index.html' ,
inject : true
} )
]
}2. webpack.dev.conf.js
Here you only need to delete the configuration integrated into webpack.base.conf.js to avoid duplication of code
'use strict'
const utils = require ( './utils' )
const config = require ( '../config' )
const baseWebpackConfig = require ( './webpack.base.conf' )
const FriendlyErrorsPlugin = require ( 'friendly-errors-webpack-plugin' )
const portfinder = require ( 'portfinder' )
module . exports = new Promise ( ( resolve , reject ) => {
portfinder . basePort = process . env . PORT || config . dev . port
portfinder . getPort ( ( err , port ) => {
if ( err ) {
reject ( err )
} else {
process . env . PORT = port
baseWebpackConfig . devServer . port = port
baseWebpackConfig . plugins . push ( new FriendlyErrorsPlugin ( {
compilationSuccessInfo : {
messages : [ `Your application is running here: http:// ${ baseWebpackConfig . devServer . host } : ${ port } ` ] ,
} ,
onErrors : config . dev . notifyOnErrors
? utils . createNotifierCallback ( )
: undefined
} ) )
resolve ( baseWebpackConfig )
}
} )
} ) After the two files of webpack.base.conf.js and webpack.dev.conf.js are adjusted, execute npm run dev again.
The above picture appears to indicate that your local service file has been modified successfully as expected~
1. webpack.build.js
The main purpose of this file is to package all component-related files in the component library together and output lib/vui.js main file
'use strict'
const webpack = require ( 'webpack' )
const config = require ( './webpack.base.conf' )
// 修改入口文件
config . entry = {
'vui' : './src/index.js'
}
// 修改输出目录
config . output = {
filename : './lib/[name].js' ,
library : 'vui' ,
libraryTarget : 'umd'
}
// 配置externals选项
config . externals = {
vue : {
root : 'Vue' ,
commonjs : 'vue' ,
commonjs2 : 'vue' ,
amd : 'vue'
}
}
// 配置plugins选项
config . plugins = [
new webpack . DefinePlugin ( {
'process.env' : require ( '../config/prod.env' )
} )
]
// 删除devtool配置
delete config . devtool
module . exports = config2. webpack.build.min.js
The main purpose of this file is to open a single package address and output the relevant files in examples to the examples/dist directory (that is, the official website entrance of the subsequent document)
const path = require ( 'path' )
const webpack = require ( 'webpack' )
const merge = require ( 'webpack-merge' )
const baseWebpackConfig = require ( './webpack.base.conf' )
const config = require ( '../config' )
const ExtractTextPlugin = require ( 'extract-text-webpack-plugin' )
const webpackConfig = merge ( baseWebpackConfig , {
output : {
chunkFilename : '[id].[hash].js' ,
filename : '[name].min.[hash].js'
} ,
plugins : [
new webpack . optimize . UglifyJsPlugin ( {
compress : {
warnings : false
} ,
output : {
comments : false
} ,
sourceMap : false
} ) ,
// extract css into its own file
new ExtractTextPlugin ( {
filename : '[name].[contenthash].css' ,
allChunks : true ,
} ) ,
// keep module.id stable when vendor modules does not change
new webpack . HashedModuleIdsPlugin ( ) ,
// enable scope hoisting
new webpack . optimize . ModuleConcatenationPlugin ( ) ,
// split vendor js into its own file
new webpack . optimize . CommonsChunkPlugin ( {
name : 'vendor' ,
minChunks ( module ) {
// any required modules inside node_modules are extracted to vendor
return (
module . resource &&
/ .js$ / . test ( module . resource ) &&
module . resource . indexOf (
path . join ( __dirname , '../node_modules' )
) === 0
)
}
} ) ,
new webpack . optimize . CommonsChunkPlugin ( {
name : 'manifest' ,
minChunks : Infinity
} ) ,
new webpack . optimize . CommonsChunkPlugin ( {
name : 'app' ,
async : 'vendor-async' ,
children : true ,
minChunks : 3
} ) ,
]
} )
module . exports = webpackConfig When we get all these files together, the last step is to write the package command to scripts of package.json .
"scripts" : {
"build:vui" : " webpack --progress --hide-modules --config build/webpack.build.js && rimraf examples/dist && cross-env NODE_ENV=production webpack --progress --hide-modules --config build/webpack.build.min.js "
}, Run the command, npm run build:vui , go
At this point, the local service and two packaged files have been transformed. Let’s try to use npm ~
Note that if you don’t have your own npm account, please register an account on your own at npm official website. Click here to enter the official website to register. The registration steps are relatively simple. I will not describe it more here. If you have any questions, you can ask me in the WeChat group.
mkdir qiangdada520-npm-test
cd qiangdada520-npm-test
# npm 包主入口js文件
touch index.js
# npm 包首页介绍(具体啥内容你自行写入即可)
touch README.md
npm init
# package name: (qiangdada520-npm-test)
# version: (1.0.0)
# description: npm test
# entry point: (index.js) index.js
# test command:
# git repository:
# keywords: npm test
# author: qiangdada
# license: (ISC) Then confirm, package.json will be generated as follows
{
"name" : "qiangdada-npm-test" ,
"version" : "1.0.0" ,
"description" : "npm test" ,
"main" : "index.js" , // npm 包主入口js文件
"scripts" : {
"test" : "echo "Error: no test specified" && exit 1"
} ,
"keywords" : [
"npm" ,
"test"
] ,
"author" : "qiangdada" ,
"license" : "MIT"
} Next, we need to connect to our registered npm account locally
npm adduser
# Username: 填写你自己的npm账号
# Password: npm账号密码
# Email: (this IS public) 你npm账号的认证邮箱
# Logged in as xuqiang521 on https://registry.npmjs.org/. 连接成功Execute npm publish to start publishing
npm publish
# + [email protected] At this time npm you can search and see the package you just released~
At present, we have written the simplest hello component in the component library, but this does not affect our publishing to npm official website at all, and the publishing steps are as simple as the example above.
Modify some descriptions in package.json file
// npm 包js入口文件改为 lib/vui.js
"main" : "lib/vui.js" ,
// npm 发布出去的包包含的文件
"files" : [
"lib" ,
"src" ,
"packages"
] ,
// 将包的属性改为公共可发布的
"private" : false , Note that when testing the npm package is released, remember that the version version in package.json is higher than the previous one.
Start publishing
# 打包,输出lib/vui.js
npm run build:vui
# 发布
npm publish
# + [email protected] Select a local vue project and enter the project
npm i component-library-test
# or
cnpm i component-library-testRegister components in project entrance file
import Vue from 'vue'
import vui from 'component-library-test'
Vue . use ( vui )Use on the page
< v-hello message =" component library " > </ v-hello > At this point, we have successfully transformed the local service files, implemented the package of the component library main file and the package of the official document website main entrance, and finally learned how to use npm for project release.
In the next chapter, I will explain the packaging of css file in the component library.
In the previous section, we have already packaged the js file. However, for component libraries, what we need to do is not only manage the js file, but also manage the css file, so as to ensure the subsequent use of the component library.
In this section, I will talk about how to reasonably use gulp to separate packaging and management of css files in projects based on webpack construction.
Before we start, we need to clarify two goals:
In order to facilitate management, every time we create a new component, we need to create a corresponding css file to manage the component's style and achieve single management.
Here, we will store all css files in packages/vui-css directory. The specific structure is as follows
├── src
│ ├── common 存放组件公用的css文件
│ ├── mixins 存放一些mixin的css文件
│ ├── index.css css主入口文件
│ ├── hello.css 对应hello组件的单一css文件
├── gulpfile.js css打包配置文件
├── package.json 相关的版本依赖Before starting to write component css, we need to clarify some points:
I personally think that the best way on the market is to manage the components in a single css and write the css using bem . If you want to know bem , click the link below
Next, let’s explain the simple hello component. Before starting, put the content of hello.vue
< template >
< div class =" v-hello " >
< p class =" v-hello__message " > hello {{ message }} </ p >
</ div >
</ template >
< script >
export default {
name : 'v-hello' ,
props : {
message : String
}
}
</ script > Create hello.css in packages/vui-css/src directory
@b v-hello {
color : # fff ;
transform : scale ( 1 );
@e message {
background : # 0067ED ;
}
} Then import the hello.css file in the main entrance index.css
@import './hello.css' ; Introducing component library styles in examples/src/index.js
import 'packages/vui-css/src/index.css' But from the hello.css content, we can see that this is a typical bem writing method and cannot be parsed normally. We need to introduce the corresponding postcss plugin to parse the bem syntax. Here we will use the postcss-salad plugin developed饿了么团队to parse the bem syntax. Secondly, this sass-like style css file also needs to use a plugin called precss . Install the dependencies first.
npm i postcss-salad precss -D After the dependency installation is completed, we need to create salad.config.json in the project root directory to configure bem rules. The specific rules are as follows
{
"browsers" : [ " ie > 8 " , " last 2 versions " ],
"features" : {
"bem" : {
"shortcuts" : {
"component" : " b " ,
"modifier" : " m " ,
"descendent" : " e "
},
"separators" : {
"descendent" : " __ " ,
"modifier" : " -- "
}
}
}
} Next we need to use postcss-salad and precss plugins in the initialized .postcssrc file, as follows
module . exports = {
"plugins" : {
"postcss-import" : { } ,
"postcss-salad" : require ( './salad.config.json' ) ,
"postcss-url" : { } ,
"precss" : { } ,
"autoprefixer" : { } ,
}
}OK, when you run the project again at this time, you will see that the css takes effect, as shown in the figure
In order to better manage the css files in the component library, and to ensure that users can also introduce the css files corresponding to the component when only one or several components in the component library are introduced. Therefore, we need to package the css file separately. Here we need to use gulp to perform the corresponding packaging operation. Before you start to get the packaging details, please make sure that you have installed gulp globally. If not, please install it
npm i gulp -g
# 查看版本
gulp -v
# CLI version 3.9.1 Next, let's take a look at what dependencies need to be used in the packages/vui-css/package.json file
{
"name" : "vui-css" ,
"version" : "1.0.0" ,
"description" : "vui css." ,
"main" : "lib/index.css" ,
"style" : "lib/index.css" ,
// 和组件发布一样,也需要指定目录
"files" : [
"lib" ,
"src"
] ,
"scripts" : {
"build" : "gulp build"
} ,
"license" : "MIT" ,
"devDependencies" : {
"gulp" : "^3.9.1" ,
"gulp-cssmin" : "^0.2.0" ,
"gulp-postcss" : "^7.0.1" ,
"postcss-salad" : "^2.0.1"
} ,
"dependencies" : { }
} We can see that this is actually similar to the dependency required for css files in the component library, except that this is the postcss plug-in based on gulp . Before starting to configure gulpfile.js , don't forget to execute npm i for dependency installation.
Next we start configuring gulpfile.js , as follows
const gulp = require ( 'gulp' )
const postcss = require ( 'gulp-postcss' )
const cssmin = require ( 'gulp-cssmin' )
const salad = require ( 'postcss-salad' ) ( require ( '../../salad.config.json' ) )
gulp . task ( 'compile' , function ( ) {
return gulp . src ( './src/*.css' )
// 使用postcss-salad
. pipe ( postcss ( [ salad ] ) )
// 进行css压缩
. pipe ( cssmin ( ) )
// 输出到 './lib' 目录下
. pipe ( gulp . dest ( './lib' ) )
} )
gulp . task ( 'build' , [ 'compile' ] ) Now you can start executing the gulp build command to package the css file. Of course, in order to facilitate and better execute packaging commands, we now need to add a css build command to package.json in the root directory of the project, as follows
"scripts" : {
"build:vui-css" : " gulp build --gulpfile packages/vui-css/gulpfile.js && rimraf lib/vui-css && cp-cli packages/vui-css/lib lib/vui-css && rimraf packages/vui-css/lib "
} Execute npm run build:vui-css , go ahead and finally packaged the js and css files of the component library as shown in the following figure
OK, by this point, you can already introduce components and their styles separately. Finally, in order to enable users to use your component's css directly, don't forget to publish it to npm official website~ The steps are as follows
# 进到vui-css目录
cd packages/vui-css
# 发布
npm publishAt this point, we have completed the management and separate packaging of the css file, and completed the single output of the css file. In this way, we can have a better way to develop and manage the component library css file while also being able to facilitate the use of the component library!
So far, we have built a new directory required for the component library, and we have also transformed the packaging of js files and css files. We have made sufficient preparations for the component library development, but we still need to do some very important pre-work to facilitate the development and maintenance of subsequent components of the component library.
For front-end testing, it is an important branch of front-end engineering. Therefore, how can such an important part be missed in our component library? For unit tests, there are mainly two types
In this chapter, I Karma lead you to unit test components in our component library using the two frameworks based on Mocha initialization.
I believe most people who have been exposed to unit tests are familiar with the two frameworks, Karma + Mocha , but here I think it is necessary to open a separate section to give a brief introduction to the two frameworks.
In order to enable the components in our component library to run in major mainstream web browsers for testing, we chose Karma . The most important thing is that Karma is a unit testing framework recommended by vue-cli . If you want to know more about Karma , please check the official website of Karma
simple , flexible , fun test frameworkPromisecoverage test reportbefore() , after() , beforeEach() , and afterEach() , so that we can set different operations at different stages to better complete our tests. Here I will introduce three basic usages of mocha , as well as the four hook functions of describe (life cycle)
describe(moduleName, function): describe is nestable, describing whether the test case is correct.
describe ( '测试模块的描述' , ( ) => {
// ....
} ) ; **it(info, function): **One it corresponds to a unit test case
it ( '单元测试用例的描述' , ( ) => {
// ....
} )Usage of assertion library
expect ( 1 + 1 ) . to . be . equal ( 2 ) Lifecycle of describe
describe ( 'Test Hooks' , function ( ) {
before ( function ( ) {
// 在本区块的所有测试用例之前执行
} ) ;
after ( function ( ) {
// 在本区块的所有测试用例之后执行
} ) ;
beforeEach ( function ( ) {
// 在本区块的每个测试用例之前执行
} ) ;
afterEach ( function ( ) {
// 在本区块的每个测试用例之后执行
} ) ;
// test cases
} ) ; Students who want to know more about mocha operations can click the link below to view it
In the above section, I briefly introduce the test frameworks Karma and Mocha , which are officially recommended by Vue. I also hope that when you see this, you can have a simple understanding of unit testing and common testing frameworks.
Before the actual unit test begins, let’s take a look at the configuration of Karma . Here we directly look at the configuration in the karma.conf.js file initialized by the vue-cli scaffold (I have commented on the specific use)
var webpackConfig = require ( '../../build/webpack.test.conf' )
module . exports = function karmaConfig ( config ) {
config . set ( {
// 浏览器
browsers : [ 'PhantomJS' ] ,
// 测试框架
frameworks : [ 'mocha' , 'sinon-chai' , 'phantomjs-shim' ] ,
// 测试报告
reporters : [ 'spec' , 'coverage' ] ,
// 测试入口文件
files : [ './index.js' ] ,
// 预处理器 karma-webpack
preprocessors : {
'./index.js' : [ 'webpack' , 'sourcemap' ]
} ,
// webpack配置
webpack : webpackConfig ,
// webpack中间件
webpackMiddleware : {
noInfo : true
} ,
// 测试覆盖率报告
coverageReporter : {
dir : './coverage' ,
reporters : [
{ type : 'lcov' , subdir : '.' } ,
{ type : 'text-summary' }
]
}
} )
} Next, let's conduct a simple test on our own hello component (only write one test case), create a new hello.spec.js file in test/unit/specs , and write the following code
import Vue from 'vue' // 导入Vue用于生成Vue实例
import Hello from 'packages/hello' // 导入组件
// 测试脚本里面应该包括一个或多个describe块,称为测试套件(test suite)
describe ( 'Hello.vue' , ( ) => {
// 每个describe块应该包括一个或多个it块,称为测试用例(test case)
it ( 'render default classList in hello' , ( ) => {
const Constructor = Vue . extend ( Hello ) // 获得Hello组件实例
const vm = new Constructor ( ) . $mount ( ) // 将组件挂在到DOM上
// 断言:DOM中包含class为v-hello的元素
expect ( vm . $el . classList . contains ( 'v-hello' ) ) . to . be . true
const message = vm . $el . querySelector ( '.v-hello__message' )
// 断言:DOM中包含class为v-hello__message的元素
expect ( message . classList . contains ( 'v-hello__message' ) ) . to . be . true
} )
} ) After writing the test example, the next step is to conduct the test. Execute npm run test , go to you~, output the result
hello.vue
✓ render default classList in hello From the above test instance of the hello component, we need to instantiate the component into a Vue instance, and sometimes we need to mount it on the DOM
const Constructor = Vue . extend ( Hello )
const vm = new Constructor ( {
propsData : {
message : 'component'
}
} ) . $mount ( ) If each component has multiple unit test instances later, this writing will cause our final test to be bloated. Here we can refer to the unit test tool util.js encapsulated by element . We need to encapsulate some commonly used methods in unit testing. Below I will list some methods provided in the tool.
/**
* 回收 vm,一般在每个测试脚本测试完成后执行回收vm。
* @param {Object} vm
*/
exports . destroyVM = function ( vm ) { }
/**
* 创建一个 Vue 的实例对象
* @param {Object|String} Compo - 组件配置,可直接传 template
* @param {Boolean=false} mounted - 是否添加到 DOM 上
* @return {Object} vm
*/
exports . createVue = function ( Compo , mounted = false ) { }
/**
* 创建一个测试组件实例
* @param {Object} Compo - 组件对象
* @param {Object} propsData - props 数据
* @param {Boolean=false} mounted - 是否添加到 DOM 上
* @return {Object} vm
*/
exports . createTest = function ( Compo , propsData = { } , mounted = false ) { }
/**
* 触发一个事件
* 注: 一般在触发事件后使用 vm.$nextTick 方法确定事件触发完成。
* mouseenter, mouseleave, mouseover, keyup, change, click 等
* @param {Element} elm - 元素
* @param {String} name - 事件名称
* @param {*} opts - 配置项
*/
exports . triggerEvent = function ( elm , name , ... opts ) { }
/**
* 触发 “mouseup” 和 “mousedown” 事件,既触发点击事件。
* @param {Element} elm - 元素
* @param {*} opts - 配置选项
*/
exports . triggerClick = function ( elm , ... opts ) { } Below we will use the defined test tool method to transform the test instance of hello component and transform the hello.spec.js file
import { destroyVM , createTest } from '../util'
import Hello from 'packages/hello'
describe ( 'hello.vue' , ( ) => {
let vm
// 测试用例执行之后销毁实例
afterEach ( ( ) => {
destroyVM ( vm )
} )
it ( 'render default classList in hello' , ( ) => {
vm = createTest ( Hello )
expect ( vm . $el . classList . contains ( 'v-hello' ) ) . to . be . true
const message = vm . $el . querySelector ( '.v-hello__message' )
expect ( message . classList . contains ( 'v-hello__message' ) ) . to . be . true
} )
} ) Re-execute npm run test and output the result
hello.vue
✓ render default classList in hello Above we introduced the usage of unit testing on static judgments. Next, we will test some asynchronous use cases and some interactive events. Before testing, we need to slightly change the code of our hello component, as follows
< template >
< div class =" v-hello " @click =" handleClick " >
< p class =" v-hello__message " > hello {{ message }} </ p >
</ div >
</ template >
< script >
export default {
name : 'v-hello' ,
props : {
message : String
} ,
methods : {
handleClick ( ) {
return new Promise ( ( resolve ) => {
resolve ( )
} ) . then ( ( ) => {
this . $emit ( 'click' , 'this is click emit' )
} )
}
}
}
</ script > Next, we want to test whether hello component can successfully emit information through Promise. For example, the test case is as follows
it ( 'create a hello for click with promise' , ( done ) => {
let result
vm = createVue ( {
template : `<v-hello @click="handleClick"></v-hello>` ,
methods : {
handleClick ( msg ) {
result = msg
}
}
} , true )
vm . $el . click ( )
// 断言消息是异步emit出去的
expect ( result ) . to . not . exist
setTimeout ( _ => {
expect ( result ) . to . exist
expect ( result ) . to . equal ( 'this is click emit' )
done ( )
} , 20 )
} ) Start the test again, execute npm run test , output the result
hello.vue
✓ render default classList in hello
✓ create a hello for click with promiseAt this point, we have learned the configuration of unit tests and some commonly used uses. If you need to know more about unit testing, please follow the link I provided earlier to go to a deeper study
Friends, follow me to practice the previous 5 chapters and have built the basic shelf for our component development. Next, I will take you all to finish the official website of the document with high important components in the component library.
Everyone should know that good open source projects must have official documentation websites, so in order to make our UI library one of the best, we should also use our own official documentation website.
A good official document website requires two things.
Since the component library I have led you to develop is suitable for mobile devices, how can we make our official document website have both API documentation descriptions and mobile example demos. This requires us to develop two sets of pages for adaptation. The following things we need to do:
Before the actual combat begins, let's take a look at the directory structure needed in this chapter.
├── assets css,图片等资源都在这
├── dist 打包好的文件都在这
├── docs PC端需要展示的markdown文件都在这
├── pages 移动端所有的demo都在这
├── src
│ ├── components demo中可以复用的模块放在这里面
│ ├── index.tpl 页面入口
│ ├── is-mobile.js 判断设备
│ ├── index.js PC端主入口js
│ ├── App.vue PC端入口文件
│ ├── mobile.js 移动端端主入口js
│ ├── MobileApp.vue 移动端入口文件
│ ├── nav.config.json 路由控制文件
│ ├── router.config.js 动态注册路由This chapter mainly takes you to realize the conversion of markdown files and routing adaptation of different devices.
After clarifying the ideas, let’s continue to develop our official document website!
From the directory I gave above, we can see that the docs folder is stored in the markdown file, and each markdown file corresponds to the API document of a component. The result we want is to convert every markdown file in the docs to turn it into Vue components, and register the converted Vue components into the route so that it can access each markdown file through the route.
For parsing markdown files into Vue components, there are many three-party webpack plug-ins on the market. Of course, if you have a deep understanding of webpack , you can also try to pick one by yourself. Here I am directly using vue-markdown-loader developed by饿了么团队.
The first step is to rely on installation
npm i vue-markdown-loader -D The second step is to use vue-markdown-loader in the webpack.base.conf.js file
{
test : / .md$ / ,
loader : 'vue-markdown-loader' ,
options : {
// 阻止提取脚本和样式标签
preventExtract : true
}
} The third step is try. First add hello.md file in docs , and then write the instructions for using hello components
## Hello
** Hello 组件,Hello 组件,Hello 组件,Hello 组件**
### 基本用法
```html
< template >
< div class = " hello-page " >
<v-hello message="my component library" @click="handleClick"></v-hello>
<p>{{ msg }}</p>
</ div >
</ template >
< script >
export default {
name : ' hello ' ,
data () {
return {
msg : ' '
}
},
methods : {
handleClick ( msg ) {
this . msg = msg
}
}
}
</ script >
```
### Attributes
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
| ---------- | -------- | ---------- | ------------- | -------- |
| message | 文本信息 | string | — | — |
### Events
| 事件名称 | 说明 | 回调参数 |
| ---------- | -------- | ---------- |
| click | 点击操作 | — | Step 4: Register hello.md into the route
route . push ( {
path : '/component/hello' ,
component : require ( '../docs/hello.md' )
} ) Finally, visit the page. At this time, you can find that the content of hello.md has been converted into a Vue component and can be accessed through routing loading, but the page is ugly~ Just like this
Of course, I don’t need to explain this situation, you may know it too. Yes, the parsed markdown file is so ugly, just because we have neither highlighted the theme for our markdown file nor set the basic style of the document page. So, next we need to add a nice highlighted theme and a clean basic style to our markdown file.
For topics, here we will use the atom-one-dark theme in highlight.js .
The first step is to install highlight.js
npm i highlight -D The second step is to introduce the theme in examples/src/App.vue , and in order to set the basic style of the document, we also need to modify the layout of App.vue
< template >
< div class =" app " >
< div class =" main-content " >
< div class =" page-container clearfix " >
< div class =" page-content " >
< router-view > </ router-view >
</ div >
</ div >
</ div >
</ div >
</ template >
< script >
import 'highlight.js/styles/atom-one-dark.css'
export default {
name : 'App'
}
</ script > The third step is to set the basic style of the document. Create new docs.css in assets and write the initial style. Since the code volume is too large, I will not post it here. You can copy the code in docs.css into the local docs.css file by yourself, and then import it in examples/src/index.js
import '../assets/docs.css' Finally, transform the markdown parsing rules. vue-markdown-loader provides a preprocess interface for us to operate freely. Next, we define the structure of the parsed markdown file and write it in the webpack.base.conf.js file.
// 定义辅助函数wrap,将<code>标签都加上名为'hljs'的class
function wrap ( render ) {
return function ( ) {
return render . apply ( this , arguments )
. replace ( '<code v-pre class="' , '<code class="hljs ' )
. replace ( '<code>' , '<code class="hljs">' )
}
}
// ...
{
test : / .md$ / ,
loader : 'vue-markdown-loader' ,
options : {
preventExtract : true ,
preprocess : function ( MarkdownIt , source ) {
// 为table标签加上名为'table'的class
MarkdownIt . renderer . rules . table_open = function ( ) {
return '<table class="table">'
} ;
MarkdownIt . renderer . rules . fence = wrap ( MarkdownIt . renderer . rules . fence ) ;
return source ;
}
}
}Then, revisit localhost:8080/#/component/hello
OK, our md file has been successfully parsed into Vue components, and has a beautiful highlighted theme and simple basic style~
As I said before, the component library developed by this article is adapted to mobile, so we need to display documents on the PC and demos on the mobile.
In this section, I will take you to adapt the routes to different ends. Of course, this thing is not difficult. It mainly uses webpack to build multi-page features. So how to do it specifically? OK, let's start right now
The first step is to register the js entry file and write it into the webpack.base.conf.js file
entry: {
// ...
'vui' : './examples/src/index.js' , // PC端入口js
'vui-mobile' : './examples/src/mobile.js' // 移动端入口js
} The second step is to register the page entrance and write it in the webpack.base.conf.js file.
plugins: [
// ...
// PC端页面入口
new HtmlWebpackPlugin ( {
chunks : [ 'manifest' , 'vendor' , 'vui' ] ,
template : 'examples/src/index.tpl' ,
filename : 'index.html' ,
inject : true
} ) ,
// 移动端页面入口
new HtmlWebpackPlugin ( {
chunks : [ 'manifest' , 'vendor' , 'vui-mobile' ] ,
template : 'examples/src/index.tpl' ,
filename : 'mobile.html' ,
inject : true
} )
] The entry file registration is completed, and what we need to do next is to determine the device environment. Here, I will use navigator.userAgent with regular expressions to determine whether the environment in which our component library runs belongs to the PC side or the mobile side?
The first step is to write the following code in examples/src/is-mobile.js file
/* eslint-disable */
const isMobile = ( function ( ) {
var platform = navigator . userAgent . toLowerCase ( )
return ( / (android|bbd+|meego).+mobile|kdtunion|weibo|m2oapp|micromessenger|avantgo|bada/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)/|plucker|pocket|psp|series(4|6)0|symbian|treo|up.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino / i ) . test ( platform ) ||
( / 1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw-(n|u)|c55/|capi|ccwa|cdm-|cell|chtm|cldc|cmd-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc-s|devi|dica|dmob|do(c|p)o|ds(12|-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(-|_)|g1 u|g560|gene|gf-5|g-mo|go(.w|od)|gr(ad|un)|haie|hcit|hd-(m|p|t)|hei-|hi(pt|ta)|hp( i|ip)|hs-c|ht(c(-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i-(20|go|ma)|i230|iac( |-|/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |/)|klon|kpt |kwc-|kyo(c|k)|le(no|xi)|lg( g|/(k|l|u)|50|54|-[a-w])|libw|lynx|m1-w|m3ga|m50/|ma(te|ui|xo)|mc(01|21|ca)|m-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|-([1-8]|c))|phil|pire|pl(ay|uc)|pn-2|po(ck|rt|se)|prox|psio|pt-g|qa-a|qc(07|12|21|32|60|-[2-7]|i-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55/|sa(ge|ma|mm|ms|ny|va)|sc(01|h-|oo|p-)|sdk/|se(c(-|0|1)|47|mc|nd|ri)|sgh-|shar|sie(-|m)|sk-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h-|v-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl-|tdg-|tel(i|m)|tim-|t-mo|to(pl|sh)|ts(70|m-|m3|m5)|tx-9|up(.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas-|your|zeto|zte- / i ) . test ( platform . substr ( 0 , 4 ) ) ;
} ) ( )
// 返回设备所处环境是否为移动端,值为boolean类型
export default isMobile The second step is to write the following judgment rules in the js entry file examples/src/index.js on the PC side
import isMobile from './is-mobile'
// 是否为生产环境
const isProduction = process . env . NODE_ENV === 'production'
router . beforeEach ( ( route , redirect , next ) => {
if ( route . path !== '/' ) {
window . scrollTo ( 0 , 0 )
}
// 获取不同环境下,移动端Demo对应的地址
const pathname = isProduction ? '/vui/mobile' : '/mobile.html'
// 如果设备环境为移动端,则直接加载移动端Demo的地址
if ( isMobile ) {
window . location . replace ( pathname )
return
}
document . title = route . meta . title || document . title
next ( )
} ) The third step is to write judgment rules similar to the previous step in the mobile js entry file examples/src/mobile.js
import isMobile from './is-mobile'
const isProduction = process . env . NODE_ENV === 'production'
router . beforeEach ( ( route , redirect , next ) => {
if ( route . path !== '/' ) {
window . scrollTo ( 0 , 0 )
}
// 获取不同环境下,PC端对应的地址
const pathname = isProduction ? '/vui/mobile' : '/mobile.html'
// 如果设备环境不是移动端,则直接加载PC端的地址
if ( ! isMobile ) {
window . location . replace ( pathname )
return
}
document . title = route . meta . title || document . title
next ( )
} ) Finally, improve examples/src/mobile.js file and the mobile page portal MobileApp.vue file
Write the following code in examples/src/mobile.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import MobileApp from './MobileApp'
import Vui from 'src/index'
import isMobile from './is-mobile.js'
import Hello from '../pages/hello.vue'
import 'packages/vui-css/src/index.css'
Vue . use ( Vui )
Vue . use ( VueRouter )
const isProduction = process . env . NODE_ENV === 'production'
const router = new VueRouter ( {
base : isProduction ? '/vui/' : __dirname ,
routes : [ {
path : '/component/hello' ,
component : Hello
} ]
} )
router . beforeEach ( ( route , redirect , next ) => {
if ( route . path !== '/' ) {
window . scrollTo ( 0 , 0 )
}
const pathname = isProduction ? '/vui/' : '/'
if ( ! isMobile ) {
window . location . replace ( pathname )
return
}
document . title = route . meta . title || document . title
next ( )
} )
new Vue ( {
el : '#app-container' ,
router ,
components : { MobileApp } ,
template : '<MobileApp/>'
} ) Write in MobileApp.vue
< template >
< div class =" mobile-container " >
< router-view > </ router-view >
</ div >
</ template >Next, you can try the effect in the browser to see if different device environments can display the corresponding content~
At this point, all the plans we have formulated in this chapter have been completed. "Perfect" conversion of md files, and adaptation of routing in different device environments. The development of the document official website (Part 1) is coming to an end here. In the next chapter, we will continue to complete the remaining development work of the document official website!
In the previous chapter, we have completed:
In this chapter, we will improve the details of the document official website and develop a complete document official website.
From the directory given in the previous chapter, we can know that the docs directory is used to store md files that the PC needs to display, and the pages directory is used to store mobile demo files. So how can components display their corresponding files in different device environments (PC side displays the md files corresponding to components, and mobile side displays the vue files corresponding to components)? How can we reasonably manage the routing of our component library in this case? Next, we continue the development below based on these issues. is-mobile.js will definitely be used here to determine the device environment. Please follow me to do the specific work.
The first step is to create a new file nav.config.json file under examples/src and write the following content
{
// 为了之后组件文档多语言化
"zh-CN" : [
{
"name" : "Vui 组件" ,
"showInMobile" : true ,
"groups" : [
{
// 管理相同类型下的所有组件
"groupName" : "基础组件" ,
"list" : [
{
// 访问组件的相对路径
"path" : "/hello" ,
// 组件描述
"title" : "Hello"
}
]
}
]
}
]
} The second step is to improve the router.config.js file and change it into a helper function for routing registration.
const registerRoute = ( navConfig , isMobile ) => {
let route = [ ]
// 目前只有中文版的文档
let navs = navConfig [ 'zh-CN' ]
// 遍历路由文件,逐一进行路由注册
navs . forEach ( nav => {
if ( isMobile && ! nav . showInMobile ) {
return
}
if ( nav . groups ) {
nav . groups . forEach ( group => {
group . list . forEach ( nav => {
addRoute ( nav )
} )
} )
} else if ( nav . children ) {
nav . children . forEach ( nav => {
addRoute ( nav )
} )
} else {
addRoute ( nav )
}
} )
// 进行路由注册
function addRoute ( page ) {
// 不同的设备环境引入对应的路由文件
const component = isMobile
? require ( `../pages ${ page . path } .vue` )
: require ( `../docs ${ page . path } .md` )
route . push ( {
path : '/component' + page . path ,
component : component . default || component
} )
}
return route
}
export default registerRoute The third step is to register the route in the main portal js file examples/src/index.js of the main portal js file examples/src/mobile.js of the main portal js file examples/src/mobile.js of the mobile side, and write the following code
import registerRoute from './router.config'
import navConfig from './nav.config'
const routesConfig = registerRoute ( navConfig )
const router = new VueRouter ( {
routes : routesConfig
} )Then visit our current component library document official website
From the final renderings of the previous chapter, we can see that the PC terminal is divided into three parts, namely:
Next, let's start to display the PC API
The header is relatively simple. We only need to create a new page-header.vue file under examples/src/components and write the following content
< template >
< div class =" page-header " >
< div class =" page-header__top " >
< h1 class =" page-header__logo " >
< a href =" # " > Vui.js </ a >
</ h1 >
< ul class =" page-header__navs " >
< li class =" page-header__item " >
< a href =" / " class =" page-header__link " >组件</ a >
</ li >
< li class =" page-header__item " >
< a href =" https://github.com/Brickies/vui " class =" page-header__github " target =" _blank " > </ a >
</ li >
< li class =" page-header__item " >
< span class =" page-header__link " > </ span >
</ li >
</ ul >
</ div >
</ div >
</ template >For specific styles, please visit page-header.vue directly to view them.
On the left side, we show the component routes and titles. In fact, it is to parse and display examples/src/nav.config.json .
We create a new side-nav.vue file under examples/src/components . The normal structure of the file is as follows
< li class =" nav-item " >
< a href =" javascript:void(0) " > Vui 组件</ a >
< div class =" nav-group " >
< div class =" nav-group__title " >基础组件</ div >
< ul class =" pure-menu-list " >
< li class =" nav-item " >
< router-link
active-class =" active "
:to =" /component/hello "
v-text =" navItem.title " > Hello
</ router-link >
</ li >
</ ul >
</ div >
</ li > But we now need to parse examples/src/nav.config.json based on the current structure. The improved code is as follows
< li class =" nav-item " v-for =" item in data " >
< a href =" javascript:void(0) " @click =" handleTitleClick(item) " > {{ item.name }} </ a >
< template v-if =" item.groups " >
< div class =" nav-group " v-for =" group in item.groups " >
< div class =" nav-group__title " > {{ group.groupName }} </ div >
< ul class =" pure-menu-list " >
< template v-for =" navItem in group.list " >
< li class =" nav-item " v-if =" !navItem.disabled " >
< router-link
active-class =" active "
:to =" base + navItem.path "
v-text =" navItem.title " />
</ li >
</ template >
</ ul >
</ div >
</ template >
</ li >Click here for the complete code side-nav.vue
We use page-header.vue and side-nav.vue we wrote in App.vue
< template >
< div class =" app " >
< page-header > </ page-header >
< div class =" main-content " >
< div class =" page-container clearfix " >
< side-nav :data =" navConfig['zh-CN'] " base =" /component " > </ side-nav >
< div class =" page-content " >
< router-view > </ router-view >
</ div >
</ div >
</ div >
</ div >
</ template >
< script >
import 'highlight.js/styles/atom-one-dark.css'
import navConfig from './nav.config.json'
import PageHeader from './components/page-header'
import SideNav from './components/side-nav'
export default {
name : 'App' ,
components : { PageHeader , SideNav } ,
data ( ) {
return {
navConfig : navConfig
}
}
}
</ script >Then, visit the page again, the result is as shown in the picture
The principles of mobile demo and PC are similar. Both have to parse the nav.config.json file for display
At present, except for the main entrance page MobileApp.vue , our mobile terminal has no root component dependency. Next, we will complete the development of the root component first, create a new demo-list.vue file under examples/src/components , and write some content
< template >
< div class =" side-nav " >
< h1 class =" vui-title " > </ h1 >
< h2 class =" vui-desc " > VUI 移动组件库</ h2 >
</ div >
</ template > Then we need to reference it in the route and write it in the mobile.js file
import DemoList from './components/demo-list.vue'
routesConfig . push ( {
path : '/' ,
component : DemoList
} ) Then start to improve demo-list.vue file
< template >
< div class =" side-nav " >
< h1 class =" vui-title " > </ h1 >
< h2 class =" vui-desc " > VUI 移动组件库</ h2 >
< div class =" mobile-navs " >
< div v-for =" (item, index) in data " :key =" index " >
< div class =" mobile-nav-item " v-if =" item.showInMobile " >
< mobile-nav v-for =" (group, s) in item.groups " :group =" group " :base =" base " :key =" s " > </ mobile-nav >
</ div >
</ div >
</ div >
</ div >
</ template >
< script >
import navConfig from '../nav.config.json' ;
import MobileNav from './mobile-nav' ;
export default {
data ( ) {
return {
data : navConfig [ 'zh-CN' ] ,
base : '/component'
} ;
} ,
components : {
MobileNav
}
} ;
</ script >
< style lang =" postcss " >
.side-nav {
width: 100%;
box-sizing: border-box;
padding: 90px 15px 20px;
position: relative;
z-index: 1;
.vui-title,
.vui-desc {
text-align: center;
font-weight: normal;
user-select: none;
}
.vui-title {
padding-top: 40px;
height: 0;
overflow: hidden;
background: url(https://raw.githubusercontent.com/xuqiang521/vui/master/src/assets/logo.png) center center no-repeat;
background-size: 40px 40px;
margin-bottom: 10px;
}
.vui-desc {
font-size: 14px;
color: #666;
margin-bottom: 50px;
}
}
</ style > Here we reference the mobile-nav.vue file, which is also the mobile demo list display component we will complete next
Create a new mobile-nav.vue file under examples/src/components , parse the nav.config.json file, and display the demo list.
< template >
< div class =" mobile-nav-group " >
< div
class =" mobile-nav-group__title mobile-nav-group__basetitle "
:class =" {
'mobile-nav-group__title--open': isOpen
} "
@click =" isOpen = !isOpen " >
{{group.groupName}}
</ div >
< div class =" mobile-nav-group__list-wrapper " :class =" { 'mobile-nav-group__list-wrapper--open': isOpen } " >
< ul class =" mobile-nav-group__list " :class =" { 'mobile-nav-group__list--open': isOpen } " >
< template v-for =" navItem in group.list " >
< li
class =" mobile-nav-group__title "
v-if =" !navItem.disabled " >
< router-link
active-class =" active "
:to =" base + navItem.path " >
< p >
{{ navItem.title }}
</ p >
</ router-link >
</ li >
</ template >
</ ul >
</ div >
</ div >
</ template >
< script >
export default {
props : {
group : {
type : Object ,
default : ( ) => {
return [ ] ;
}
} ,
base : String
} ,
data ( ) {
return {
isOpen : false
} ;
}
} ;
</ script >Then write the list style
< style lang =" postcss " >
@component-namespace mobile {
@b nav-group {
border-radius: 2px;
margin-bottom: 15px;
background-color: #fff;
box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.1);
@e basetitle {
padding-left: 20px;
}
@e title {
font-size: 16px;
color: #333;
line-height: 56px;
position: relative;
user-select: none;
@m open {
color: #38f;
}
a {
color: #333;
display: block;
user-select: none;
padding-left: 20px;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
&:active {
background: #ECECEC;
}
> p {
border-top: 1px solid #e5e5e5;
}
}
}
@e list-wrapper {
height: 0;
overflow: hidden;
@m open {
height: auto;
}
}
@e list {
transform: translateY(-50%);
transition: transform .2s ease-out;
@m open {
transform: translateY(0);
}
}
li {
list-style: none;
}
ul {
padding: 0;
margin: 0;
overflow: hidden;
}
}
}
</ style >Next, revisit http://localhost:8080/mobile.html, and if nothing unexpected happens, you can access the results we expected.
By this point, our "rude" component library shelves have been built.
The blog post is almost over here. All the code in the article has been hosted on github . I will write an article in the future to gradually improve some details in our component library with the construction so that our component library can be more perfect.
github address: https://github.com/xuqiang521/personal-component-library
There is another wave of advertisements at the end of the article~~~
Front-end communication group: 731175396
Meituan Dianping has been recruiting people for a long time. If you are interested, you are welcome to work together. There are explanations in the resume delivery method exchange group~
Friends, what are you waiting for? Hurry up and like the article first, then follow me, and then join the group to communicate with the big guys~~~