浅谈Vue CLI 3结合Lerna进行UI框架设计


Posted in Javascript onApril 14, 2019

当前大部分UI框架设计的Webpack配置都相对复杂,例如 Element 、 Ant Design Vue 和Muse-UI等Vue组件库。例如Element,为了实现业务层面的两种引入形式( 完整引入按需引入 ),以及抛出一些可供业务层面通用的 utilsi18n 等,Webpack配置变得非常复杂。为了简化UI框架的设计难度,这里介绍一种简单的UI框架设计,在此之前先简单介绍一下 Element 的构建流程,以便对比新的UI框架设计。

一般组件库的设计者将引入形式设计成 完整引入按需引入 两种形式: 完整引入 的开发相对便利,针对一些大型业务或者对于打包体积不是特别注重的业务, 按需引入 开发的颗粒度相对精细,可以减少业务的打包体积。

设计的UI框架实践项目的github地址是ziyi2/vue-cli3-lerna-ui,包括了preset.json、自己设计的Vue CLI插件以及自己设计的一系列UI组件(和生成的UI框架示例稍有不同),如果觉得整体结构有不合理的或者考虑不够全面的地方,欢迎大家提issue,这样我也可以对它进行完善。如果大家感兴趣,希望大家能够Star一下,这里拜谢大家了!

Element

首先了解 Element 的构建流程,查看 Element 2.7.0 版本 package.json 的npm 脚本 :

// 其中的`node build/bin/build-entry.js` 生成Webpack构建入口
"build:file": "node build/bin/iconInit.js & node build/bin/build-entry.js & node build/bin/i18n.js & node build/bin/version.js",
// 构建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",
// 构建commonjs规范的`utils`
"build:utils": "cross-env BABEL_ENV=utils babel src --out-dir lib --ignore src/index.js",
// 构建umd模块的语言包
"build:umd": "node build/bin/build-locale.js",
// 清除构建文件夹`lib`
"clean": "rimraf lib && rimraf packages/*/lib && rimraf test/**/coverage",
// 总体构建
"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",
// 执行eslint校验
"lint": "eslint src/**/* test/**/* packages/**/* build/**/* --quiet"

这里重点关注Element的构建脚本,忽略 测试、发布、启动开发态调试页面、构建演示页面 等脚本。

npm run dist

Element 构建相关的npm脚本繁多,但是 总体构建脚本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"

&& 是继发执行,只有当前任务成功,才能执行下一个任务。

总体构建脚本包含了以下按顺序执行的脚本命令

  • npm run clean - 清除构建文件夹lib
  • npm run build:file - 其中的node build/bin/build-entry.js 生成Webpack构建入口
  • npm run lint - 执行eslint校验
  • webpack --config build/webpack.conf.js - 构建umd总文件
  • webpack --config build/webpack.common.js - 构建commonjs2总文件
  • webpack --config build/webpack.component.js - 构建commonjs2组件(提供按需引入)
  • npm run build:utils - 构建commonjs的utils(供commonjs2总文件、commonjs2组件以及业务使用)
  • npm run build:umd - 构建umd语言包
  • npm run build:theme - 构建css样式

如果对于对于 umdcommonjs2amd 等模块定义不是特别清晰,可参考Webpack文档模块定义系统。

执行 npm run dist 后会在当前根目录生成新的 lib 文件夹,包含以下构建内容:

lib
├── directives     # commonjs指令(这里归为utils)
├── locale      # commonjs国际化(commonjs语言包和API)
├── mixins  		# commonjs mixins(这里归为utils)
├── theme-chalk 		# css 样式文件
├── transitions			# commonjs transitions(这里归为utils)
├── umd   		# umd语言包 
├── utils
├── alert.js     # commonjs组件
├── aside.js
├── ...
├── element-ui.common.js 	# commonjs2总文件 
├── ...
├── index.js     # umd总文件
├── ...

从Element官方文档的使用指南结合 lib 可以看出, Element 为我们提供了以下能力:

1、CDN引入(umd 总文件)
2、npm包完整引入(抛出commonjs2总文件)
3、按需引入(抛出commonjs2的所有UI组件)
4、支持国际化
5、提供utils方法(官方文档没有说明,但事实上业务可以使用)

