淺談Vue CLI 3結(jié)合Lerna進(jìn)行UI框架設(shè)計(jì)
當(dāng)前大部分UI框架設(shè)計(jì)的Webpack配置都相對(duì)復(fù)雜,例如 Element 、 Ant Design Vue 和Muse-UI等Vue組件庫(kù)。例如Element,為了實(shí)現(xiàn)業(yè)務(wù)層面的兩種引入形式( 完整引入 和 按需引入 ),以及拋出一些可供業(yè)務(wù)層面通用的 utils 、 i18n 等,Webpack配置變得非常復(fù)雜。為了簡(jiǎn)化UI框架的設(shè)計(jì)難度,這里介紹一種簡(jiǎn)單的UI框架設(shè)計(jì),在此之前先簡(jiǎn)單介紹一下 Element 的構(gòu)建流程,以便對(duì)比新的UI框架設(shè)計(jì)。
一般組件庫(kù)的設(shè)計(jì)者將引入形式設(shè)計(jì)成 完整引入 和 按需引入 兩種形式: 完整引入 的開發(fā)相對(duì)便利,針對(duì)一些大型業(yè)務(wù)或者對(duì)于打包體積不是特別注重的業(yè)務(wù), 按需引入 開發(fā)的顆粒度相對(duì)精細(xì),可以減少業(yè)務(wù)的打包體積。
設(shè)計(jì)的UI框架實(shí)踐項(xiàng)目的github地址是ziyi2/vue-cli3-lerna-ui,包括了preset.json、自己設(shè)計(jì)的Vue CLI插件以及自己設(shè)計(jì)的一系列UI組件(和生成的UI框架示例稍有不同),如果覺得整體結(jié)構(gòu)有不合理的或者考慮不夠全面的地方,歡迎大家提issue,這樣我也可以對(duì)它進(jìn)行完善。如果大家感興趣,希望大家能夠Star一下,這里拜謝大家了!
Element
首先了解 Element 的構(gòu)建流程,查看 Element 2.7.0 版本 package.json 的npm 腳本 :
// 其中的`node build/bin/build-entry.js` 生成Webpack構(gòu)建入口 "build:file": "node build/bin/iconInit.js & node build/bin/build-entry.js & node build/bin/i18n.js & node build/bin/version.js", // 構(gòu)建css樣式 "build:theme": "node build/bin/gen-cssfile && gulp build --gulpfile packages/theme-chalk/gulpfile.js && cp-cli packages/theme-chalk/lib lib/theme-chalk", // 構(gòu)建commonjs規(guī)范的`utils` "build:utils": "cross-env BABEL_ENV=utils babel src --out-dir lib --ignore src/index.js", // 構(gòu)建umd模塊的語(yǔ)言包 "build:umd": "node build/bin/build-locale.js", // 清除構(gòu)建文件夾`lib` "clean": "rimraf lib && rimraf packages/*/lib && rimraf test/**/coverage", // 總體構(gòu)建 "dist": "npm run clean && npm run build:file && npm run lint && webpack --config build/webpack.conf.js && webpack --config build/webpack.common.js && webpack --config build/webpack.component.js && npm run build:utils && npm run build:umd && npm run build:theme", // 執(zhí)行eslint校驗(yàn) "lint": "eslint src/**/* test/**/* packages/**/* build/**/* --quiet"
這里重點(diǎn)關(guān)注Element的構(gòu)建腳本,忽略 測(cè)試、發(fā)布、啟動(dòng)開發(fā)態(tài)調(diào)試頁(yè)面、構(gòu)建演示頁(yè)面 等腳本。
npm run dist
與 Element 構(gòu)建相關(guān)的npm腳本繁多,但是 總體構(gòu)建腳本 是 dist :
"dist": "npm run clean && npm run build:file && npm run lint && webpack --config build/webpack.conf.js && webpack --config build/webpack.common.js && webpack --config build/webpack.component.js && npm run build:utils && npm run build:umd && npm run build:theme"
&& 是繼發(fā)執(zhí)行,只有當(dāng)前任務(wù)成功,才能執(zhí)行下一個(gè)任務(wù)。
總體構(gòu)建腳本包含了以下按順序執(zhí)行的腳本命令
- npm run clean - 清除構(gòu)建文件夾lib
- npm run build:file - 其中的node build/bin/build-entry.js 生成Webpack構(gòu)建入口
- npm run lint - 執(zhí)行eslint校驗(yàn)
- webpack --config build/webpack.conf.js - 構(gòu)建umd總文件
- webpack --config build/webpack.common.js - 構(gòu)建commonjs2總文件
- webpack --config build/webpack.component.js - 構(gòu)建commonjs2組件(提供按需引入)
- npm run build:utils - 構(gòu)建commonjs的utils(供commonjs2總文件、commonjs2組件以及業(yè)務(wù)使用)
- npm run build:umd - 構(gòu)建umd語(yǔ)言包
- npm run build:theme - 構(gòu)建css樣式
如果對(duì)于對(duì)于 umd 、 commonjs2 、 amd 等模塊定義不是特別清晰,可參考Webpack文檔模塊定義系統(tǒng)。
執(zhí)行 npm run dist 后會(huì)在當(dāng)前根目錄生成新的 lib 文件夾,包含以下構(gòu)建內(nèi)容:
lib ├── directives # commonjs指令(這里歸為utils) ├── locale # commonjs國(guó)際化(commonjs語(yǔ)言包和API) ├── mixins # commonjs mixins(這里歸為utils) ├── theme-chalk # css 樣式文件 ├── transitions # commonjs transitions(這里歸為utils) ├── umd # umd語(yǔ)言包 ├── utils ├── alert.js # commonjs組件 ├── aside.js ├── ... ├── element-ui.common.js # commonjs2總文件 ├── ... ├── index.js # umd總文件 ├── ...
從Element官方文檔的使用指南結(jié)合 lib 可以看出, Element 為我們提供了以下能力:
1、CDN引入(umd 總文件)
2、npm包完整引入(拋出commonjs2總文件)
3、按需引入(拋出commonjs2的所有UI組件)
4、支持國(guó)際化
5、提供utils方法(官方文檔沒有說明,但事實(shí)上業(yè)務(wù)可以使用)
CDN引入的umd總文件一般是全量構(gòu)建的,不會(huì)有依賴問題,但是commonjs2模塊的文件需要在業(yè)務(wù)層面再次使用Webpack構(gòu)建。例如需要在業(yè)務(wù)層面支持 國(guó)際化 和 提供utils 的功能,那么就不能將 國(guó)際化 和 提供utils 的代碼 bundle 到 commonjs2總文件 或 commonjs2的所有UI組件 中(每一個(gè)組件都 bundle utils 的方法或者國(guó)際化API顯然是不合理的),如果需要在業(yè)務(wù)層面支持 按需引入 的功能,那么不建議將 所有UI組件 的源碼 bundle 到 commonjs2總文件 中,這樣便可以實(shí)現(xiàn)層層引用,對(duì)外拋出功能的同時(shí)在業(yè)務(wù)層面可以防止Webpack二次打包,從而導(dǎo)致引入兩遍甚至多遍相同的代碼的問題。
在組件庫(kù)中開發(fā)時(shí),為了構(gòu)建commonjs2模塊的文件,需要對(duì)各個(gè) utils 、組件等引入的路徑做出強(qiáng)約定,這樣不僅產(chǎn)生的Webpack配置會(huì)變得很難維護(hù),對(duì)于開發(fā)者的開發(fā)也需要做出一定的規(guī)范限制。
接下來分析一下各個(gè)腳本的構(gòu)建功能。
npm run build:file
build:file 腳本是自動(dòng)生成一些源碼文件的腳本:
"build:file": "node build/bin/iconInit.js & node build/bin/build-entry.js & node build/bin/i18n.js & node build/bin/version.js",
其中與構(gòu)建相關(guān)的腳本是 node build/bin/build-entry.js ,主要用于生成Webpack構(gòu)建的入口源文件 src/index.js :
// 注釋說明該文件由build-entry.js腳本自動(dòng)生成
/* Automatically generated by './build/bin/build-entry.js' */
import Pagination from '../packages/pagination/index.js';
// ... 這里省略大部分組件引入
import TimelineItem from '../packages/timeline-item/index.js';
import locale from 'element-ui/src/locale';
import CollapseTransition from 'element-ui/src/transitions/collapse-transition';
const components = [
Pagination,
// ... 這里省略大部分組件
TimelineItem,
CollapseTransition
];
const install = function(Vue, opts = {}) {
locale.use(opts.locale);
locale.i18n(opts.i18n);
components.forEach(component => {
Vue.component(component.name, component);
});
Vue.use(Loading.directive);
Vue.prototype.$ELEMENT = {
size: opts.size || '',
zIndex: opts.zIndex || 2000
};
Vue.prototype.$loading = Loading.service;
// ...
};
/* istanbul ignore if */
if (typeof window !== 'undefined' && window.Vue) {
install(window.Vue);
}
export default {
version: '2.7.0',
locale: locale.use,
i18n: locale.i18n,
install,
CollapseTransition,
Loading,
Pagination,
// ... 這里省略大部分組件
TimelineItem
};
在組件的開發(fā)過程中如果組件較多,建議使用腳本自動(dòng)生成構(gòu)建入口文件。
npm run lint
構(gòu)建之前使用 lint 腳本對(duì)構(gòu)建的源碼文件進(jìn)行 eslint 校驗(yàn):
"lint": "eslint src/**/* test/**/* packages/**/* build/**/* --quiet",
Element 對(duì) eslint 做了嚴(yán)格控制,一旦 eslint 報(bào)錯(cuò)那么 dist 總體構(gòu)建腳本 執(zhí)行停止,整體構(gòu)建失敗。這里的 eslint 校驗(yàn)可以使用eslint-loader 進(jìn)行處理(如果你希望 eslint 校驗(yàn)失敗也可以進(jìn)行構(gòu)建可以查看Errors and Warning )。
webpack --config build/webpack.conf.js
webpack --config build/webpack.conf.js 腳本用于構(gòu)建umd總文件,執(zhí)行該腳本最終會(huì)在 lib 下生成 index.js 文件:
lib ├── index.js # umd 總文件
webpack.conf.js 配置如下:
// build/webpack.conf.js
// ...忽略
module.exports = {
mode: 'production',
// 指定入口文件src/index.js,該入口文件由`build:file`腳本自動(dòng)生成
entry: {
app: ['./src/index.js']
},
output: {
// 在lib文件中生成
path: path.resolve(process.cwd(), './lib'),
// 生成lib/index.js
filename: 'index.js',
// 生成umd模塊
libraryTarget: 'umd',
// src/index.js文件采用export default語(yǔ)法拋出,因此需要設(shè)置libraryExport
// 否則引入的UI組件庫(kù)需要使用.default才能引用到拋出的對(duì)象
// if your entry has a default export of `MyDefaultModule`
// var MyDefaultModule = _entry_return_.default;
// 這里踩過坑,所以說明一下,不配置的話遇到的問題是引入的UI組件庫(kù)沒法解構(gòu)
libraryExport: 'default',
},
resolve: {
extensions: ['.js', '.vue', '.json'],
// 'element-ui': path.resolve(__dirname, '../')
// alias中的'element-ui'作為npm包拋出后指向了業(yè)務(wù)項(xiàng)目node_modules所在的npm包路徑
alias: config.alias
},
externals: {
// 構(gòu)建只排除vue
// umd模塊通過CDN形式引入,因此將所有的組件、utils、i18n等構(gòu)建在內(nèi)
// umd模塊沒有按需引入功能
vue: config.vue
},
// ...忽略
};
構(gòu)建文件 lib/index.js 主要的功能是用于CDN形式引入項(xiàng)目,并且無(wú)法做到按需加載,產(chǎn)生的體積非常大,對(duì)于簡(jiǎn)單的應(yīng)用可能不適用。
webpack --config build/webpack.common.js
webpack --config build/webpack.common.js 腳本用于構(gòu)建commonjs2總文件,執(zhí)行該腳本最終會(huì)在 lib 下生成 element-ui.common.js 文件:
lib ├── element-ui.common.js # commonjs2 總文件
由于該文件需要在業(yè)務(wù)層面再次使用Webpack構(gòu)建,因此考量的方面較多。在分析Webpack配置之前,再次回顧一下 Element 能為我們做什么:
1、完整引入(拋出commonjs2總文件)
2、按需引入(拋出commonjs2的所有UI組件)
3、支持國(guó)際化(commonjs2)
4、提供utils方法(commonjs2,當(dāng)然官方?jīng)]有對(duì)外說明)
webpack --config build/webpack.common.js 腳本主要用于構(gòu)建完整引入功能,同時(shí)為了可以在業(yè)務(wù)層面拋出 按需引入、支持國(guó)際化 等功能,構(gòu)建 element-ui.common.js 時(shí)需要將 UI組件、支持國(guó)際化、utils方法 的源代碼排除。
webpack.common.js 配置如下:
// build/webpack.common.js
// ...忽略
module.exports = {
mode: 'production',
entry: {
app: ['./src/index.js']
},
output: {
path: path.resolve(process.cwd(), './lib'),
publicPath: '/dist/',
filename: 'element-ui.common.js',
chunkFilename: '[id].js',
libraryExport: 'default',
library: 'ELEMENT',
// 生成commonjs2模塊
libraryTarget: 'commonjs2'
},
resolve: {
extensions: ['.js', '.vue', '.json'],
// 'element-ui': path.resolve(__dirname, '../')
alias: config.alias,
modules: ['node_modules']
},
// 這里用于排除UI組件、支持國(guó)際化、utils方法的源代碼,這些源代碼需要額外的腳本進(jìn)行構(gòu)建
externals: config.externals,
optimization: {
// commonjs2無(wú)須壓縮處理
minimize: false
},
// ...忽略
};
重點(diǎn)需要關(guān)注一下 config.externals 屬性,打印輸出該變量的值:
[{
vue: 'vue',
// 排除所有UI組件的源代碼
'element-ui/packages/option':'element-ui/lib/option',
// ...
// 排除國(guó)際化的源代碼
'element-ui/src/locale': 'element-ui/lib/locale',
// 排除utils方法的源代碼
'element-ui/src/utils/vue-popper': 'element-ui/lib/utils/vue-popper',
'element-ui/src/mixins/emitter': 'element-ui/lib/mixins/emitter',
'element-ui/src/transitions/collapse-transition': 'element-ui/lib/transitions/collapse-transition'
// ...
},
// var nodeExternals = require('webpack-node-externals');
// nodeExternals()
[Function]
];
externals屬性可以將一些特定的依賴從輸出的bundle中排除,例如在開發(fā)態(tài)中組件之間有依賴關(guān)系, element-ui/packages/pagination 中引入 element-ui/packages/option 組件:
pagecages/pagination/src/pagination.js
// pagination組件中需要用到option組件 import ElOption from 'element-ui/packages/option'; // ...
Webpack構(gòu)建后,可以發(fā)現(xiàn)在 element-ui.common.js 中并沒有將 element-ui/packages/option 組件打包在內(nèi),而只是更改了它的引入路徑 element-ui/lib/option (在實(shí)現(xiàn) 按需引入 功能時(shí)會(huì)用 webpack --config build/webpack.component.js 腳本構(gòu)建出該文件)。
// lib/element-ui.common.js
module.exports = require("element-ui/lib/option");
因此以上列出的 config.externals 屬性的 key 和 value 可以排除 UI組件、支持國(guó)際化、utils方法 功能的代碼。
config.externals 屬性的最后一個(gè)值是 [Function] ,是由webpack-node-externals 生成的。這里解釋一下 webpack-node-externals 的作用:
Webpack allows you to define externals - modules that should not be bundled. When bundling with Webpack for the backend - you usually don't want to bundle its node_modules dependencies. This library creates an externals function that ignores node_modules when bundling in Webpack.
例如在 Elment 組件庫(kù)開發(fā)中需要依賴 deepmerge ,那么Webpack構(gòu)建的時(shí)候不需要將該依賴bundle到 element-ui.common.js 中,而是將其添加到 Element 組件庫(kù)(作為npm包發(fā)布)的 dependencies ,這樣通過npm安裝 Element 的同時(shí)也會(huì)安裝它的依賴 deepmerge ,從而使得 element-ui.common.js 通過 require("deepmerge") 的形式引入該依賴不會(huì)報(bào)錯(cuò)。
這里列出 element-ui.common.js 排除的一些代碼:
// 排除utils源碼(utils源碼會(huì)通過`npm run build:utils`腳本構(gòu)建)
module.exports = require("element-ui/lib/utils/dom");
// 排除vue
module.exports = require("vue");
// 排除國(guó)際化源碼(國(guó)際化源碼會(huì)通過`npm run build:utils`腳本構(gòu)建)
module.exports = require("element-ui/lib/locale");
// 需要注意和Vue相關(guān)的JSX依賴(Vue CLI3系統(tǒng)構(gòu)建的包也會(huì)有一個(gè)該功能的依賴)
module.exports = require("babel-helper-vue-jsx-merge-props");
// 排除一些Elment組件使用的其他依賴
module.exports = require("throttle-debounce/throttle");
// 排除UI組件源碼(UI組件源碼會(huì)通過`webpack --config build/webpack.component.js`腳本構(gòu)建)
module.exports = require("element-ui/lib/option");
需要注意 Element 發(fā)布的npm包入口文件就是 element-ui.common.js ,可以通過package.json中的 main 字段信息查看。
webpack --config build/webpack.component.js
webpack --config build/webpack.component.js 腳本用于構(gòu)建commonjs2的UI組件(提供按需引入功能),執(zhí)行該腳本最終會(huì)在 lib 下生成所有 Element 支持的UI組件(同時(shí)這些文件也會(huì)被 element-ui.common.js 總?cè)肟谖募茫?/p>
lib ├── alert.js # commonjs 組件 ├── aside.js ├── button.js ├── ...
查看 build/webpack.component.js 配置:
// ...忽略
const Components = require('../components.json');
// Components是所有組件的構(gòu)建入口列表
// {
// "pagination": "./packages/pagination/index.js",
// ...
// "timeline-item": "./packages/timeline-item/index.js"
// }
const webpackConfig = {
mode: 'production',
// 多入口
entry: Components,
output: {
path: path.resolve(process.cwd(), './lib'),
publicPath: '/dist/',
filename: '[name].js',
chunkFilename: '[id].js',
libraryTarget: 'commonjs2'
},
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: config.alias,
modules: ['node_modules']
},
// 排除其他UI組件、支持國(guó)際化、utils的源碼,這些源碼會(huì)額外構(gòu)建
externals: config.externals,
},
// ...忽略
};
構(gòu)建單個(gè)組件和構(gòu)建總體入口文件 element-ui.common.js 的Webpack配置類似,需要將 utils 、 locale 以及其他一些依賴排除。
npm run build:utils
build:utils 腳本主要用于構(gòu)建commonjs的 utils (提供國(guó)際化以及 utils 功能):
"build:utils": "cross-env BABEL_ENV=utils babel src --out-dir lib --ignore src/index.js",
可以發(fā)現(xiàn)該命令并不是通過Webpack進(jìn)行多文件構(gòu)建,而是通過Babel直接進(jìn)行轉(zhuǎn)義處理(Webpack構(gòu)建會(huì)產(chǎn)生額外的Webpack代碼,并且配置繁瑣,Babel轉(zhuǎn)義處理構(gòu)建的代碼非常干凈),將 src 目錄下除了Webpack構(gòu)建入口文件 src/index.js 以外的所有其他文件進(jìn)行轉(zhuǎn)義處理。執(zhí)行該腳本最終會(huì)在 lib 下生成所有的 utils 文件:
lib ├── directives # commonjs 指令 ├── locale # commonjs 國(guó)際化API和語(yǔ)言包 ├── mixins # commonjs 混入 ├── transitions # commonjs 過度動(dòng)畫 ├── utils # commonjs 工具方法
生成的這些工具方法會(huì)被 lib 下的 element-ui.common.js 和各個(gè)組件引用,同時(shí)在業(yè)務(wù)層面也可以引用這些工具方法。查看 .babelrc 文件的配置信息:
{
"presets": [
[
"env",
{
"loose": true,
"modules": false,
"targets": {
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
}
}
],
"stage-2"
],
"plugins": ["transform-vue-jsx"],
"env": {
// cross-env BABEL_ENV=utils
"utils": {
"presets": [
[
"env",
{
// 松散模式,更像人手寫的ES5代碼
"loose": true,
// es6轉(zhuǎn)成commonjs
"modules": "commonjs",
"targets": {
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
}
}
],
],
"plugins": [
["module-resolver", {
"root": ["element-ui"],
"alias": {
// 類似于Webpack的externals功能
// 將源代碼的引入路徑更改成目標(biāo)代碼的引入路徑
"element-ui/src": "element-ui/lib"
}
}]
]
},
"test": {
"plugins": ["istanbul"]
}
}
}
utils 文件源代碼之間互相引用的路徑是 element-ui/src ,轉(zhuǎn)義成目標(biāo)代碼后互相之間的引用路徑是 element-ui/lib ,因此需要有類似于Webpack的 externals 的功能去更改目標(biāo)代碼的引用路徑,進(jìn)行Babel轉(zhuǎn)義時(shí)插件babel-plugin-module-resolver 可以實(shí)現(xiàn)該功能。
npm run build:theme
build:theme 腳本主要用于構(gòu)建UI組件的css樣式:
"build:theme": "node build/bin/gen-cssfile && gulp build --gulpfile packages/theme-chalk/gulpfile.js && cp-cli packages/theme-chalk/lib lib/theme-chalk",
這里主要關(guān)注 gulp build --gulpfile packages/theme-chalk/gulpfile.js 腳本,該腳本使用Gulp構(gòu)建工具構(gòu)建css樣式文件,Glup構(gòu)建多文件樣式會(huì)非常簡(jiǎn)單。最終將當(dāng)前構(gòu)建的 packages/theme-chalk/lib 目錄下的內(nèi)容拷貝到 lib/theme-chalk 目錄下供外部業(yè)務(wù)使用:
lib ├── theme-chalk # css 樣式文件 │ ├── fonts # icons │ ├── alert.css # 按需引入的組件樣式 │ ├── ... # 按需引入的組件樣式 │ └── index.css # 完整引入樣式
查看 gulpfile.js 文件:
'use strict';
const { series, src, dest } = require('gulp');
const sass = require('gulp-sass');
const autoprefixer = require('gulp-autoprefixer');
const cssmin = require('gulp-cssmin');
function compile() {
return src('./src/*.scss')
// sass轉(zhuǎn)化成css
.pipe(sass.sync())
// Parse CSS and add vendor prefixes to rules by Can I Use
// css瀏覽器兼容處理
.pipe(autoprefixer({
browsers: ['ie > 9', 'last 2 versions'],
cascade: false
}))
// 壓縮css
.pipe(cssmin())
.pipe(dest('./lib'));
}
function copyfont() {
return src('./src/fonts/**')
.pipe(cssmin())
.pipe(dest('./lib/fonts'));
}
exports.build = series(compile, copyfont);
Vue CLI 3 & Lerna
構(gòu)建整個(gè) Element 組件庫(kù)的腳本繁多,構(gòu)建的代碼之間互相還有引用關(guān)系,對(duì)于開發(fā)的引用路徑也會(huì)產(chǎn)生一定的約束。因此設(shè)計(jì)類似于 Element 的UI框架相對(duì)開發(fā)者而言需要一定的開發(fā)門檻。
這里基于Vue CLI 3的 開發(fā)/構(gòu)建目標(biāo)/庫(kù) 能力以及 Lerna 工具設(shè)計(jì)了一個(gè)UI框架,這個(gè)UI框架集成了以下特點(diǎn):
1、 結(jié)構(gòu)特點(diǎn) :每個(gè)UI組件都是一個(gè)npm包, 多語(yǔ)言、工具和樣式 都是自成體系的npm包,可被業(yè)務(wù)或UI組件靈活引用,同時(shí)天然按需加載。
2、 配置特點(diǎn) :如果需要進(jìn)行構(gòu)建處理,那么每個(gè)npm包可單獨(dú)進(jìn)行構(gòu)建配置,配置變得更加簡(jiǎn)單。結(jié)合Vue CLI3的 構(gòu)件庫(kù) 能力,對(duì)于簡(jiǎn)單UI組件的構(gòu)建幾乎可以做到webpack零配置,當(dāng)然需要特殊的webpack loader除外。
3、 發(fā)布特點(diǎn) :組件庫(kù)的版本迭代可以更快,不需要進(jìn)行整體構(gòu)建,每個(gè)組件可單獨(dú)快速發(fā)布 PATCH 或 MINOR 版本。
這里設(shè)定業(yè)務(wù)層面需要進(jìn)行webpack構(gòu)建處理,因此可以對(duì)UI框架的組件不進(jìn)行構(gòu)建處理,當(dāng)然如果UI組件的設(shè)計(jì)需要特殊的webpack loader處理除外,否則業(yè)務(wù)層面需要做額外的webpack配置。當(dāng)然不構(gòu)建處理是相對(duì)于一定的使用場(chǎng)景的,不構(gòu)建處理可能也會(huì)產(chǎn)生額外的一些問題。
這個(gè)UI框架的設(shè)計(jì)也會(huì)有一些缺陷:
1、沒有完整引入功能(也可以進(jìn)行整體構(gòu)建,但是這里不推薦)
2、不提供UMD模塊
3、業(yè)務(wù)層面引入繁瑣(可以出額外的引入工具,簡(jiǎn)化業(yè)務(wù)中的UI組件引入)
Vue CLI 3
構(gòu)建庫(kù)
為了簡(jiǎn)化UI框架的webpack配置,這里將Vue CLI 3作為開發(fā)的容器引入,借用Vue CLI 3的構(gòu)建庫(kù)功能( 構(gòu)建web-components-組件 功能應(yīng)該更合適,這里沒有進(jìn)行驗(yàn)證),幾乎可以做到UI組件構(gòu)建的零配置。通過審查項(xiàng)目的-webpack-配置 能力,可以查看Vue CLI 3為我們預(yù)先設(shè)置的通用webpack配置(幾乎可以滿足大部分的UI組件構(gòu)建)。
插件體系
這里使用Vue CLI 3的插件和Preset功能開發(fā)了幾個(gè)插件,以便于快速構(gòu)建起步的UI設(shè)計(jì)框架,具體的 preset.json 配置如下:
{
"useConfigFiles": true,
"router": true,
"routerHistoryMode": true,
"vuex": false,
"cssPreprocessor": "less",
// MAC OS X下生效,Windows下不生效,具體未深入研究
"packageManager": "yarn",
"plugins": {
"@vue/cli-plugin-babel": {},
"@vue/cli-plugin-eslint": {
"lintOn": ["save", "commit"]
},
"@ziyi2/vue-cli-plugin-ui-base": {},
"@ziyi2/vue-cli-plugin-ui-cz": {},
"@ziyi2/vue-cli-plugin-ui-lint": {}
}
}
這里采用了官方設(shè)計(jì)的 @vue/cli-plugin-babel 和 @vue/cli-plugin-eslint 插件,同時(shí)自己設(shè)計(jì)了額外的三個(gè)插件來支持整個(gè)新的UI框架的起步:
@ziyi2/vue-cli-plugin-ui-base :UI框架基礎(chǔ)插件,生成Monorepo結(jié)構(gòu)的源碼目錄(加入Lerna管理工具),生成基礎(chǔ)通用的webpack配置(在VUE CLI 3的webpack配置上進(jìn)行再配置,VUE CLI3提供了 vue.config.js 文件供開發(fā)者進(jìn)行webpack再配置),提供了幾個(gè)基礎(chǔ)UI組件的示例(僅參考價(jià)值)。
@ziyi2/vue-cli-plugin-ui-cz : UI框架的 cz 適配器插件,加入了 cz-customizable 、 commitlint 、 conventional-changelog ,用于生成Angular規(guī)范的Git提交說明、檢測(cè)提交說明是否符合規(guī)范以及自動(dòng)生成UI框架的升級(jí)日志等。
@ziyi2/vue-cli-plugin-ui-lint :UI框架的lint-staged 插件,代碼提交前會(huì)執(zhí)行Eslint校驗(yàn),校驗(yàn)不通過則不允許提交辣雞代碼。
這三個(gè)插件已經(jīng)發(fā)布在npm的倉(cāng)庫(kù)里,如果是已有的Vue CLI 3項(xiàng)目,可直接通過 vue add @ziyi2/ui-cz 等命令進(jìn)行安裝使用,插件源碼地址ziyi2/vue-cli3-lerna-ui/plugins ,如果想學(xué)習(xí)設(shè)計(jì)Vue CLI 3插件,可參考插件開發(fā)指南,不過官方文檔可能不夠詳細(xì),建議參考官方設(shè)計(jì)的插件或者別人設(shè)計(jì)的優(yōu)秀插件。
Lerna
Lerna是一個(gè)Monorepo管理工具,使所有的組件(npm包)設(shè)計(jì)都集成在一個(gè)git倉(cāng)庫(kù)里,同時(shí)可以利用yarn的workspace特性,模擬發(fā)布的組件環(huán)境,從而使組件的開發(fā)和測(cè)試變得簡(jiǎn)單,不需要多次進(jìn)行組件的發(fā)布測(cè)試(這里用Lerna進(jìn)行Vue CLI插件開發(fā)也非常方便)。
同時(shí)Lerna還集成了版本發(fā)布工具,可以快速的對(duì)UI框架進(jìn)行版本發(fā)布。
UI組件各自修復(fù)問題可以各自快速發(fā)布 patch 版本,如果UI組件整體有非兼容性更新,可以利用Lerna進(jìn)行 MAJOR 版本發(fā)布,更多關(guān)于版本發(fā)布規(guī)范可查看語(yǔ)義化版本。
UI框架實(shí)踐
利用Vue CLI 3的遠(yuǎn)程Preset,這里將自己設(shè)計(jì)的UI框架分享給大家進(jìn)行實(shí)踐使用:
// 可能獲取會(huì)有點(diǎn)慢,大家耐心等待 vue create --preset ziyi2/vue-cli3-lerna-ui my-project
如果報(bào)錯(cuò) unable to get local issuer certificate ,可以設(shè)置 git config --global http.sslVerify false 。
如果遠(yuǎn)程確實(shí)獲取preset.json失敗,可以采用本地的方式,將preset.json 配置復(fù)制下來,放入新建的 preset.json 文件,執(zhí)行以下命令生成UI框架:
vue create --preset preset.json my-project
執(zhí)行后的生成過程如下:
腳本命令
// 啟動(dòng)開發(fā)服務(wù) "serve": "vue-cli-service serve", // 生成靜態(tài)資源 "build": "vue-cli-service build", // Eslint校驗(yàn) "lint": "vue-cli-service lint", // 安裝和鏈接Lerna repo的依賴 "bootstrap": "lerna bootstrap", // 更新升級(jí)日志 "cz:changelog": "conventional-changelog -p angular -i CHANGELOG.md -s && git add CHANGELOG.md", // 構(gòu)建 "lib": "lerna run lib"
如果需要利用GitHub Pages發(fā)布靜態(tài)資源,可以新增命令 "deploy": "npm run build && gh-pages -d dist" ,需要安裝 gh-page 依賴。
啟動(dòng)
進(jìn)入項(xiàng)目目錄,使用 yarn serve 命令啟動(dòng)開發(fā)態(tài)視圖,如果是Windows系統(tǒng),可能會(huì)報(bào)以下錯(cuò)誤:

