在参考过一些资料和开源的UI组件库后,写下了这篇文章,但愿能给你们一些帮助。css
UI组件库先后摸索了差很少近一个星期才勉强学会,其实组件库开发不算特别复杂(复杂组件当我没说 #滑稽保命),期间主要是卡在了目录组织和打包的问题上,目录组织的问题主要是涉及到按需加载的问题,若是一把梭所有引入反而没那么复杂。而后打包问题也是由于按需加载的须要,须要配置不一样的webpack配置文件,这里摸索了特别久,最后再看了vant、nutui这些开源的组件库后,还算是入门了吧,这里其实我主要参考了nutui的组织结构,webpack的配置基本也是这么来的。文章虽然含金量很少,但愿能帮助到须要本身学习组件库开发的同窗。html
好了,废话很少说,直接开始!vue
本次开发没有基于Vue CLI来搭建开发环境,而是基于我上次webpack4本身搭建的环境来构建的环境,虽然官方CLI也能够,并且也有本身的库模式来发布组件库。可是为了可以更多的有本身自定义的选项,仍是没选择官方脚手架。若是你想要使用官方CLI来开发,那么本文章虽然不能手把手教你,但应该仍是能够起到抛砖引玉的做用。node
接下来咱们须要作这几步,先把目录结构定下来:webpack
examples文件夹下用来展现你的组件基本用法。其实至关于把src的文件目录改成了examplesgit
packages文件夹下用来书写你的组件。github
修改告终构后还涉及到一些细微的修改,好比文件夹改成你项目的名字(我这儿叫cookie-ui),package.json里面的name根据本身的须要来修改。web
postcss.config.js里面关于px转rem和px转vw|vh那个也要去掉,这儿咱们使用px单位来开发。npm
同时咱们须要修改咱们的入口,由于默认搭建出来的入口是src。在bulid目录下修改webpack.config.js中如下代码:json
module.exports = {
/* 省略代码 */
entry: {
main: path.resolve(__dirname, "../examples/main.js")
},
resolve: {
/* 省略代码 */
'@': path.resolve(__dirname, '../examples')
/* 省略代码 */
}
/* 省略代码 */
}
复制代码
这样一来,再把examples改造一下,便于咱们写演示代码。examples目录以下图:
这个文件夹目录的结构就跟咱们平时作单页面开发同样,这里我就再也不赘述各个文件夹的功能,其实这个结构也不是非得这样来布置,能够根据本身的状况来规划目录便可。
重点主要在packages目录下组件库的编写,目前个人目录结构以下所示:
style文件夹下的样式你们根据本身须要来规划就好,不必定按我这个方式来
接下来我以一个button组件来举例,先按图建好相关文件,相关代码我会加一些注释,若是你不太清楚Vue插件开发,Sass等这些知识点建议先学习一下相关知识,这里再也不展开。
@import '../../style/common/variable.scss';
.cookie-button {
position: relative;
display: inline-block;
width: 90px;
height: 40px;
line-height: 40px;
border-radius: 4px;
outline: 0;
border: 0;
appearance: none;
color: #fff;
font-size: 16px;
&.cookie-button--primary {
background-color: $button-primary;
border: 1px solid $button-primary;
}
&.cookie-button--danger {
background-color: $button-danger;
border: 1px solid $button-danger;
}
&.cookie-button--warning {
background-color: $button-warning;
border: 1px solid $button-warning;
}
&.cookie-button--info {
background-color: $button-info;
border: 1px solid $button-info;
}
}
/* 加这个代码是为了让按钮点击看起有个反馈效果 */
.cookie-button::before {
position: absolute;
content: "";
left: 50%;
top: 50%;
width: 100%;
height: 100%;
background-color: #000;
opacity: 0;
transform: translate(-50%, -50%);
border: inherit;
border-color: #000;
border-radius: inherit;
}
.cookie-button:active::before {
opacity: 0.1;
}
复制代码
<template>
<button :class="classSet" @click="handleClick">
// 这里使用了插槽知识
<slot></slot>
</button>
</template>
<script>
export default {
name: 'ck-button',
props: {
type: {
type: String,
default: 'primary'
}
},
computed: {
classSet() {
let classResult = `cookie-button cookie-button--${this.type}`;
return classResult;
}
},
methods: {
handleClick() {
this.$emit('click');
}
}
}
</script>
复制代码
// 配置对外引用
import Button from './Button.vue';
import './button.scss';
// 提供install方法
// 这里提供一次install是为了便于单独引入buttton组件时进行注册
Button.install = function(Vue) {
Vue.component(Button.name, Button);
};
// 默认导出方式导出
export default Button;
复制代码
这样咱们就实现了一个简单的按钮组件。 咱们到根目录的index.js下进行install。在index.js中加入如下代码:
/* 组件库对外导出的组件集合,对整个组件进行导出 */
// 导入组件(用于注册全部组件)
import Button from './components/button';
// 定义组件列表
const componentsList = [
Button
];
const install = function(Vue) {
// 判断是否安装过
if(install.installed) return;
// 注册全部组件
componentsList.map((component) => {
Vue.component(component.name, component);
})
}
if(typeof window !== 'undefined' && window.Vue) {
install(window.Vue);
}
export default {
install,
Button
}
复制代码
而后咱们进入咱们的examples目录来展现咱们的按钮组件,在main.js中所有引入
// 引入组件(注册全部)
import CookieUI from '../packages/index.js';
Vue.use(CookieUI);
复制代码
在组件中使用按钮:
<div class="box"><ck-button type="primary" @click="testClick">基本按钮</ck-button></div>
<div class="box"><ck-button type="danger">危险按钮</ck-button></div>
<div class="box"><ck-button type="warning">警告按钮</ck-button></div>
<div class="box"><ck-button type="info">信息按钮</ck-button></div>
复制代码
<ck-button>
这个跟你写的组件的name属性相关(好比我这里就是Button.vue里面的name属性),名字只要符合规范便可。这种组件不须要Vue.component()方法来注册,好比常见的Toast、Dialog,我这儿是直接绑定到Vue原型上,在项目里面能够直接使用this调用。
// 定义的变量
@import '../../style/common/variable.scss';
// 使用的弹性布局
@import '../../style/mixins/flex_style.scss';
// 动画相关的样式
@import '../../style/mixins/animation.scss';
.cookie-toast--mask {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 100;
@include flex-all-center; // Sass的混入语法
.cookie-toast--dialog {
max-width: 80vw;
background-color: rgba(0, 0, 0, 1);
padding: 16px;
box-sizing: border-box;
border-radius: 4px;
animation: zoomIn .3s ease-out 0s forwards;
}
.cookie-toast--content {
font-size: $font-size-s;
color: #fff;
}
}
@include anima-zoomIn
复制代码
<template>
<div v-if="show" class="cookie-toast--mask">
<div class="cookie-toast--dialog">
<p class="cookie-toast--content">{{ message }}</p>
</div>
</div>
</template>
<script>
export default {
name: 'ck-toast',
data() {
return {
show: false,
message: ''
}
},
methods: {
}
}
</script>
复制代码
import Vue from 'vue';
import toastComponent from './Toast.vue';
import './toast.scss';
const toastConstructor = Vue.extend(toastComponent);
let instance;
/**
* 打开toast
* @param options {Object} 消息内容options.message,不可省略。停留时间options.duration,可省略,默认为2000(毫秒)
**/
let toast = function(options = {}) {
if(!instance) {
instance = new toastConstructor({
el: document.createElement('div')
});
}
if(instance.show === true) return;
instance.message = options.message;
instance.show = true;
document.body.appendChild(instance.$el)
let timer = setTimeout(() => {
instance.show = false;
clearTimeout(timer);
}, options.duration || 2000);
}
export default toast;
复制代码
这样,一个Toast就完成了,而后咱们须要到index.js里面注册
/* 组件库对外导出的组件集合,对整个组件进行导出 */
// 导入组件(用于注册全部组件)
import Button from './components/button';
import Toast from './components/toast';
// 定义组件列表
const componentsList = [
Button
];
const install = function(Vue) {
// 判断是否安装过
if(install.installed) return;
// 注册全部组件
componentsList.map((component) => {
Vue.component(component.name, component);
})
Vue.prototype.$toast = Toast;
}
if(typeof window !== 'undefined' && window.Vue) {
install(window.Vue);
}
export default {
install,
Button,
Toast,
}
复制代码
这样咱们就注册好了toast,结合上面mian.js引入button的那段代码,咱们能够在项目里面这样使用:
this.$toast({message: 'Hello,Toast演示', duration: 1500});
复制代码
另付Dialog演示,代码这里再也不贴出来,后面我会把代码传到github,须要的自取。
注:后期我把packages目录下的index.js改成了cookieui.js。
组件库开发完毕,咱们是须要发布到npm上供其余人使用的,否则单独提出来的意义也不大,因此咱们首先要作的的就是将UI组件库打包,这儿仍是借助了webpack,它专门有针对library进行设置。
这种方式是把全部相关的打包到js中,而后把样式单独抽离出来,造成css文件,最终你打包下来的目录就是一个js和一个css,咱们把它发布到npm,当别人下载下来事后,引入方式大概就变成这种样子(这里只是举个例子):
import CookieUI from 'cookie-ui';
import '../cookie-ui/index.css';
Vue.use(CookieUI);
复制代码
首先咱们在build的目录下新建三个文件webpack.lib.base.js
、webpack.lib.prod.js
、webpack.lib.prod.disperse.js
。
// 库打包的主要配置
// 引入vue-loader插件
const VueLoaderPlugin = require('vue-loader/lib/plugin');
// 引入清除打包后文件的插件(最新版的须要解构,否则会报不是构造函数的错,并且名字必须写CleanWebpackPlugin)
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
// 咱们打包组件库时不须要把Vue打包进去
externals: {
'vue': {
root: 'Vue',
commonjs: 'vue',
commonjs2: 'vue',
amd: 'vue',
}
},
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader'
}
]
},
{
test: /\.vue$/,
use: [
{
loader: 'vue-loader',
options: {
compilerOptions: {
preserveWhitespace: false
}
}
}
]
},
{
test: /\.(jpe?g|png|gif)$/i,
use: [
{
loader: 'url-loader',
options: {
limit: 5120,
esModule: false,
fallback: 'file-loader',
name: 'images/[name].[ext]'
}
}
]
}
]
},
plugins: [
new CleanWebpackPlugin(),
new VueLoaderPlugin()
],
resolve: {
alias: {
'vue$': 'vue/dist/vue.runtime.esm.js',
},
extensions: ['*', '.js', '.vue']
}
};
复制代码
// 打包全部
// node.js里面自带的操做路径的模块
const path = require("path");
const merge = require('webpack-merge');
const webpackLibBaseConfig = require('./webpack.lib.base.js');
// 用于提取css到文件中
const miniCssExtractPlugin = require('mini-css-extract-plugin');
// 用于压缩css代码
const optimizeCssnanoPlugin = require('@intervolga/optimize-cssnano-plugin');
module.exports = merge(webpackLibBaseConfig, {
mode: 'production',
devtool: 'source-map',
entry: {
cookieui: path.resolve(__dirname, "../packages/cookieui.js")
},
output: {
// 打包事后的文件的输出的路径
path: path.resolve(__dirname, "../lib"),
// 打包后生成的js文件
filename: "[name].js",
publicPath: "/",
library: 'cookieui',
libraryTarget: 'umd',
libraryExport: 'default',
umdNamedDefine: true
},
module: {
rules: [
{
test: /\.(scss|sass)$/,
use: [
{
loader: miniCssExtractPlugin.loader, // 使用miniCssExtractPlugin.loader代替style-loader
},
{
loader: 'css-loader',
},
{
loader: 'sass-loader',
options: {
implementation: require('dart-sass')
}
},
{
loader: 'postcss-loader'
}
]
},
]
},
plugins: [
// 新建miniCssExtractPlugin实例并配置
new miniCssExtractPlugin({
filename: '[name].css'
}),
// 压缩css
new optimizeCssnanoPlugin({
sourceMap: true,
cssnanoOptions: {
preset: ['default', {
discardComments: {
removeAll: true,
},
}],
},
}),
]
})
复制代码
// 用于对组件单独打包,便于按需加载
// 用于拷贝的插件
const copyWebpackPlugin = require('copy-webpack-plugin');
const path = require('path');
const miniCssExtractPlugin = require('mini-css-extract-plugin');
const optimizeCssnanoPlugin = require('@intervolga/optimize-cssnano-plugin');
const merge = require('webpack-merge');
const webpackLibBaseConfig = require('./webpack.lib.base.js');
// 引入入口配置文件
const entryConfig = require('../packages/entry_config.js');
//定义入口
let entry = {};
entryConfig.configList.map((item) => {
let componentName = item.name.toLowerCase();
entry[componentName] = path.resolve(__dirname, '../packages/components/' + componentName + '/index.js');
});
module.exports = merge(webpackLibBaseConfig, {
mode: 'production',
devtool: '#source-map',
entry,
output: {
// 打包事后的文件的输出的路径
path: path.resolve(__dirname, "../lib/packages"),
// 打包后生成的js文件
// 解释下这个[name]是怎么来的,它是根据你的entry命名来的,入口叫啥,出口的[name]就叫啥
filename: "[name]/index.js",
// 我这儿目前尚未资源引用
publicPath: "/",
library: '[name]',
libraryTarget: 'umd',
libraryExport: 'default',
umdNamedDefine: true
},
module: {
rules: [
{
test: /\.(scss|sass)$/,
use: [
{
loader: miniCssExtractPlugin.loader, // 使用miniCssExtractPlugin.loader代替style-loader
},
{
loader: 'css-loader',
},
{
loader: 'sass-loader',
options: {
implementation: require('dart-sass')
}
},
{
loader: 'postcss-loader'
}
]
},
]
},
plugins: [
// 新建miniCssExtractPlugin实例并配置
new miniCssExtractPlugin({
filename: '[name]/style.css'
}),
// 压缩css
new optimizeCssnanoPlugin({
sourceMap: true,
cssnanoOptions: {
preset: ['default', {
discardComments: {
removeAll: true,
},
}],
},
}),
]
})
复制代码
同时在packages目录下新建一个entry_config.js,这个是用来单独打包组件时的配置
module.exports = {
configList: [
{
name: 'button',
author: 'LEE'
},
{
name: 'toast',
author: 'LEE'
},
{
name: 'dialog',
author: 'LEE'
}
]
}
复制代码
注:上面涉及的相关的包若是你没有安装的话,手动安装一次便可。
这样一来,webpack就配置完毕了,如今咱们还须要修改package.json里面的script,加上如下几句:
"lib:all": "webpack --config ./build/webpack.lib.prod.js",
"lib:disp": "webpack --config ./build/webpack.lib.prod.disperse.js"
复制代码
接下来咱们分别执行上面的命令,而后你会发现目录下有个lib目录,生成项目以下:
首先你得有一个npm的帐号,到官网注册一个便可。npm。
修改你目录下的package.json
这个根据你状况来写,通常来讲name、version、main这几个属性不可省略。同时你得name不能跟npm上其它开发者发布的包重名,像我这个cookie-ui就重复了,因此我改为了vue-cookie-ui。-_-!。。。这里我给个大概参数配置,须要看完整的到仓库自取哈
{
"name": "vue-cookie-ui",
"version": "1.0.0",
"description": "A Personal Learning UI library For Vue",
"main": "lib/cookieui.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack-dev-server --config ./build/webpack.dev.js",
"build": "webpack --config ./build/webpack.prod.js",
"lib:all": "webpack --config ./build/webpack.lib.prod.js",
"lib:disp": "webpack --config ./build/webpack.lib.prod.disperse.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/cookiepool/cookie-ui.git"
},
"keywords": [
"UI",
"Vue",
"UI-Library"
],
"author": "LEE",
"license": "MIT",
"bugs": {
"url": "https://github.com/cookiepool/cookie-ui/issues"
},
"homepage": "https://github.com/cookiepool/cookie-ui#readme",
/* 省略代码 */
}
复制代码
touch .npmignore
复制代码
加入如下代码
.DS_Store
node_modules
/dist
/build
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw*
examples/
packages/
public/
babel.config.js
build
postcss.config.js
教程.MD
.gitignore
*.map
*.html
复制代码
前面注册好了和配置好package.json等工做后,在根目录打开bash,输入
npm login
复制代码
这里你按照提示登陆好帐号便可,登陆成功事后,再来一个发布命令便可
npm publish
复制代码
发布成功事后你就能够到npm查到本身的包了。
咱们发布到npm后就能够从npm下载并使用了
npm i vue-cookie-ui
复制代码
下载完成后去咱们的项目里面引用(main.js)
// 引入组件(注册全部)
import CookieUI from 'vue-cookie-ui';
import 'vue-cookie-ui/lib/cookieui.css';
Vue.use(CookieUI);
复制代码
// 按需加载
// 引入组件
import Button from 'vue-cookie-ui/lib/packages/button';
import 'vue-cookie-ui/lib/packages/button/style.css';
Vue.use(Button)
// 引入modal(Dialog和Toast都要这样注册)
import Toast from 'vue-cookie-ui/lib/packages/toast';
import 'vue-cookie-ui/lib/packages/toast/style.css';
Vue.prototype.$toast = Toast;
复制代码
这里我只按需引入了Toast、Button,Dialog没有引入,演示你会发现Toast正常,Dialog没法工做并出现了报错。
安装依赖
npm install babel-plugin-component
复制代码
在babel.config.js中加入如下代码:
plugins: [[
// 配置按需引入插件babel-plugin-component
"component",
{
// 库的名字为VUI
"libraryName": "vue-cookie-ui",
// 存放库文件的文件夹为lib/packages
"libDir": "lib/packages",
}
]]
复制代码
而后你就能够这样引入了,插件会自动帮你转换路径
// 使用babel-plugin-component
import { Button, Toast, Dialog } from 'vue-cookie-ui';
Vue.use(Button);
Vue.prototype.$toast = Toast;
Vue.prototype.$dialog = Dialog;
复制代码
到这里就告一段落了,特别感谢nutui的源代码,给了不少参考,若有错误,还请多多包涵,并指出错误。
前期在社区上也找了许多开发组件库的文章,也感谢这些开源分享的大佬。若是帮助到了你点个赞再走吧!
这里附上代码的github地址:cookie-ui