【Vuejs】1454- 深入了解 vue-cli

发表于 2年以前  | 总阅读数:857 次

转转内部脚手架的 Webpack 部分,是基于 @vue/cli 进行二次封装的。选择二次封装而不是自己搞一套 Webpack 配置,是为了减少维护的成本。比如最近新出的 Vue2.7 版本,如果自行维护 Webpack 配置,可能还要对 vue-loader 进行一些调整。遇到重难点问题,还需要去看 @vue/cli 的源码作为参考,重新实现一遍它里面的逻辑。在看任何开源库的源码之前,必须先了解它有哪些功能,这样才能针对性地分模块阅读源码。根据 @vue/cli 的文档,它大体上分为两块功能:

  • 项目模板生成 npm 包 @vue/cli 是这个功能的入口包,提供了 vue create 命令生成项目模板。
  • 开发阶段功能 这个功能对应 npm 包 @vue/cli-service,包含了一些开发时实用的命令。
  • vue-cli-service serve 用于启动开发服务器。
  • vue-cli-service lint 命令对代码进行 lint。
  • vue-cli-service inspect 查看被所有 cli 插件修改后的 Webpack 配置。

文章将分为两个部分,第一个部分是对 @vue/cli plugin 和 preset 的介绍,第二个部分是@vue/cli 的关键部分源码实现,包括插件系统实现,Webpack 配置处理等内容。

plugin 插件

cli 插件的组成

@vue/cli 设计了插件系统,一个插件是一个 npm 包,总共由 generator (模板) 和 service (服务) 两个部分组成。一个简单的插件目录是这样的:

.
├── generator.js
├── index.js
├── package.json
├── pnpm-lock.yaml

generator.js 文件对应上文的 generator 部分,负责说明该插件希望对生成的模板做出哪些改动。index.js 文件对应上文的 service 部分,可以为 vue-cli-service 这个主命令注册新的副命令,或者对 @vue/cli 自带的一些命令做出修改。

cli 生成项目模板流程

@vue/cli 在生成项目时,会在目标目录下新建一个 package.json 文件,并在 devDependencies 中列出所有使用到的 cli 插件。此时会执行第一次 npm install ,来安装 cli 插件,@vue/cli 会调用这些插件的 generator.js,得到最终输出到目标目录的项目结构,并写入硬盘。由于 cli 插件会向 package.json 中声明一些新的依赖(比如 vue、vue-router),所以此时 @vue/cli 会执行第二次 npm install,确保这些依赖被全部安装。此时项目已经基本上创建完成,@vue/cli 调用每个插件 tempalte 部分注册的 onCreateComplete 钩子函数,执行一些项目创建完成后的逻辑。创建流程到此就结束了。

generator.js - generator 部分

接下来简单介绍 generator.js 该怎么写,它的签名如下:

/**
 * @type {import('@vue/cli').GeneratorPlugin}
 */
module.exports = function generator(api, pluginOptions, preset) {
  // 这里写插件的代码
}

generator.js 文件的逻辑很简单,只需要导出一个函数即可。@vue/cli 为 generator.js 提供了三个参数。

  • api 是 @vue/cli 内部一个名为 GeneratorAPI 的类的实例,提供了各种操作模板的方法。
  • pluginOptions @vue/cli 有一个预设的概念,预设可以指定每个 cli 插件的选项。详见下文 preset 部分。
  • preset 预设对象,详见下文 preset 部分。其中 api 这个参数最为关键。它是一个对象,常用的属性和方法有:
  • api.extendPackage()package.json 文件进行扩展和修改。
  • api.render() 将一个文件夹 render 到创建项目的目录。可以简单地理解为,将一个文件夹复制到目标文件夹中。与复制不同的是,render 的对象支持 ejs 语法,可以在里面写部分 JS 逻辑。比如希望根据 pluginOptions 选项,来 render 不同的内容。

index.js - service 部分

generator.js 类似,index.js 同样导出一个函数。

/**
 * @type {import('@vue/cli-service').ServicePlugin}
 */
module.exports = function service(api, projectOptions) {

}
  • api @vue/cli 内部名为 ServiceAPI 的类的实例。需要注意与 GeneratorAPI 进行区分。
  • projectOptions 即 vue.config.js 文件中的选项。api 参数常用的方法有:
  • api.configureWepback 使用 webpack-merge 修改 Webpack 配置
  • api.chainConfig 使用 webpack-chain 修改 Webpack 配置
  • api.registerCommandvue-cli-service 注册新的命令。

preset 预设

在使用 vue create 命令创建项目时,需要使用者做出几个选择,包含 Vue 版本、是否使用 TS 和 Babel 等选项。这些选项会被合并成一个对象,@vue/cli 将这个对象称为 preset。如果你曾经使用 @vue/cli 创建过项目,并选择将选项保存为一个预设,那么可以通过 cat ~/.vuerc 命令来找到保存的配置。这个配置一般长这样:

