Vue CLI 原理与实践

理解 Vue CLI 的目的

Vue CLI 架构上的一些设计,值得前端开发者去了解和学习。

前端开发过程中用到的很多的工具,基本上都是基于类似的架构去设计。

通过理解 Vue CLI 的原理和实践,了解一个可扩展性、适应能力更强的工具是怎样去设计和架构的。

vue-cli 早期面貌

Vue CLI 3 之前的模块是 vue-clinpmjs 上已经被标记为弃用。

近几年开发都使用的 @vue/cli

@vue/cli也集成了早期 vue-cli 的功能,需要安装 @vue/cli-init,内部还是依赖 vue-cli@2.9.6这个模块。

npm install -g @vue/cli-init
# vue init [模板名称] [项目名称]
vue init webpack old-vue-cli

不仅仅可以创建 Vue.js 项目

早期的 vue-cli 的实现方式是基于模板创建项目。

  1. 从 github(vuejs-templates) 下载模板
  2. 询问一系列配置问题
  3. 把模板中的文件经过渲染,替换一些询问后的内容
  4. 输出到目标目录,并安装依赖

根据不同的模板可以创建不同的项目,所以早期的 vue-cli 并不仅仅给 Vue.js 项目用的,还可以创建其他各种类型的项目,只需要找到对应的模板。

只是一个「单纯」的脚手架工具

vue-cli 只是一个「单纯」的脚手架工具

脚手架只是创建项目的一个工具。

它作为前端工程化的起始点,就是用来自动创建项目所需要的一个必需的结构。

创建完结构之后,脚手架就和项目没有什么关系了。

「单纯」脚手架的不足

「单纯」脚手架 的不足就是用完即丢

创建项目的过程倒没有什么。

核心是在于创建完项目后,把所需要用到的一系列配置,例如 webpack、babel等,全部以原始文件的方式存在了项目里面。

在这里插入图片描述

如果某个模块过时了,或者模块中使用的 API 已经删除了,开发者就需要维护这些配置中细节上的变化。

维护这些不属于业务范围的代码,对开发者的维护成本都是一种挑战。

解决思路:把这些繁琐的工具和配置装进一个「黑盒子」,把它交出去,不再由自身去维护。

就是把与框架相关的公共的配置和依赖的工具,全部抽象,封装成一个个的模块,一方维护,多方共用。

Vue CLI 升级后,就是一个黑盒子,由 Vue 专门的团队负责Vue CLI 工具链的维护。

目前主流的框架都采用这种方案提供自己的开发工具链:

  • @vue/cli
  • @angular/cli
  • create-react-app

create-react-app 不足

create-react-app 相比其他两个框架不足的是,要么完全听它的,要么完全自己配置。

它安装一个 react-scripts 的模块,所有的配置都包含在其中。

例如,如果想要自定义 webpack 的配置或一些优化,就要把它的配置 eject 出来。

执行eject 命令时会警告,这是一个不可逆的操作。

执行后,会发现项目中多出了很多文件,最核心的就是一些配置和构建命令。

在这里插入图片描述

在这里插入图片描述

其实就是回到了早期 vue-cli 创建的项目的样子,所有的配置都在项目本地,需要自己维护。

对于一些想自定义一些配置,但又不想影响其他配置的开发者来说就不太友好。

相比下来,Vue CLI 几乎可以做到完全可配置,Angular CLI 也是类似。

不过 React 也可以使用 react-app-rewired 来解决。

Vue CLI 工具链

新版 Vue CLI 已经不仅仅是一个脚手架,它整体是一个工具链,里面包含了基于 Vue 开发的所有功能。

相当于提供了 Vue.js 类型的项目整体工程化的绝大部分。

Vue CLI 核心功能

Vue CLI 最核心的两个功能:

  • 脚手架工具 - CLI 交互自动化创建项目基础机构
    • 没有像早期那样在一开始就下载模板,而是集成在了模块中。
  • 开发工具 - 提供 Vue.js 开发环境的 CLI 服务
    • 本地测试、生产环境构建等

**可扩展性:**除了最核心的两功能,还有很多可扩展的空间,可以通过插件,提供更多的功能。

Vue CLI 的优势

