业务后续的需求会复用很多之前开发的组件,于是打算抽成组件库,提升后续开发效率。本文主要讲解如何搭建并发布基于vue的组件库,以及利用Vuese自动生成组件文档。
vue/cli 3.x初始化项目
vue create zui复制代码
初始化过程中按默认配置即可。
修改项目结构
修改前:
修改后:
1.为了更具语意化,将src重命名成为examples,同时需要新增vue.config.js文件来配置项目启动入口。
// vue.config.js
module.exports = {
pages: {
index: {
entry: 'examples/main.js',
template: 'public/index.html',
filename: 'index.html'
}
}
}复制代码
2.新增文件夹components,在这里开发我们的组件。
开发一个测试组件
1.在lib下新建自定义组件目录
(1) main.vue 编写组件逻辑
// src/main.vue<template>
<h1 class="z-demo">Demo</h1>
</template>
<script>
export default {
name: 'Demo'
}
</script>复制代码
(2) demo.scss 组件样式
// demo.scss
.z-demo{
color: aqua;
}复制代码
(3) index.js 导出组件
// index.js
import Demo from './src/main.vue'
// eslint-disable-next-line func-names
Demo.install = function(Vue) {
Vue.component(Demo.name, Demo)
}
export default Demo复制代码
到此即可按需载入组件,下一步是为了实现全局引用功能。
2.在components目录下,配置index.js来导出所有组件,配置index.scss引入所有样式。
// components/index.js
import Demo from './demo';
import { version } from '../../package.json';
const components = {
Demo
};
const install = function (Vue) {
if (install.installed) return;
Object.keys(components).forEach(key => {
Vue.component(components[key].name, components[key]);
})
};
if (typeof window !== 'undefined' && window.Vue) {
install(window.Vue);
}
const API = {
version,
install,
...components
};
export default API;复制代码
// components/css/index.scss
@import './demo.scss';复制代码
本地测试
1.引入组件库
// examples/main.js
import Vue from 'vue'
import App from './App.vue'
import '../components/css/index.scss'import Zui from '../components/lib'
Vue.use(Zui)
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app')复制代码
2.使用组件
// examples/App.vue
<template>
<div id="app">
<Demo/>
</div>
</template>
<script>
export default {
name: 'app',
}
</script>复制代码
3.效果
组件库打包
目前为止,采用的是后编译形式,只要把该组件库发布到npm上,就可以直接使用。
但一般的第三方库,都会采用预编译形式,提前打包以提供各种版本的文件。
1.使用gulp打包css文件
// gulpfile.js
const gulp = require("gulp")
const sass = require('gulp-sass')
const minifyCSS = require('gulp-minify-css')
const del = require('del');
gulp.task("sass", async function() {
await del(['dist/css']);
return gulp.src("components/css/**/*.scss")
.pipe(sass())
.pipe(minifyCSS())
.pipe(gulp.dest("dist/css"))
})复制代码
2.打包js,这里我分别提供了rollup和webpack两种打包方式。
建议采用rollup,因为可以导出es6模块-未来标准。
(1) rollup 方式
// rollup.js
const rollup = require('rollup');
const resolve = require('rollup-plugin-node-resolve');//可以告诉 Rollup 如何查找外部模块
const vue = require('rollup-plugin-vue');
const commonjs = require('rollup-plugin-commonjs');//将 CommonJS 模块转换为 ES6
const json = require('rollup-plugin-json');
const babel = require('rollup-plugin-babel');
const { terser } = require("rollup-plugin-terser");
const fs = require('fs');
const path = require('path');
const glob = require("glob");
async function makeList(dirPath){
const list = {};
const files = glob.sync(`${dirPath}/**/index.js`);
for(let file of files){
const output = file.split(/[/.]/)[2];
list[output] = {
input: file,
output
};
}
return list;
}
const formatTypeList = [
{ format: 'cjs', min: false, suffix: '.js' },
{ format: 'cjs', min: true, suffix: '.common.min.js' },
{ format: 'umd', min: false, suffix: '.umd.js' },
{ format: 'umd', min: true, suffix: '.umd.min.js' },
{ format: 'es', min: false, suffix: '.js' },
{ format: 'es', min: true, suffix: '.es.min.js' },
]
start('dist/','components/lib');
async function start(outputPath, libPath){
fsExistsSync(outputPath) && removeDir(outputPath);
createDir(outputPath);
const list = await makeList(libPath);
for({format,min,suffix} of formatTypeList){
await build(list, format, min, suffix)
}
}
async function build(list, format, min, suffix){
console.log(`开始打包成 ${format}${min?'.min':''} 格式`);
for(moduleName of Object.keys(list)){
await buildFile(list[moduleName].input, list[moduleName].output, format, min, suffix);
}
console.log(`${format}${min?'.min':''} 格式文件打包完成`);
console.log('=========================================');
}
async function buildFile(input, outputName, format, min, suffix){
console.log(`start to build file:${outputName}`)
const bundle = await rollup.rollup({
input,
output: {
file: `dist/${outputName}${suffix}`,
format,
name: outputName
},
plugins: [
resolve(),
commonjs(),
vue(),
json(),
babel({
babelrc: false,// 忽略外部配置文件
exclude: 'node_modules/**',
runtimeHelpers: true,
}),
min && terser()
]
})
const { output: outputData } = await bundle.generate({
format,
name: outputName
});
await write({ output: outputData, fileName: outputName, suffix })
console.log(`finished building file:${outputName}${suffix}`)
}
async function write({ output, fileName, suffix } = {}) {
for (const { code } of output) {
fs.writeFileSync(`dist/${fileName}${suffix}`, code)
}
}
function removeDir(dir) {
let files = fs.readdirSync(dir)
for(var i=0;i<files.length;i++){
let newPath = path.join(dir,files[i]);
let stat = fs.statSync(newPath)
if(stat.isDirectory()){
//如果是文件夹就递归下去
removeDir(newPath);
}else {
//删除文件
fs.unlinkSync(newPath);
}
}
fs.rmdirSync(dir)//如果文件夹是空的,就将自己删除掉
}
function createDir(dir) {
let paths = dir.split('/');
for(let i =1;i<paths.length;i++){
let newPath = paths.slice(0,i).join('/');
try{
//是否能访问到这个文件,如果能访问到,说明这个文件已经存在,进入循环的下一步。
//accessSync的第二个参数就是用来判断该文件是否能被读取
fs.accessSync(newPath,fs.constants.R_OK);
}catch (e){
fs.mkdirSync(newPath);
}
}
}
function fsExistsSync(dir){
try {
fs.accessSync(dir, fs.F_OK)
} catch (e) {
return false
}
return true
}复制代码
(2) webpack 方式
// webpack.component.js
const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');// 清理文件夹
const { VueLoaderPlugin } = require('vue-loader')
const glob = require("glob");
const list = {};
async function makeList(dirPath,list){
const files = glob.sync(`${dirPath}/**/index.js`);
for(let file of files){
const output = file.split(/[/.]/)[2];
list[output] = `./${file}`;
}
}
makeList('components/lib',list);
module.exports = {
entry: list,
mode: 'development',
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist2'),
library: 'zui-pure',
libraryTarget: 'umd'
},
plugins: [
new CleanWebpackPlugin(),
new VueLoaderPlugin()
],
module: {
rules: [
{
test: /.vue$/,
use: [
{
loader: 'vue-loader',
}
]
}
]
},
};复制代码
3.更新package.json,新增构建命令
{ ...
"scripts": {
"serve": "vue-cli-service serve",
"lint": "vue-cli-service lint",
"build:js": "node rollup.js",
"build:css": "npx gulp sass",
"build": "npm run build:js && npm run build:css"
}, ...
}复制代码
组件库发布
1.更新package.json和README.md
其中 files字段设置要上传到npm上的文件。
// package.json{
"name": "zui-pure",
"version": "0.0.1",
"description": "基于vue的管理端组件库",
"main": "dist/index.umd.js",
"keywords": [
"zui",
"vue",
"ui"
],
"author": "zackguo",
"license": "ISC", "files": [ "dist", "components" ]
}复制代码
// README.md# Zui 组件库
> 在 main.js 中引入组件库
// 全部引入
import ZUI from "@tencent/zui-pure";
Vue.use(ZUI);
// 按需引入
import { Demo } from "@tencent/zui-pure";
Vue.use(Demo);
Copyright (c) 2019-present zackguo
复制代码
2.注册/登录npm账户
npm adduser复制代码
- 发布tnpm私有包(在npm-package目录下)
npm publish复制代码
4.登录npm官网查看
组件库测试
1.全量引入方式
(1) 新建vue工程。
(2) 安装组件库。
tnpm i @tencent/zui-pure复制代码
(3) 在main.js引入组件库,在App.vue使用组件。
测试成功
2.按需加载
(1) 安装babel-plugin-component插件,并且在babel.config.js中新增配置。
tnpm i babel-plugin-component复制代码
// babel.config.js
module.exports = {
presets: [
'@vue/app'
],
plugins: [
[
"component",
{
"libraryName": "@tencent/zui-pure", "libDir": "dist",
"styleLibrary": { "base": false, "name": "css" }
}
]
]
}复制代码
(2) 在main.js按需加载组件
// src/main.js
import Vue from 'vue'
import App from './App.vue'
// import ZUI from '@tencent/zui-pure'
// Vue.use(ZUI)
import { Demo } from '@tencent/zui-pure'
Vue.use(Demo);
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app')复制代码
测试成功
接入Vuese自动生成文档
1.按照Vuese
tnpm i vuese --save-d复制代码
2.在根目录下新增配置文件 .vueserc
{
"include": [
"./components/**/*.vue"
],
"title": "zui-doc",
"genType": "docute",
"outDir": "./docs"
}复制代码
include:指定构建目录。
genType: 指定生成的文档类型,docute 会把vue文件构建出的所有markdown,整合为一个单页应用。
outDir:指定文档输出目录,这里指定为./docs,是为了配和在master分支接入OA Pages。
3.在package.json新增脚本,并启动。
// package.json{
"name": "zui",
...
"scripts": {
...
"build_doc": "npx vuese gen && npx vuese serve --open"
},
...
}
复制代码
vuese gen:构建文档。
vuese serve --open:启动文档服务器,打开浏览器查看生成的文档。
npm run build_doc复制代码
注:由于demo组件结构过于简单,在生成时被vuese忽略了,于是新增了props。
还有一个问题,发现首页报404:
解决:在docs根目录下添加readme.md
到此,整个vue自定义组件库架子已搭建完毕~
完整的组件库还应该包含单元测试和类型定义,这里就不再赘述了,可以直接参考demo代码。
附录
- 文章demo地址:github.com/Best921/zui…
- npm包地址:www.npmjs.com/package/zui…
- Vuese使用指南:vuese.org/
注:vuese定制的文档不是很方便写演示demo,目前项目改用vuepress。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。