{
  // 是否使用淘宝源
  "useTaobaoRegistry": false,
  // 使用 cli 创建项目时,使用哪个包管理器安装依赖。
  "packageManager": "npm",
  // 被保存的 cli 预设
  "presets": {
    "vue3-preset": {
      "useConfigFiles": true,
      // 创建模板时,使用哪些 cli 插件。
      "plugins": {
        // key 为插件的名称,value 是插件的配置。
        "@vue/cli-plugin-babel": {},
        "@vue/cli-plugin-typescript": {
          "classComponent": false,
          "useTsWithBabel": true
        },
        "@vue/cli-plugin-router": {
          "historyMode": false
        },
        "@vue/cli-plugin-vuex": {},
        "@vue/cli-plugin-eslint": {
          "config": "prettier",
          "lintOn": [
            "save"
          ]
        }
      },
      // 新项目使用vue2还是vue3
      "vueVersion": "3",
      // 新项目使用什么css预处理器
      "cssPreprocessor": "less"
    }
  },
  // @vue/cli 的最新版本
  "latestVersion": "5.0.8",
  // 上次检查 @vue/cli 最新版本的时间
  "lastChecked": 1657541617415
}

如果 ~/.vuerc 文件中保存了历史预设,下次使用 vue create 时,就可以选择这些预设,跳过一堆问题的选择。如果希望对预设有更深入的定制,可以仿照 .vuerc 文件的格式,将预设的内容写在一个 json 文件中。比如这样一份文件:

{
  "useConfigFiles": true,
  "plugins": {
    // 为了自定义 @vue/cli 而编写的插件
    "@zz-common/vue-cli-plugin-zz": {
      "version": "^0.0.7"
    },
    "@vue/cli-plugin-babel": {},
    "@vue/cli-plugin-typescript": {
      "classComponent": false,
      "useTsWithBabel": true
    },
    "@vue/cli-plugin-router": {
      "historyMode": true
    },
    "@vue/cli-plugin-vuex": {},
    "@vue/cli-plugin-eslint": {
      "config": "prettier",
      "lintOn": ["save"]
    }
  },
  "vueVersion": "2",
  "cssPreprocessor": "dart-sass"
}

假设这个文件的名字是 vueCliPreset.json ,那么可以通过 vue create <project-name> --preset ./vueCliPreset.json 命令,来使用这个预设文件,创建对应的项目模板。

@vue/cli 运行流程

仓库概览

vue-cli 是一个基于 yarn 的 monorepo,核心包都位于 packages/@vue 文件夹下,包含:

  • @vue/cli 核心包
  • @vue/cli-service 核心包
  • @vue/cli-plugin-babel 插件
  • @vue/cli-plugin-typescript 插件
  • @vue/cli-plugin-vuex 插件
  • @vue/cli-plugin-router 插件 其中, @vue/cli 对外暴露了 vue 命令,并统筹各个 cli 插件的运作,可以将其称为入口包。它负责命令行交互、预设存取、插件统筹等工作。

@vue/cli 包含这些功能:

  • vue create 创建一个模板项目
  • vue invoke 调用某个 cli 插件的 generator 部分
  • vue add 添加一个 cli 插件
  • vue upgrade 升级一个 cli 插件
  • vue inspect 查看当前项目的 Webpack 配置

@vue/cli-service 包含这些功能:

  • vue-cli-service serve
  • vue-cli-service build 由于篇幅的原因,这里仅介绍 vue createvue-cli-service build/serve 这两个核心功能。

vue create

通常使用 vue create <project-name> 来创建一个新的项目。

  • Creator 类: @vue/cli 包中使用 commander 这个包,声明了 create 命令和对应的参数。项目创建由名为 Creator 的 class 负责,Creator 实例中的 create 方法,接收了 commander 传递的所有命令行选项,进行项目的创建。
  • prompt: 在上文提到,vue create 支持使用一个 json 文件作为预设。如果使用 json 文件预设,那么 create 命令的 prompt 询问阶段会被跳过,否则会问使用者一些问题,来生成 preset 对象。
  • 包管理器: @vue/cli 支持使用命令行参数指定包管理器。如果没有指定,则会依次降级到 .vuerc 文件、yarn、pnpm、npm。另外由于创建项目的过程中,与包管理器相关的命令调用非常多,且需要抹平不同包管理器之间的区别,所以源码中使用 PackageManager 这个类来封装包管理器操作。这是值得学习的一点。
  • 第一次安装依赖: 在一次项目创建的过程中,需要使用各种官方和非官方的插件,所以 cli 会首先根据 preset 对象中指定的插件名,来创建 package.json 文件,并使用 PackageManager.install 方法,来进行第一次安装。
  • 调用 cli 插件的 generator 部分: 在第一次安装完成后,所有的 cli 插件都被安装了。@vue/cli 此时会调用所有插件的 generator 部分,生成最终需要输出到硬盘的文件内容。
  • 调用 hook 函数: cli 会调用插件注册的一些函数,在项目创建完成后运行。
  • 完成创建