CDN引入的umd总文件一般是全量构建的,不会有依赖问题,但是commonjs2模块的文件需要在业务层面再次使用Webpack构建。例如需要在业务层面支持 国际化提供utils 的功能,那么就不能将 国际化提供utils 的代码 bundlecommonjs2总文件commonjs2的所有UI组件 中(每一个组件都 bundle utils 的方法或者国际化API显然是不合理的),如果需要在业务层面支持 按需引入 的功能,那么不建议将 所有UI组件 的源码 bundlecommonjs2总文件 中,这样便可以实现层层引用,对外抛出功能的同时在业务层面可以防止Webpack二次打包,从而导致引入两遍甚至多遍相同的代码的问题。

在组件库中开发时,为了构建commonjs2模块的文件,需要对各个 utils 、组件等引入的路径做出强约定,这样不仅产生的Webpack配置会变得很难维护,对于开发者的开发也需要做出一定的规范限制。

接下来分析一下各个脚本的构建功能。

npm run build:file

build:file 脚本是自动生成一些源码文件的脚本:

"build:file": "node build/bin/iconInit.js & node build/bin/build-entry.js & node build/bin/i18n.js & node build/bin/version.js",

其中与构建相关的脚本是 node build/bin/build-entry.js ,主要用于生成Webpack构建的入口源文件 src/index.js

// 注释说明该文件由build-entry.js脚本自动生成
/* 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
};

在组件的开发过程中如果组件较多,建议使用脚本自动生成构建入口文件。

npm run lint

构建之前使用 lint 脚本对构建的源码文件进行 eslint 校验:

"lint": "eslint src/**/* test/**/* packages/**/* build/**/* --quiet",

Elementeslint 做了严格控制,一旦 eslint 报错那么 dist 总体构建脚本 执行停止,整体构建失败。这里的 eslint 校验可以使用eslint-loader 进行处理(如果你希望 eslint 校验失败也可以进行构建可以查看Errors and Warning )。

webpack --config build/webpack.conf.js

webpack --config build/webpack.conf.js 脚本用于构建umd总文件,执行该脚本最终会在 lib 下生成 index.js 文件:

lib
├── index.js     # umd 总文件

webpack.conf.js 配置如下:

// build/webpack.conf.js

// ...忽略

module.exports = {
 mode: 'production',
 // 指定入口文件src/index.js,该入口文件由`build:file`脚本自动生成
 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语法抛出,因此需要设置libraryExport
 // 否则引入的UI组件库需要使用.default才能引用到抛出的对象
 // if your entry has a default export of `MyDefaultModule`
 // var MyDefaultModule = _entry_return_.default;
 // 这里踩过坑,所以说明一下,不配置的话遇到的问题是引入的UI组件库没法解构
 libraryExport: 'default',
 },
 resolve: {
 extensions: ['.js', '.vue', '.json'],
	// 'element-ui': path.resolve(__dirname, '../')
	// alias中的'element-ui'作为npm包抛出后指向了业务项目node_modules所在的npm包路径
 alias: config.alias
 },
 externals: {
 // 构建只排除vue
 // umd模块通过CDN形式引入,因此将所有的组件、utils、i18n等构建在内
 // umd模块没有按需引入功能
 vue: config.vue
 },
 // ...忽略
};

构建文件 lib/index.js 主要的功能是用于CDN形式引入项目,并且无法做到按需加载,产生的体积非常大,对于简单的应用可能不适用。

webpack --config build/webpack.common.js

webpack --config build/webpack.common.js 脚本用于构建commonjs2总文件,执行该脚本最终会在 lib 下生成 element-ui.common.js 文件:

lib
├── element-ui.common.js 		# commonjs2 总文件

由于该文件需要在业务层面再次使用Webpack构建,因此考量的方面较多。在分析Webpack配置之前,再次回顾一下 Element 能为我们做什么:

1、完整引入(抛出commonjs2总文件)
2、按需引入(抛出commonjs2的所有UI组件)
3、支持国际化(commonjs2)
4、提供utils方法(commonjs2,当然官方没有对外说明

webpack --config build/webpack.common.js 脚本主要用于构建完整引入功能,同时为了可以在业务层面抛出 按需引入、支持国际化 等功能,构建 element-ui.common.js 时需要将 UI组件、支持国际化、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组件、支持国际化、utils方法的源代码,这些源代码需要额外的脚本进行构建
 externals: config.externals,
 optimization: {
 // commonjs2无须压缩处理
 minimize: false
 },
 // ...忽略
};

