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

当前大部分UI框架设计的Webpack配置都相对复杂,例如 ElementAnt Design VueMuse-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 的构建流程,查看 Element2.7.0 版本 package.jsonnpm 脚本

// 其中的`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-customizablecommitlintconventional-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

执行后的生成过程如下:

脚本命令

// 启动开发服务
"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系统,可能会报以下错误:

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

lerna bootstrap

执行 yarn serve

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

构建

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

这里分别对 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.jsonpublishConfig 字段信息。

总结

对比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一下,这里拜谢大家了!

参考链接

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

(0)

相关推荐

  • vue-cli脚手架build目录下utils.js工具配置文件详解

    此文章用来解释vue-cli脚手架build目录中的utils.js配置文件 1.此配置文件是vue开发环境的wepack相关配置文件,主要用来处理css-loader和vue-style-loader 2.关于注释 •当涉及到较复杂的解释我将通过标识的方式(如(1))将解释写到单独的注释模块,请自行查看 3.上代码 // 引入nodejs路径模块 var path = require('path') // 引入config目录下的index.js配置文件 var config = requir

  • 解决Vue-cli npm run build生产环境打包,本地不能打开的问题

    问题: Tip: built files are meant to be served over an HTTP server. Opening index.html over file:// won't work. 解释: npm run dev是开发环境, npm run build是生产环境, 在开发环境完成代码和测试, 之后用生产环境生成代码,执行npm run build 命令后,会生成dist目录,里边包含index.html和static文件夹. npm run build的时候,

  • 详解vue-cli脚手架build目录中的dev-server.js配置文件

    本文系统讲解vue-cli脚手架build目录中的dev-server.js配置文件 1.这个配置文件是命令npm run dev 和 npm run start 的入口配置文件,主要用于开发环境 2.由于这是一个系统的配置文件,将涉及很多的模块和插件,所以这部分内容我将分多个文章讲解,请关注我博客的其他文章 3.关于注释 •当涉及到较复杂的解释我将通过标识的方式(如(1))将解释写到单独的注释模块,请自行查看 4.上代码 // 导入check-versions.js文件,并且执行导入的函数,用

  • vue-cli结合Element-ui基于cropper.js封装vue实现图片裁剪组件功能

    前端工作中,经常需要图片裁剪的场景,cropper.js是一款优秀的前端插件,api十分丰富. 本文是在vue-cli项目下封装图片裁剪插件,效果图如下: 话不多说,看步骤吧. 第一步:准备开发环境 cropper.js是基于jquery的,所以要先安装jquery 执行命令: npm  install --save-dev jquery cropper 为webpack配置添加jquery的映射 修改webpack.base.conf.js配置,添加标红的一行 第二步:新建图片裁剪组件 ind

  • vue-cli3.0+element-ui上传组件el-upload的使用

    最近项目中涉及很多文件上传的地方,然后文件上传又有很多限制.比如文件大小限制,文件个数限制,文件类型限制,文件上传后的列表样式自定义,包括上传进度条等问题.下面是我对element-ui的上传组件的一些改造, 点击查看源码. 我是自己维护了一个列表数据,再对这个列表数据进行一些操作,没用组件自带的.先看看我的组件模版 <template> <el-upload class="upload-demo" :limit="limit" :action=&

  • Vue cli+mui 区域滚动的实例代码

    vue cli+mui配合使用达到区域滚到的效果 ,方法如下 第一步 引入mui css js @import url("/static/mui.min.css"); JS import mui from '../../../static/mui.min.js'; 第二步 写结构 mui的区域滚动结构是这样的 <div class="mui-scroll-wrapper"> <div class="mui-scroll">

  • 使用vue-cli导入Element UI组件的方法

    首先第一步,现在命令行中输入npm i element-ui,如: 接着在mian.js 中添加 import ElementUI from 'element-ui' import 'element-ui/lib/theme-chalk/index.css' Vue.use(ElementUI) 如:=> 最后在命令行输入npm run dev打开自己创建的项目就可以使用了 总结 以上所述是小编给大家介绍的使用vue-cli导入Element UI组件的方法,希望对大家有所帮助,如果大家有任何疑

  • 详解vue-cli 3.0 build包太大导致首屏过长的解决方案

    前言:最近用vue-cli 3.0 构建一个小型的工单管理系统,完工后build发现一个chunk-vendors包就达到985kb,加上其他一些资源文件,首页的下载总共大小快要2M.测试给的第一个反馈就是首屏慢慢慢慢慢! 根据首屏加载资源文件过大,进行一下优化: 1. 路由懒加载 结合Vue的异步组件再结合webpack的代码分割,我们可以轻松的实现路由懒加载. vue-cli 3.0 模式就使用了Babel,我们需要添加 syntax-dynamic-import 插件,才能使 Babel

  • 基于vue-cli npm run build之后vendor.js文件过大的解决方法

    问题 vue-cli npm run build命令默认把dependencies中的依赖统一打包,导致vendor.js文件过大,出现首屏加载过于缓慢的问题. 解决方案 像vue.axios.element-ui这些基本上不会改变的依赖我们可以把它们用cdn导入,没有必要打包到vendor.js中. 1.在项目根目录index.html使用cdn节点导入 <div id="app"></div> <!-- 先引入 Vue --> <!--开发

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

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

  • 浅谈vue首次渲染全过程

    目录 1.vue初始化 vue入口文件 完整版和运行时版本的区别 1.1.src/core/instace/index.js 1.2.src/core/index.js 1.3.src/platforms/web/runtime/index.js 1.4.src/platforms/web/entry-runtime-with-compiler.js 1.5.vue初始化总结 2.vue构造函数执行 2.1.beforeCreate钩子 2.2.created钩子 2.3.$mount函数 2.

  • 浅谈Vue单页面做SEO的四种方案

    目录 1.Nuxt 服务端渲染应用部署 (SSR服务器渲染) 优势: 不足:(开发中遇到的坑) 2.Nuxt 静态应用部署 优势: 不足: 3.预渲染prerender-spa-plugin 优势: 不足: 4.Phantomjs 针对爬虫做处理 优势: 不足: 总结 众所周知,Vue SPA单页面应用对SEO不友好,当然也有相应的解决方案,通过查找资料,大概有以下4种方法.(本人只用过第一,第三种方案) 1.Nuxt 服务端渲染应用部署 (SSR服务器渲染) 关于服务器渲染:Vue官网介绍 ,

  • 浅谈Vue父子组件和非父子组件传值问题

    本文介绍了浅谈Vue父子组件和非父子组件传值问题,分享给大家,具体如下: 1.如何创建组件 1.新建一个组件,如:在goods文件夹下新建goodsList.vue <template> <div class='tmpl'> goodsList组件 </div> </template> <style> </style> <script> export default { data() { return{} }, creat

  • 浅谈Vue.js

    vue.js的总体评价"简单却不失优雅,小巧而不乏大匠" Vue.js简介 Vue.js的作者为Evan You(尤雨溪),任职于Google Creative Lab,虽然Vue是一个个人项目,但在发展前景上个人认为绝不输于Google的AngularJs,下面我会将Vue与Angular(Angular 1.0+版本)做一些简单的比较. Vue的主要特点就和它官网(http://cn.vuejs.org/)所介绍的那样: (1) 简洁 (2) 轻量 (3)快速 (4) 数据驱动 (

  • 浅谈Vue.js 1.x 和 2.x 实例的生命周期

    在Vue.js中,在实例化Vue之前,它们都是以HTML的文本形式存在文本编辑器中.当实例化后将经历创建.编译.销毁三个主要阶段. 以下是Vue.js 1.x 实例的生命周期图示: Vue.js 1.x 的生命周期钩子 1. init 在实例开始初始化时同步调用.此时数据观测.事件和Watcher都尚未初始化. 2. created 在实例化创建之后同步调用.此时实例已经结束解析选项,已建立:数据绑定.计算属性.方法.Watcher/事件回调,但是还没有开始DOM编译,$el还不存在. 3. b

  • 浅谈vue的iview列表table render函数设置DOM属性值的方法

    如下所示: { title: '负责人社保照片', key: 'leaderIdNumber', render: (h, params) => { return h('img',{domProps:{ src:params.row.leaderIdNumber }}) } }, 找了好多,终于找到了原因,如果想要让列表返回的是一个img标签,并且设置img的src,这里不能用props,而是要用domProps就ok了. 以上这篇浅谈vue的iview列表table render函数设置DOM属

  • 浅谈vue的踩坑路

    ------>axios模拟get json一直拿不到文件,先把data放到根目录,再去dev-server.js(就是npm执行的那个文件)里面设置静态资源访问路径app.use('/data',express.static('./data')) ... app.use(hotMiddleware) // serve pure static assets var staticPath = path.posix.join(config.dev.assetsPublicPath, config.d

  • 浅谈vue中改elementUI默认样式引发的static与assets的区别

    首先从这说起 vue项目中的elementUI的默认样式怎么改 由于elementUI的样式太单调,比如这个slider滑块 elementUI中的API是没办法改变这个slider的颜色的,可是老板喜欢很黄,非要用yellow色.

  • 浅谈VUE监听窗口变化事件的问题

    Vuejs 本身就是一个 MVVM 的框架.但是在监听 window 上的 事件 时,往往会显得 力不从心. 比如 这次是 window.resize恩,我做之前也是百度了一下.看到大家伙都为这个问题而发愁. 问题: 今天我也 遇到了这样一个问题, 是关于canvas 自适应. 根据窗口的变化去变化 canvas 的宽度备注: 重要的问题说三遍 解决 框架内的bug 先说 框架 版本 版本 版本 (这里用的 Vue 2.x .ES6) 解决方案: 第一步: 先在 data 中去 定义 一个记录宽

随机推荐