跳到主要内容

vuecli源码解析(1)

· 阅读需 19 分钟
Oxygen

image-20220122161749258

vue-cli是 vue 官方出品的脚手架项目,用来快速搭建vue项目,一键生成项目基础代码。

一直对其内部的运行原理比较好奇,并且开发脚手架的能力也是一名前端开发人员需要掌握的技能,所以这里以v4.5.15版本为例,记录一下源码研究的过程和学习点。

入口

vue-cli采用的仍然是通过lerna管理的 monorepo,不知道未来会不会迁移到 pnpm,从package.json可以看到核心仓库就是以下三个,其中@vue就是一些核心代码,包括脚手架@vue/clivue-cli-service以及一些插件。

image-20220122103920356

vue-cli
├─ docs // 文档
├─ packages
│ ├─ @vue
│ │ ├─ babel-preset-app
│ │ ├─ cli // @vue/cli
│ │ ├─ cli-init
│ │ ├─ cli-overlay
│ │ ├─ cli-plugin-babel
│ │ ├─ cli-plugin-e2e-cypress
│ │ ├─ cli-plugin-e2e-nightwatch
│ │ ├─ cli-plugin-e2e-webdriverio
│ │ ├─ cli-plugin-eslint
│ │ ├─ cli-plugin-pwa
│ │ ├─ cli-plugin-router
│ │ ├─ cli-plugin-typescript
│ │ ├─ cli-plugin-unit-jest
│ │ ├─ cli-plugin-unit-mocha
│ │ ├─ cli-plugin-vuex
│ │ ├─ cli-service // vue-cli-service
│ │ ├─ cli-shared-utils
│ │ ├─ cli-test-utils // 一些第三库的源代码,或者一些工具函数等
│ │ ├─ cli-ui
│ │ ├─ cli-ui-addon-webpack
│ │ └─ cli-ui-addon-widgets
├─ scripts // 一些管理monorepo的程序

@vue/cli

先从脚手架启动引导程序@vue/cli开始分析,从package.json注册的bin属性找到入口程序为bin/vue.js.

{
"bin": {
"vue": "bin/vue.js"
},
}

检查Nodejs版本

首先会通过semver这个库检查使用者本地 Nodejs 的版本和在@vue/clipackage.json下要求的版本是否符合。

image-20220122105655018

这里简单了解下 Nodejs 的版本管理,Nodejs 版本有以下几种:

  • CURRENT:当前状态,也就是当前最新的 Nodejs 版本,ACTIVE版本的 Nodejs 会维护持续 6 个月时间,6 个月之后奇数版本会不再维护,而偶数版本会变成ACTIVE状态的LTS版本
  • ACTIVE:活跃状态,是正在积极维护和升级的版本,包括一些 BUG 修复,功能改进等
  • MAINTENANCE:维护状态,只修复 BUG,维护时间不定
  • EOL:End of Life,也就是终止维护的版本
  • LTS:long-term support,也就是长期维护版本,这意味着重大的 Bug 将在后续的 30 个月内持续得到不断地修复。

如下图所示,Nodejs 12 已进入维护状态,并且在 2022 年 4 月份就会终止维护,到时候Nodejs 18 也会发布,Nodejs 17 也会终止维护。 反正一个原则是始终用 LTS 版本就行了。可以使用nvm便捷的管理 Nodejs 的版本。

Releases

注册命令

然后使用commanderjs注册命令create,并且包含必填的app-name参数,可以看到create注册以后,会去加载上层lib下的create.js程序,这里还传递了项目名称和额外的 CLI 参数。

image-20220122125035080

校验项目名称

create.js内部首先会通过validate-npm-package-name这个库去校验项目名称,并且会额外处理使用.作为当前目录的情况,考虑的非常周到。

image-20220122150200380

如果通过 CLI 指定--merge,则会清空目标文件夹,否则会使用inquirer在 CLI 发起选项选择是否合并目录文件或者选择清空。这里使用了fs-extra来操作文件系统。

初始化Creator实例

在确认了目标文件夹以后,会初始化Creator的实例,并传递 CLI 参数来调用实例的create方法。

image-20220122131148494

