浅谈 vue-cli 扩展性和插件设计

这是一个新开的'实验性'文章系列,如其名‘技术地图’,这个系列计划剖析一些前端开源项目,可能会探讨这些项目的设计和组织、整理他们使用到技术栈。 首先拿vue-cli小试牛刀,再决定后续要不要继续这个系列.javascript


我一直在思考咱们编程主要在作什么?咱们有一大部分工做就是选择各类工具/库/框架,来黏合业务. 工具和场景越匹配、原理了解越多,运用越娴熟,咱们效率可能就越高. 这种说法颇有争议,就像程序=算法+数据结构不能彻底表达现今的软件工程同样, 说咱们的工做就是堆砌工具,黏合业务, 必定程度上有自贬的意思。 但这确实是大部分程序员的真实写照。css

这系列文章其实有点相似于 github 上面的Awesome项目. 这些 Awesome 项目就是一个生态展览馆, 里面项目琳琅满目. 由于数量太多了,并且缺乏评分机制,大部分状况咱们不可能一个个去查看,很难从中选择符合需求的项目(固然你带着明确的目的,且目标范围很是小,可能比较有用)。html

是否能够尝试换个角度,选取一些有趣的开源项目,看看它是怎么应用这些工具的, 有序的罗列出来? 对于有相同场景的项目, 参考或者模仿价值可能会更大一些. 这些开源项目就是巨人,站在巨人肩膀上显然省事多了前端

只是技术栈罗列未免过于简单,笔者还但愿从这些项目中学点东西,好比他的设计和项目组织. 我会尝试简化和通俗解释里面的关键知识或亮点, 可是不求甚解。为了不陷入细节泥潭,我会尽可能使用图形化方式展现他们程序流程,避免拘泥于细节。你也能够把这些文章做为深刻阅读这些项目源码的引导vue

2019.10.23 技术地图系列失败java

我也但愿读者同我交流反馈,共同窗习和进步。node




vue-cli

说到 CLI, 不得不提Rails框架,它多是框架提供 CLI 的先祖(具体历史没有深刻考究). Rails 有一个重要的指导思想,即约定大于配置, 它为 Web 应用的大多数需求都提供了最好的解决方法,而且默认使用这些约定,而不是在长长的配置文件中设置每一个细节react

CLI 也是这个指导思想下的产物, 例如经过它提供的 CLI,能够在15 分钟内构建一个简易的博客, 能够经过 CLI 启动服务器和 REPL、生成项目脚手架、生成代码文件、路由、数据库迁移等等:webpack

Rails 的不少设计在那个年代就是就是一个明星(闪瞎 PHP、JSP、 ASP..., 想一想要配置各类服务器,各类 xml 文件),它的不少设计模式深入影响了后面的 web 框架,好比 Django、Laravel, 甚至不少模仿 Rails 命名的,如 Sails、Grails.git

Rails 对于前端开发影响也很深远,好比在 Nodejs 出来以前,Rails 社区就开始使用 coffeescript + sass预编译语言进行前端开发了, Asset Pipeline能够说是最先的'前端工程化', 配合Turbolink可让传统后端渲染页面拥有不亚于单页应用的用户体验...

当初 Rails 给我带来的各类震撼还历历在目, Ruby China 社区也是国内最好社区之一. 可是目前 Rails 的关注度不如从前, 在前端社区像 Rails 这种集大成的框架也早已不吃香(参考 Ember, 某种程度上 Angular 也算吧?).

说实在话若是一辈子只学一门语言,我会选 Ruby,若是选一个 web 框架,那就是 Rails。

推荐你们阅读The Rails Doctrine - Rails 信条 这篇文章里面有一句话笔者很是喜欢: "只要放下了自负的我的喜爱,即可以跳过无谓的世俗决定,专一在最重要的地方下更快的决定。"。为人写程序,而不是为了机器写程序.

约定大于配置能够减小咱们作决定的数量,减小无谓的争论和考虑,让咱们能够专一于更重要的事情. 这个原则能够提升开发和团队协做效率, 甚至能够凝聚一个社区.

以 Webpack 为例,恶心复杂的配置被人诟病,因此才须要 vue-cli 或者 create-react-app 这些工具.

没有用 Ruby/Rails 工做过, 默默写了个 Ruby China 小程序(微信搜Ruby CN),算是感恩回馈社区吧


Ok, 忍不住吹了一波 Rails, 回到正题.

笔者是使用 React 做为主力开发的,Vue 也是我很是喜欢的一个开源项目,不说别的,在开发者的'用户体验'方面 Vue 是我见过最好之一,主要体如今 API 的简洁性和易用性、文档还有项目构建工具(今天的主角).

vue-cli-ui 是我想写这系列文章的动机之一. 前阵子用了一下vue-cli-ui, 感受很不错, 支持可视化配置和任务运行,比我在终端下一个项目一个项目跑 task 清爽多了. 很想在咱们自家的构建工具上也搞一套,怎搞? 学习它的源码, 我以为能够做为博客记录下来.