重点需要关注一下 config.externals 属性,打印输出该变量的值:

[{ 
 vue: 'vue',
 // 排除所有UI组件的源代码
 'element-ui/packages/option':'element-ui/lib/option',
 // ...
 // 排除国际化的源代码
 '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中排除,例如在开发态中组件之间有依赖关系, element-ui/packages/pagination 中引入 element-ui/packages/option 组件:

pagecages/pagination/src/pagination.js

// pagination组件中需要用到option组件
import ElOption from 'element-ui/packages/option';
// ...

Webpack构建后,可以发现在 element-ui.common.js 中并没有将 element-ui/packages/option 组件打包在内,而只是更改了它的引入路径 element-ui/lib/option (在实现 按需引入 功能时会用 webpack --config build/webpack.component.js 脚本构建出该文件)。

// lib/element-ui.common.js
module.exports = require("element-ui/lib/option");

因此以上列出的 config.externals 属性的 keyvalue 可以排除 UI组件、支持国际化、utils方法 功能的代码。

config.externals 属性的最后一个值是 [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 组件库开发中需要依赖 deepmerge ,那么Webpack构建的时候不需要将该依赖bundle到 element-ui.common.js 中,而是将其添加到 Element 组件库(作为npm包发布)的 dependencies ,这样通过npm安装 Element 的同时也会安装它的依赖 deepmerge ,从而使得 element-ui.common.js 通过 require("deepmerge") 的形式引入该依赖不会报错。

这里列出 element-ui.common.js 排除的一些代码:

// 排除utils源码(utils源码会通过`npm run build:utils`脚本构建)
module.exports = require("element-ui/lib/utils/dom");
// 排除vue
module.exports = require("vue");
// 排除国际化源码(国际化源码会通过`npm run build:utils`脚本构建)
module.exports = require("element-ui/lib/locale");
// 需要注意和Vue相关的JSX依赖(Vue CLI3系统构建的包也会有一个该功能的依赖)
module.exports = require("babel-helper-vue-jsx-merge-props");
// 排除一些Elment组件使用的其他依赖
module.exports = require("throttle-debounce/throttle"); 
// 排除UI组件源码(UI组件源码会通过`webpack --config build/webpack.component.js`脚本构建)
module.exports = require("element-ui/lib/option");

需要注意 Element 发布的npm包入口文件就是 element-ui.common.js ,可以通过package.json中的 main 字段信息查看。

webpack --config build/webpack.component.js

webpack --config build/webpack.component.js 脚本用于构建commonjs2的UI组件(提供按需引入功能),执行该脚本最终会在 lib 下生成所有 Element 支持的UI组件(同时这些文件也会被 element-ui.common.js 总入口文件引用):

lib
├── alert.js     # commonjs 组件
├── aside.js
├── button.js
├── ...

查看 build/webpack.component.js 配置:

// ...忽略
const Components = require('../components.json');

// Components是所有组件的构建入口列表
// {
// "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组件、支持国际化、utils的源码,这些源码会额外构建
 externals: config.externals,
 },
 // ...忽略
};

构建单个组件和构建总体入口文件 element-ui.common.js 的Webpack配置类似,需要将 utilslocale 以及其他一些依赖排除。

npm run build:utils

build:utils 脚本主要用于构建commonjs的 utils (提供国际化以及 utils 功能):

"build:utils": "cross-env BABEL_ENV=utils babel src --out-dir lib --ignore src/index.js",

可以发现该命令并不是通过Webpack进行多文件构建,而是通过Babel直接进行转义处理(Webpack构建会产生额外的Webpack代码,并且配置繁琐,Babel转义处理构建的代码非常干净),将 src 目录下除了Webpack构建入口文件 src/index.js 以外的所有其他文件进行转义处理。执行该脚本最终会在 lib 下生成所有的 utils 文件:

lib
├── directives     # commonjs 指令
├── locale      # commonjs 国际化API和语言包
├── mixins  		# commonjs 混入
├── transitions			# commonjs 过度动画
├── utils      # commonjs 工具方法

生成的这些工具方法会被 lib 下的 element-ui.common.js 和各个组件引用,同时在业务层面也可以引用这些工具方法。查看 .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转成commonjs
   "modules": "commonjs",
   "targets": {
    "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
   }
   }
  ],
  ],
  "plugins": [
  ["module-resolver", {
   "root": ["element-ui"],
   "alias": {
   // 类似于Webpack的externals功能
   // 将源代码的引入路径更改成目标代码的引入路径
   "element-ui/src": "element-ui/lib"
   }
  }]
  ]
 },
 "test": {
  "plugins": ["istanbul"]
 }
 }
}

