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.
第一次在掘金发文章,有点啰里啰嗦,大家见谅。
当前大部分UI框架设计的Webpack配置都相对复杂,例如Element、Ant Design Vue和Muse-UI等Vue组件库。例如Element,为了实现业务层面的两种引入形式(完整引入和按需引入),以及抛出一些可供业务层面通用的
utils
、i18n
等,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.json
的npm 脚本:npm run dist
与
Element
构建相关的npm脚本繁多,但是总体构建脚本是dist
:总体构建脚本包含了以下按顺序执行的脚本命令
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样式执行
npm run dist
后会在当前根目录生成新的lib
文件夹,包含以下构建内容:从Element官方文档的使用指南结合
lib
可以看出,Element
为我们提供了以下能力:utils
方法(官方文档没有说明,但事实上业务可以使用)CDN引入的umd总文件一般是全量构建的,不会有依赖问题,但是commonjs2模块的文件需要在业务层面再次使用Webpack构建。例如需要在业务层面支持国际化和提供utils的功能,那么就不能将国际化和提供utils的代码bundle到commonjs2总文件或commonjs2的所有UI组件中(每一个组件都bundle
utils
的方法或者国际化API显然是不合理的),如果需要在业务层面支持按需引入的功能,那么不建议将所有UI组件的源码bundle到commonjs2总文件中,这样便可以实现层层引用,对外抛出功能的同时在业务层面可以防止Webpack二次打包,从而导致引入两遍甚至多遍相同的代码的问题。接下来分析一下各个脚本的构建功能。
npm run build:file
build:file
脚本是自动生成一些源码文件的脚本:其中与构建相关的脚本是
node build/bin/build-entry.js
,主要用于生成Webpack构建的入口源文件src/index.js
:npm run lint
构建之前使用
lint
脚本对构建的源码文件进行eslint
校验:webpack --config build/webpack.conf.js
webpack --config build/webpack.conf.js
脚本用于构建umd总文件,执行该脚本最终会在lib
下生成index.js
文件:webpack.conf.js
配置如下:构建文件
lib/index.js
主要的功能是用于CDN形式引入项目,并且无法做到按需加载,产生的体积非常大,对于简单的应用可能不适用。webpack --config build/webpack.common.js
webpack --config build/webpack.common.js
脚本用于构建commonjs2总文件,执行该脚本最终会在lib
下生成element-ui.common.js
文件:由于该文件需要在业务层面再次使用Webpack构建,因此考量的方面较多。在分析Webpack配置之前,再次回顾一下
Element
能为我们做什么:utils
方法(commonjs2,当然官方没有对外说明)webpack --config build/webpack.common.js
脚本主要用于构建完整引入功能,同时为了可以在业务层面抛出按需引入、支持国际化等功能,构建element-ui.common.js
时需要将UI组件、支持国际化、utils方法的源代码排除。webpack.common.js
配置如下:重点需要关注一下
config.externals
属性,打印输出该变量的值:externals属性可以将一些特定的依赖从输出的bundle中排除,例如在开发态中组件之间有依赖关系,
element-ui/packages/pagination
中引入element-ui/packages/option
组件:pagecages/pagination/src/pagination.js
Webpack构建后,可以发现在
element-ui.common.js
中并没有将element-ui/packages/option
组件打包在内,而只是更改了它的引入路径element-ui/lib/option
(在实现按需引入功能时会用webpack --config build/webpack.component.js
脚本构建出该文件)。因此以上列出的
config.externals
属性的key
和value
可以排除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
排除的一些代码:webpack --config build/webpack.component.js
webpack --config build/webpack.component.js
脚本用于构建commonjs2的UI组件(提供按需引入功能),执行该脚本最终会在lib
下生成所有Element
支持的UI组件(同时这些文件也会被element-ui.common.js
总入口文件引用):查看
build/webpack.component.js
配置:构建单个组件和构建总体入口文件
element-ui.common.js
的Webpack配置类似,需要将utils
、locale
以及其他一些依赖排除。npm run build:utils
build:utils
脚本主要用于构建commonjs的utils
(提供国际化以及utils
功能):可以发现该命令并不是通过Webpack进行多文件构建,而是通过Babel直接进行转义处理(Webpack构建会产生额外的Webpack代码,并且配置繁琐,Babel转义处理构建的代码非常干净),将
src
目录下除了Webpack构建入口文件src/index.js
以外的所有其他文件进行转义处理。执行该脚本最终会在lib
下生成所有的utils
文件:生成的这些工具方法会被
lib
下的element-ui.common.js
和各个组件引用,同时在业务层面也可以引用这些工具方法。查看.babelrc
文件的配置信息:npm run build:theme
build:theme
脚本主要用于构建UI组件的css样式:这里主要关注
gulp build --gulpfile packages/theme-chalk/gulpfile.js
脚本,该脚本使用Gulp构建工具构建css样式文件,Glup构建多文件样式会非常简单。最终将当前构建的packages/theme-chalk/lib
目录下的内容拷贝到lib/theme-chalk
目录下供外部业务使用:查看
gulpfile.js
文件:Vue CLI 3 & Lerna
构建整个
Element
组件库的脚本繁多,构建的代码之间互相还有引用关系,对于开发的引用路径也会产生一定的约束。因此设计类似于Element
的UI框架相对开发者而言需要一定的开发门槛。这里基于Vue CLI 3的开发/构建目标/库能力以及Lerna工具设计了一个UI框架,这个UI框架集成了以下特点:
PATCH
或MINOR
版本。这个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配置如下:
这里采用了官方设计的@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校验,校验不通过则不允许提交辣鸡代码。Lerna
Lerna是一个Monorepo管理工具,使所有的组件(npm包)设计都集成在一个git仓库里,同时可以利用yarn的workspace特性,模拟发布的组件环境,从而使组件的开发和测试变得简单,不需要多次进行组件的发布测试(这里用Lerna进行Vue CLI插件开发也非常方便)。
同时Lerna还集成了版本发布工具,可以快速的对UI框架进行版本发布。
UI框架实践
利用Vue CLI 3的远程Preset,这里将自己设计的UI框架分享给大家进行实践使用:
如果远程确实获取preset.json失败,可以采用本地的方式,将preset.json配置复制下来,放入新建的
preset.json
文件,执行以下命令生成UI框架:执行后的生成过程如下:
脚本命令
启动
进入项目目录,使用
yarn serve
命令启动开发态视图这里给出了国际化、选择器、警告以及按钮等UI设计示例。
构建
执行
lerna run lib
后(构建可以配合npm run lint
校验,校验不通过则构建失败),Lerna工具会对每一个npm包执行lib
脚本:这里分别对
utils
、btn
、theme
包进行了构建处理,其中btn
采用了Vue CLI 3默认的构建库配置。Monorepo结构
UI框架生成并构建后的Monorepo结构如下:
这里重点说明
src
文件,src
文件可以根据开发需要自行选定方案:.vue
文件形式进行Demo演示,如果想使用.md
文件进行演示,可以采用vue-markdown-loader。发布
发布完全可以按照语义化版本进行:
npm publish
快速发布MINOR
和PATCH
版本。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一下,这里拜谢大家了!参考链接