使用 Vue CLI 相比 webpack 的优势:

  • 开箱即用
    • 默认情况下不需要任何的配置,所有配置和工具都包含在 @vue/cli-service
  • 渐进主义
    • 可以傻瓜式使用,也可以完全自定义,中间存在所有的灰度
  • 零维护成本(几乎)
    • 对开发者而言,开发所用的工具和配置都被包装到 @vue/cli-service内部,一旦这些工具和配置需要升级,只要 @vue/cli-service 有人维护,开发者只需要在项目中升级这个模块即可。
    • 开发者只需要维护自定义的部分即可

@vue/cli-service

Vue CLI 创建的项目使用的构建命令 vue-cli-service@vue/cli-service 提供的。

它内部做了两件事情:

  • 提供一个适用于大多数 Vue.js 项目的 Webpack 配置
  • 把 Webpack 包装进来

开发一个开箱即用的 webpack

开发一个开箱即用的打包工具,里面有一些默认的工具和配置。

例如,将 webpack 的配置都放在模块内部,在模块内部调用 webpack,使用模块内的配置。

创建CLI工具项目结构

在这里插入图片描述

安装 webpack

# 注意这里 webpack 要作为这个工具的生产依赖全装
npm install webpack --save

编写 webpack 配置

// /lib/index.js
// 多个项目中公用的 Webpack 配置

/** @type {import('webpack').Configuration} */
module.exports = {
  entry: './src/main.js',
  output: {
    filename: 'bundle.js'
  }
}

编写 cli 命令

在 cli 文件中使用 webpack。

webpack 除了作为命令行方式被调用,也对外暴露了API,可以以编程的方式去使用。

它作为一个模块被导入的时候,实际是一个函数。

#!/usr/bin/env node

// /bin/cli.js

const webpack = require('webpack')
// 默认会寻找 package.json 中的 main 字段指向的文件
const config = require('..')

webpack(config, (err,stats) => {
  // err 是构建过程中发生的错误
  // stats 包含打包的文件信息,其中也包含错误信息
  if (err || stats.hasErrors()) {
    return console.log('build failed')
  }
  console.log('success')
})

链接到全局

npm link

创建测试项目的结构

# 返回上一级目录
cd ../
# 创建项目目录
mkdir mypack-demo

项目只需要包含 /src/main.js 文件:

// /src/main.js
console.log('Hellor Mypack~')

测试

# 直接在mypack-demo目录下运行工具命令
mypack
# success

在这里插入图片描述

所以只要这些公共的配置能够适用于当下的项目,都可以封装到这个工具中使用。

甚至可以发布到 NPM 上,在公司其他项目中安装并使用,不再需要额外的配置。

@vue/cli-service 做的就是类似的事情。

Vue CLI 架构

基于插件的架构

开发一个适用于大多数 Vue.js 项目的通用 Webpack 配置的模块,还需要考虑对于使用它的开发者有哪些可能性。

例如使用 less 还是 sass,是否使用 postcss,使用 typescript 或者 ES6新特性。

这时就会想到把所有有可能用到的配置全部加进去。

事实上这些可能用到的配置也并没有很多。

但是如果这样做,这个模块内部就会依赖很多其他模块,它将会变得超级重

那些没有用到的模块和配置,就显得很多余

为了解决这样的问题,Vue CLI 就采用了一套基于插件的架构,使得 Vue CLI 更加灵活、更容易扩展、适应能力更强。

图解

在这里插入图片描述

  • 最外层就是 @vue/cli ,它仅仅是用来创建项目基础结构的脚手架工具。
  • 创建的项目包含:
    • Application 应用代码
    • @vue/cli-service
    • 插件
  • 另一个核心功能 - 提供 Vue.js 开发环境的 CLI 服务
    • @vue/cli-service 只包含最通用的配置
      • 例如Vue.js项目必然用到的 vue-loader
    • 个性化的配置,单独做成一个插件
      • 例如 @vue/cli-plugin-typescript 包含 TypeScript 相关配置

@vue/cli 源码

通过源码查看 Vue CLI 如何实现的这个架构(看源码建议看 tag 版本)。

Vue CLI 也是采用的 Monorepo 的方式,将相关模块放在同一个仓库中管理。

所有模块都放在 packages 目录下。

@vue/cli/bin/vue.js

首先查看 @vue/cli 模块的 vue create 命令入口文件

在这里插入图片描述

@vue/cli/lib/create.js

create 模块导出一个函数,函数返回调用 create方法的结果。