在Windows下 vue create 可能會(huì)采用npm進(jìn)行依賴安裝,MAC OS X下無(wú)此問題,此時(shí)需要額外使用yarn進(jìn)行再一次安裝操作(這里使用了yarn的workspace特性,因此安裝依賴建議都使用yarn進(jìn)行操作):
lerna bootstrap
執(zhí)行 yarn serve :

這里給出了國(guó)際化、選擇器、警告以及按鈕等UI設(shè)計(jì)示例。
構(gòu)建
執(zhí)行 lerna run lib 后(構(gòu)建可以配合 npm run lint 校驗(yàn),校驗(yàn)不通過則構(gòu)建失?。?,Lerna工具會(huì)對(duì)每一個(gè)npm包執(zhí)行 lib 腳本:

這里分別對(duì) utils 、 btn 、 theme 包進(jìn)行了構(gòu)建處理,其中 btn 采用了Vue CLI 3默認(rèn)的構(gòu)建庫(kù)配置。
Monorepo結(jié)構(gòu)
UI框架生成并構(gòu)建后的Monorepo結(jié)構(gòu)如下:
. ├── packages # workspaces │ ├── alert # 警告(不構(gòu)建) │ │ ├── alert.vue # 組件源碼 │ │ ├── index.js # npm包入口文件 │ │ └── package.json # npm包描述文件 │ ├── btn # 按鈕 │ │ ├── lib # 目標(biāo)文件 │ │ │ └── lib.common.js # npm包入口文件 │ │ ├── btn.vue # 組件源碼 │ │ ├── index.js # 構(gòu)建入口文件 │ │ ├── package.json # npm包描述文件(需要vue cli的開發(fā)態(tài)依賴) │ │ └── vue.config.js # 構(gòu)建配置文件 │ ├── locale # 國(guó)際化 │ │ ├── lang # 語(yǔ)言包 │ │ │ ├── enjs # 英文 │ │ │ └── zh_CN.js # 中文 │ │ ├── mixins # 各個(gè)組件調(diào)用的國(guó)際化API │ │ ├── src # 源碼 │ │ ├── index.js # npm包入口文件 │ │ ├── alert.vue # 組件源碼 │ │ ├── index.js # npm包入口文件 │ │ └── package.json # npm包描述文件 │ ├── select # 選擇器(類似于alert) │ ├── theme # 樣式 │ │ ├── lib # 目標(biāo)文件 │ │ │ ├── alert.css # 警告樣式 │ │ │ ├── btn.css # 按鈕樣式 │ │ │ ├── index.css # 總體樣式 │ │ │ └── select.css # 選擇器樣式 │ │ ├── src # 源文件 │ │ │ ├── utils # 通用方法和變量 │ │ │ ├── alert.less # 警告樣式 │ │ │ ├── btn.less # 按鈕樣式 │ │ │ ├── index.less # 總體樣式 │ │ │ └── select.less # 選擇器樣式 │ │ ├── gulpfile.js # 構(gòu)建配置文件 │ │ └── package.json # npm包描述文件 │ └── utils # 工具方法 │ ├── lib # 目標(biāo)文件(這里也可以采用lodash的方式,去掉lib文件夾這一層) │ ├── src # 源文件 │ ├── babel.config.js # 構(gòu)建配置文件 │ └── package.json # npm包描述文件 ├── public # 公共資源目錄 ├── src # 開發(fā)態(tài)目錄 ├── .browserslistrc # UI框架目標(biāo)瀏覽器配置 ├── .cz-config.js # cz定制化提交說明配置 ├── .gitignore # git忽略配置 ├── .lintstagedrc # lint-staged配置 ├── babel.config.js # vue cli的babel配置 ├── lerna.json # lerna配置 ├── package.json # vue cli容器描述文件(容器不是npm包) ├── postcss.config.js # postcss配置 ├── README.md # 說明 └── vue.common.js # 通用的組件構(gòu)建配置文件
這里重點(diǎn)說明 src 文件, src 文件可以根據(jù)開發(fā)需要自行選定方案:
1、使用默認(rèn)的CLI服務(wù)進(jìn)行開發(fā)和UI框架Demo演示,這里UI框架采用原生的 .vue 文件形式進(jìn)行Demo演示,如果想使用 .md 文件進(jìn)行演示,可以采用vue-markdown-loader 。
2、使用Vue 驅(qū)動(dòng)的靜態(tài)網(wǎng)站生成器VuePress,這個(gè)目前不是很穩(wěn)定。 發(fā)布
發(fā)布
完全可以按照語(yǔ)義化版本進(jìn)行:
1、各自npm包可以使用 npm publish 快速發(fā)布 MINOR 和 PATCH 版本。
2、如果某個(gè)npm包有非兼容性更新,那么可以使用 lerna publish 發(fā)布 MAJOR 版本。
使用Lerna工具發(fā)布的npm包建議采用scope的形式發(fā)布,UI框架示例沒有給出Demo,如果想采用scope形式發(fā)布可以查看 ziyi2/vue-cli3-lerna-ui ,需要在每個(gè)npm包的 package.json 中做額外的配置,具體可查看 vue-cli3-lerna-ui/plugins/vue-cli-plugin-ui-base/package.json 的 publishConfig 字段信息。
總結(jié)
對(duì)比Element的UI框架設(shè)計(jì),采用Vue CLI 3 & Lerna的形式可以簡(jiǎn)化UI框架的配置,使各個(gè)UI組件的構(gòu)建配置互相獨(dú)立,對(duì)于簡(jiǎn)單的UI組件可以利用Vue CLI 3的默認(rèn)webpack配置。同時(shí)采用Monorepo的設(shè)計(jì)結(jié)構(gòu)( Why is Babel a monorepo? ),配合Lerna工具,可以使得UI框架修復(fù)問題和發(fā)布新功能的響應(yīng)能力變得更快。
生成UI框架實(shí)踐項(xiàng)目的github地址是 ziyi2/vue-cli3-lerna-ui ,包括了 preset.json 、自己設(shè)計(jì)的Vue CLI插件以及自己設(shè)計(jì)的一系列UI組件(和生成的UI框架示例稍有不同),如果覺得整體結(jié)構(gòu)有不合理的或者考慮不夠全面的地方,歡迎大家提issue,這樣我也可以對(duì)它進(jìn)行完善。如果大家感興趣,希望大家能夠Star一下,這里拜謝大家了!
參考鏈接
- Element - github
- npm scripts 使用指南 - 阮一峰
- umd - github
- Element官方文檔
- eslint - eslint文檔
- Module Definition Systems - Webpack文檔
- eslint-loader - github
- webpack-node-externals - github
- Externals - Webpack文檔
- babel-plugin-module-resolver - github
- Gulp - Gulp文檔
- Vue CLI 3/開發(fā)/構(gòu)建目標(biāo)/庫(kù) - Vue CLI文檔
- Vue CLI 3/開發(fā)/webpack相關(guān)/審查項(xiàng)目的webpack配置 - Vue CLI文檔
- Vue CLI 3/基礎(chǔ)/插件和Preset - Vue CLI文檔
- @vue/cli-plugin-babel - Vue CLI Plugin
- @vue/cli-plugin-eslint - Vue CLI Plugin
- cz - Git提交說明工具
- cz-customizable - Cz適配器,自定義說明
- commitlint - Cz適配器,提交說明檢測(cè)
- conventional-changelog - Cz適配器,生成日志
- lint-staged - 代碼提交審核工具
- 插件開發(fā)指南 - Vue CLI文檔
- 語(yǔ)義化版本 - 版本發(fā)布規(guī)范
- Vue CLI3/基礎(chǔ)/CLI服務(wù) - Vue CLI文檔
- Why is Babel a monorepo?
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
vue router解決路由帶參數(shù)跳轉(zhuǎn)時(shí)出現(xiàn)404問題
我的頁(yè)面是從一個(gè)vue頁(yè)面router跳轉(zhuǎn)到另一個(gè)vue頁(yè)面,并且利用windows.open() 瀏覽器重新創(chuàng)建一個(gè)頁(yè)簽,但是不知道為什么有時(shí)候可以有時(shí)候又不行,所以本文給大家介紹了vue router解決路由帶參數(shù)跳轉(zhuǎn)時(shí)出現(xiàn)404問題,需要的朋友可以參考下2024-03-03
uniapp?vue3中使用webview在微信小程序?qū)崿F(xiàn)雙向通訊功能
微信小程序的存在許多功能上的限制和約束,有些情況不得不去使用webview進(jìn)行開發(fā)實(shí)現(xiàn)需求,這篇文章主要給大家介紹了關(guān)于uniapp?vue3中使用webview在微信小程序?qū)崿F(xiàn)雙向通訊功能的相關(guān)資料,需要的朋友可以參考下2024-07-07
vue-router如何實(shí)現(xiàn)history模式配置
這篇文章主要介紹了vue-router如何實(shí)現(xiàn)history模式配置,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06
VUE+node(express)實(shí)現(xiàn)前后端分離
在本篇文章里小編給大家分享的是關(guān)于VUE+node(express)前后端分離實(shí)例內(nèi)容,有需要的朋友們參考下。2019-10-10
element-plus dialog v-loading不生效問題及解決
這篇文章主要介紹了element-plus dialog v-loading不生效問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03
vue中關(guān)于this.refs為空出現(xiàn)原因及分析
這篇文章主要介紹了vue中關(guān)于this.refs為空出現(xiàn)原因及分析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-05-05
Vue?監(jiān)聽視頻播放時(shí)長(zhǎng)的實(shí)例代碼
本文介紹了如何通過源碼實(shí)現(xiàn)對(duì)視頻實(shí)時(shí)時(shí)長(zhǎng)、播放時(shí)長(zhǎng)和暫停時(shí)長(zhǎng)的監(jiān)聽,詳細(xì)闡述了相關(guān)技術(shù)的應(yīng)用方法,幫助開發(fā)者更好地掌握視頻監(jiān)控技術(shù),提高用戶體驗(yàn)2024-10-10