如今前端工程师也有‘webpack 配置工程师’的戏称,这能说明 webpack 配置是费时费力的苦事(Angular 例外). 这不后来就有了parcel宣称零配置的轮子, 还有 React 社区的create-react-app, vue-cli 前期是基于模板的建立项目, 不算此列。

后来 vue-cli 汲取着前者的不少优势,把这块作大作优了(看来 vue 很擅长作这些事情). 咱们能够来对比一下这些工具:


Vue CLI create-react-app parcel
快速原型开发 支持 - 支持
全局模式 零配置原型开发就是全局的 - 支持
插件 支持 - 支持,扩展文件类型和文件输出
扩展性 强,经过插件扩展 wepack 配置 弱, 强约定, 没法配置 webpack,能够 eject, 而后手工配置;支持 babel-macro;(严格说能够经过react-app-rewired进行扩展) 中(能够配置 babel,postcss,Typescript); 提供了 Node API; 支持插件扩展文件类型
多页面 支持 - 支持
适用范围 Vue 组件的第一公民。经过扩展能够支持任意前端框架 针对 React 开发,不支持其余框架 parcel 是一个通用的打包工具,它的竞争对手是 webpack
编译速度 cache-loader,thread-loader 来加速 JS 和 TS 编译 babel-loader 开启了 cache 编译速度号称是 webpack 的两倍
可升级性 支持升级 cli-service, 插件须要单独升级, 插件须要遵循语义化版本. 太多插件存在升级风险 支持升级 react-script, 官方维护,且强约定基本能够保障向下兼容 支持升级 parcel-bundler
UI 图形化管理是 CLI 的特点之一 - -

经过上面的对比,能够看出 vue-cli 是一个扩展性很是强的构建工具,以至于它不只限于 Vue,也能够用来构建 React 甚至其余前端框架

相比而言 create-react-app 就是一个很是 Opinionated(坚持己见) 的工具,强约定. 一个典型的例子就是它不内置开启 babel 装饰器转译,CRA 团队认为已经废弃(或者不成熟)的语言特性不该该带到 CRA 中; 后面为了给‘优雅’地给 babel 扩展插件,就捣鼓出来了babel-macro, 这是一种'免配置'的 babel 插件规范.

这种强约定也是有好处的,好比不须要管理配置; 并且 CRA 团队谨慎可靠地维护着 CRA,这使得开发者能够通常无痛地升级 CRA. 若是要扩展 webpack,通常只有 eject,这就走回了手动配置 webpack 的老路, 不可取.

vue-cli 也是一个'渐进式'的 cli,vue-cli 提供了默认的 preset,但不阻止你对其进行扩展. vue-cli 的扩展接口也很是简洁(合理, 很少很多), 还有 UI 管理界面,可视化管理项目的配置和插件,用户体验很棒,计划在下一篇文章介绍 vue ui. 惟一比较不舒服的是若是滥用这种扩展性,装 N 多插件,并且插件之间还存在依赖关系时,也会成为升级维护的负担.




基本设计

注意,本文不是 vue-cli 的教程,最好的教程是官方文档.


目录结构

下面是 vue-cli 的基本目录结构. 大部分大型的前端项目都使用 lerna 实现 mono-repo 模式, 而后统一分发到 npm. 这种模式有利于项目模块组织


分离 CLI 层和 Service 层

这个设计是借鉴create-react-app的, CLI 层只是一些基础的命令通常不须要频繁升级,并且是全局安装; 而 Service 层是多变的, 做为项目的局部依赖,不该该硬编码在 CLI 里面. CLI 和 Service 的职责划分以下:


  • CLI: 用于项目建立和管理

    • 全局安装
    • vue create 建立项目脚手架. 拉取最新的 Service,并选择配置须要的插件
    • vue ui. 启动 UI 管理界面
    • 快速原型开发: vue serve | vue build, 直接伺服和编译一个 Vue 文件
    • 插件管理: vue add | vue invoke 安装插件和调用插件生成器
  • Service: 负责项目的实际构建

    • 局部安装
    • 集成 webpack 构建环境,Service 自己只有一个插件机制, 全部构建相关逻辑都由内置插件和外部插件提供
    • 内置插件(命令): serve, build, inspect


插件系统

vue-cli 提供了相似 babel、eslint 的插件机制。


插件

插件机制是 vue-cli 的核心, 用于扩展 Service. Service 的命令和 webpack 配置都由插件提供.

其实插件机制自己并无什么技术难度, 换句话说插件其实就是一个协议的设计. vue-cli 插件的协议以下:

  • 命名: @vue/cli-plugin-*vue-cli-plugin-*. package.json 中按着这个命名约定的依赖会被识别为 vue-cli 插件,另外命名约定也有利于在 github 或 npm 上筛选

  • 生命周期: 一个插件的生命周期能够分为安装阶段运行阶段. vue create命令建立项目脚手架、vue add以及vue invoke插件安装命令都属于安装阶段; 而 cli-service 命令执行时属于运行阶段.

  • 基本结构: 区分了生命周期后,插件的结构就比较清晰了:

    .
    ├── README.md
    ├── generator.js  # generator (可选)
    ├── prompts.js    # prompt 文件 (可选)
    ├── index.js      # service 插件
    └── package.json
    复制代码
    • 安装阶段:
      • prompts: 收集用户意见和配置
      • gernerator: 在安装阶段生成模板文件
    • 运行时: index.js
      • 注入 service 命令
      • 扩展和修改 webpack 配置. vue-cli 经过webpack-chainwebpack-merge来实现 webpack 可配置化