utils 文件源代码之间互相引用的路径是 element-ui/src ,转义成目标代码后互相之间的引用路径是 element-ui/lib ,因此需要有类似于Webpack的 externals 的功能去更改目标代码的引用路径,进行Babel转义时插件babel-plugin-module-resolver 可以实现该功能。

npm run build:theme

build:theme 脚本主要用于构建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",

这里主要关注 gulp build --gulpfile packages/theme-chalk/gulpfile.js 脚本,该脚本使用Gulp构建工具构建css样式文件,Glup构建多文件样式会非常简单。最终将当前构建的 packages/theme-chalk/lib 目录下的内容拷贝到 lib/theme-chalk 目录下供外部业务使用:

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转化成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

构建整个 Element 组件库的脚本繁多,构建的代码之间互相还有引用关系,对于开发的引用路径也会产生一定的约束。因此设计类似于 Element 的UI框架相对开发者而言需要一定的开发门槛。

这里基于Vue CLI 3的 开发/构建目标/库 能力以及 Lerna 工具设计了一个UI框架,这个UI框架集成了以下特点:

1、 结构特点 :每个UI组件都是一个npm包, 多语言、工具和样式 都是自成体系的npm包,可被业务或UI组件灵活引用,同时天然按需加载。

2、 配置特点 :如果需要进行构建处理,那么每个npm包可单独进行构建配置,配置变得更加简单。结合Vue CLI3的 构件库 能力,对于简单UI组件的构建几乎可以做到webpack零配置,当然需要特殊的webpack loader除外。

3、 发布特点 :组件库的版本迭代可以更快,不需要进行整体构建,每个组件可单独快速发布 PATCHMINOR 版本。

这里设定业务层面需要进行webpack构建处理,因此可以对UI框架的组件不进行构建处理,当然如果UI组件的设计需要特殊的webpack loader处理除外,否则业务层面需要做额外的webpack配置。当然不构建处理是相对于一定的使用场景的,不构建处理可能也会产生额外的一些问题。

这个UI框架的设计也会有一些缺陷:

1、没有完整引入功能(也可以进行整体构建,但是这里不推荐)

2、不提供UMD模块

3、业务层面引入繁琐(可以出额外的引入工具,简化业务中的UI组件引入)

 Vue CLI 3

构建库

为了简化UI框架的webpack配置,这里将Vue CLI 3作为开发的容器引入,借用Vue CLI 3的构建库功能( 构建web-components-组件 功能应该更合适,这里没有进行验证),几乎可以做到UI组件构建的零配置。通过审查项目的-webpack-配置 能力,可以查看Vue CLI 3为我们预先设置的通用webpack配置(几乎可以满足大部分的UI组件构建)。

插件体系

这里使用Vue CLI 3的插件和Preset功能开发了几个插件,以便于快速构建起步的UI设计框架,具体的 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": {}
 }
}

这里采用了官方设计的 @vue/cli-plugin-babel 和 @vue/cli-plugin-eslint 插件,同时自己设计了额外的三个插件来支持整个新的UI框架的起步:

@ziyi2/vue-cli-plugin-ui-base :UI框架基础插件,生成Monorepo结构的源码目录(加入Lerna管理工具),生成基础通用的webpack配置(在VUE CLI 3的webpack配置上进行再配置,VUE CLI3提供了 vue.config.js 文件供开发者进行webpack再配置),提供了几个基础UI组件的示例(仅参考价值)。

@ziyi2/vue-cli-plugin-ui-cz : UI框架的 cz 适配器插件,加入了 cz-customizable 、 commitlint 、 conventional-changelog ,用于生成Angular规范的Git提交说明、检测提交说明是否符合规范以及自动生成UI框架的升级日志等。

@ziyi2/vue-cli-plugin-ui-lint :UI框架的lint-staged 插件,代码提交前会执行Eslint校验,校验不通过则不允许提交辣鸡代码。