这里传递了三个初始化参数:

  • name:项目名称
  • targetDir:创建项目的文件夹
  • getPromptModules():加载位于上层promptModules文件夹下的一些函数

image-20220122134405339

位于promptModules下的都是一些用户在创建项目时手动选择的功能项,例如vue的版本,是否使用 TS,CSS预处理器等。

../promptModules/babel.js为例,可以看到其内部是一个函数,接收一个cli对象,并且调用了cli对象暴露的injectFeatureonPromptComplete这两个方法。

image-20220122143703009

获取prompt选项

image-20220122132912054

Creator的构造函数内部会初始化一些实例属性:

  • name:项目名称

  • context:当前创建的目录

  • presetPromptpreset的选项;通过resolveIntroPrompts获取preset信息,其中包括加载用户创建的保存在本地.vuerc下的preset,以及@vue/cli内置默认的preset信息,然后组合成inquirer选项参数保存在这个属性下

image-20220122155504150

默认的preset只有两个:选择vue2vue3的版本,并且都会包含@vue/cli-plugin-babel@vue/cli-plugin-eslint两个plugin

image-20220122143427655

  • featurePrompt:如果用户选择不使用任何preset而是手动选择一些功能来创建项目,例如是否使用 TS,选择 CSS 预处理器等,那么就会从featurePrompt加载一些选项

image-20220122135142250

  • outroPrompts:主要是通过inquirer指定的一些选项,例如保存配置文件的方式、选择使用的依赖管理工具npm,yarn,pnpm等

之后会通过传入当前实例对象this来初始化一个PromptModuleAPI的实例。

image-20220122135843585

PromptModuleAPI内部会往自身的实例上挂载一个creator对象,也就是Creator的实例,这样在PromptModuleAPI内部的实例就可以通过creator对象访问Creator的实例内部的属性和方法。

image-20220122135238009

这时候初始化Creator实例时传入的promptModules内部的每个函数就会被调用,并传入PromptModuleAPI的实例作为参数,每个函数内部通过调用PromptModuleAPI内部的方法再向Creator实例内部的属性注入自身的prompt选项,不得不说逻辑有点绕,但是提高了程序的拓展性,以后@vue/cli内部想拓展一些功能,只需要在promptModules文件夹下编写程序即可。

  • injectFeature:往实例的featurePrompt.choices推入一些feature
  • injectPrompt:往实例的injectedPrompts推入一些prompt
  • injectOptionForPrompt:往injectedPrompts.choices推入一些选项
  • onPromptComplete:往实例的promptCompleteCbs推入回调函数,在prompt执行完以后执行

组合prompt并获取preset

初始化Creator实例以后会调用create方法,接收 CLI 命令行指定的所有参数,在使用vue create xx命令没有指定任何其他参数的情况下会进入promptAndResolvePreset方法。promptAndResolvePreset内部主要做了两件事:

  • 合并Creator实例的presetPromptfeaturePromptinjectedPrompts以及outroPrompts这些选项,并且默认的选项presetPrompt作为第一个,后续的prompt会通过inquirerwhen函数来判断其是否需要执行,如果用户选择不使用preset,那么才会执行后续手动选择feature的部分

image-20220122154431693

  • 当用户选择了preset以后,就会通过resolvePreset再次获取preset的信息

image-20220122154736822

注入vue-cli-service

vue-cli-service在这里也是作为一个plugin,上文获取preset信息以后,会在其默认plugins的基础上再注册vue-cli-service

image-20220122160008434

生成package.json

获取所有plugin以后,会创建package.json对象,写入plugin的版本,并生成文件

image-20220122161035813

初始化git

如果通过 CLI 指定初始化 git,这里还会调用git init命令,初始化 git 本地存储服务。

image-20220123221844974

安装依赖

@vue/cli内部定义了依赖管理基类PackageManager,内部会判断客户端使用的依赖管理工具,使用的源地址等信息,代码很多,就不一一展开了。这里安装的依赖从前面来看主要有三个:

  • @vue/cli-plugin-babel
  • @vue/cli-plugin-eslint
  • @vue/cli-service

image-20220123222843578