一个简单的插件结构是这样子的:


preset

这个 preset 和 babel 的 preset 概念其实是不同的:

vue-cli 的 preset 一个脚手架建立方案, 也就是说它只做用于vue create阶段。好比vue create时默认使用的就是 babel+eslint preset. preset 能够简化项目脚手架的建立。团队能够共享一个 preset 来建立脚手架

而 babel 中的 preset 是一个插件集合,他能够统一收纳和管理一组插件方案. 例如babel-preset-reactbabel-preset-env. 上文说到若是扩展性被滥用,装 N 多插件,并且插件之间还存在依赖关系时,也会成为升级维护的负担. 而 'babel 式'的 preset 可让插件更方便维护和和一键式升级

尽管目前 vue 也提供了vue upgrade对插件进行升级,这个是基于语义化版本约定的, 且当插件之间存在依赖关系时, 不排除升级存在风险. 尤为对于团队项目仍是推荐有统一地管理这些插件, 实现傻瓜化的升级。 实际上这种 'babel 式'的 preset 是能够经过 vue-plugin 实现和转发的。


配置

vue 支持在 package.json 的 vue 字段或vue.config.js中进行配置。这里能够对 Service 核心功能和插件进行配置, 也能够直接修改 webpack 配置. 另外部分构建行为是经过环境变量进行影响的,这些能够经过.env.*文件进行配置


基本流程

如今来看看一个 vue-cli 内部的基本流程, Service 的插件实现是 vue-cli 比较有意思的点. 以vue serve为例:

Service 对象是 vue-cli 的核心对象,负责管理和应用插件,全部命令和 webpack 配置都是以插件的形式存在:

首先划分为配置阶段和运行阶段。 配置阶段 vue-cli 会加载配置文件,并查找和应用全部插件。将 PluginAPI 实例和项目配置传递给插件运行时, 插件运行时经过 PluginAPI 注入命令(registerCommand)和 扩展 webpack 配置(chainWebpack, configureWebpack).

运行阶段则根据用户传入的命令名调用插件注入命令。在命令实现函数中,能够调用 resolveWebpackConfig()来生成最终的 webpack 配置。以 serve 命令为例,获取到 webpackConfig 后会建立一个 webpack 编译器,并开启 webpack-dev-server 开发服务器.


技术地图

  • 组织
  • cli 命令行相关工具
    • chalk: 命令行字体颜色样式
    • cli-highlight: 终端语法高亮输出, 相似于 Highlight.js
    • cliui: 在终端中进行多列输出
    • didyoumean: 根据单词类似度,来对用户输入纠正提示
    • semver: 提供语义化版本号相关的工具函数。 例如比较,规范化
    • commander TJ 写的命令行选项和参数解析器,支持子命令,选项校验和类型转换,帮组信息生成等等. API 简单优雅
    • minimist: 一个极简的命令行参数解析器。若是只是简单的选项解析,能够用这个库
    • inquirer 命令行询问
    • ora 命令行 spinner
    • launch-editor 打开编辑器. 经过 node 打开编辑器,前端能够 express 暴露接口调用打开
    • open 打开 URL、文件、可执行文件
    • execa 更好的 child_process,修复了原生 exec 的一些问题
    • validate-npm-package-name: 验证 npm 包名称,好比建立的项目名是否合法
    • dotenv & dotenv-expand: 从.env 文件中加载配置,环境变量
  • 网络相关
    • portfinder: 获取可用的端口
    • address: 获取当前主机的 ip,MAC 和 DNS 服务器
  • 文件处理相关
    • slash 一致化处理路径中的分隔符
    • fs-extra node fs 模块扩展
    • globby: glob 模式匹配
    • rimraf 跨平台文件删除命令
    • memfs 兼容 Node fs API 的内存文件系统
  • 数据检验
  • 调试
    • debug: 这是一个 debug 日志利器, 支持经过环境变量或动态设置来肯定是否须要输出; 支持 printf 风格格式化
  • 算法
    • hash-sum: 散列值计算
    • deepmerge 深合并
  • 其余
    • recast Javascript 语法树转换器,支持非破坏性的格式化输出. 经常使用于扩展 js 代码
    • javascript-stringify: 相似于 JSON.stringify, 将对象字符串化。
  • webpack
    • 配置定义
      • webpack-merge: 合并 webpack 配置对象
      • webpack-chain: 链式配置 webpack. 这两个库是 vue-cli 插件的重要成员
    • webpack-dev-server: webpack 开发服务器,支持代码热重载,错误信息展现,接口代理等等
    • webpack-bundle-analyzer: webpack 包分析器
  • 扩展(一些相关的技术栈)
    • http-server 快速伺服静态文件
    • plop 模板生成器
    • yeoman 项目脚手架工具
相关文章
相关标签/搜索