这三个插件已经发布在npm的仓库里,如果是已有的Vue CLI 3项目,可直接通过 vue add @ziyi2/ui-cz 等命令进行安装使用,插件源码地址ziyi2/vue-cli3-lerna-ui/plugins ,如果想学习设计Vue CLI 3插件,可参考插件开发指南,不过官方文档可能不够详细,建议参考官方设计的插件或者别人设计的优秀插件。

Lerna

Lerna是一个Monorepo管理工具,使所有的组件(npm包)设计都集成在一个git仓库里,同时可以利用yarn的workspace特性,模拟发布的组件环境,从而使组件的开发和测试变得简单,不需要多次进行组件的发布测试(这里用Lerna进行Vue CLI插件开发也非常方便)。

同时Lerna还集成了版本发布工具,可以快速的对UI框架进行版本发布。

UI组件各自修复问题可以各自快速发布 patch 版本,如果UI组件整体有非兼容性更新,可以利用Lerna进行 MAJOR 版本发布,更多关于版本发布规范可查看语义化版本。

UI框架实践

利用Vue CLI 3的远程Preset,这里将自己设计的UI框架分享给大家进行实践使用:

// 可能获取会有点慢,大家耐心等待
vue create --preset ziyi2/vue-cli3-lerna-ui my-project

如果报错 unable to get local issuer certificate ,可以设置 git config --global http.sslVerify false

如果远程确实获取preset.json失败,可以采用本地的方式,将preset.json 配置复制下来,放入新建的 preset.json 文件,执行以下命令生成UI框架:

vue create --preset preset.json my-project

执行后的生成过程如下:

浅谈Vue CLI 3结合Lerna进行UI框架设计 

脚本命令

// 启动开发服务
"serve": "vue-cli-service serve",
// 生成静态资源
"build": "vue-cli-service build",
// Eslint校验
"lint": "vue-cli-service lint",
// 安装和链接Lerna repo的依赖
"bootstrap": "lerna bootstrap",
// 更新升级日志
"cz:changelog": "conventional-changelog -p angular -i CHANGELOG.md -s && git add CHANGELOG.md",
// 构建
"lib": "lerna run lib"

如果需要利用GitHub Pages发布静态资源,可以新增命令 "deploy": "npm run build && gh-pages -d dist" ,需要安装 gh-page 依赖。

启动

进入项目目录,使用 yarn serve 命令启动开发态视图,如果是Windows系统,可能会报以下错误:

浅谈Vue CLI 3结合Lerna进行UI框架设计

在Windows下 vue create 可能会采用npm进行依赖安装,MAC OS X下无此问题,此时需要额外使用yarn进行再一次安装操作(这里使用了yarn的workspace特性,因此安装依赖建议都使用yarn进行操作):

lerna bootstrap

执行 yarn serve

浅谈Vue CLI 3结合Lerna进行UI框架设计

这里给出了国际化、选择器、警告以及按钮等UI设计示例。

构建

执行 lerna run lib 后(构建可以配合 npm run lint 校验,校验不通过则构建失败),Lerna工具会对每一个npm包执行 lib 脚本:

浅谈Vue CLI 3结合Lerna进行UI框架设计

这里分别对 utilsbtntheme 包进行了构建处理,其中 btn 采用了Vue CLI 3默认的构建库配置。

Monorepo结构

UI框架生成并构建后的Monorepo结构如下:

