一、前言
随着业务的持续完善,前端项目的体积也在不断变大。如何有效的对代码进行管理、公共组件和业务组件的复用是需要考虑的问题。为了去较好的解决这个问题,看了很多的方案,各有各的优点,各有各的适用场景。
突然想到,既然是组件库,为何不去看那些优秀的组件库源码呢?于是,花了些时间,把Element UI的源码仔细的看了下,获益匪浅,这里也推荐大家先去学习学习。最后,以它的结构为主,做了一定调整,完成了本示例项目。
二、效果演示
1、项目使用
/** 全量引入 **/
import vueCompBusiness from 'vue-comp-business';
import 'vue-comp-business/lib/css/index.css';
Vue.use(vueCompBusiness);
/** 按需引入 **/
// 1、安装babel
npm install babel-plugin-component -D
// 2、main.js
import { CbButton } from 'vue-comp-business';
Vue.use(CbButton);
// 3、babel.config.js
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
],
// vue-comp-business组件按需加载
plugins: [
[
'import',
{
libraryName: 'vue-comp-business',
// 定义寻找组件css文件路径逻辑,不调整的话,会找cb-button.css文件
customStyleName: (name) => {
console.log('customStyleName', name)
return `vue-comp-business/lib/css/${name.slice(3)}.css`
},
// 定义寻找组件路径逻辑,不调整的话,会找cb-button.js文件
customName: (name) => {
console.log('customName', name)
return `vue-comp-business/lib/${name.slice(3)}`
}
}
]
]
}
2、效果截图
三、实现步骤
涉及到的知识点有:Vue Cli、Vue、Vuepress、scss、bem,不熟悉的可以先去官网熟悉熟悉。
1、代码结构
vue-comp-business
├─ build --------------------------------------------------------- 打包配置文件夹
│ ├─ config.dev.js -------------------------------------------- 测试环境打包配置文件
│ └─ config.prod.js ---------------------------------------- 测试环境打包配置文件
├─ deploy ------------------------------------------------------ 文档打包配置文件夹
│ └─ docs.sh ---------------------------------------------------- 文档打包配置文件
│
├─ docs ---------------------------------------------------------- Vuepress相关文件夹
│ ├─ .vuepress ------------------------------------------------------- Vuepress默认
│ │ ├─ components ------------------------------------------ Vuepress拓展组件文件夹
│ │ │ ├─ press-row.vue ------------------------------------------------- 行组件
│ │ │ └─ source-block.vue ---------------------------------------- 代码查看组件
│ │ ├─ config.js ------------------------------------------ Vuepress菜单导航等配置
│ │ ├─ enhanceApp.js --------------------------------------- 异步导入第三方组件实现
│ │ └─ public --------------------------------------------- Vuepress 公共资源文件夹
│ │ └─ element.png ------------------------------------------------------ logo
│ ├─ guide ---------------------------------------------------------- 导航资源文件夹
│ │ ├─ CBButton.md ----------------------------------------------- 自定义按钮md
│ │ ├─ CBPanel.md --------------------------------------------------- 自定义面板md
│ │ └─ README.md -------------------------------------------------- guide 主页md
│ └─ README.md ---------------------------------------------------- Vuepress主页md
│
├─ examples ------------------------------------------------------ 开发环境示例文件夹
│ ├─ App.vue ------------------------------------------------------- 开发环境主页
│ └─ main.js ------------------------------------------------------- 开发环境入口
│
├─ lib ---------------------------------------- 打包后资源存放文件夹(普通模式\UMD模式)
│ ├─ button.js
│ ├─ css
│ │ ├─ button.css
│ │ ├─ index.css
│ │ └─ panel.css
│ ├─ index.js
│ └─ panel.js
│
├─ packages --------------------------------------------------- 业务组件库统一文件夹
│ ├─ components ------------------------------------------------ 业务组件文件夹
│ │ ├─ button ---------------------------------------------------- 按钮组件文件夹
│ │ │ ├─ index.js ------------------------------------------------- 组件入口
│ │ │ └─ src
│ │ │ └─ button.vue ----------------------------------------- 按钮组件实现
│ │ └─ panel
│ │ ├─ index.js
│ │ └─ src
│ │ └─ panel.vue
│ │ ├─ index.js ------------------------------------------------- 全部组件入口
│ └─ css ---------------------------------------------------- 组件css统一文件夹
│ ├─ common -------------------------------------------------------- 变量scss
│ │ └─ var.scss ----------------------------------------- 测试环境打包配置
│ ├─ fonts ------------------------------------------------------------ 字体
│ │ ├─ element-icons.ttf
│ │ └─ element-icons.woff
│ ├─ mixins -------------------------------------------------------- scss混入
│ │ ├─ config.scss --------------------------------------------- bem前缀配置
│ │ ├─ function.scss ---------------------------------------- 工具方法scss
│ │ ├─ mixins.scss -------------------------------------------- 工具混入scss
│ │ └─ utils.scss ---------------------------------------- 常用样式混入scss
│ ├─ index.scss --------------------------------------------- 全部组件样式入口
│ ├─ button.scss ----------------------------------------------- 按钮样式
│ └─ panel.scss --------------------------------------------------- 面板样式
│
├─ public ------------------------------------------------------ 静态资源文件夹
│ ├─ favicon.ico
│ └─ index.html -------------------------------------------------- html模板
│
├─ vue.config.js --------------------------------------------------- 打包配置
├─ jsconfig.json -------------------------------------------- 文件路径识别配置
├─ components.json ------------------------------------------------ 组件路径配置
├─ .gitignore
├─ .npmignore
├─ babel.config.js -------------------------------------------------- babel配置
├─ package-lock.json
├─ package.json
├─ README.md
2、package.json
这里使用的脚手架版本是4.5,使用5版本的脚手架时,要么sass有问题,有么vuepress文档运行时出错,直接放弃了,哈哈…
{
"name": "vue-comp-business",
"version": "0.1.0",
"private": true,
"/**main**/": "注释:指定包的入口,npm下载后看到的入口",
"main": "./lib/index.js",
"style": "./lib/theme/index.css",
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"build:umd": "vue-cli-service build --target lib --dest lib --name vue-comp-business packages/components/index.js",
"docs:dev": "vuepress dev docs",
"docs:build": "vuepress build docs",
"docs:deploy": "bash ./deploy/docs.sh"
},
"dependencies": {
"core-js": "^3.6.5",
"vue": "^2.6.11"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"@vuepress/plugin-back-to-top": "^1.9.7",
"node-sass": "^4.14.1",
"sass": "^1.49.0",
"sass-loader": "^7.3.1",
"vue-template-compiler": "^2.6.11",
"vuepress": "^1.9.7"
},
"author": "admin",
"license": "MIT"
}
3、打包配置
- dev打包配置
const path = require('path');
module.exports = {
configureWebpack: {
entry: path.join(__dirname, '../examples/main.js')
}
};
- prod打包配置
// components.json
{
"button": "./packages/components/button/index.js",
"panel": "./packages/components/panel/index.js",
"index": "./packages/components/index.js"
}
const path = require('path');
const components = require('../components.json');
const resolve = dir => path.join(__dirname, '../', dir);
// 获取每个组件的绝对路径
const getComponentEntries = () => {
const entryKeys = Object.keys(components);
entryKeys.forEach(key => {
components[key] = resolve(components[key]);
});
return components;
};
// { button: '/Users/xxx/packages/button/index.js' }
const componentEntries = getComponentEntries();
module.exports = {
outputDir: resolve('lib'),
configureWebpack: {
entry: componentEntries,
output: {
// 文件名称
filename: '[name].js',
// 构建依赖类型
libraryTarget: 'umd',
// 库中被导出的项
libraryExport: 'default',
// 引用时的依赖名
library: 'vueTestComp'
}
},
// 如果你不需要生产环境的 source map,可以将其设置为 false 以加速生产环境构建
productionSourceMap: false,
css: {
extract: {
filename: 'css/[name].css' //在lib文件夹中建立 theme 文件夹中,生成对应的css文件。
}
},
/**
* 删除splitChunks,因为每个组件是独立打包,不需要抽离每个组件的公共js出来。
* 删除copy,不要复制public文件夹内容到lib文件夹中。
* 删除html,只打包组件,不生成html页面。
* 删除preload以及prefetch,因为不生成html页面,所以这两个也没用。
* 删除hmr,删除热更新。
* 删除自动加上的入口App。
*/
chainWebpack: config => {
config.optimization.delete('splitChunks');
config.plugins.delete('copy');
config.plugins.delete('html');
config.plugins.delete('preload');
config.plugins.delete('prefetch');
config.plugins.delete('hmr');
config.entryPoints.delete('app');
}
};
4、按钮组件的实现
<template>
<button
class="cb-button"
:class="[
type ? 'cb-button--' + type : ''
]"
>
<span><slot /></span>
</button>
</template>
<script>
export default {
name: 'CBButton',
props: {
type: {
type: String,
default: 'default'
}
}
}
</script>
<style lang="scss" src="../../../css/button.scss"></style>
// button.scss文件
@charset "UTF-8";
@import "common/var";
@import "mixins/mixins";
@include b(button) {
display: inline-block;
line-height: 1;
white-space: nowrap;
cursor: pointer;
background: $--button-default-background-color;
border: $--border-base;
border-color: $--button-default-border-color;
color: $--button-default-font-color;
-webkit-appearance: none;
text-align: center;
box-sizing: border-box;
outline: none;
margin: 0;
padding: $--button-small-padding-vertical;
// 两个按钮间距10像素
& + & {
margin-left: 10px;
}
&:hover,
&:focus {
color: $--color-primary;
border-color: $--color-primary-light-7;
background-color: $--color-primary-light-9;
}
@include m(primary) {
color: $--button-primary-font-color;
background-color: $--button-primary-background-color;
border-color: $--button-primary-border-color;
&:hover,
&:focus {
background: mix($--color-white, $--button-primary-background-color, $--button-hover-tint-percent);
border-color: mix($--color-white, $--button-primary-border-color, $--button-hover-tint-percent);
color: $--button-primary-font-color;
}
}
@include m(success) {
color: $--button-success-font-color;
background-color: $--button-success-background-color;
border-color: $--button-success-border-color;
&:hover,
&:focus {
background: mix($--color-white, $--button-success-background-color, $--button-hover-tint-percent);
border-color: mix($--color-white, $--button-success-border-color, $--button-hover-tint-percent);
color: $--button-primary-font-color;
}
}
}
// 按钮组件入口
import CommonButton from './src/button.vue'
CommonButton.install = (Vue) => {
Vue.component(CommonButton.name, CommonButton)
}
export default CommonButton
5、全部组件导出
import CBButton from './button/index.js'
import CBPanel from './panel/index.js'
const components = [
CBButton,
CBPanel
]
// 全局引入: 引入的组件是个对象时,必须要有install函数
const install = (Vue) => {
components.forEach(component => {
Vue.component(component.name, component);
});
}
/* istanbul ignore if */
if (typeof window !== 'undefined' && window.Vue) {
install(window.Vue);
}
export default {
install,
CBButton,
CBPanel
}
6、开发环境测试
- App.vue
<template>
<div id="app">
<div class="mb10">
<CBButton>按钮组件测试</CBButton>
<CBButton type="primary">按钮组件测试</CBButton>
<CBButton type="success">按钮组件测试</CBButton>
</div>
<CBPanel></CBPanel>
</div>
</template>
<script>
export default {
name: 'App'
};
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
margin-top: 60px;
}
.mb10 {
margin-bottom: 10px;
}
</style>
- main.js
import Vue from 'vue';
import App from './App.vue';
// 批量注册组件
import CbComponent from '../packages/components';
import '../packages/css/index.scss';
Vue.use(CbComponent);
console.log(CbComponent, Vue)
Vue.config.productionTip = false;
new Vue({
render: h => h(App)
}).$mount('#app');
7、文档编写
- 菜单等配置
module.exports = {
title: 'vue-comp-bussiness',
description: 'vue-comp-bussiness 组件库文档',
markdown: {
lineNumbers: true
},
base: '/vue-comp-bussiness/',
locales: {
'/': {
lang: 'zh-CN',
title: '个人业务组件库',
description: '这是国际化相关配置'
},
'/en/': {
lang: 'en-US',
title: 'vue-comp-business',
description: 'this is locales config'
}
},
themeConfig: {
locales: {
'/': {
selectText: '选择语言',
label: '简体中文',
nav: [
{
text: '指南',
link: '/guide/'
},
{
text: 'github',
link: 'https://github.com'
}
],
sidebar: {
'/guide/': [
['', '介绍'],
{
title: '组件',
children: [
['../guide/CBButton.md', 'CBButton'],
['../guide/CBPanel.md', 'CBPanel']
]
}
]
}
}
// '/en/': {
// selectText: 'Languages',
// label: 'English',
// nav: [
// {
// text: 'guide',
// link: '/en/guide/'
// },
// {
// text: 'github',
// link: 'https://github.com'
// }
// ],
// sidebar: {
// '/en/guide/': [
// ['', 'introduction'],
// {
// title: 'Components',
// children: [
// ['../guide/Button.md', 'Button'],
// ['../guide/Card.md', 'Card']
// ]
// }
// ]
// }
// }
}
},
plugins: ['@vuepress/back-to-top']
};
- 第三方组件导入
// 引入打包后的样式和文件。
import '../../lib/css/index.css';
export default async ({ Vue, options, router, siteData, isServer }) => {
if (!isServer) {
// 直接导入 window is not defined 原因就是编译的时候需要 node.js 服务端渲染。所以要改成异步导入
await import('../../lib').then(vueComp => {
Vue.use(vueComp);
});
}
};
四、npm推送
1、关键备注
package.json
:配置信息。
-
private
: 选项改为 false;当private
为 true 时,是不允许发布到 npm 的。 -
name
: 当前应用的名称或库名称。 -
version
: 原则上每次发布版本,版本号都应该是递增的。 -
main
:项目入口文件。例如:./lib/test-comp-base.umd.min.js
-
author
: 作者名称。 -
license
:使用哪种开源协议。
.npmignore
:过滤不需要上传的文件
2、推送
- 之前没有提交到npm的先做准备工作:
- cmd窗口添加全局用户信息配置:npm adduser
-- 426错误:镜像地址切换一下,还不行,检查一下网络问题
npm config set registry https://registry.npmjs.org/
- 在项目根路径运行,npm publish