create方法的主要内容:

  • validateProjectName 校验项目名称
    • validate-npm-package-name - 用 npm 模块包的约定校验项目名称
  • 判断输出的目标目录是否存在
  • 创建 Creator 对象,接收参数:
    • 项目名称
    • 目标目录
    • 用于 CLI 交互提问的模块
      • getPromptModules() 获取所有用于 CLI 交互提问的模块
  • 调用 Creator 对象的 create 方法。

在这里插入图片描述

@vue/cli/lib/Creator.js

  • Creator 构造函数中初始化问题变量,收集 CLI 交互提问的模块的问题
  • create 方法中收集所有问题,包括注册的插件,获取答案,生成 preset
    • 之后会向preset 中添加一些插件,如 @vue/cli-service
    • preset中收集要安装的依赖
  • 开始创建项目所需的文件
    • 根据用户回答添加插件
    • 执行插件中的 Generator
    • 最终分别创建所需文件

收集问题

  1. 首先构造函数中初始化几个空数组,用于存放问题。
  2. 然后创建 PromptModuleAPI 对象,它提供一个接口 injectPrompt,用于向 injectedPrompts 添加问题。
  3. 遍历 CLI 交互提问的模块,每个模块都接收 PromptModuleAPI 对象,如果有问题,就会调用 injectPrompt 方法,添加问题。
  4. 在 create 方法中进行提问的时候,通过 resolveFinalPrompts 方法合并所有的问题,包括插件的。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

其他注册的插件,会提供一个提问的模块(如果需要提问)。

在这里插入图片描述

在这里插入图片描述

可以打印 prompts 看看。

Vue CLI 内部使用了 debug 模块。

开发应用的时候,一般会使用 debug 这样的工具做一些内部的日志输出。

每个操作系统使用会不一样,可以查看文档。

# PowerShell
$env:DEBUG='vue-cli:prompts'
vue create demo

在这里插入图片描述

收集问题的答案

create 方法中会把所有内部、外部插件等模块的问题进行合并,并提问,然后把答案收集起来,最终整理出一个 preset 对象。

create() -> promptAndResolvePreset() -> resolveFinalPrompts() -> prompts -> preset

创建项目所需的文件

查看 Vue CLI 创建的项目,会发现它会先创建一个初始的 package.json 文件,没有其他任何文件,然后立即 npm install

然后才会扩展修改 package.json,一点一点生成其他文件,例如 App.vue

开始创建的 package.json(选择了默认预设):

{
  "name": "demo",
  "version": "0.1.0",
  "private": true,
  "devDependencies": {
    "@vue/cli-plugin-babel": "~4.5.0",
    "@vue/cli-plugin-eslint": "~4.5.0",
    "@vue/cli-service": "~4.5.0"
  }
}

创建完成后的 package.json

{
  "name": "demo",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
  "dependencies": {
    "core-js": "^3.6.5",
    "vue": "^2.6.11"
  },
  "devDependencies": {
    "@vue/cli-plugin-babel": "~4.5.0",
    "@vue/cli-plugin-eslint": "~4.5.0",
    "@vue/cli-service": "~4.5.0",
    "babel-eslint": "^10.1.0",
    "eslint": "^6.7.2",
    "eslint-plugin-vue": "^6.2.2",
    "vue-template-compiler": "^2.6.11"
  },
  "eslintConfig": {
    "root": true,
    "env": {
      "node": true
    },
    "extends": [
      "plugin:vue/essential",
      "eslint:recommended"
    ],
    "parserOptions": {
      "parser": "babel-eslint"
    },
    "rules": {}
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not dead"
  ]
}

cli 本身只是用来在项目根目录中安装插件,真正负责在项目目录下生成文件的是 cli 所安装的插件。

每个插件目录下都有一个 generator,它会在 create 方法的 invoking-generators 环节执行,进行生成文件,修改 package.json 等操作。

在这里插入图片描述

cli-service 为例:

在这里插入图片描述

为什么要在插件内设计一个 generator?

使用 Babel ,就要有 babel.config 文件;使用 TypeScript 就要有 tsconfig 文件…

选择不同的功能,可能会用到不同的插件,让插件自己去决定它要生成哪些文件更合理一些。

各司其职,而不是由 cli 主观去决定需要什么文件。

所以插件内部不仅仅是做一些额外的配置,还要承担生成一部分文件的工作。

插件中的 Generator 才是真正生成文件的地方。

@vue/cli 本身只是在安装插件。