.
├── packages     		# workspaces
│ ├── alert    		# 警告(不构建)
│ │  ├── alert.vue  		# 组件源码
│ │  ├── index.js  		# npm包入口文件
│ │  └── package.json 		# npm包描述文件
│ ├── btn     		# 按钮
│ │  ├── lib  	  		# 目标文件
│ │  │ └── lib.common.js	# npm包入口文件
│ │  ├── btn.vue  		# 组件源码
│ │  ├── index.js  		# 构建入口文件
│ │  ├── package.json 		# npm包描述文件(需要vue cli的开发态依赖)
│ │  └── vue.config.js 		# 构建配置文件
│ ├── locale    		# 国际化
│ │  ├── lang  	  	# 语言包
│ │  │ ├── enjs 		# 英文
│ │  │ └── zh_CN.js		# 中文
│ │  ├── mixins  		# 各个组件调用的国际化API
│ │  ├── src  		# 源码
│ │  ├── index.js  		# npm包入口文件
│ │  ├── alert.vue  		# 组件源码
│ │  ├── index.js  		# npm包入口文件
│ │  └── package.json 		# npm包描述文件
│ ├── select    		# 选择器(类似于alert)
│ ├── theme    		# 样式
│ │  ├── lib  	  		# 目标文件
│ │  │ ├── alert.css 		# 警告样式
│ │  │ ├── btn.css 		# 按钮样式
│ │  │ ├── index.css 		# 总体样式
│ │  │ └── select.css		# 选择器样式
│ │  ├── src  	  		# 源文件
│ │  │ ├── utils 		# 通用方法和变量
│ │  │ ├── alert.less		# 警告样式
│ │  │ ├── btn.less 		# 按钮样式
│ │  │ ├── index.less 	# 总体样式
│ │  │ └── select.less		# 选择器样式
│ │  ├── gulpfile.js  	 	# 构建配置文件
│ │  └── package.json 		# npm包描述文件
│ └── utils    		# 工具方法
│   ├── lib  		# 目标文件(这里也可以采用lodash的方式,去掉lib文件夹这一层)
│   ├── src  		# 源文件
│   ├── babel.config.js		# 构建配置文件
│   └── package.json 		# npm包描述文件
├── public     		# 公共资源目录
├── src      		# 开发态目录
├── .browserslistrc     	# UI框架目标浏览器配置
├── .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			# 通用的组件构建配置文件

这里重点说明 src 文件, src 文件可以根据开发需要自行选定方案:

1、使用默认的CLI服务进行开发和UI框架Demo演示,这里UI框架采用原生的 .vue 文件形式进行Demo演示,如果想使用 .md 文件进行演示,可以采用vue-markdown-loader 。

2、使用Vue 驱动的静态网站生成器VuePress,这个目前不是很稳定。 发布

发布

完全可以按照语义化版本进行:

1、各自npm包可以使用 npm publish 快速发布 MINORPATCH 版本。

2、如果某个npm包有非兼容性更新,那么可以使用 lerna publish 发布 MAJOR 版本。

使用Lerna工具发布的npm包建议采用scope的形式发布,UI框架示例没有给出Demo,如果想采用scope形式发布可以查看 ziyi2/vue-cli3-lerna-ui ,需要在每个npm包的 package.json 中做额外的配置,具体可查看 vue-cli3-lerna-ui/plugins/vue-cli-plugin-ui-base/package.json 的 publishConfig 字段信息。

总结

对比Element的UI框架设计,采用Vue CLI 3 & Lerna的形式可以简化UI框架的配置,使各个UI组件的构建配置互相独立,对于简单的UI组件可以利用Vue CLI 3的默认webpack配置。同时采用Monorepo的设计结构( Why is Babel a monorepo? ),配合Lerna工具,可以使得UI框架修复问题和发布新功能的响应能力变得更快。

