在參考過一些資料和開源的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