@vue/cli 过程总结

  1. 先准备命令行交互的问题
  2. 根据用户的回答决定是否使用某个插件(这里都是内置/官方插件,第三方插件需要后续自己手动加到项目中)
  3. 调用 npm / yarn 自动在项目本地安装这些插件
  4. 调用每个插件内部的 generator
  5. generator 内部创建所有文件、修改package.json

@vue/cli-service 源码

@vue/cli 负责创建项目,@vue/cli-service负责构建环节(CLI 服务)。

项目使用 vue-cli-service 命令进行运行开发环境、打包项目操作。

@vue/cli-service/vue-cli-service

内部主要导入 Service 模块,创建了一个 Service 实例,调用了它的 run 方法。

在这里插入图片描述

@vue/cli-service/lib/Service.js

获取全部插件列表

Service 构造函数中主要工作就是获取所有的插件列表 resolvePlugins

在这里插入图片描述

run()

内部主要根据命令行参数,决定执行哪个命令。

在这里插入图片描述

有的插件也会注册一些命令,例如 eslint 插件注册了 lint 命令。

在这里插入图片描述

vue-cli-service serve

serve 命令为例:

在这里插入图片描述

在这里插入图片描述

每个插件内部其实都对 webpack 进行了一些修改,例如 eslint:

在这里插入图片描述

@vue/cli-service 过程总结

  • 创建 Service 对象
  • 获取项目中所有已安装 cli 插件
  • 执行 run 方法
    • 执行 init 方法
      • 加载 .env 文件
      • vue.config.js用户项目配置文件
      • 执行所有 cli 插件,加载这个插件对应的 webpack 配置
    • 根据命令行参数执行命令

cli 插件

  • 创建项目环节
  • 构建环节
    • 注入对应功能的一些必要的 Webpack 配置
    • Plugin API

开发本地插件

参考文档:

命令插件

项目中可以开发一个本地插件,通过 package.jsonvuePlugins字段注册使用。

{
  "vuePlugins": {
    "service": [
      "clean-commander.js"
    ]
  },
  "scripts": {
    "clean": "vue-cli-service clean"
  },
}

项目根目录下创建插件文件 clean-commander.js

// /clean-commander.js
/**@type {import('@vue/cli-service').ServicePlugin} */
module.exports = (api, options) => {
  // 注册命令
  api.registerCommand('clean', (args, rawArgs) => {
    console.log('clean-commander')
  })
}

执行命令:

npm run clean
# or
# npx vue-cli-service clean

# clean-commander

继续扩展该插件,实现删除 dist 目录的功能。

为避免出现文件被占用无法删除的情况,可以安装 rimraf 模块来删除。

rimraf 模块可以在 node 环境执行强制删除命令 rm -rf

# 由于开发的是构建命令插件,所以依赖安装在开发环境
npm install rimraf --save-dev

修改 clean-commander.js

// /clean-commander.js
const rimraf = require('rimraf')

/**@type {import('@vue/cli-service').ServicePlugin} */
module.exports = (api, options) => {
  // 注册命令
  api.registerCommand('clean', (args, rawArgs) => {
    // console.log('clean-commander')
    rimraf('./dist', err => {
      if (err) return console.log('failed')
      console.log('success')
    })
  })
}

devServer 插件

Vue CLI 运行的 devServer,在路由跳转时默认不会有任何日志。

这里开发一个插件,修改 devServer 配置。

参考:Plugin API - configureDevServer

创建文件 server-log.js

// /server-log.js
/** @type {import('@vue/cli-service').ServicePlugin} */
module.exports = (api, options) => {
  // app 是 express 实例
  api.configureDevServer(app => {
    // 添加一个中间件
    app.use((req, res, next) => {
      console.log(`${req.method.toUpperCase()} ${req.url}`)
      next()
    })
  })
}

添加到 vuePlugins

{
  "vuePlugins": {
    "service": [
      "clean-commander.js",
      "server-log.js"
    ]
  }
}

运行项目 npm run serve,点击页面路由跳转,查看效果

在这里插入图片描述

Vue CLI 打包优化

  • 使用 vue-cli-service inspect >> webpack.config.js 命令将项目最终构建采用的 Webpack 配置输出
  • 根据项目实际需要,找到用不到的 多余 的配置
  • vue.config.js 中通过 api 删除或禁用这些配置

不过优化空间并不大。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值