生成UI框架实践项目的github地址是 ziyi2/vue-cli3-lerna-ui ,包括了 preset.json 、自己设计的Vue CLI插件以及自己设计的一系列UI组件(和生成的UI框架示例稍有不同),如果觉得整体结构有不合理的或者考虑不够全面的地方,欢迎大家提issue,这样我也可以对它进行完善。如果大家感兴趣,希望大家能够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/开发/构建目标/库 - Vue CLI文档
  • Vue CLI 3/开发/webpack相关/审查项目的webpack配置 - Vue CLI文档
  • Vue CLI 3/基础/插件和Preset - Vue CLI文档
  • @vue/cli-plugin-babel - Vue CLI Plugin
  • @vue/cli-plugin-eslint - Vue CLI Plugin
  • cz - Git提交说明工具
  • cz-customizable - Cz适配器,自定义说明
  • commitlint - Cz适配器,提交说明检测
  • conventional-changelog - Cz适配器,生成日志
  • lint-staged - 代码提交审核工具
  • 插件开发指南 - Vue CLI文档
  • 语义化版本 - 版本发布规范
  • Vue CLI3/基础/CLI服务 - Vue CLI文档
  • Why is Babel a monorepo?

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
运算符&amp;&amp;的三个不同层次
Apr 07 Javascript
js单独获取一个checkbox看其是否被选中
Sep 22 Javascript
js焦点文字滚动效果代码分享
Aug 25 Javascript
js制作网站首页图片轮播特效代码
Aug 30 Javascript
javascript实现图片左右滚动效果【可自动滚动,有左右按钮】
Sep 19 Javascript
想用好React的你必须要知道的一些事情
Jul 24 Javascript
bootstrap table服务端实现分页效果
Aug 10 Javascript
Javascript 严格模式use strict详解
Sep 16 Javascript
JS设计模式之数据访问对象模式的实例讲解
Sep 30 Javascript
vue element-ui之怎么封装一个自己的组件的详解
May 20 Javascript
Vue 使用typescript如何优雅的调用swagger API
Sep 01 Javascript
详解Vite的新体验
Feb 22 Javascript
vue使用axios上传文件(FormData)的方法
Apr 14 #Javascript
详解如何理解vue的key属性
Apr 14 #Javascript
axios+Vue实现上传文件显示进度功能
Apr 14 #Javascript
Vue 使用formData方式向后台发送数据的实现
Apr 14 #Javascript
说说如何使用Vuex进行状态管理(小结)
Apr 14 #Javascript
基于Vue2-Calendar改进的日历组件(含中文使用说明)
Apr 14 #Javascript
浅谈Vue页面级缓存解决方案feb-alive(上)
Apr 14 #Javascript
You might like
编写PHP程序检查字符串中的中文字符个数的实例分享
2016/03/17 PHP
jQuery源码分析-05异步队列 Deferred 使用介绍
2011/11/14 Javascript
JavaScript学习笔记记录我的旅程
2012/05/23 Javascript
jQuery查询数据返回object和字符串影响原因是什么
2013/08/09 Javascript
javascript页面加载完执行事件代码
2014/02/11 Javascript
JavaScript实现随机替换图片的方法
2015/04/16 Javascript
javascript新闻跑马灯实例代码
2020/07/29 Javascript
jQuery Tags Input Plugin(添加/删除标签插件)详解
2016/06/20 Javascript
JS获取及验证开始结束日期的方法
2016/08/20 Javascript
利用JS实现文字的聚合动画效果
2017/01/22 Javascript
js实现百度搜索提示框
2017/02/05 Javascript
Vue项目中引入外部文件的方法(css、js、less)
2017/07/24 Javascript
JavaScript中运算符规则和隐式类型转换示例详解
2017/09/06 Javascript
基于Vue生产环境部署详解
2017/09/15 Javascript
js实现微信/QQ直接跳转到支付宝APP打开口令领红包功能
2018/01/09 Javascript
vue与vue-i18n结合实现后台数据的多语言切换方法
2018/03/08 Javascript
vue2.0基于vue-cli+element-ui制作树形treeTable
2019/04/30 Javascript
VUEX 数据持久化,刷新后重新获取的例子
2019/11/12 Javascript
JavaScript实现图片伪异步上传过程解析
2020/04/10 Javascript
深入理解 ES6中的 Reflect用法
2020/07/18 Javascript
[00:37]2016完美“圣”典风云人物:AMS宣传片
2016/12/06 DOTA
[01:25:33]完美世界DOTA2联赛PWL S3 INK ICE vs Magma 第二场 12.20
2020/12/23 DOTA
Tensorflow卷积神经网络实例进阶
2018/05/24 Python
PyQt5重写QComboBox的鼠标点击事件方法
2019/06/25 Python
Python实现非正太分布的异常值检测方式
2019/12/09 Python
解决django无法访问本地static文件(js,css,img)网页里js,cs都加载不了
2020/04/07 Python
PyCharm中如何直接使用Anaconda已安装的库
2020/05/28 Python
Python连接Mysql进行增删改查的示例代码
2020/08/03 Python
CSS3教程(8):CSS3透明度指南
2009/04/02 HTML / CSS
HTML5 placeholder属性详解
2016/06/22 HTML / CSS
amazeui页面分析之登录页面的示例代码
2020/08/25 HTML / CSS
Craghoppers德国官网:户外和旅行服装
2020/02/14 全球购物
网页设计个人找工作求职信
2013/11/28 职场文书
后备干部考察材料
2014/02/12 职场文书
MSSQL基本语法操作
2022/04/11 SQL Server
优化Mysql查询的示例
2022/04/26 MySQL