在这个流程中,最值得关注的是 @vue/cli 与 cli 插件的交互部分。在一个拥有插件系统的设计中,有插件容器和插件两个部分。容器需要将上下文内容和用户选项,提供给插件,让插件实现它的功能。所以于上下文和用户选项的整合尤为关键。以插件的 generator 部分为例,@vue/cli 使用单独的类 GeneratorAPI,为插件提供 render 文件夹、扩展 package.json 等各种实用的功能。 @vue/cli 使用 files 对象来记录最终输出到硬盘的文件内容,key 是文件路径,value 是文件内容。GeneratorAPI.render 作用是将插件指定的文件夹,render 到最终生成的项目中去。这个 API 实质是在读取 render 方法指定的文件夹,使用 ejs 模板引擎处理源文件内容,并将处理后的内容记录在 files 对象中。最后只需要根据 files 对象,将文件一一写入硬盘即可。 除了 files 对象,cli 中还有一个 pkg 对象来记录 package.json 中的内容,当插件调用 GeneratorAPI.extendPackage 时,实际上是在修改 pkg 对象。之所以 pkg 不在 files 对象中,是因为 package.json 与其它文件差异较大,cli 插件需要对它有更细粒度的操作。

总结下插件的交互部分,一共有三个关键点:

  • 合适的数据结构 在 @vue/cli 是两个对象,也可视情况采用 Set Map WeakMap WeakSet 等。
  • 操作数据结构的接口 这个情景下对应的是 GeneratorAPI,其中的方法都是在用不同的方式操作数据结构。事实上这里有两种选择,一种是直接将 files 对象暴露给插件,让插件自由发挥。优点是插件的上限更高,可以完成更复杂的功能;缺点是操作不便,files 对象的 value 是字符串,通常需要使用正则或者 ast 来操作。如果希望使用 ast,还需要根据字符串的内容,来选择不同的 parser。另一种是对 files 对象操作的方法进行封装,就像 GeneratorAPI 这个类一样。这样做的优点是,插件的代码量大大下降,出 bug 的几率更低,行为更加统一。@vue/cli 则同时采用了这两种做法,向插件传递 GeneratorAPI 实例的同时,也暴露了 files 对象。
  • hook 设计 理论上来说,插件容器暴露的 hook 数量越多,插件的上限就越高。@vue/cli 中插件暴露的 hook 并不多,比如 eslint 等配置文件的转换、项目创建结束后的 hook 等。原因之一是创建工程模板这个需求的复杂度不够高,也就不需要过多的 hook。良好的插件容器,需要将内部所有关键流程的都暴露给插件,比如配置的合并策略、插件的执行顺序、数据结构的便捷修改方法、原始数据结构等。

vue-cli-service build/serve

build 与 serve 的原理是类似的,它们都由 @vue/cli-service 这个包实现。@vue/cli-service 是一个官方的 @vue/cli 插件,它通过 ServiceAPI.registerCommand 注册了 servebuild 命令,处理 Webpack 相关的操作。这同时体现了插件系统的好处,可以将打包逻辑提取到单独的插件中,不必与 @vue/cli 的代码放在同一个包中。 build 的主体逻辑比较简单,加载 vue.config.js 文件,调用 cli 插件,得到修改后的 Webpack 配置,并使用 Webpack 进行打包。有一个点是,@vue/cli 支持 modern 模式的构建。当 modern 模式开启时,它会进行两次构建,第一次构建会通过 script 标签进行模块加载,第二次构建基于浏览器模块系统(type="module" VS nomodule)。

@vue/cli 的不足之处

@vue/cli 是一个优秀的脚手架,但仍有一些令人遗憾的设计存在。比如它对 JS API 的支持度较差,配置与 vue.config.js 文件强绑定。在进行 modern 模式的打包时,它的内部使用子进程的形式,递归地调用自身来完成功能。这会导致通过 JS API 传入的参数,被 vue.config.js 文件内容覆盖,造成意料外的行为。

vue.config.js 不支持 ts 写法,需要使用类型注释,来获得类型提示。如果希望使用 esm 格式,需要使用 .mjs 后缀,且通过环境变量传入 vue.config.mjs ,来覆盖默认的文件名。

另外,插件的 service 函数部分,返回的 Promise 没有被 await。基于 Promise 的 API 都不适合在 插件的 service 部分使用,比如 fs.readFile``fs.writeFile,需要使用同步版本的 API 代替。如果这个部分使用 cjs 代码编写,依赖了一个 esm 格式的库,那么这个库需要使用 import() 函数来导入。由于 top level await 的存在,import() 函数是一个异步函数,可能导致一部分 cli 插件代码,实际上被没有被执行完,但 @vue/cli 却误认为它已经执行完了,从而产生报错。并且报错的信息通常和 Webpack 相关,不容易注意到这是一个异步相关的问题。

最后

尽管文章中提到了 @vue/cli 的一些设计缺陷,但多少有些吹毛求疵的成分。如果将时间倒回到 @vue/cli 被创建的时间点,这样一个

  • 拥有插件系统
  • 采用了最佳实践的同时,仍保留高度自定义 Webpack 配置的能力的脚手架,给 Vue 开发者一个方便、快捷启动项目的途径,已经十分优秀且值得借鉴了。

本文是笔者在实现公司内部脚手架的 Webpack 部分时,看 @vue/cli 源码的一些心得。若有不足之处,欢迎在评论中指出。

本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/geBhbnrYrDNBxYDu8o5HqA

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 目录