警告

这份文档是对应 @vue/cli。老版本的 vue-cli 文档请移步这里

Vue CLI 是一个基于 Vue.js 进行快速开发的完整系统,提供:

  • 通过 @vue/cli 搭建交互式的项目脚手架。
  • 通过 @vue/cli + @vue/cli-service-global 快速开始零配置原型开发。
  • 一个运行时依赖 (@vue/cli-service),该依赖:可升级;基于 webpack 构建,并带有合理的默认配置;可以通过项目内的配置文件进行配置;可以通过插件进行扩展。
  • 一个丰富的官方插件集合,集成了前端生态中最好的工具。
  • 一套完全图形化的创建和管理 Vue.js 项目的用户界面。

Vue CLI 致力于将 Vue 生态中的工具基础标准化。它确保了各种构建工具能够基于智能的默认配置即可平稳衔接,这样你可以专注在撰写应用上,而不必花好几天去纠结配置的问题。与此同时,它也为每个工具提供了调整配置的灵活性,无需 eject。

该系统的组件

Vue CLI 有几个独立的部分——如果你看到了我们的源代码,你会发现这个仓库里同时管理了多个单独发布的包。

CLI

CLI (@vue/cli) 是一个全局安装的 npm 包,提供了终端里的 vue 命令。它可以通过 vue create 快速创建一个新项目的脚手架,或者直接通过 vue serve 构建新想法的原型。你也可以通过 vue ui 通过一套图形化界面管理你的所有项目。我们会在接下来的指南中逐章节深入介绍。

CLI 服务

CLI 服务 (@vue/cli-service) 是一个开发环境依赖。它是一个 npm 包,局部安装在每个 @vue/cli 创建的项目中。

CLI 服务是构建于 webpack 和 webpack-dev-server 之上的。它包含了:

  • 加载其它 CLI 插件的核心服务;
  • 一个针对绝大部分应用优化过的内部的 webpack 配置;
  • 项目内部的 vue-cli-service 命令,提供 serve、build 和 inspect 命令。

如果你熟悉 create-react-app 的话,@vue/cli-service 实际上大致等价于 react-scripts,尽管功能集合不一样。

CLI 服务章节涵盖了它的具体用法。

CLI 插件

CLI 插件是向你的 Vue 项目提供可选功能的 npm 包,例如 Babel/TypeScript 转译、ESLint 集成、单元测试和 end-to-end 测试等。Vue CLI 插件的名字以 @vue/cli-plugin- (内建插件) 或 vue-cli-plugin- (社区插件) 开头,非常容易使用。

当你在项目内部运行 vue-cli-service 命令时,它会自动解析并加载 package.json 中列出的所有 CLI 插件。

插件可以作为项目创建过程的一部分,或在后期加入到项目中。它们也可以被归成一组可复用的 preset。我们会在插件和 preset 章节进行深入讨论。


关于旧版本

如果您已经安装了旧版本的vue-cli(1.x或2.x),您需要先通过npm卸载vue-cli -g或yarn global remove vue-cli卸载它。

节点版本要求

Vue CLI需要  Node.js  8.9或更高版本(推荐8.11.0+)。您可以使用  nvm  或  nvm-windows  在同一台计算机中管理多个Node版本。

可以使用以下任一命令安装这个新的包:

npm install -g @vue/cli# ORyarn global add @vue/cli

安装之后,您就可以在命令行中访问vue命令。您可以通过简单运行vue,看看是否展示出了一份所有可用命令的帮助信息,来验证它是否安装成功。

你还可以用这个命令来检查其版本是否正确:

vue --version


你可以使用 vue serve 和 vue build 命令对单个 *.vue 文件进行快速原型开发,不过这需要先额外安装一个全局的扩展:

npm install -g @vue/cli-service-global

vue serve 的缺点就是它需要安装全局依赖,这使得它在不同机器上的一致性不能得到保证。因此这只适用于快速原型开发。

vue serve

Usage: serve [options] [entry]在开发环境模式下零配置为 .js 或 .vue 文件启动一个服务器Options:  -o, --open  打开浏览器  -c, --copy  将本地 URL 复制到剪切板  -h, --help  输出用法信息

你所需要的仅仅是一个 App.vue 文件:

<template>  <h1>Hello!</h1></template>

然后在这个 App.vue 文件所在的目录下运行:

vue serve

vue serve 使用了和 vue create 创建的项目相同的默认设置 (webpack、Babel、PostCSS 和 ESLint)。它会在当前目录自动推导入口文件——入口可以是 main.js、index.js、App.vue 或 app.vue 中的一个。你也可以显式地指定入口文件:

vue serve MyComponent.vue

如果需要,你还可以提供一个 index.html、package.json、安装并使用本地依赖、甚至通过相应的配置文件配置 Babel、PostCSS 和 ESLint。

vue build

Usage: build [options] [entry]在生产环境模式下零配置构建一个 .js 或 .vue 文件Options:  -t, --target <target>  构建目标 (app | lib | wc | wc-async, 默认值:app)  -n, --name <name>      库的名字或 Web Components 组件的名字 (默认值:入口文件名)  -d, --dest <dir>       输出目录 (默认值:dist)  -h, --help             输出用法信息

你也可以使用 vue build 将目标文件构建成一个生产环境的包并用来部署:

vue build MyComponent.vue

vue build 也提供了将组件构建成为一个库或一个 Web Components 组件的能力。查阅构建目标了解更多。


vue create

运行以下命令来创建一个新项目:

vue create hello-world

警告

如果你在 Windows 上通过 minTTY 使用 Git Bash,交互提示符并不工作。你必须通过 winpty vue.cmd create hello-world 启动这个命令。不过,如果你仍想使用 vue create hello-world,则可以通过在 ~/.bashrc 文件中添加以下行来为命令添加别名。 alias vue='winpty vue.cmd' 你需要重新启动 Git Bash 终端会话以使更新后的 bashrc 文件生效。

你会被提示选取一个 preset。你可以选默认的包含了基本的 Babel + ESLint 设置的 preset,也可以选“手动选择特性”来选取需要的特性。

CLI 预览

这个默认的设置非常适合快速创建一个新项目的原型,而手动设置则提供了更多的选项,它们是面向生产的项目更加需要的。

CLI 预览

如果你决定手动选择特性,在操作提示的最后你可以选择将已选项保存为一个将来可复用的 preset。我们会在下一个章节讨论 preset 和插件。

~/.vuerc

被保存的 preset 将会存在用户的 home 目录下一个名为 .vuerc 的 JSON 文件里。如果你想要修改被保存的 preset / 选项,可以编辑这个文件。

在项目创建的过程中,你也会被提示选择喜欢的包管理器或使用淘宝 npm 镜像源以更快地安装依赖。这些选择也将会存入 ~/.vuerc。

vue create 命令有一些可选项,你可以通过运行以下命令进行探索:

vue create --help
用法:create [options] <app-name>创建一个由 `vue-cli-service` 提供支持的新项目选项:  -p, --preset <presetName>       忽略提示符并使用已保存的或远程的预设选项  -d, --default                   忽略提示符并使用默认预设选项  -i, --inlinePreset <json>       忽略提示符并使用内联的 JSON 字符串预设选项  -m, --packageManager <command>  在安装依赖时使用指定的 npm 客户端  -r, --registry <url>            在安装依赖时使用指定的 npm registry  -g, --git [message]             强制 / 跳过 git 初始化,并可选的指定初始化提交信息  -n, --no-git                    跳过 git 初始化  -f, --force                     覆写目标目录可能存在的配置  -c, --clone                     使用 git clone 获取远程预设选项  -x, --proxy                     使用指定的代理创建项目  -b, --bare                      创建项目时省略默认组件中的新手指导信息  -h, --help                      输出使用帮助信息

使用图形化界面

你也可以通过 vue ui 命令以图形化界面创建和管理项目:

vue ui

上述命令会打开一个浏览器窗口,并以图形化界面将你引导至项目创建的流程。

图形化界面预览

拉取 2.x 模板 (旧版本)

Vue CLI >= 3 和旧版使用了相同的 vue 命令,所以 Vue CLI 2 (vue-cli) 被覆盖了。如果你仍然需要使用旧版本的 vue init 功能,你可以全局安装一个桥接工具:

npm install -g @vue/cli-init# `vue init` 的运行效果将会跟 `vue-cli@2.x` 相同vue init webpack my-project


插件

Vue CLI 使用了一套基于插件的架构。如果你查阅一个新创建项目的 package.json,就会发现依赖都是以 @vue/cli-plugin- 开头的。插件可以修改 webpack 的内部配置,也可以向 vue-cli-service 注入命令。在项目创建的过程中,绝大部分列出的特性都是通过插件来实现的。

基于插件的架构使得 Vue CLI 灵活且可扩展。如果你对开发一个插件感兴趣,请翻阅插件开发指南

提示

你可以通过 vue ui 命令使用 GUI 安装和管理插件。

在现有的项目中安装插件

每个 CLI 插件都会包含一个 (用来创建文件的) 生成器和一个 (用来调整 webpack 核心配置和注入命令的) 运行时插件。当你使用 vue create 来创建一个新项目的时候,有些插件会根据你选择的特性被预安装好。如果你想在一个已经被创建好的项目中安装一个插件,可以使用 vue add 命令:

vue add eslint

提示

vue add 的设计意图是为了安装和调用 Vue CLI 插件。这不意味着替换掉普通的 npm 包。对于这些普通的 npm 包,你仍然需要选用包管理器。

警告

我们推荐在运行 vue add 之前将项目的最新状态提交,因为该命令可能调用插件的文件生成器并很有可能更改你现有的文件。

这个命令将 @vue/eslint 解析为完整的包名 @vue/cli-plugin-eslint,然后从 npm 安装它,调用它的生成器。

# 这个和之前的用法等价vue add cli-plugin-eslint

如果不带 @vue 前缀,该命令会换作解析一个 unscoped 的包。例如以下命令会安装第三方插件 vue-cli-plugin-apollo:

# 安装并调用 vue-cli-plugin-apollovue add apollo

你也可以基于一个指定的 scope 使用第三方插件。例如如果一个插件名为 @foo/vue-cli-plugin-bar,你可以这样添加它:

vue add @foo/bar

你可以向被安装的插件传递生成器选项 (这样做会跳过命令提示):

vue add eslint --config airbnb --lintOn save

如果一个插件已经被安装,你可以使用 vue invoke 命令跳过安装过程,只调用它的生成器。这个命令会接受和 vue add 相同的参数。

提示

如果出于一些原因你的插件列在了该项目之外的其它 package.json 文件里,你可以在自己项目的 package.json 里设置 vuePlugins.resolveFrom 选项指向包含其它 package.json 的文件夹。

例如,如果你有一个 .config/package.json 文件:

{  "vuePlugins": {    "resolveFrom": ".config"  }}

项目本地的插件

如果你需要在项目里直接访问插件 API 而不需要创建一个完整的插件,你可以在 package.json 文件中使用 vuePlugins.service 选项:

{  "vuePlugins": {    "service": ["my-commands.js"]  }}

每个文件都需要暴露一个函数,接受插件 API 作为第一个参数。关于插件 API 的更多信息可以查阅插件开发指南

你也可以通过 vuePlugins.ui 选项添加像 UI 插件一样工作的文件:

{  "vuePlugins": {    "ui": ["my-ui.js"]  }}

更多信息请阅读 UI 插件 API

Preset

一个 Vue CLI preset 是一个包含创建新项目所需预定义选项和插件的 JSON 对象,让用户无需在命令提示中选择它们。

在 vue create 过程中保存的 preset 会被放在你的 home 目录下的一个配置文件中 (~/.vuerc)。你可以通过直接编辑这个文件来调整、添加、删除保存好的 preset。

这里有一个 preset 的示例:

{  "useConfigFiles": true,  "cssPreprocessor": "sass",  "plugins": {    "@vue/cli-plugin-babel": {},    "@vue/cli-plugin-eslint": {      "config": "airbnb",      "lintOn": ["save", "commit"]    },    "@vue/cli-plugin-router": {},    "@vue/cli-plugin-vuex": {}  }}

Preset 的数据会被插件生成器用来生成相应的项目文件。除了上述这些字段,你也可以为集成工具添加配置:

{  "useConfigFiles": true,  "plugins": {...},  "configs": {    "vue": {...},    "postcss": {...},    "eslintConfig": {...},    "jest": {...}  }}

这些额外的配置将会根据 useConfigFiles 的值被合并到 package.json 或相应的配置文件中。例如,当 "useConfigFiles": true 的时候,configs 的值将会被合并到 vue.config.js 中。

Preset 插件的版本管理

你可以显式地指定用到的插件的版本:

{  "plugins": {    "@vue/cli-plugin-eslint": {      "version": "^3.0.0",      // ... 该插件的其它选项    }  }}

注意对于官方插件来说这不是必须的——当被忽略时,CLI 会自动使用 registry 中最新的版本。不过我们推荐为 preset 列出的所有第三方插件提供显式的版本范围。

允许插件的命令提示

每个插件在项目创建的过程中都可以注入它自己的命令提示,不过当你使用了一个 preset,这些命令提示就会被跳过,因为 Vue CLI 假设所有的插件选项都已经在 preset 中声明过了。

在有些情况下你可能希望 preset 只声明需要的插件,同时让用户通过插件注入的命令提示来保留一些灵活性。

对于这种场景你可以在插件选项中指定 "prompts": true 来允许注入命令提示:

{  "plugins": {    "@vue/cli-plugin-eslint": {      // 让用户选取他们自己的 ESLint config      "prompts": true    }  }}

远程 Preset

你可以通过发布 git repo 将一个 preset 分享给其他开发者。这个 repo 应该包含以下文件:

  • preset.json: 包含 preset 数据的主要文件(必需)。
  • generator.js: 一个可以注入或是修改项目中文件的 Generator
  • prompts.js 一个可以通过命令行对话为 generator 收集选项的 prompts 文件

发布 repo 后,你就可以在创建项目的时候通过 --preset 选项使用这个远程的 preset 了:

# 从 GitHub repo 使用 presetvue create --preset username/repo my-project

GitLab 和 BitBucket 也是支持的。如果要从私有 repo 获取,请确保使用 --clone 选项:

vue create --preset gitlab:username/repo --clone my-projectvue create --preset bitbucket:username/repo --clone my-project

加载文件系统中的 Preset

当开发一个远程 preset 的时候,你必须不厌其烦的向远程 repo 发出 push 进行反复测试。为了简化这个流程,你也可以直接在本地测试 preset。如果 --preset 选项的值是一个相对或绝对文件路径,或是以 .json 结尾,则 Vue CLI 会加载本地的 preset:

# ./my-preset 应当是一个包含 preset.json 的文件夹vue create --preset ./my-preset my-project# 或者,直接使用当前工作目录下的 json 文件:vue create --preset my-preset.json my-project


使用命令

在一个 Vue CLI 项目中,@vue/cli-service 安装了一个名为 vue-cli-service 的命令。你可以在 npm scripts 中以 vue-cli-service、或者从终端中以 ./node_modules/.bin/vue-cli-service 访问这个命令。

这是你使用默认 preset 的项目的 package.json:

{  "scripts": {    "serve": "vue-cli-service serve",    "build": "vue-cli-service build"  }}

你可以通过 npm 或 Yarn 调用这些 script:

npm run serve# ORyarn serve

如果你可以使用 npx (最新版的 npm 应该已经自带),也可以直接这样调用命令:

npx vue-cli-service serve

提示

你可以通过 vue ui 命令使用 GUI 运行更多的特性脚本。

这里有一个 GUI 的 webpack Analyzer:

UI webpack Analyzer

vue-cli-service serve

用法:vue-cli-service serve [options] [entry]选项:  --open    在服务器启动时打开浏览器  --copy    在服务器启动时将 URL 复制到剪切版  --mode    指定环境模式 (默认值:development)  --host    指定 host (默认值:0.0.0.0)  --port    指定 port (默认值:8080)  --https   使用 https (默认值:false)

vue-cli-service serve 命令会启动一个开发服务器 (基于 webpack-dev-server) 并附带开箱即用的模块热重载 (Hot-Module-Replacement)。

除了通过命令行参数,你也可以使用 vue.config.js 里的 devServer 字段配置开发服务器。

命令行参数 [entry] 将被指定为唯一入口,而非额外的追加入口。尝试使用 [entry] 覆盖 config.pages 中的 entry 将可能引发错误。

vue-cli-service build

用法:vue-cli-service build [options] [entry|pattern]选项:  --mode        指定环境模式 (默认值:production)  --dest        指定输出目录 (默认值:dist)  --modern      面向现代浏览器带自动回退地构建应用  --target      app | lib | wc | wc-async (默认值:app)  --name        库或 Web Components 模式下的名字 (默认值:package.json 中的 "name" 字段或入口文件名)  --no-clean    在构建项目之前不清除目标目录  --report      生成 report.html 以帮助分析包内容  --report-json 生成 report.json 以帮助分析包内容  --watch       监听文件变化

vue-cli-service build 会在 dist/ 目录产生一个可用于生产环境的包,带有 JS/CSS/HTML 的压缩,和为更好的缓存而做的自动的 vendor chunk splitting。它的 chunk manifest 会内联在 HTML 里。

这里还有一些有用的命令参数:

  • --modern 使用现代模式构建应用,为现代浏览器交付原生支持的 ES2015 代码,并生成一个兼容老浏览器的包用来自动回退。
  • --target 允许你将项目中的任何组件以一个库或 Web Components 组件的方式进行构建。更多细节请查阅构建目标
  • --report 和 --report-json 会根据构建统计生成报告,它会帮助你分析包中包含的模块们的大小。

vue-cli-service inspect

用法:vue-cli-service inspect [options] [...paths]选项:  --mode    指定环境模式 (默认值:development)

你可以使用 vue-cli-service inspect 来审查一个 Vue CLI 项目的 webpack config。更多细节请查阅审查 webpack config

#查看所有的可用命令

有些 CLI 插件会向 vue-cli-service 注入额外的命令。例如 @vue/cli-plugin-eslint 会注入 vue-cli-service lint 命令。你可以运行以下命令查看所有注入的命令:

npx vue-cli-service help

你也可以这样学习每个命令可用的选项:

npx vue-cli-service help [command]

缓存和并行处理

  • cache-loader 会默认为 Vue/Babel/TypeScript 编译开启。文件会缓存在 node_modules/.cache 中——如果你遇到了编译方面的问题,记得先删掉缓存目录之后再试试看。
  • thread-loader 会在多核 CPU 的机器上为 Babel/TypeScript 转译开启。

Git Hook

在安装之后,@vue/cli-service 也会安装 yorkie,它会让你在 package.json 的 gitHooks 字段中方便地指定 Git hook:

{  "gitHooks": {    "pre-commit": "lint-staged"  },   "lint-staged": {    "*.{js,vue}": [      "vue-cli-service lint",      "git add"    ]  }}

注意

yorkie fork 自 husky 且并与后者不兼容。

配置时无需 Eject

通过 vue create 创建的项目无需额外的配置就已经可以跑起来了。插件的设计也是可以相互共存的,所以绝大多数情况下,你只需要在交互式命令提示中选取需要的功能即可。

不过我们也知道满足每一个需求是不太可能的,而且一个项目的需求也会不断改变。通过 Vue CLI 创建的项目让你无需 eject 就能够配置工具的几乎每个角落。更多细节请查阅配置参考


browserslist

你会发现有 package.json 文件里的 browserslist 字段 (或一个单独的 .browserslistrc 文件),指定了项目的目标浏览器的范围。这个值会被 @babel/preset-env 和 Autoprefixer 用来确定需要转译的 JavaScript 特性和需要添加的 CSS 浏览器前缀。

现在查阅这里了解如何指定浏览器范围。

Polyfill

useBuiltIns: 'usage'

一个默认的 Vue CLI 项目会使用 @vue/babel-preset-app,它通过 @babel/preset-env 和 browserslist 配置来决定项目需要的 polyfill。

默认情况下,它会把 useBuiltIns: 'usage' 传递给 @babel/preset-env,这样它会根据源代码中出现的语言特性自动检测需要的 polyfill。这确保了最终包里 polyfill 数量的最小化。然而,这也意味着如果其中一个依赖需要特殊的 polyfill,默认情况下 Babel 无法将其检测出来。

如果有依赖需要 polyfill,你有几种选择:

  1. 如果该依赖基于一个目标环境不支持的 ES 版本撰写: 将其添加到 vue.config.js 中的 transpileDependencies 选项。这会为该依赖同时开启语法转换和根据使用情况检测 polyfill。
  2. 如果该依赖交付了 ES5 代码并显式地列出了需要的 polyfill: 你可以使用 @vue/babel-preset-app 的 polyfills 选项预包含所需要的 polyfill。注意 es6.promise 将被默认包含,因为现在的库依赖 Promise 是非常普遍的。// babel.config.jsmodule.exports = { presets: [ ['@vue/app', { polyfills: [ 'es6.promise', 'es6.symbol' ] }] ]}提示我们推荐以这种方式添加 polyfill 而不是在源代码中直接导入它们,因为如果这里列出的 polyfill 在 browserslist 的目标中不需要,则它会被自动排除。
  3. 如果该依赖交付 ES5 代码,但使用了 ES6+ 特性且没有显式地列出需要的 polyfill (例如 Vuetify):请使用 useBuiltIns: 'entry' 然后在入口文件添加 import 'core-js/stable'; import 'regenerator-runtime/runtime';。这会根据 browserslist 目标导入所有 polyfill,这样你就不用再担心依赖的 polyfill 问题了,但是因为包含了一些没有用到的 polyfill 所以最终的包大小可能会增加。

更多细节可查阅 @babel-preset/env 文档

构建库或是 Web Component 时的 Polyfills

当使用 Vue CLI 来构建一个库或是 Web Component 时,推荐给 @vue/babel-preset-app 传入 useBuiltIns: false 选项。这能够确保你的库或是组件不包含不必要的 polyfills。通常来说,打包 polyfills 应当是最终使用你的库的应用的责任。

现代模式

有了 Babel 我们可以兼顾所有最新的 ES2015+ 语言特性,但也意味着我们需要交付转译和 polyfill 后的包以支持旧浏览器。这些转译后的包通常都比原生的 ES2015+ 代码会更冗长,运行更慢。现如今绝大多数现代浏览器都已经支持了原生的 ES2015,所以因为要支持更老的浏览器而为它们交付笨重的代码是一种浪费。

Vue CLI 提供了一个“现代模式”帮你解决这个问题。以如下命令为生产环境构建:

vue-cli-service build --modern

Vue CLI 会产生两个应用的版本:一个现代版的包,面向支持 ES modules 的现代浏览器,另一个旧版的包,面向不支持的旧浏览器。

最酷的是这里没有特殊的部署要求。其生成的 HTML 文件会自动使用 Phillip Walton 精彩的博文中讨论到的技术:

  • 现代版的包会通过 <script type="module"> 在被支持的浏览器中加载;它们还会使用 <link rel="modulepreload"> 进行预加载。
  • 旧版的包会通过 <script nomodule> 加载,并会被支持 ES modules 的浏览器忽略。
  • 一个针对 Safari 10 中 <script nomodule> 的修复会被自动注入。

对于一个 Hello World 应用来说,现代版的包已经小了 16%。在生产环境下,现代版的包通常都会表现出显著的解析速度和运算速度,从而改善应用的加载性能。

提示

<script type="module"> 需要配合始终开启的 CORS 进行加载。这意味着你的服务器必须返回诸如 Access-Control-Allow-Origin: * 的有效的 CORS 头。如果你想要通过认证来获取脚本,可使将 crossorigin 选项设置为 use-credentials。

同时,现代浏览器使用一段内联脚本来避免 Safari 10 重复加载脚本包,所以如果你在使用一套严格的 CSP,你需要这样显性地允许内联脚本:

Content-Security-Policy: script-src 'self' 'sha256-4RS22DYeB7U14dra4KcQYxmwt5HkOInieXK1NUMB


HTML

Index 文件

public/index.html 文件是一个会被 html-webpack-plugin 处理的模板。在构建过程中,资源链接会被自动注入。另外,Vue CLI 也会自动注入 resource hint (preload/prefetch、manifest 和图标链接 (当用到 PWA 插件时) 以及构建过程中处理的 JavaScript 和 CSS 文件的资源链接。

插值

因为 index 文件被用作模板,所以你可以使用 lodash template 语法插入内容:

  • <%= VALUE %> 用来做不转义插值;
  • <%- VALUE %> 用来做 HTML 转义插值;
  • <% expression %> 用来描述 JavaScript 流程控制。

除了被 html-webpack-plugin 暴露的默认值之外,所有客户端环境变量也可以直接使用。例如,BASE_URL 的用法:

<link rel="icon" href="<%= BASE_URL %>favicon.ico">

更多内容可以查阅:

Preload

<link rel="preload"> 是一种 resource hint,用来指定页面加载后很快会被用到的资源,所以在页面加载的过程中,我们希望在浏览器开始主体渲染之前尽早 preload。

默认情况下,一个 Vue CLI 应用会为所有初始化渲染需要的文件自动生成 preload 提示。

这些提示会被 @vue/preload-webpack-plugin 注入,并且可以通过 chainWebpack 的 config.plugin('preload') 进行修改和删除。

Prefetch

<link rel="prefetch"> 是一种 resource hint,用来告诉浏览器在页面加载完成后,利用空闲时间提前获取用户未来可能会访问的内容。

默认情况下,一个 Vue CLI 应用会为所有作为 async chunk 生成的 JavaScript 文件 (通过动态 import() 按需 code splitting 的产物) 自动生成 prefetch 提示。

这些提示会被 @vue/preload-webpack-plugin 注入,并且可以通过 chainWebpack 的 config.plugin('prefetch') 进行修改和删除。

示例:

// vue.config.jsmodule.exports = {  chainWebpack: config => {    // 移除 prefetch 插件    config.plugins.delete('prefetch')    // 或者    // 修改它的选项:    config.plugin('prefetch').tap(options => {      options[0].fileBlacklist = options[0].fileBlacklist || []      options[0].fileBlacklist.push(/myasyncRoute(.)+?.js$/)      return options    })  }}

当 prefetch 插件被禁用时,你可以通过 webpack 的内联注释手动选定要提前获取的代码区块:

import(/* webpackPrefetch: true */ './someAsyncComponent.vue')

webpack 的运行时会在父级区块被加载之后注入 prefetch 链接。

提示

Prefetch 链接将会消耗带宽。如果你的应用很大且有很多 async chunk,而用户主要使用的是对带宽较敏感的移动端,那么你可能需要关掉 prefetch 链接并手动选择要提前获取的代码区块。

不生成 index

当基于已有的后端使用 Vue CLI 时,你可能不需要生成 index.html,这样生成的资源可以用于一个服务端渲染的页面。这时可以向 vue.config.js 加入下列代码:

// vue.config.jsmodule.exports = {  // 去掉文件名中的 hash  filenameHashing: false,  // 删除 HTML 相关的 webpack 插件  chainWebpack: config => {    config.plugins.delete('html')    config.plugins.delete('preload')    config.plugins.delete('prefetch')  }}

然而这样做并不是很推荐,因为:

  • 硬编码的文件名不利于实现高效率的缓存控制。
  • 硬编码的文件名也无法很好的进行 code-splitting (代码分段),因为无法用变化的文件名生成额外的 JavaScript 文件。
  • 硬编码的文件名无法在现代模式下工作。

你应该考虑换用 indexPath 选项将生成的 HTML 用作一个服务端框架的视图模板。

构建一个多页应用

不是每个应用都需要是一个单页应用。Vue CLI 支持使用 vue.config.js 中的 pages 选项构建一个多页面的应用。构建好的应用将会在不同的入口之间高效共享通用的 chunk 以获得最佳的加载性能。

处理静态资源

静态资源可以通过两种方式进行处理:

  • 在 JavaScript 被导入或在 template/CSS 中通过相对路径被引用。这类引用会被 webpack 处理。
  • 放置在 public 目录下或通过绝对路径被引用。这类资源将会直接被拷贝,而不会经过 webpack 的处理。

从相对路径导入

当你在 JavaScript、CSS 或 *.vue 文件中使用相对路径 (必须以 . 开头) 引用一个静态资源时,该资源将会被包含进入 webpack 的依赖图中。在其编译过程中,所有诸如 <img src="...">、background: url(...) 和 CSS @import 的资源 URL 都会被解析为一个模块依赖。

例如,url(./image.png) 会被翻译为 require('./image.png'),而:

<img src="./image.png">

将会被编译到:

h('img', { attrs: { src: require('./image.png') }})

在其内部,我们通过 file-loader 用版本哈希值和正确的公共基础路径来决定最终的文件路径,再用 url-loader 将小于 4kb 的资源内联,以减少 HTTP 请求的数量。

你可以通过 chainWebpack 调整内联文件的大小限制。例如,下列代码会将其限制设置为 10kb:

// vue.config.jsmodule.exports = {  chainWebpack: config => {    config.module      .rule('images')        .use('url-loader')          .loader('url-loader')          .tap(options => Object.assign(options, { limit: 10240 }))  }}

URL 转换规则

  • 如果 URL 是一个绝对路径 (例如 /images/foo.png),它将会被保留不变。
  • 如果 URL 以 . 开头,它会作为一个相对模块请求被解释且基于你的文件系统中的目录结构进行解析。
  • 如果 URL 以 ~ 开头,其后的任何内容都会作为一个模块请求被解析。这意味着你甚至可以引用 Node 模块中的资源:<img src="~some-npm-package/foo.png">
  • 如果 URL 以 @ 开头,它也会作为一个模块请求被解析。它的用处在于 Vue CLI 默认会设置一个指向 <projectRoot>/src 的别名 @。(仅作用于模版中)

public 文件夹

任何放置在 public 文件夹的静态资源都会被简单的复制,而不经过 webpack。你需要通过绝对路径来引用它们。

注意我们推荐将资源作为你的模块依赖图的一部分导入,这样它们会通过 webpack 的处理并获得如下好处:

  • 脚本和样式表会被压缩且打包在一起,从而避免额外的网络请求。
  • 文件丢失会直接在编译时报错,而不是到了用户端才产生 404 错误。
  • 最终生成的文件名包含了内容哈希,因此你不必担心浏览器会缓存它们的老版本。

public 目录提供的是一个应急手段,当你通过绝对路径引用它时,留意应用将会部署到哪里。如果你的应用没有部署在域名的根部,那么你需要为你的 URL 配置 publicPath 前缀:

  • 在 public/index.html 或其它通过 html-webpack-plugin 用作模板的 HTML 文件中,你需要通过 <%= BASE_URL %> 设置链接前缀:<link rel="icon" href="<%= BASE_URL %>favicon.ico">
  • 在模板中,你首先需要向你的组件传入基础 URL:data () { return { publicPath: process.env.BASE_URL }}然后:<img :src="`${publicPath}my-image.png`">

何时使用 public 文件夹

  • 你需要在构建输出中指定一个文件的名字。
  • 你有上千个图片,需要动态引用它们的路径。
  • 有些库可能和 webpack 不兼容,这时你除了将其用一个独立的 <script> 标签引入没有别的选择。


Vue CLI 项目天生支持 PostCSSCSS Modules 和包含 SassLessStylus 在内的预处理器。

引用静态资源

所有编译后的 CSS 都会通过 css-loader 来解析其中的 url() 引用,并将这些引用作为模块请求来处理。这意味着你可以根据本地的文件结构用相对路径来引用静态资源。另外要注意的是如果你想要引用一个 npm 依赖中的文件,或是想要用 webpack alias,则需要在路径前加上 ~ 的前缀来避免歧义。更多细节请参考处理静态资源

预处理器

你可以在创建项目的时候选择预处理器 (Sass/Less/Stylus)。如果当时没有选好,内置的 webpack 仍然会被预配置为可以完成所有的处理。你也可以手动安装相应的 webpack loader:

# Sassnpm install -D sass-loader node-sass# Lessnpm install -D less-loader less# Stylusnpm install -D stylus-loader stylus

然后你就可以导入相应的文件类型,或在 *.vue 文件中这样来使用:

<style lang="scss">$color: red;</style>

自动化导入

如果你想自动化导入文件 (用于颜色、变量、mixin……),你可以使用 style-resources-loader。这里有一个关于 Stylus 的在每个单文件组件和 Stylus 文件中导入 ./src/styles/imports.styl 的例子:

// vue.config.jsconst path = require('path')module.exports = {  chainWebpack: config => {    const types = ['vue-modules', 'vue', 'normal-modules', 'normal']    types.forEach(type => addStyleResource(config.module.rule('stylus').oneOf(type)))  },}function addStyleResource (rule) {  rule.use('style-resource')    .loader('style-resources-loader')    .options({      patterns: [        path.resolve(__dirname, './src/styles/imports.styl'),      ],    })}

你也可以选择使用 vue-cli-plugin-style-resources-loader

PostCSS

Vue CLI 内部使用了 PostCSS。

你可以通过 .postcssrc 或任何 postcss-load-config 支持的配置源来配置 PostCSS。也可以通过 vue.config.js 中的 css.loaderOptions.postcss 配置 postcss-loader

我们默认开启了 autoprefixer。如果要配置目标浏览器,可使用 package.json 的 browserslist 字段。

关于 CSS 中浏览器前缀规则的注意事项

在生产环境构建中,Vue CLI 会优化 CSS 并基于目标浏览器抛弃不必要的浏览器前缀规则。因为默认开启了 autoprefixer,你只使用无前缀的 CSS 规则即可。

CSS Modules

你可以通过 <style module> 以开箱即用的方式在 *.vue 文件中使用 CSS Modules

如果想在 JavaScript 中作为 CSS Modules 导入 CSS 或其它预处理文件,该文件应该以 .module.(css|less|sass|scss|styl) 结尾:

import styles from './foo.module.css'// 所有支持的预处理器都一样工作import sassStyles from './foo.module.scss'

如果你想去掉文件名中的 .module,可以设置 vue.config.js 中的 css.requireModuleExtension 为 false:

// vue.config.jsmodule.exports = {  css: {    requireModuleExtension: false  }}

如果你希望自定义生成的 CSS Modules 模块的类名,可以通过 vue.config.js 中的 css.loaderOptions.css 选项来实现。所有的 css-loader 选项在这里都是支持的,例如 localIdentName 和 camelCase:

// vue.config.jsmodule.exports = {  css: {    loaderOptions: {      css: {        // 注意:以下配置在 Vue CLI v4 与 v3 之间存在差异。        // Vue CLI v3 用户可参考 css-loader v1 文档        // https://github.com/webpack-contrib/css-loader/tree/v1.0.1        modules: {          localIdentName: '[name]-[hash]'        },        localsConvention: 'camelCaseOnly'      }    }  }}

向预处理器 Loader 传递选项

有的时候你想要向 webpack 的预处理器 loader 传递选项。你可以使用 vue.config.js 中的 css.loaderOptions 选项。比如你可以这样向所有 Sass/Less 样式传入共享的全局变量:

// vue.config.jsmodule.exports = {  css: {    loaderOptions: {      // 给 sass-loader 传递选项      sass: {        // @/ 是 src/ 的别名        // 所以这里假设你有 `src/variables.sass` 这个文件        // 注意:在 sass-loader v7 中,这个选项名是 "data"        prependData: `@import "~@/variables.sass"`      },      // 默认情况下 `sass` 选项会同时对 `sass` 和 `scss` 语法同时生效      // 因为 `scss` 语法在内部也是由 sass-loader 处理的      // 但是在配置 `data` 选项的时候      // `scss` 语法会要求语句结尾必须有分号,`sass` 则要求必须没有分号      // 在这种情况下,我们可以使用 `scss` 选项,对 `scss` 语法进行单独配置      scss: {        prependData: `@import "~@/variables.scss";`      },      // 给 less-loader 传递 Less.js 相关选项      less:{        // http://lesscss.org/usage/#less-options-strict-units `Global Variables`        // `primary` is global variables fields name        globalVars: {          primary: '#fff'        }      }    }  }}

Loader 可以通过 loaderOptions 配置,包括:

提示

这样做比使用 chainWebpack 手动指定 loader 更推荐,因为这些选项需要应用在使用了相应 loader 的多个地方。


简单的配置方式

调整 webpack 配置最简单的方式就是在 vue.config.js 中的 configureWebpack 选项提供一个对象:

// vue.config.jsmodule.exports = {  configureWebpack: {    plugins: [      new MyAwesomeWebpackPlugin()    ]  }}

该对象将会被 webpack-merge 合并入最终的 webpack 配置。

警告

有些 webpack 选项是基于 vue.config.js 中的值设置的,所以不能直接修改。例如你应该修改 vue.config.js 中的 outputDir 选项而不是修改 output.path;你应该修改 vue.config.js 中的 publicPath 选项而不是修改 output.publicPath。这样做是因为 vue.config.js 中的值会被用在配置里的多个地方,以确保所有的部分都能正常工作在一起。

如果你需要基于环境有条件地配置行为,或者想要直接修改配置,那就换成一个函数 (该函数会在环境变量被设置之后懒执行)。该方法的第一个参数会收到已经解析好的配置。在函数内,你可以直接修改配置,或者返回一个将会被合并的对象:

// vue.config.jsmodule.exports = {  configureWebpack: config => {    if (process.env.NODE_ENV === 'production') {      // 为生产环境修改配置...    } else {      // 为开发环境修改配置...    }  }}

链式操作 (高级)

Vue CLI 内部的 webpack 配置是通过 webpack-chain 维护的。这个库提供了一个 webpack 原始配置的上层抽象,使其可以定义具名的 loader 规则和具名插件,并有机会在后期进入这些规则并对它们的选项进行修改。

它允许我们更细粒度的控制其内部配置。接下来有一些常见的在 vue.config.js 中的 chainWebpack 修改的例子。

提示

当你打算链式访问特定的 loader 时,vue inspect 会非常有帮助。

修改 Loader 选项

// vue.config.jsmodule.exports = {  chainWebpack: config => {    config.module      .rule('vue')      .use('vue-loader')        .loader('vue-loader')        .tap(options => {          // 修改它的选项...          return options        })  }}

提示

对于 CSS 相关 loader 来说,我们推荐使用 css.loaderOptions 而不是直接链式指定 loader。这是因为每种 CSS 文件类型都有多个规则,而 css.loaderOptions 可以确保你通过一个地方影响所有的规则。

添加一个新的 Loader

// vue.config.jsmodule.exports = {  chainWebpack: config => {    // GraphQL Loader    config.module      .rule('graphql')      .test(/.graphql$/)      .use('graphql-tag/loader')        .loader('graphql-tag/loader')        .end()      // 你还可以再添加一个 loader      .use('other-loader')        .loader('other-loader')        .end()  }}

替换一个规则里的 Loader

如果你想要替换一个已有的基础 loader,例如为内联的 SVG 文件使用 vue-svg-loader 而不是加载这个文件:

// vue.config.jsmodule.exports = {  chainWebpack: config => {    const svgRule = config.module.rule('svg')    // 清除已有的所有 loader。    // 如果你不这样做,接下来的 loader 会附加在该规则现有的 loader 之后。    svgRule.uses.clear()    // 添加要替换的 loader    svgRule      .use('vue-svg-loader')        .loader('vue-svg-loader')  }}

修改插件选项

// vue.config.jsmodule.exports = {  chainWebpack: config => {    config      .plugin('html')      .tap(args => {        return [/* 传递给 html-webpack-plugin's 构造函数的新参数 */]      })  }}

你需要熟悉 webpack-chain 的 API 并阅读一些源码以便了解如何最大程度利用好这个选项,但是比起直接修改 webpack 配置,它的表达能力更强,也更为安全。

比方说你想要将 index.html 默认的路径从 /Users/username/proj/public/index.html 改为 /Users/username/proj/app/templates/index.html。通过参考 html-webpack-plugin 你能看到一个可以传入的选项列表。我们可以在下列配置中传入一个新的模板路径来改变它:

// vue.config.jsmodule.exports = {  chainWebpack: config => {    config      .plugin('html')      .tap(args => {        args[0].template = '/Users/username/proj/app/templates/index.html'        return args      })  }}

你可以通过接下来要讨论的工具 vue inspect 来确认变更。

审查项目的 webpack 配置

因为 @vue/cli-service 对 webpack 配置进行了抽象,所以理解配置中包含的东西会比较困难,尤其是当你打算自行对其调整的时候。

vue-cli-service 暴露了 inspect 命令用于审查解析好的 webpack 配置。那个全局的 vue 可执行程序同样提供了 inspect 命令,这个命令只是简单的把 vue-cli-service inspect 代理到了你的项目中。

该命令会将解析出来的 webpack 配置、包括链式访问规则和插件的提示打印到 stdout。

你可以将其输出重定向到一个文件以便进行查阅:

vue inspect > output.js

注意它输出的并不是一个有效的 webpack 配置文件,而是一个用于审查的被序列化的格式。

你也可以通过指定一个路径来审查配置的一小部分:

# 只审查第一条规则vue inspect module.rules.0

或者指向一个规则或插件的名字:

vue inspect --rule vuevue inspect --plugin html

最后,你可以列出所有规则和插件的名字:

vue inspect --rulesvue inspect --plugins

以一个文件的方式使用解析好的配置

有些外部工具可能需要通过一个文件访问解析好的 webpack 配置,比如那些需要提供 webpack 配置路径的 IDE 或 CLI。在这种情况下你可以使用如下路径:

<projectRoot>/node_modules/@vue/cli-service/webpack.config.js

该文件会动态解析并输出 vue-cli-service 命令中使用的相同的 webpack 配置,包括那些来自插件甚至是你自定义的配置。


你可以替换你的项目根目录中的下列文件来指定环境变量:

.env                # 在所有的环境中被载入.env.local          # 在所有的环境中被载入,但会被 git 忽略.env.[mode]         # 只在指定的模式中被载入.env.[mode].local   # 只在指定的模式中被载入,但会被 git 忽略

一个环境文件只包含环境变量的“键=值”对:

FOO=barVUE_APP_SECRET=secret

被载入的变量将会对 vue-cli-service 的所有命令、插件和依赖可用。

环境加载属性

为一个特定模式准备的环境文件的 (例如 .env.production) 将会比一般的环境文件 (例如 .env) 拥有更高的优先级。

此外,Vue CLI 启动时已经存在的环境变量拥有最高优先级,并不会被 .env 文件覆写。

NODE_ENV

如果在环境中有默认的 NODE_ENV,你应该移除它或在运行 vue-cli-service 命令的时候明确地设置 NODE_ENV。

模式

模式是 Vue CLI 项目中一个重要的概念。默认情况下,一个 Vue CLI 项目有三个模式:

  • development 模式用于 vue-cli-service serve
  • production 模式用于 vue-cli-service build 和 vue-cli-service test:e2e
  • test 模式用于 vue-cli-service test:unit

注意模式不同于 NODE_ENV,一个模式可以包含多个环境变量。也就是说,每个模式都会将 NODE_ENV 的值设置为模式的名称——比如在 development 模式下 NODE_ENV 的值会被设置为 "development"。

你可以通过为 .env 文件增加后缀来设置某个模式下特有的环境变量。比如,如果你在项目根目录创建一个名为 .env.development 的文件,那么在这个文件里声明过的变量就只会在 development 模式下被载入。

你可以通过传递 --mode 选项参数为命令行覆写默认的模式。例如,如果你想要在构建命令中使用开发环境变量,请在你的 package.json 脚本中加入:

"dev-build": "vue-cli-service build --mode development",

示例:Staging 模式

假设我们有一个应用包含以下 .env 文件:

VUE_APP_TITLE=My App

和 .env.staging 文件:

NODE_ENV=productionVUE_APP_TITLE=My App (staging)
  • vue-cli-service build 会加载可能存在的 .env、.env.production 和 .env.production.local 文件然后构建出生产环境应用;
  • vue-cli-service build --mode staging 会在 staging 模式下加载可能存在的 .env、.env.staging 和 .env.staging.local 文件然后构建出生产环境应用。

这两种情况下,根据 NODE_ENV,构建出的应用都是生产环境应用,但是在 staging 版本中,process.env.VUE_APP_TITLE 被覆写成了另一个值。

在客户端侧代码中使用环境变量

只有以 VUE_APP_ 开头的变量会被 webpack.DefinePlugin 静态嵌入到客户端侧的包中。你可以在应用的代码中这样访问它们:

console.log(process.env.VUE_APP_SECRET)

在构建过程中,process.env.VUE_APP_SECRET 将会被相应的值所取代。在 VUE_APP_SECRET=secret 的情况下,它会被替换为 "secret"。

除了 VUE_APP_* 变量之外,在你的应用代码中始终可用的还有两个特殊的变量:

  • NODE_ENV - 会是 "development"、"production" 或 "test" 中的一个。具体的值取决于应用运行的模式
  • BASE_URL - 会和 vue.config.js 中的 publicPath 选项相符,即你的应用会部署到的基础路径。

所有解析出来的环境变量都可以在 public/index.html 中以 HTML 插值中介绍的方式使用。

提示

你可以在 vue.config.js 文件中计算环境变量。它们仍然需要以 VUE_APP_ 前缀开头。这可以用于版本信息 process.env.VUE_APP_VERSION = require('./package.json').version。

只在本地有效的变量

有的时候你可能有一些不应该提交到代码仓库中的变量,尤其是当你的项目托管在公共仓库时。这种情况下你应该使用一个 .env.local 文件取而代之。本地环境文件默认会被忽略,且出现在 .gitignore 中。

.local 也可以加在指定模式的环境文件上,比如 .env.development.local 将会在 development 模式下被载入,且被 git 忽略。


当你运行 vue-cli-service build 时,你可以通过 --target 选项指定不同的构建目标。它允许你将相同的源代码根据不同的用例生成不同的构建。

应用

应用模式是默认的模式。在这个模式中:

  • index.html 会带有注入的资源和 resource hint
  • 第三方库会被分到一个独立包以便更好的缓存
  • 小于 4kb 的静态资源会被内联在 JavaScript 中
  • public 中的静态资源会被复制到输出目录中

关于 IE 兼容性的提醒

在库模式中,项目的 publicPath 是根据主文件的加载路径动态设置的(用以支持动态的资源加载能力)。但是这个功能用到了 document.currentScript,而 IE 浏览器并不支持这一特性。所以如果网站需要支持 IE 的话,建议使用库之前先在页面上引入 current-script-polyfill

注意对 Vue 的依赖

在库模式中,Vue 是外置的。这意味着包中不会有 Vue,即便你在代码中导入了 Vue。如果这个库会通过一个打包器使用,它将尝试通过打包器以依赖的方式加载 Vue;否则就会回退到一个全局的 Vue 变量。

要避免此行为,可以在build命令中添加--inline-vue标志。

vue-cli-service build --target lib --inline-vue

你可以通过下面的命令将一个单独的入口构建为一个库:

vue-cli-service build --target lib --name myLib [entry]
File                     Size                     Gzippeddist/myLib.umd.min.js    13.28 kb                 8.42 kbdist/myLib.umd.js        20.95 kb                 10.22 kbdist/myLib.common.js     20.57 kb                 10.09 kbdist/myLib.css           0.33 kb                  0.23 kb

这个入口可以是一个 .js 或一个 .vue 文件。如果没有指定入口,则会使用 src/App.vue。

构建一个库会输出:

  • dist/myLib.common.js:一个给打包器用的 CommonJS 包 (不幸的是,webpack 目前还并没有支持 ES modules 输出格式的包)
  • dist/myLib.umd.js:一个直接给浏览器或 AMD loader 使用的 UMD 包
  • dist/myLib.umd.min.js:压缩后的 UMD 构建版本
  • dist/myLib.css:提取出来的 CSS 文件 (可以通过在 vue.config.js 中设置 css: { extract: false } 强制内联)

警告

如果你在开发一个库或多项目仓库 (monorepo),请注意导入 CSS 是具有副作用的。请确保在 package.json 中移除 "sideEffects": false,否则 CSS 代码块会在生产环境构建时被 webpack 丢掉。

Vue vs. JS/TS 入口文件

当使用一个 .vue 文件作为入口时,你的库会直接暴露这个 Vue 组件本身,因为组件始终是默认导出的内容。

然而,当你使用一个 .js 或 .ts 文件作为入口时,它可能会包含具名导出,所以库会暴露为一个模块。也就是说你的库必须在 UMD 构建中通过 window.yourLib.default 访问,或在 CommonJS 构建中通过 const myLib = require('mylib').default 访问。如果你没有任何具名导出并希望直接暴露默认导出,你可以在 vue.config.js 中使用以下 webpack 配置:

module.exports = {  configureWebpack: {    output: {      libraryExport: 'default'    }  }}

Web Components 组件

兼容性提示

Web Components 模式不支持 IE11 及更低版本。更多细节

注意对 Vue 的依赖

在 Web Components 模式中,Vue 是外置的。这意味着包中不会有 Vue,即便你在代码中导入了 Vue。这里的包会假设在页面中已经有一个可用的全局变量 Vue。

你可以通过下面的命令将一个单独的入口构建为一个 Web Components 组件:

vue-cli-service build --target wc --name my-element [entry]

注意这里的入口应该是一个 *.vue 文件。Vue CLI 将会把这个组件自动包裹并注册为 Web Components 组件,无需在 main.js 里自行注册。也可以在开发时把 main.js 作为 demo app 单独使用。

该构建将会产生一个单独的 JavaScript 文件 (及其压缩后的版本) 将所有的东西都内联起来。当这个脚本被引入网页时,会注册自定义组件 <my-element>,其使用 @vue/web-component-wrapper 包裹了目标的 Vue 组件。这个包裹器会自动代理属性、特性、事件和插槽。请查阅 @vue/web-component-wrapper 的文档了解更多细节。

注意这个包依赖了在页面上全局可用的 Vue。

这个模式允许你的组件的使用者以一个普通 DOM 元素的方式使用这个 Vue 组件:

<script src="https://unpkg.com/vue" rel="external nofollow"  rel="external nofollow" ></script><script src="path/to/my-element.js"></script><!-- 可在普通 HTML 中或者其它任何框架中使用 --><my-element></my-element>

注册多个 Web Components 组件的包

当你构建一个 Web Components 组件包的时候,你也可以使用一个 glob 表达式作为入口指定多个组件目标:

vue-cli-service build --target wc --name foo 'src/components/*.vue'

当你构建多个 web component 时,--name 将会用于设置前缀,同时自定义元素的名称会由组件的文件名推导得出。比如一个名为 HelloWorld.vue 的组件携带 --name foo 将会生成的自定义元素名为 <foo-hello-world>。

异步 Web Components 组件

当指定多个 Web Components 组件作为目标时,这个包可能会变得非常大,并且用户可能只想使用你的包中注册的一部分组件。这时异步 Web Components 模式会生成一个 code-split 的包,带一个只提供所有组件共享的运行时,并预先注册所有的自定义组件小入口文件。一个组件真正的实现只会在页面中用到自定义元素相应的一个实例时按需获取:

vue-cli-service build --target wc-async --name foo 'src/components/*.vue'
File                Size                        Gzippeddist/foo.0.min.js    12.80 kb                    8.09 kbdist/foo.min.js      7.45 kb                     3.17 kbdist/foo.1.min.js    2.91 kb                     1.02 kbdist/foo.js          22.51 kb                    6.67 kbdist/foo.0.js        17.27 kb                    8.83 kbdist/foo.1.js        5.24 kb                     1.64 kb

现在用户在该页面上只需要引入 Vue 和这个入口文件即可:

<script src="https://unpkg.com/vue" rel="external nofollow"  rel="external nofollow" ></script><script src="path/to/foo.min.js"></script><!-- foo-one 的实现的 chunk 会在用到的时候自动获取 --><foo-one></foo-one>


通用指南

如果你用 Vue CLI 处理静态资源并和后端框架一起作为部署的一部分,那么你需要的仅仅是确保 Vue CLI 生成的构建文件在正确的位置,并遵循后端框架的发布方式即可。

如果你独立于后端部署前端应用——也就是说后端暴露一个前端可访问的 API,然后前端实际上是纯静态应用。那么你可以将 dist 目录里构建的内容部署到任何静态文件服务器中,但要确保正确的 publicPath

本地预览

dist 目录需要启动一个 HTTP 服务器来访问 (除非你已经将 publicPath 配置为了一个相对的值),所以以 file:// 协议直接打开 dist/index.html 是不会工作的。在本地预览生产环境构建最简单的方式就是使用一个 Node.js 静态文件服务器,例如 serve

npm install -g serve# -s 参数的意思是将其架设在 Single-Page Application 模式下# 这个模式会处理即将提到的路由问题serve -s dist

使用 history.pushState 的路由

如果你在 history 模式下使用 Vue Router,是无法搭配简单的静态文件服务器的。例如,如果你使用 Vue Router 为 /todos/42/ 定义了一个路由,开发服务器已经配置了相应的 localhost:3000/todos/42 响应,但是一个为生产环境构建架设的简单的静态服务器会却会返回 404。

为了解决这个问题,你需要配置生产环境服务器,将任何没有匹配到静态文件的请求回退到 index.html。Vue Router 的文档提供了常用服务器配置指引

CORS

如果前端静态内容是部署在与后端 API 不同的域名上,你需要适当地配置 CORS

PWA

如果你使用了 PWA 插件,那么应用必须架设在 HTTPS 上,这样 Service Worker 才能被正确注册。

平台指南

GitHub Pages

手动推送更新

  1. 在 vue.config.js 中设置正确的 publicPath。如果打算将项目部署到 https://<USERNAME>.github.io/ 上, publicPath 将默认被设为 "/",你可以忽略这个参数。如果打算将项目部署到 https://<USERNAME>.github.io/<REPO>/ 上 (即仓库地址为 https://github.com/<USERNAME>/<REPO>),可将 publicPath 设为 "/<REPO>/"。举个例子,如果仓库名字为“my-project”,那么 vue.config.js 的内容应如下所示:module.exports = { publicPath: process.env.NODE_ENV === 'production' ? '/my-project/' : '/'}
  2. 在项目目录下,创建内容如下的 deploy.sh (可以适当地取消注释) 并运行它以进行部署:   #!/usr/bin/env sh# 当发生错误时中止脚本set -e# 构建npm run build# cd 到构建输出的目录下 cd dist# 部署到自定义域域名# echo 'www.example.com' > CNAMEgit initgit add -Agit commit -m 'deploy'# 部署到 https://<USERNAME>.github.io# git push -f git@github.com:<USERNAME>/<USERNAME>.github.io.git master# 部署到 https://<USERNAME>.github.io/<REPO># git push -f git@github.com:<USERNAME>/<REPO>.git master:gh-pagescd -

使用 Travis CI 自动更新

  1. 仿照上面在 vue.config.js 中设置正确的 publicPath。
  2. 安装 Travis CLI 客户端:gem install travis && travis --login
  3. 生成一个拥有“repo”权限的 GitHub 访问令牌
  4. 授予 Travis 访问仓库的权限:travis set GITHUB_TOKEN=xxx (xxx 是第三步中的个人访问令牌)
  5. 在项目根目录下创建一个 .travis.yml 文件。language: node_jsnode_js: - "node"cache: npmscript: npm run builddeploy:provider: pagesskip_cleanup: truegithub_token: $GITHUB_TOKENlocal_dir: diston: branch: master
  6. 将 .travis.yml 文件推送到仓库来触发第一次构建。

GitLab Pages

根据 GitLab Pages 文档的描述,所有的配置都在根目录中的.gitlab-ci.yml 文件中。下面的范例是一个很好的入门:

# .gitlab-ci.yml 文件应放在你仓库的根目录下 pages: # 必须定义一个名为 pages 的 job  image: node:latest  stage: deploy  script:    - npm ci    - npm run build    - mv public public-vue # GitLab Pages 的钩子设置在 public 文件夹    - mv dist public # 重命名 dist 文件夹 (npm run build 之后的输出位置)  artifacts:    paths:      - public # artifact path 一定要在 /public , 这样 GitLab Pages 才能获取  only:    - master

通常, 你的静态页面将托管在 https://yourUserName.gitlab.io/yourProjectName 上, 所以你可以创建一个 initial vue.config.js 文件去 更新 BASE_URL 要匹配的值 :

// vue.config.js 位于仓库的根目录下// 确保用 GitLab 项目的名称替换了 `YourProjectName`module.exports = {  publicPath: process.env.NODE_ENV === 'production'    ? '/yourProjectName/'    : '/'}

请阅读在 GitLab Pages domains 的文档来学习更多关于页面部署 URL 的信息。注意,你也可以使用自定义域名

在推送到仓库之前提交 .gitlab-ci.yml 和 vue.config.js 文件。GitLab CI 的管道将会被触发: 当成功时候, 到 Settings > Pages 查看关于网站的链接。

Netlify

  1. 在 Netlify 上,使用以下设置从 GitHub 创建新项目:构建命令: npm run build 或 yarn build发布目录: dist
  2. 点击“deploy”按钮!

也可以查看 vue-cli-plugin-netlify-lambda

如果使用 Vue Router 的 history 模式,你需要在 /public 目录下创建一个 _redirects 文件:

# 单页应用的 Netlify 设置/*    /index.html   200

详细信息请查看 Netlify 重定向文档

Render

Render 提供带有全托管 SSL,全球 CDN 和 GitHub 持续自动部署的免费静态站点托管服务。

  1. 在 Render 上创建一个新的 Web Service,并授予 Render 的 GitHub 应用访问你的 Vue 仓库的权限。
  2. 在创建过程中使用以下设置:环境:Static Site构建命令:npm run build 或者 yarn build发布目录:dist

大功告成!构建结束时你的应用便会在你的 Render URL 上线。

如果使用 Vue Router 的 history 模式,你需要在站点的 Redirects/Rewrites 设置中添加以下改写规则:

  • Source: /*
  • Destination: /index.html
  • Status: Rewrite

详细信息请查看 Render 的重定向和改写自定义域名文档。

Amazon S3

参见 vue-cli-plugin-s3-deploy

Firebase

创建一个新的 Firebase 项目 Firebase console。 请参考文档

确保已经全局安装了 firebase-tools :

npm install -g firebase-tools

在项目的根目录下, 用以下命令初始化 firebase :

firebase init

Firebase 将会询问有关初始化项目的一些问题。

  • 选择需要 Firebase CLI 的功能。 一定要选择 hosting 。
  • 选择默认的 Firebase 项目。
  • 将 public 目录设为 dist (或构建输出的位置) 这将会上传到 Firebase Hosting。
// firebase.json{  "hosting": {    "public": "dist"  }}
  • 选择 yes 设置项目为一个单页应用。 这将会创建一个 index.html 在 dist 文件夹并且配置 hosting 信息。
// firebase.json{  "hosting": {    "rewrites": [      {        "source": "**",        "destination": "/index.html"      }    ]  }}

执行 npm run build 去构建项目。

在 Firebase Hosting 部署项目,执行以下命令 :

firebase deploy --only hosting

如果需要在部署的项目中使用的其他 Firebase CLI 功能, 执行 firebase deploy 去掉 --only 参数。

现在可以到 https://<YOUR-PROJECT-ID>.firebaseapp.com 访问你的项目了。

请参考 Firebase 文档 来获取更多细节。

ZEIT Now

ZEIT Now 是一个网站和无服务器 (Serverless) API 云平台,你可以使用你的个人域名 (或是免费的 .now.sh URL) 部署你的 Vue 项目。

步骤一:安装 Now CLI

要使用 npm 安装其命令行界面,运行以下命令:

npm install -g now

步骤二:部署

在项目根目录运行以下命令部署你的应用:

now

此外,你还可以使用他们的 GitHub 或 GitLab 集成服务。

大功告成!

你的站点会开始部署,你将获得一个形如 https://vue.now-examples.now.sh/ 的链接。

开箱即用地,请求会被自动改写到 index.html (除了自定义的静态文件) 并带有合适的缓存请求头。你可以改写这些规则。

Stdlib

未完成 | 欢迎参与贡献。

Heroku

  1. 安装 Heroku CLI
  2. 创建 static.json 文件:{ "root": "dist", "clean_urls": true, "routes": { "/**": "index.html" }}
  3. 将 static.json 加入 Gitgit add static.jsongit commit -m "add static configuration"
  4. 部署到 Herokuheroku loginheroku createheroku buildpacks:add heroku/nodejsheroku buildpacks:add https://github.com/heroku/heroku-buildpack-staticgit push heroku master

详细信息:https://gist.github.com/hone/24b06869b4c1eca701f9

Surge

要使用 Surge 进行部署,步骤非常简单。

首先,通过运行 npm run build 来构建项目。如果还没有安装 Surge 的命令行工具,可以通过运行命令来执行此操作:

npm install --global surge

然后 cd 进入项目的 dist/ 文件夹,然后运行 surge 并按照屏幕提示操作 。如果是第一次使用 Surge,它会要求设置电子邮件和密码。确认项目文件夹以及输入首选域来查看正在部署的项目,如下所示。

            project: /Users/user/Documents/myawesomeproject/dist/         domain: myawesomeproject.surge.sh         upload: [====================] 100% eta: 0.0s (31 files, 494256 bytes)            CDN: [====================] 100%             IP: **.**.***.***   Success! - Published to myawesomeproject.surge.sh

通过访问 myawesomeproject.surge.sh 来确保你的项目已经成功的用 Surge 发布,有关自定义域名等更多设置详细信息,可以到 Surge's help page 查看。

Bitbucket Cloud

  1. 如 Bitbucket 文档 创建一个命名为 <USERNAME>.bitbucket.io 的仓库。
  2. 如果你想拥有多个网站, 想要发布到主仓库的子文件夹中。这种情况下就要在 vue.config.js 设置 publicPath。如果部署到 https://<USERNAME>.bitbucket.io/, publicPath 默认将被设为 "/",你可以选择忽略它。如果要部署到 https://<USERNAME>.bitbucket.io/<SUBFOLDER>/,设置 publicPath 为 "/<SUBFOLDER>/"。在这种情况下,仓库的目录结构应该反映 url 结构,例如仓库应该有 /<SUBFOLDER> 目录。
  3. 在项目中, deploy.sh 使用以下内容创建并运行它以进行部署: #!/usr/bin/env sh# 当发生错误时中止脚本set -e# 构建npm run build# cd 到构建输出的目录cd distgit initgit add -Agit commit -m 'deploy'git push -f git@bitbucket.org:<USERNAME>/<USERNAME>.bitbucket.io.git mastercd -

Docker (Nginx)

在 Docker 容器中使用 Nginx 部署你的应用。

  1. 安装 Docker
  2. 在项目根目录创建 Dockerfile 文件FROM node:10COPY ./ /appWORKDIR /appRUN npm install && npm run buildFROM nginxRUN mkdir /appCOPY --from=0 /app/dist /appCOPY nginx.conf /etc/nginx/nginx.conf
  3. 在项目根目录创建 .dockerignore 文件设置 .dockerignore 文件能防止 node_modules 和其他中间构建产物被复制到镜像中导致构建问题。**/node_modules**/dist
  4. 在项目根目录创建 nginx.conf 文件Nginx 是一个能在 Docker 容器中运行的 HTTP(s) 服务器。它使用配置文件决定如何提供内容、要监听的端口等。参阅 Nginx 设置文档 以了解所有可能的设置选项。下面是一个简单的 Nginx 设置文件,它会在 80 端口上提供你的 Vue 项目。页面未找到 / 404 错误使用的是 index.html,这让我们可以使用基于 pushState() 的路由。user nginx;worker_processes 1;error_log /var/log/nginx/error.log warn;pid /var/run/nginx.pid;events { worker_connections 1024;}http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; keepalive_timeout 65; server { listen 80; server_name localhost; location / { root /app; index index.html; try_files $uri $uri/ /index.html; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } }}
  5. 构建你的 Docker 镜像docker build . -t my-app# Sending build context to Docker daemon 884.7kB# ...# Successfully built 4b00e5ee82ae# Successfully tagged my-app:latest
  6. 运行你的 Docker 镜像这个例子基于官方 Nginx 镜像,因此已经设置了日志重定向并关闭了自我守护进程。它也提供了其他有利于 Nginx 在 Docker 容器中运行的默认设置。更多信息参阅 Nginx Docker 仓库。docker run -d -p 8080:80 my-appcurl localhost:8080# <!DOCTYPE html><html lang=en>...</html>


全局 CLI 配置

有些针对 @vue/cli 的全局配置,例如你惯用的包管理器和你本地保存的 preset,都保存在 home 目录下一个名叫 .vuerc 的 JSON 文件。你可以用编辑器直接编辑这个文件来更改已保存的选项。

你也可以使用 vue config 命令来审查或修改全局的 CLI 配置。

目标浏览器

请查阅指南中的浏览器兼容性章节。

vue.config.js

vue.config.js 是一个可选的配置文件,如果项目的 (和 package.json 同级的) 根目录中存在这个文件,那么它会被 @vue/cli-service 自动加载。你也可以使用 package.json 中的 vue 字段,但是注意这种写法需要你严格遵照 JSON 的格式来写。

这个文件应该导出一个包含了选项的对象:

// vue.config.jsmodule.exports = {  // 选项...}

baseUrl

从 Vue CLI 3.3 起已弃用,请使用publicPath

publicPath

  • Type: string
  • Default: '/'
  • 部署应用包时的基本 URL。用法和 webpack 本身的 output.publicPath 一致,但是 Vue CLI 在一些其他地方也需要用到这个值,所以请始终使用 publicPath 而不要直接修改 webpack 的 output.publicPath。默认情况下,Vue CLI 会假设你的应用是被部署在一个域名的根路径上,例如 https://www.my-app.com/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.my-app.com/my-app/,则设置 publicPath 为 /my-app/。这个值也可以被设置为空字符串 ('') 或是相对路径 ('./'),这样所有的资源都会被链接为相对路径,这样打出来的包可以被部署在任意路径,也可以用在类似 Cordova hybrid 应用的文件系统中。相对 publicPath 的限制相对路径的 publicPath 有一些使用上的限制。在以下情况下,应当避免使用相对 publicPath:当使用基于 HTML5 history.pushState 的路由时;当使用 pages 选项构建多页面应用时。这个值在开发环境下同样生效。如果你想把开发服务器架设在根路径,你可以使用一个条件式的值:module.exports = { publicPath: process.env.NODE_ENV === 'production' ? '/production-sub-path/' : '/'}

outputDir

  • Type: string
  • Default: 'dist'
  • 当运行 vue-cli-service build 时生成的生产环境构建文件的目录。注意目标目录在构建之前会被清除 (构建时传入 --no-clean 可关闭该行为)。提示请始终使用 outputDir 而不要修改 webpack 的 output.path。

assetsDir

  • Type: string
  • Default: ''
  • 放置生成的静态资源 (js、css、img、fonts) 的 (相对于 outputDir 的) 目录。提示从生成的资源覆写 filename 或 chunkFilename 时,assetsDir 会被忽略。

indexPath

  • Type: string
  • Default: 'index.html'
  • 指定生成的 index.html 的输出路径 (相对于 outputDir)。也可以是一个绝对路径。

filenameHashing

  • Type: boolean
  • Default: true
  • 默认情况下,生成的静态资源在它们的文件名中包含了 hash 以便更好的控制缓存。然而,这也要求 index 的 HTML 是被 Vue CLI 自动生成的。如果你无法使用 Vue CLI 生成的 index HTML,你可以通过将这个选项设为 false 来关闭文件名哈希。

pages

  • Type: Object
  • Default: undefined
  • 在 multi-page 模式下构建应用。每个“page”应该有一个对应的 JavaScript 入口文件。其值应该是一个对象,对象的 key 是入口的名字,value 是:一个指定了 entry, template, filename, title 和 chunks 的对象 (除了 entry 之外都是可选的);或一个指定其 entry 的字符串。
  • module.exports = {  pages: {    index: {      // page 的入口      entry: 'src/index/main.js',      // 模板来源      template: 'public/index.html',      // 在 dist/index.html 的输出      filename: 'index.html',      // 当使用 title 选项时,      // template 中的 title 标签需要是 <title><%= htmlWebpackPlugin.options.title %></title>      title: 'Index Page',      // 在这个页面中包含的块,默认情况下会包含      // 提取出来的通用 chunk 和 vendor chunk。      chunks: ['chunk-vendors', 'chunk-common', 'index']    },    // 当使用只有入口的字符串格式时,    // 模板会被推导为 `public/subpage.html`    // 并且如果找不到的话,就回退到 `public/index.html`。    // 输出文件名会被推导为 `subpage.html`。    subpage: 'src/subpage/main.js'  }}
  •  提示当在 multi-page 模式下构建时,webpack 配置会包含不一样的插件 (这时会存在多个 html-webpack-plugin 和 preload-webpack-plugin 的实例)。如果你试图修改这些插件的选项,请确认运行 vue inspect。

lintOnSave

  • Type: boolean | 'warning' | 'default' | 'error'
  • Default: true
  • 是否在开发环境下通过 eslint-loader 在每次保存时 lint 代码。这个值会在 @vue/cli-plugin-eslint 被安装之后生效。设置为 true 或 'warning' 时,eslint-loader 会将 lint 错误输出为编译警告。默认情况下,警告仅仅会被输出到命令行,且不会使得编译失败。如果你希望让 lint 错误在开发时直接显示在浏览器中,你可以使用 lintOnSave: 'error'。这会强制 eslint-loader 将 lint 错误输出为编译错误,同时也意味着 lint 错误将会导致编译失败。或者,你也可以通过设置让浏览器 overlay 同时显示警告和错误:// vue.config.jsmodule.exports = { devServer: { overlay: { warnings: true, errors: true } }}当 lintOnSave 是一个 truthy 的值时,eslint-loader 在开发和生产构建下都会被启用。如果你想要在生产构建时禁用 eslint-loader,你可以用如下配置:// vue.config.jsmodule.exports = { lintOnSave: process.env.NODE_ENV !== 'production'}

runtimeCompiler

  • Type: boolean
  • Default: false
  • 是否使用包含运行时编译器的 Vue 构建版本。设置为 true 后你就可以在 Vue 组件中使用 template 选项了,但是这会让你的应用额外增加 10kb 左右。更多细节可查阅:Runtime + Compiler vs. Runtime only

transpileDependencies

  • Type: Array<string | RegExp>
  • Default: []
  • 默认情况下 babel-loader 会忽略所有 node_modules 中的文件。如果你想要通过 Babel 显式转译一个依赖,可以在这个选项中列出来。

productionSourceMap

  • Type: boolean
  • Default: true
  • 如果你不需要生产环境的 source map,可以将其设置为 false 以加速生产环境构建。

crossorigin

  • Type: string
  • Default: undefined
  • 设置生成的 HTML 中 <link rel="stylesheet"> 和 <script> 标签的 crossorigin 属性。需要注意的是该选项仅影响由 html-webpack-plugin 在构建时注入的标签 - 直接写在模版 (public/index.html) 中的标签不受影响。更多细节可查阅: CORS settings attributes

integrity

  • Type: boolean
  • Default: false
  • 在生成的 HTML 中的 <link rel="stylesheet"> 和 <script> 标签上启用 Subresource Integrity (SRI)。如果你构建后的文件是部署在 CDN 上的,启用该选项可以提供额外的安全性。需要注意的是该选项仅影响由 html-webpack-plugin 在构建时注入的标签 - 直接写在模版 (public/index.html) 中的标签不受影响。另外,当启用 SRI 时,preload resource hints 会被禁用,因为 Chrome 的一个 bug 会导致文件被下载两次。

configureWebpack

  • Type: Object | Function
  • 如果这个值是一个对象,则会通过 webpack-merge 合并到最终的配置中。如果这个值是一个函数,则会接收被解析的配置作为参数。该函数及可以修改配置并不返回任何东西,也可以返回一个被克隆或合并过的配置版本。更多细节可查阅:配合 webpack > 简单的配置方式

chainWebpack

  • Type: Function
  • 是一个函数,会接收一个基于 webpack-chain 的 ChainableConfig 实例。允许对内部的 webpack 配置进行更细粒度的修改。更多细节可查阅:配合 webpack > 链式操作

css.modules

从 v4 起已弃用,请使用css.requireModuleExtension。 在 v3 中,这个选项含义与 css.requireModuleExtension 相反。

css.requireModuleExtension

  • Type: boolean
  • Default: true默认情况下,只有 *.module.[ext] 结尾的文件才会被视作 CSS Modules 模块。设置为 false 后你就可以去掉文件名中的 .module 并将所有的 *.(css|scss|sass|less|styl(us)?) 文件视为 CSS Modules 模块。提示如果你在 css.loaderOptions.css 里配置了自定义的 CSS Module 选项,则 css.requireModuleExtension 必须被显式地指定为 true 或者 false,否则我们无法确定你是否希望将这些自定义配置应用到所有 CSS 文件中。更多细节可查阅:配合 CSS > CSS Modules

css.extract

  • Type: boolean | Object
  • Default: 生产环境下是 true,开发环境下是 false是否将组件中的 CSS 提取至一个独立的 CSS 文件中 (而不是动态注入到 JavaScript 中的 inline 代码)。同样当构建 Web Components 组件时它总是会被禁用 (样式是 inline 的并注入到了 shadowRoot 中)。当作为一个库构建时,你也可以将其设置为 false 免得用户自己导入 CSS。提取 CSS 在开发环境模式下是默认不开启的,因为它和 CSS 热重载不兼容。然而,你仍然可以将这个值显性地设置为 true 在所有情况下都强制提取。

css.sourceMap

  • Type: boolean
  • Default: false
  • 是否为 CSS 开启 source map。设置为 true 之后可能会影响构建的性能。

css.loaderOptions

  • Type: Object
  • Default: {}
  • 向 CSS 相关的 loader 传递选项。例如:module.exports = { css: { loaderOptions: { css: { // 这里的选项会传递给 css-loader }, postcss: { // 这里的选项会传递给 postcss-loader } } }}支持的 loader 有:css-loaderpostcss-loadersass-loaderless-loaderstylus-loader另外,也可以使用 scss 选项,针对 scss 语法进行单独配置(区别于 sass 语法)。更多细节可查阅:向预处理器 Loader 传递选项提示相比于使用 chainWebpack 手动指定 loader 更推荐上面这样做,因为这些选项需要应用在使用了相应 loader 的多个地方。

devServer

  • Type: Object
  • 所有 webpack-dev-server 的选项都支持。注意:有些值像 host、port 和 https 可能会被命令行参数覆写。有些值像 publicPath 和 historyApiFallback 不应该被修改,因为它们需要和开发服务器的 publicPath 同步以保障正常的工作。

devServer.proxy

  • Type: string | Object
  • 如果你的前端应用和后端 API 服务器没有运行在同一个主机上,你需要在开发环境下将 API 请求代理到 API 服务器。这个问题可以通过 vue.config.js 中的 devServer.proxy 选项来配置。devServer.proxy 可以是一个指向开发环境 API 服务器的字符串:module.exports = { devServer: { proxy: 'http://localhost:4000' }}这会告诉开发服务器将任何未知请求 (没有匹配到静态文件的请求) 代理到http://localhost:4000。如果你想要更多的代理控制行为,也可以使用一个 path: options 成对的对象。完整的选项可以查阅 http-proxy-middleware 。module.exports = { devServer: { proxy: { '/api': { target: '<url>', ws: true, changeOrigin: true }, '/foo': { target: '<other_url>' } } }}

parallel

  • Type: boolean
  • Default: require('os').cpus().length > 1是否为 Babel 或 TypeScript 使用 thread-loader。该选项在系统的 CPU 有多于一个内核时自动启用,仅作用于生产构建。

pwa

pluginOptions

  • Type: Object
  • 这是一个不进行任何 schema 验证的对象,因此它可以用来传递任何第三方插件选项。例如:module.exports = { pluginOptions: { foo: { // 插件可以作为 `options.pluginOptions.foo` 访问这些选项。 } }}

Babel

Babel 可以通过 babel.config.js 进行配置。

提示

Vue CLI 使用了 Babel 7 中的新配置格式 babel.config.js。和 .babelrc 或 package.json 中的 babel 字段不同,这个配置文件不会使用基于文件位置的方案,而是会一致地运用到项目根目录以下的所有文件,包括 node_modules 内部的依赖。我们推荐在 Vue CLI 项目中始终使用 babel.config.js 取代其它格式。

所有的 Vue CLI 应用都使用 @vue/babel-preset-app,它包含了 babel-preset-env、JSX 支持以及为最小化包体积优化过的配置。通过它的文档可以查阅到更多细节和 preset 选项。

同时查阅指南中的 Polyfill 章节。

ESLint

ESLint 可以通过 .eslintrc 或 package.json 中的 eslintConfig 字段来配置。

更多细节可查阅 @vue/cli-plugin-eslint

TypeScript

TypeScript 可以通过 tsconfig.json 来配置。

更多细节可查阅 @vue/cli-plugin-typescript

单元测试

Jest

更多细节可查阅 @vue/cli-plugin-unit-jest

Mocha (配合 mocha-webpack)

更多细节可查阅 @vue/cli-plugin-unit-mocha

E2E 测试

Cypress

更多细节可查阅 @vue/cli-plugin-e2e-cypress

Nightwatch

更多细节可查阅 @vue/cli-plugin-e2e-nightwatch


核心概念

系统里有两个主要的部分:

  • @vue/cli:全局安装的,暴露 vue create <app> 命令;
  • @vue/cli-service:局部安装,暴露 vue-cli-service 命令。

两者皆应用了基于插件的架构。

Creator

Creator 是调用 vue create <app> 时创建的类。负责偏好对话、调用 generator 和安装依赖。

Service

Service 是调用 vue-cli-service <command> [...args] 时创建的类。负责管理内部的 webpack 配置、暴露服务和构建项目的命令等。

CLI 插件

CLI 插件是一个可以为 @vue/cli 项目添加额外特性的 npm 包。它应该始终包含一个 Service 插件作为其主要导出,且可选的包含一个 Generator 和一个 Prompt 文件

一个典型的 CLI 插件的目录结构看起来是这样的:

.├── README.md├── generator.js  # generator (可选)├── prompts.js    # prompt 文件 (可选)├── index.js      # service 插件└── package.json

Service 插件

Service 插件会在一个 Service 实例被创建时自动加载——比如每次 vue-cli-service 命令在项目中被调用时。

注意我们这里讨论的“service 插件”的概念要比发布为一个 npm 包的“CLI 插件”的要更窄。前者涉及一个会被 @vue/cli-service 在初始化时加载的模块,也经常是后者的一部分。

此外,@vue/cli-service 的内建命令配置模块也是全部以 service 插件实现的。

一个 service 插件应该导出一个函数,这个函数接受两个参数:

  • 一个 PluginAPI 实例
  • 一个包含 vue.config.js 内指定的项目本地选项的对象,或者在 package.json 内的 vue 字段。

这个 API 允许 service 插件针对不同的环境扩展/修改内部的 webpack 配置,并向 vue-cli-service 注入额外的命令。例如:

module.exports = (api, projectOptions) => {  api.chainWebpack(webpackConfig => {    // 通过 webpack-chain 修改 webpack 配置  })  api.configureWebpack(webpackConfig => {    // 修改 webpack 配置    // 或返回通过 webpack-merge 合并的配置对象  })  api.registerCommand('test', args => {    // 注册 `vue-cli-service test`  })}

为命令指定模式

注意:插件设置模式的方式从 beta.10 开始已经改变了。

如果一个已注册的插件命令需要运行在特定的默认模式下,则该插件需要通过 module.exports.defaultModes 以 { [commandName]: mode } 的形式来暴露:

module.exports = api => {  api.registerCommand('build', () => {    // ...  })}module.exports.defaultModes = {  build: 'production'}

这是因为我们需要在加载环境变量之前知道该命令的预期模式,所以需要提前加载用户选项/应用插件。

在插件中解析 webpack 配置

一个插件可以通过调用 api.resolveWebpackConfig() 取回解析好的 webpack 配置。每次调用都会新生成一个 webpack 配置用来在需要时进一步修改。

module.exports = api => {  api.registerCommand('my-build', args => {    const configA = api.resolveWebpackConfig()    const configB = api.resolveWebpackConfig()    // 针对不同的目的修改 `configA` 和 `configB`...  })}// 请确保为正确的环境变量指定默认模式module.exports.defaultModes = {  'my-build': 'production'}

或者,一个插件也可以通过调用 api.resolveChainableWebpackConfig() 获得一个新生成的链式配置

api.registerCommand('my-build', args => {  const configA = api.resolveChainableWebpackConfig()  const configB = api.resolveChainableWebpackConfig()  // 针对不同的目的链式修改 `configA` 和 `configB`...  const finalConfigA = configA.toConfig()  const finalConfigB = configB.toConfig()})

第三方插件的自定义选项

vue.config.js 的导出将会通过一个 schema 的验证以避免笔误和错误的配置值。然而,一个第三方插件仍然允许用户通过 pluginOptions 字段配置其行为。例如,对于下面的 vue.config.js:

module.exports = {  pluginOptions: {    foo: { /* ... */ }  }}

该第三方插件可以读取 projectOptions.pluginOptions.foo 来做条件式的决定配置。

Generator

一个发布为 npm 包的 CLI 插件可以包含一个 generator.js 或 generator/index.js 文件。插件内的 generator 将会在两种场景下被调用:

  • 在一个项目的初始化创建过程中,如果 CLI 插件作为项目创建 preset 的一部分被安装。
  • 插件在项目创建好之后通过 vue invoke 独立调用时被安装。

这里的 GeneratorAPI 允许一个 generator 向 package.json 注入额外的依赖或字段,并向项目中添加文件。

一个 generator 应该导出一个函数,这个函数接收三个参数:

  1. 一个 GeneratorAPI 实例:
  2. 这个插件的 generator 选项。这些选项会在项目创建对话过程中被解析,或从一个保存在 ~/.vuerc 中的 preset 中加载。例如,如果保存好的 ~/.vuerc 像如下的这样:{ "presets" : { "foo": { "plugins": { "@vue/cli-plugin-foo": { "option": "bar" } } } }}如果用户使用 preset foo 创建了一个项目,那么 @vue/cli-plugin-foo 的 generator 就会收到 { option: 'bar' } 作为第二个参数。对于一个第三方插件来说,该选项将会解析自对话或用户执行 vue invoke 时的命令行参数中 (详见第三方插件的对话)。
  3. 整个 preset (presets.foo) 将会作为第三个参数传入。

示例:

module.exports = (api, options, rootOptions) => {  // 修改 `package.json` 里的字段  api.extendPackage({    scripts: {      test: 'vue-cli-service test'    }  })  // 复制并用 ejs 渲染 `./template` 内所有的文件  api.render('./template')  if (options.foo) {    // 有条件地生成文件  }}

Generator 的模板处理

当你调用 api.render('./template') 时,该 generator 将会使用 EJS 渲染 ./template 中的文件 (相对于 generator 中的文件路径进行解析)

此外,你可以使用 YAML 前置元信息继承并替换已有的模板文件的一部分:

---extend: '@vue/cli-service/generator/template/src/App.vue'replace: !!js/regexp /<script>[^]*?</script>/---<script>export default {  // 替换默认脚本}</script>

你也可以完成多处替换,当然你需要将要替换的字符串用 <%# REPLACE %> 和 <%# END_REPLACE %> 块包裹起来:

---extend: '@vue/cli-service/generator/template/src/App.vue'replace:  - !!js/regexp /欢迎来到你的 Vue.js 应用/  - !!js/regexp /<script>[^]*?</script>/---<%# REPLACE %>替换欢迎信息<%# END_REPLACE %><%# REPLACE %><script>export default {  // 替换默认脚本}</script><%# END_REPLACE %>

文件名的极端情况

如果你想要渲染一个以点开头的模板文件 (例如 .env),则需要遵循一个特殊的命名约定,因为以点开头的文件会在插件发布到 npm 的时候被忽略:

# 以点开头的模板需要使用下划线取代那个点:/generator/template/_env# 调用 api.render('./template') 会在项目目录中渲染成为:.env

同时这也意味着当你想渲染以下划线开头的文件时,同样需要遵循一个特殊的命名约定:

# 这种模板需要使用两个下划线来取代单个下划线:/generator/template/__variables.scss# 调用 api.render('./template') 会在项目目录中渲染成为:_variables.scss

Prompts

内建插件的对话

只有内建插件可以定制创建新项目时的初始化对话,且这些对话模块放置在 @vue/cli 包的内部

一个对话模块应该导出一个函数,这个函数接收一个 PromptModuleAPI 实例。这些对话的底层使用 inquirer 进行展示:

module.exports = api => {  // 一个特性对象应该是一个有效的 inquirer 选择对象  api.injectFeature({    name: 'Some great feature',    value: 'my-feature'  })  // injectPrompt 期望接收一个有效的 inquirer 对话对象  api.injectPrompt({    name: 'someFlag',    // 确认对话只在用户已经选取了特性的时候展示    when: answers => answers.features.include('my-feature'),    message: 'Do you want to turn on flag foo?',    type: 'confirm'  })  // 当所有的对话都完成之后,将你的插件注入到  // 即将传递给 Generator 的 options 中  api.onPromptComplete((answers, options) => {    if (answers.features.includes('my-feature')) {      options.plugins['vue-cli-plugin-my-feature'] = {        someFlag: answers.someFlag      }    }  })}

第三方插件的对话

第三方插件通常会在一个项目创建完毕后被手动安装,且用户将会通过调用 vue invoke 来初始化这个插件。如果这个插件在其根目录包含一个 prompts.js,那么它将会用在该插件被初始化调用的时候。这个文件应该导出一个用于 Inquirer.js 的问题的数组。这些被解析的答案对象会作为选项被传递给插件的 generator。

或者,用户可以通过在命令行传递选项来跳过对话直接初始化插件,比如:

vue invoke my-plugin --mode awesome

发布插件

为了让一个 CLI 插件能够被其它开发者使用,你必须遵循 vue-cli-plugin-<name> 的命名约定将其发布到 npm 上。插件遵循命名约定之后就可以:

  • 被 @vue/cli-service 发现;
  • 被其它开发者搜索到;
  • 通过 vue add <name> 或 vue invoke <name> 安装下来。

开发核心插件的注意事项

注意

这个章节只用于 vuejs/vue-cli 仓库内部的内建插件工作。

一个带有为本仓库注入额外依赖的 generator 的插件 (比如 chai 会通过 @vue/cli-plugin-unit-mocha/generator/index.js 被注入) 应该将这些依赖列入其自身的 devDependencies 字段。这会确保:

  1. 这个包始终存在于该仓库的根 node_modules 中,因此我们不必在每次测试的时候重新安装它们。
  2. yarn.lock 会保持其一致性,因此 CI 程序可以更好地利用缓存。


你的插件在其 UI 中使用时,可以展示不同的附加信息,使其更容易被发现和辨认。

Logo

你可以在将要发布到 npm 上的目录的根上放置一个 logo.png。它会展示在以下几个地方:

  • 搜索插件以安装时
  • 在已安装的插件列表中

插件

这个 logo 应该是一个不透明的正方形图 (最好 84x84).

可发现性

为了提升你的插件在搜索时的可见度,请将描述插件的关键字放到插件的 package.json 文件的 description 字段。

示例:

{  "name": "vue-cli-plugin-apollo",  "version": "0.7.7",  "description": "vue cli plugin to add Apollo and GraphQL"}

你应该将插件网站的 URL 或仓库添加添加到 homepage 或 repository 字段,这样“More Info”按钮就会在你的插件描述中展示出来。

{  "repository": {    "type": "git",    "url": "git+https://github.com/Akryum/vue-cli-plugin-apollo.git"  },  "homepage": "https://github.com/Akryum/vue-cli-plugin-apollo#readme"}

搜索到的插件项目


这个 cli-ui 暴露一个 API,允许增强项目的配置和任务,也可以分享数据和在进程间进行通信。

UI 插件架构

UI 文件

在每个安装好的 vue-cli 插件里,cli-ui 都会尝试从其插件的根目录加载一个可选的 ui.js 文件。注意你也可以使用文件夹 (例如 ui/index.js)。

该文件应该导出一个函数,函数会以 API 对象作为第一个参数:

module.exports = api => {  // 在这里使用 API...}

警告

当试图在“项目插件 (Project plugins)”中获取插件列表时,这些文件将会被重新加载。点击 UI 左侧边栏导航“项目插件 (Project plugins)”按钮可以应用更改。

这里是一个使用 UI API 的 vue-cli 插件的文件夹结构示例:

- vue-cli-plugin-test  - package.json  - index.js  - generator.js  - prompts.js  - ui.js  - logo.png

项目本地的插件

如果你需要在项目里访问插件 API 而不需要创建一个完整的插件,你可以在 package.json 文件中使用 vuePlugins.ui 选项:

{  "vuePlugins": {    "ui": ["my-ui.js"]  }}

每个文件都需要暴露一个函数,将插件 API 作为第一个参数携带。

开发模式

当构建你自己的插件时,你可能想在开发环境下运行 cli-ui,所以这样运行会输出较为实用的日志:

vue ui --dev

或:

vue ui -D

项目的配置

配置 UI

你可以通过 api.describeConfig 方法添加一个项目配置。

首先你需要传入一些信息:

api.describeConfig({  // 唯一的配置 ID  id: 'org.vue.eslintrc',  // 展示名称  name: 'ESLint configuration',  // 展示在名称下方的描述  description: 'Error checking & Code quality',  // “更多信息 (More info)”链接  link: 'https://eslint.org'})

危险

请确定为 id 设置正确的命名空间,因为它需要跨所有插件保持唯一。我们推荐使用反向域名记号 (reverse domain name notation)

配置图标

可以是一个 Material 图标代码或一个自定义的图片 (详见公共静态文件):

api.describeConfig({  /* ... */  // 配置图标  icon: 'application_settings'})

如果你没有定义图标,那就展示该插件可能存在的 logo (详见 Logo)。

配置文件

默认情况下,配置 UI 可能会读写一个或多个配置文件,例如 .eslintrc 和 vue.config.js。

你可以提供可能需要在用户项目中检测的文件:

api.describeConfig({  /* ... */  // 该配置所有可能的文件  files: {    // eslintrc.js    eslint: {      js: ['.eslintrc.js'],      json: ['.eslintrc', '.eslintrc.json'],      // 会从 `package.json` 读取      package: 'eslintConfig'    },    // vue.config.js    vue: {      js: ['vue.config.js']    }  },})

支持的类型有:json、yaml、js、package。这个顺序是很重要的:如果这项配置不存在,则会创建列表中的第一个文件。

展示配置提示符

使用 onRead 钩子来返回一个提示符列表,用以配置展示:

api.describeConfig({  /* ... */  onRead: ({ data, cwd }) => ({    prompts: [      // 提示符对象    ]  })})

这些提示符会展示在配置的详情面板中。

查阅提示符了解更多信息。

这个 data 对象包含了每个配置文件内容的 JSON 结果。

例如,假设用户在其项目中的 vue.config.js 有以下内容:

module.exports = {  lintOnSave: false}

我们在插件中像这样声明配置文件:

api.describeConfig({  /* ... */  // 该配置所有可能的文件  files: {    // vue.config.js    vue: {      js: ['vue.config.js']    }  },})

则这个 data 对象会是:

{  // 文件  vue: {    // 文件数据    lintOnSave: false  }}

多个文件的例子:如果我们在用户的项目中添加以下 eslintrc.js 文件:

module.exports = {  root: true,  extends: [    'plugin:vue/essential',    '@vue/standard'  ]}

那么在我们的插件中将 files 选项改变成为:

api.describeConfig({  /* ... */  // 该配置所有可能的文件  files: {    // eslintrc.js    eslint: {      js: ['.eslintrc.js'],      json: ['.eslintrc', '.eslintrc.json'],      // 会从 `package.json` 读取      package: 'eslintConfig'    },    // vue.config.js    vue: {      js: ['vue.config.js']    }  },})

则这个 data 对象会是:

{  eslint: {    root: true,    extends: [      'plugin:vue/essential',      '@vue/standard'    ]  },  vue: {    lintOnSave: false  }}

配置选项卡

你可以将这些提示符组织成为几个选项卡:

api.describeConfig({  /* ... */  onRead: ({ data, cwd }) => ({    tabs: [      {        id: 'tab1',        label: 'My tab',        // 可选的        icon: 'application_settings',        prompts: [          // 提示符对象们        ]      },      {        id: 'tab2',        label: 'My other tab',        prompts: [          // 提示符对象们        ]      }    ]  })})

保存配置变更

使用 onWrite 钩子将数据写入配置文件 (或者执行任何 Node.js 代码):

api.describeConfig({  /* ... */  onWrite: ({ prompts, answers, data, files, cwd, api }) => {    // ...  }})

参数:

  • prompts: 当前提示符们的运行时对象 (详见下方)
  • answers: 来自用户输入的回答数据
  • data: 从配置文件读取的只读的初始化数据
  • files: 被找到的文件的描述器 ({ type: 'json', path: '...' })
  • cwd: 当前工作目录
  • api: onWrite API (详见下方)

提示符的运行时对象:

{  id: data.name,  type: data.type,  name: data.short || null,  message: data.message,  group: data.group || null,  description: data.description || null,  link: data.link || null,  choices: null,  visible: true,  enabled: true,  // 当前值 (未被过滤的)  value: null,  // 如果用户修改过了则为 true  valueChanged: false,  error: null,  tabId: null,  // 原始的 inquirer 提示符对象  raw: data}

onWrite API:

  • assignData(fileId, newData): 在写入前使用 Object.assign 来更新配置文件。
  • setData(fileId, newData): newData 的每个 key 在写入之前都将会被深设置在配置数据上 (或当值为 undefined 时被移除)。
  • async getAnswer(id, mapper): 为一个给定的提示符 id 获取答复并通过可能提供了的 mapper 函数 (例如 JSON.parse) 进行 map 处理。

示例 (来自 ESLint 插件):

api.describeConfig({  // ...  onWrite: async ({ api, prompts }) => {    // 更新 ESLint 规则    const result = {}    for (const prompt of prompts) {      result[`rules.${prompt.id}`] = await api.getAnswer(prompt.id, JSON.parse)    }    api.setData('eslint', result)  }})

项目的任务

任务 UI

任务是从项目 package.json 文件的 scripts 字段生成的。

因为有 api.describeTask 方法,你可以为任务“增强”额外的信息和钩子:

api.describeTask({  // 用于匹配脚本命令的 RegExp 对象,来选择要被描述的任务  match: /vue-cli-service serve/,  description: 'Compiles and hot-reloads for development',  // “More info”链接  link: 'https://github.com/vuejs/vue-cli/blob/dev/docs/cli-service.md#serve'})

任务图标

可以是一个 Material 图标代码或一个自定义的图片 (详见公共静态文件):

api.describeTask({  /* ... */  // 任务图标  icon: 'application_settings'})

如果你没有定义图标,那就展示该插件可能存在的 logo (详见 Logo)。

任务参数

你可以添加提示符来修改命令参数。它们会展示在一个“参数”模态框中。

Example:

api.describeTask({  // ...  // 选填参数 (inquirer 提示符)  prompts: [    {      name: 'open',      type: 'confirm',      default: false,      description: 'Open browser on server start'    },    {      name: 'mode',      type: 'list',      default: 'development',      choices: [        {          name: 'development',          value: 'development'        },        {          name: 'production',          value: 'production'        },        {          name: 'test',          value: 'test'        }      ],      description: 'Specify env mode'    }  ]})

详见提示符

任务钩子

有一些钩子是可用的:

  • onBeforeRun
  • onRun
  • onExit

例如,你可以将 (上述) 提示符的回答作为一个新参数添加到命令上:

api.describeTask({  // ...  // 钩子  // 在这里修改参数  onBeforeRun: async ({ answers, args }) => {    // 参数    if (answers.open) args.push('--open')    if (answers.mode) args.push('--mode', answers.mode)    args.push('--dashboard')  },  // 任务运行之后立即执行  onRun: async ({ args, child, cwd }) => {    // child: Node 子进程    // cwd: 进程所在目录  },  onExit: async ({ args, child, cwd, code, signal }) => {    // code: 退出码    // signal: 可能会被使用的杀进程信号  }})

任务视图

你可以在任务详情面板中使用 ClientAddon API 展示自定义视图:

api.describeTask({  // ...  // 额外的视图 (例如 webpack 仪表盘)  // 默认情况下,这里是展示终端输出的 `output` 视图  views: [    {      // 唯一 ID      id: 'vue-webpack-dashboard-client-addon',      // 按钮文字      label: 'Dashboard',      // 按钮图标      icon: 'dashboard',      // 要加载的动态组件 (详见下述“客户端 addon”章节)      component: 'vue-webpack-dashboard'    }  ],  // 展示任务详情时默认选择的视图 (默认是 `output`)  defaultView: 'vue-webpack-dashboard-client-addon'})

详见客户端 addon

新增任务

你也可以不使用 api.describeTask,而是通过 api.addTask 添加一个 package.json 脚本中没有的全新任务。这些任务只会出现在 cli UI 中。

你需要提供一个 command 选项替代掉 match 选项。

示例:

api.addTask({  // 必填  name: 'inspect',  command: 'vue-cli-service inspect',  // 选填  // 其余部分类似 `describeTask` 但是没有 `match` 选项  description: '...',  link: 'https://github.com/vuejs/vue-cli/...',  prompts: [ /* ... */ ],  onBeforeRun: () => {},  onRun: () => {},  onExit: () => {},  views: [ /* ... */ ],  defaultView: '...'})

警告

command 将会运行一个 Node 上下文。也就是说你可以像在 package.json 脚本中一样调用 Node 的 bin 命令。

提示符

提示符对象必须是合法的 inquirer 对象。

不过你也可以添加下列额外的字段 (只会被 UI 使用的可选项):

{  /* ... */  // 用来将提示符按章节分组  group: 'Strongly recommended',  // 附加描述  description: 'Enforce attribute naming style in template (`my-prop` or `myProp`)',  // “More info”链接  link: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/attribute-hyphenation.md',}

支持的 inquirer 类型有:checkbox、confirm、input、password、list、rawlist。

此外,UI 还支持了仅在这里工作的特殊类型:

  • color:展示一个取色器。

Switch 示例

{  name: 'open',  type: 'confirm',  default: false,  description: 'Open the app in the browser'}

Select 示例

{  name: 'mode',  type: 'list',  default: 'development',  choices: [    {      name: 'Development mode',      value: 'development'    },    {      name: 'Production mode',      value: 'production'    },    {      name: 'Test mode',      value: 'test'    }  ],  description: 'Build mode',  link: 'https://link-to-docs'}

Input 示例

{  name: 'host',  type: 'input',  default: '0.0.0.0',  description: 'Host for the development server'}

Checkbox 示例

展示多个 switch。

{  name: 'lintOn',  message: 'Pick additional lint features:',  when: answers => answers.features.includes('linter'),  type: 'checkbox',  choices: [    {      name: 'Lint on save',      value: 'save',      checked: true    },    {      name: 'Lint and fix on commit' + (hasGit() ? '' : chalk.red(' (requires Git)')),      value: 'commit'    }  ]}

取色器示例

{  name: 'themeColor',  type: 'color',  message: 'Theme color',  description: 'This is used to change the system UI color around the app',  default: '#4DBA87'}

提示符的改进

在 vue-cli 插件中,你可能已经有一个 prompts.js 文件,在 (用 CLI 或 UI) 安装该插件的时候询问用户一些问题。你可以向那些提示符对象额外添加只支持 UI 的上述字段,这样的话如果用户使用 UI 的话可以看到更多的信息。

警告

目前,那些不支持的 inquirer 类型不会在 UI 中正常工作。

客户端 addon

客户端 addon 是一个动态加载到 cli-ui 中的 JS 包。用于加载自定义组件和路由。

创建一个客户端 addon

推荐的创建一个客户端 addon 的方式是通过 vue cli 创建一个新项目。你也可以在插件的子目录或不同的 npm 包中这样做。

作为开发依赖安装 @vue/cli-ui。

然后添加一个 vue.config.js 文件并附带以下内容:

const { clientAddonConfig } = require('@vue/cli-ui')module.exports = {  ...clientAddonConfig({    id: 'org.vue.webpack.client-addon',    // 开发环境端口 (默认值 8042)    port: 8042  })}

这个 clientAddonConfig 方法将会生成需要的 vue-cli 配置。除此之外,它会禁用 CSS extraction 并将代码输出到在客户端 addon 目录的 ./dist/index.js。

危险

请确定为 id 设置正确的命名空间,因为它需要跨所有插件保持唯一。我们推荐使用反向域名记号 (reverse domain name notation)

然后修改 .eslintrc.json 文件以添加一些允许的全局对象:

{  // ...  "globals": {    "ClientAddonApi": false,    "mapSharedData": false,    "Vue": false  }}

你现在可以在开发环境下运行 serve 脚本,也可以在准备发布时运行 build 脚本。

ClientAddonApi

在客户端 addon 资源中打开 main.js 文件并删除所有代码。

警告

别在客户端 addon 源文件总导入 Vue ,请从浏览器 window 使用全局的 Vue 对象。

这里是一个 main.js 的示例代码:

import VueProgress from 'vue-progress-path'import WebpackDashboard from './components/WebpackDashboard.vue'import TestView from './components/TestView.vue'// 你可以安装额外的 Vue 插件// 使用全局的 'Vue' 变量Vue.use(VueProgress, {  defaultShape: 'circle'})// 注册一个自定义组件// (工作原理类似 'Vue.component')ClientAddonApi.component('org.vue.webpack.components.dashboard', WebpackDashboard)// 在 vue-router 中为 /addon/<id> 添加子路由。// 例如,addRoutes('foo', [ { path: '' }, { path: 'bar' } ])// 将会向路由器添加 /addon/foo/ 和 /addon/foo/bar。// 我们在此用 'test-webpack-route' 名称创建一个新的 '/addon/vue-webpack/' 路由ClientAddonApi.addRoutes('org.vue.webpack', [  { path: '', name: 'org.vue.webpack.routes.test', component: TestView }])// 你可以翻译插件组件// (通过使用 vue-i18n) 加载语言文件const locales = require.context('./locales', true, /[a-z0-9]+.json$/i)locales.keys().forEach(key => {  const locale = key.match(/([a-z0-9]+)./i)[1]  ClientAddonApi.addLocalization(locale, locales(key))})

危险

请确定为 id 设置正确的命名空间,因为它需要跨所有插件保持唯一。我们推荐使用反向域名记号 (reverse domain name notation)

cli-ui 在 window 作用域内注册了 Vue 和 ClientAddonApi 作为全局变量。

你可以在自己的组件里使用 @vue/ui 和 @vue/cli-ui 所有的组件和 CSS class 以保持样式和体验的一致性。你也可以用内置的 vue-i18n 翻译字符串。

注册客户端 addon

回到 ui.js 文件,使用 api.addClientAddon 方法并带一个指向构建后的文件夹的 require 字符串:

api.addClientAddon({  id: 'org.vue.webpack.client-addon',  // 包含构建出来的 JS 文件的文件夹  path: '@vue/cli-ui-addon-webpack/dist'})

这会使用 Node.js 的 require.resolve API 查找文件夹并为从客户端 addon 构建的文件 index.js 启动一个服务器。

或者当开发插件时指定一个 URL (理想中你需要在 Vue 的测试项目的 vue-cli-ui.js 中做这些)

// 用于开发环境// 如果已经在插件中定义过,则会覆写路径api.addClientAddon({  id: 'org.vue.webpack.client-addon',  // 使用你之前配置过低同样的端口  url: 'http://localhost:8042/index.js'})

使用客户端 addon

现在你可以在这些视图中使用客户端 addon 了。例如,你可以在一个被描述的任务中指定一个视图:

api.describeTask({  /* ... */  // 额外的视图 (例如 webpack dashboard)  // 默认情况下,这是展示终端输出的 'output' 视图  views: [    {      // 唯一的 ID      id: 'org.vue.webpack.views.dashboard',      // 按钮文字      label: 'Dashboard',      // 按钮图标 (material-icons)      icon: 'dashboard',      // 加载的动态组件,会用 ClientAddonApi 进行注册      component: 'org.vue.webpack.components.dashboard'    }  ],  // 展示任务详情时默认选择的视图 (默认情况下就是 output)  defaultView: 'org.vue.webpack.views.dashboard'})

这是一个客户端 addon 代码,注册了 `'org.vue.webpack.components.dashboard' 组件 (像我们之前看到的一样):

/* 在 `main.js` 中 */// 导入组件import WebpackDashboard from './components/WebpackDashboard.vue'// 注册自定义组件// (工作原理类似 'Vue.component')ClientAddonApi.component('org.vue.webpack.components.dashboard', WebpackDashboard)

任务视图示例

自定义视图

你可以使用 api.addView 方法在标准的“Project plugins”、“Project configuration”和“Project tasks”之下添加一个新的视图:

api.addView({  // 唯一的 id  id: 'org.vue.webpack.views.test',  // 路由名称 (来自 Vue Router)  // 使用 'ClientAddonApi.addRoutes' 方法中相同的名字 (详见之前的客户端 addon 章节)  name: 'org.vue.webpack.routes.test',  // 按钮图标 (material-icons)  icon: 'pets',  // 你也可以指定一个自定义图片 (详见之前的公共静态文件章节):  // icon: 'http://localhost:4000/_plugin/%40vue%2Fcli-service/webpack-icon.svg',  // 按钮的提示文字  tooltip: 'Test view from webpack addon'})

这里是注册了 'org.vue.webpack.routes.test' 的客户端 addon 里的代码 (之前已经见过了):

/* 在 `main.js` 里 */// 导入组件import TestView from './components/TestView.vue'// 在 vue-router 中为 /addon/<id> 添加子路由// 例如,addRoutes('foo', [ { path: '' }, { path: 'bar' } ])// 将为 Vue Router 添加 /addon/foo/ 和 /addon/foo/bar 路由。// 我们这里创建一个新的 '/addon/vue-webpack/' 路由,并命名为 'test-webpack-route'。ClientAddonApi.addRoutes('org.vue.webpack', [  { path: '', name: 'org.vue.webpack.routes.test', component: TestView }])

自定义视图示例

共享的数据

一种简易的自定义组件之间通过共享的数据互通信息的方式。

例如,webpack 仪表盘在 UI 客户端和 UI 服务端之间通过这个 API 共享了构建的统计信息。

在插件 ui.js (Node.js) 中:

// 设置或更新api.setSharedData('com.my-name.my-variable', 'some-data')// 获取const sharedData = api.getSharedData('com.my-name.my-variable')if (sharedData) {  console.log(sharedData.value)}// 移除api.removeSharedData('com.my-name.my-variable')// 侦听变化const watcher = (value, id) => {  console.log(value, id)}api.watchSharedData('com.my-name.my-variable', watcher)// 取消侦听api.unwatchSharedData('com.my-name.my-variable', watcher)// 带命名空间的版本const {  setSharedData,  getSharedData,  removeSharedData,  watchSharedData,  unwatchSharedData} = api.namespace('com.my-name.')

危险

请确定为 id 设置正确的命名空间,因为它需要跨所有插件保持唯一。我们推荐使用反向域名记号 (reverse domain name notation)

在其自定义组件中:

// Vue 组件export default {  // 同步共享的数据  sharedData () {    return {      // 你可以在模板中使用 `myVariable`      myVariable: 'com.my-name.my-variable'      // 也可以映射带命名空间的共享数据      ...mapSharedData('com.my-name.', {        myVariable2: 'my-variable2'      })    }  },  // 手动方法  async created () {    const value = await this.$getSharedData('com.my-name.my-variable')    this.$watchSharedData(`com.my-name.my-variable`, value => {      console.log(value)    })    await this.$setSharedData('com.my-name.my-variable', 'new-value')  }}

如果你使用了 sharedData 选项,共享的数据就可以一个相应的属性被赋值时进行更新。

<template>  <VueInput v-model="message"/></template><script>export default {  sharedData: {    // 将会在服务端同步 'my-message' 共享的数据    message: 'com.my-name.my-message'  }}</script>

例如在创建一个设置组件时,这个特性是非常有用的。

插件的 action

插件的 action 就是在 cli-ui (浏览器) 和插件 (Node.js) 直接的调用。

例如,你可能有一个自定义组件里的按钮 (详见客户端 addon),这个按钮会通过这个 API 向服务端调用一些 Node.js 代码。

在插件 (Node.js) 的 ui.js 文件里,你可以从 PluginApi 使用两个方法:

// 调用一个 actionapi.callAction('com.my-name.other-action', { foo: 'bar' }).then(results => {  console.log(results)}).catch(errors => {  console.error(errors)})
// 监听一个 actionapi.onAction('com.my-name.test-action', params => {  console.log('test-action called', params)})

危险

请确定为 id 设置正确的命名空间,因为它需要跨所有插件保持唯一。我们推荐使用反向域名记号 (reverse domain name notation)

你可以通过 api.namespace 使用带命名空间的版本 (类似共享的数据):

const { onAction, callAction } = api.namespace('com.my-name.')

在客户端 addon 组件 (浏览器) 中,你可以访问 $onPluginActionCalled、$onPluginActionResolved 和 $callPluginAction:

// Vue 组件export default {  created () {    this.$onPluginActionCalled(action => {      // 当 action 被调用时      // 且在运行之前      console.log('called', action)    })    this.$onPluginActionResolved(action => {      // 当 action 运行完毕之后      console.log('resolved', action)    })  },  methods: {    testPluginAction () {      // 调用一个插件的 action      this.$callPluginAction('com.my-name.test-action', {        meow: 'meow'      })    }  }}

进程间通信 (IPC)

IPC 就是进程间通信 (Inter-Process Communication) 的缩写。该系统允许你轻松的从子进程 (例如任务) 发送消息,并且轻量快速。

为了在 webpack 仪表盘 UI 上展示数据,@vue/cli-service 的 serve 和 build 命令会在 --dashboard 参数被传入时向 cli-ui Node.js 服务器发送 IPC 消息。

在进程代码中 (可以是一个 webpack 插件或一个 Node.js 的任务脚本),你可以使用 @vue/cli-shared-utils 中的 IpcMessenger 类:

const { IpcMessenger } = require('@vue/cli-shared-utils')// 创建一个新的 IpcMessenger 实例const ipc = new IpcMessenger()function sendMessage (data) {  // 发送一条消息给 cli-ui 服务器  ipc.send({    'com.my-name.some-data': {      type: 'build',      value: data    }  })}function messageHandler (data) {  console.log(data)}// 监听消息ipc.on(messageHandler)// 不再监听ipc.off(messageHandler)function cleanup () {  // 从 IPC 网络断开连接  ipc.disconnect()}

手动连接:

const ipc = new IpcMessenger({  autoConnect: false})// 这条消息会被放入队列ipc.send({ ... })ipc.connect()

闲时自动断开连接 (在没有任何消息一段时间之后):

const ipc = new IpcMessenger({  disconnectOnIdle: true,  idleTimeout: 3000 // 默认值})ipc.send({ ... })setTimeout(() => {  console.log(ipc.connected) // false}, 3000)

连接到另一个 IPC 网络:

const ipc = new IpcMessenger({  networkId: 'com.my-name.my-ipc-network'})

在一个 vue-cli 插件的 ui.js 文件中,你可以使用 ipcOn、ipcOff 和 ipcSend 方法:

function onWebpackMessage ({ data: message }) {  if (message['com.my-name.some-data']) {    console.log(message['com.my-name.some-data'])  }}// 监听任何 IPC 消息api.ipcOn(onWebpackMessage)// 不监听任何消息api.ipcOff(onWebpackMessage)// 向所有已连接的 IpcMessenger 实例发送一条消息api.ipcSend({  webpackDashboardMessage: {    foo: 'bar'  }})

本地存储

一个插件可以从 UI 服务器本地的 lowdb 数据库保存和加载数据。

// 向本地的数据库存入一个值api.storageSet('com.my-name.an-id', { some: 'value' })// 从本地的数据库取回一个值console.log(api.storageGet('com.my-name.an-id'))// 完整的 lowdb 实例api.db.get('posts')  .find({ title: 'low!' })  .assign({ title: 'hi!'})  .write()// 带命名空间的辅助函数const { storageGet, storageSet } = api.namespace('my-plugin.')

危险

请确定为 id 设置正确的命名空间,因为它需要跨所有插件保持唯一。我们推荐使用反向域名记号 (reverse domain name notation)

Notification

你可以基于用户操作系统的通知系统展示通知:

api.notify({  title: 'Some title',  message: 'Some message',  icon: 'path-to-icon.png'})

这里有一些内建的图标;

  • 'done'
  • 'error'

进度界面

你可以用一些文字或进度条来展示进度界面:

api.setProgress({  status: 'Upgrading...',  error: null,  info: 'Step 2 of 4',  progress: 0.4 // 从 0 到 1, -1 表示隐藏进度条})

移除进度界面:

api.removeProgress()

钩子

钩子可以用来响应某些 cli-ui 的事件。

onProjectOpen

当插件在当前项目中第一次被加载时触发。

api.onProjectOpen((project, previousProject) => {  // 重置数据})

onPluginReload

当插件被重新加载时触发。

api.onPluginReload((project) => {  console.log('plugin reloaded')})

onConfigRead

当一个配置界面被打开或刷新时触发。

api.onConfigRead(({ config, data, onReadData, tabs, cwd }) => {  console.log(config.id)})

onConfigWrite

当用户在保存界面里保存时触发。

api.onConfigWrite(({ config, data, changedFields, cwd }) => {  // ...})

onTaskOpen

当用户打开一项任务的详情面板时触发。

api.onTaskOpen(({ task, cwd }) => {  console.log(task.id)})

onTaskRun

当用户运行一项任务时触发。

api.onTaskRun(({ task, args, child, cwd }) => {  // ...})

onTaskExit

当一项任务退出时触发。不论任务成功或失败它都会触发。

api.onTaskExit(({ task, args, child, signal, code, cwd }) => {  // ...})

onViewOpen

当用户打开一个视图 (如 'Plugins'、'Configurations' 或 'Tasks') 时触发。

api.onViewOpen(({ view, cwd }) => {  console.log(view.id)})

建议

这里的建议是指为用户提议执行 action 的按钮。它们展示在界面的顶栏上。例如我们可以放一个按钮,在应用里没有检测到 Vue Router 包的时候建议将其安装。

api.addSuggestion({  id: 'com.my-name.my-suggestion',  type: 'action', // 必填 (未来会加入更多类型)  label: 'Add vue-router',  // 该消息会展示在一个详情模态框里  message: 'A longer message for the modal',  link: 'http://link-to-docs-in-the-modal',  // 可选的图片  image: '/_plugin/my-package/screenshot.png',  // 当该项建议被用户激活时调用的函数  async handler () {    // ...    return {      // 默认移除这个按钮      keep: false    }  }})

危险

请确定为 id 设置正确的命名空间,因为它需要跨所有插件保持唯一。我们推荐使用反向域名记号 (reverse domain name notation)

UI 建议

之后你可以移除这项建议:

api.removeSuggestion('com.my-name.my-suggestion')

你也可以给建议附带 actionLink,当用户激活它时,会换做打开一个页面:

api.addSuggestion({  id: 'com.my-name.my-suggestion',  type: 'action', // Required  label: 'Add vue-router',  // 打开一个新标签  actionLink: 'https://vuejs.org/'})

通常情况下,你会选择适当的上下文用钩子来展示建议:

const ROUTER = 'vue-router-add'api.onViewOpen(({ view }) => {  if (view.id === 'vue-project-plugins') {    if (!api.hasPlugin('vue-router')) {      api.addSuggestion({        id: ROUTER,        type: 'action',        label: 'org.vue.cli-service.suggestions.vue-router-add.label',        message: 'org.vue.cli-service.suggestions.vue-router-add.message',        link: 'https://router.vuejs.org/',        async handler () {          await install(api, 'vue-router')        }      })    }  } else {    api.removeSuggestion(ROUTER)  }})

在这个例子中,如果 Vue Router 没有安装好,我们只会在插件视图中展示安装 Vue Router 的建议。

注意

addSuggestion 和 removeSuggestion 可以通过 api.namespace() 指定命名空间。

其它方法

hasPlugin

如果项目使用了该插件则返回 true。

api.hasPlugin('eslint')api.hasPlugin('apollo')api.hasPlugin('vue-cli-plugin-apollo')

getCwd

获取当前工作目录。

api.getCwd()

resolve

在当前工程下解析一个文件:

api.resolve('src/main.js')

getProject

得出当前打开的工程。

api.getProject()

公共静态文件

你可能需要在 cli-ui 内建的 HTTP 服务器上暴露一些静态文件 (通常是为自定义视图指定图标)。

在插件包根目录里可选的放置一个 ui-public 文件夹,这个文件夹里的任何文件都会暴露至 /_plugin/:id/* 的 HTTP 路由。

例如,如果你将 my-logo.png 文件放置到 vue-cli-plugin-hello/ui-public/ 文件夹,那么 cli-ui 加载插件的时候可以通过 /_plugin/vue-cli-plugin-hello/my-logo.png 这个 URL 来访问它。

api.describeConfig({  /* ... */  // 自定义图片  icon: '/_plugin/vue-cli-plugin-hello/my-logo.png'})


标准 UI

请遵循下列简单步骤来为 CLI UI 提交一种其它语言的翻译!

  1. 运行 navigator.languages 或 navigator.language 为新的地区获取语言代码。例如:'fr'。
  2. 搜索 npm 确认名为 vue-cli-locale-<language code> 的包是否已经存在。如果存在,则请通过 PR 为它贡献!如果没找到,则创建一个新的名为 vue-cli-locale-<language code> 的地区的包。例如:vue-cli-locale-fr.
  3. 将地区的 JSON 文件放置在一个 locales 文件夹并将这个文件命名为语言代码。例如:locales/fr.json。
  4. 在 package.json 文件中,设置 unpkg 字段为地区文件的路径。例如:"unpkg": "./locales/fr.json"。
  5. 将包发布到 npm 上。

可以参考这里的英文地区文件。

作为示例,参考一份法语的包

翻译插件

你也可以在插件的根目录的 locales 文件夹放置与 vue-i18n 兼容的地区文件。这样做会在项目打开的时候自动加载,然后你可以使用 $t 在你的组件和 vue-i18n 辅助函数里翻译字符串。同样的 UI API (像 describeTask) 用到的字符串将会进入 vue-i18n,这样你就可以对它们做本地化。

示例 locales 文件夹:

vue-cli-plugin/locales/en.jsonvue-cli-plugin/locales/fr.json

API 的用法示例:

api.describeConfig({  // vue-i18n 路径  description: 'com.my-name.my-plugin.config.foo'})

危险

请确定为 id 设置正确的命名空间,因为它需要跨所有插件保持唯一。我们推荐使用反向域名记号 (reverse domain name notation)

在组件中使用的示例:

<VueButton>{{ $t('com.my-name.my-plugin.actions.bar') }}</VueButton>

如果你愿意的话,可以使用 ClientAddonApi 在一个客户端 addon 加载地区文件:

// 加载本地文件 (使用 vue-i18n)const locales = require.context('./locales', true, /[a-z0-9]+.json$/i)locales.keys().forEach(key => {  const locale = key.match(/([a-z0-9]+)./i)[1]  ClientAddonApi.addLocalization(locale, locales(key))})


警告

这份文档是对应 @vue/cli。老版本的 vue-cli 文档请移步这里

Vue CLI 是一个基于 Vue.js 进行快速开发的完整系统,提供:

  • 通过 @vue/cli 搭建交互式的项目脚手架。
  • 通过 @vue/cli + @vue/cli-service-global 快速开始零配置原型开发。
  • 一个运行时依赖 (@vue/cli-service),该依赖:可升级;基于 webpack 构建,并带有合理的默认配置;可以通过项目内的配置文件进行配置;可以通过插件进行扩展。
  • 一个丰富的官方插件集合,集成了前端生态中最好的工具。
  • 一套完全图形化的创建和管理 Vue.js 项目的用户界面。

Vue CLI 致力于将 Vue 生态中的工具基础标准化。它确保了各种构建工具能够基于智能的默认配置即可平稳衔接,这样你可以专注在撰写应用上,而不必花好几天去纠结配置的问题。与此同时,它也为每个工具提供了调整配置的灵活性,无需 eject。

该系统的组件

Vue CLI 有几个独立的部分——如果你看到了我们的源代码,你会发现这个仓库里同时管理了多个单独发布的包。

CLI

CLI (@vue/cli) 是一个全局安装的 npm 包,提供了终端里的 vue 命令。它可以通过 vue create 快速创建一个新项目的脚手架,或者直接通过 vue serve 构建新想法的原型。你也可以通过 vue ui 通过一套图形化界面管理你的所有项目。我们会在接下来的指南中逐章节深入介绍。

CLI 服务

CLI 服务 (@vue/cli-service) 是一个开发环境依赖。它是一个 npm 包,局部安装在每个 @vue/cli 创建的项目中。

CLI 服务是构建于 webpack 和 webpack-dev-server 之上的。它包含了:

  • 加载其它 CLI 插件的核心服务;
  • 一个针对绝大部分应用优化过的内部的 webpack 配置;
  • 项目内部的 vue-cli-service 命令,提供 serve、build 和 inspect 命令。

如果你熟悉 create-react-app 的话,@vue/cli-service 实际上大致等价于 react-scripts,尽管功能集合不一样。

CLI 服务章节涵盖了它的具体用法。

CLI 插件

CLI 插件是向你的 Vue 项目提供可选功能的 npm 包,例如 Babel/TypeScript 转译、ESLint 集成、单元测试和 end-to-end 测试等。Vue CLI 插件的名字以 @vue/cli-plugin- (内建插件) 或 vue-cli-plugin- (社区插件) 开头,非常容易使用。

当你在项目内部运行 vue-cli-service 命令时,它会自动解析并加载 package.json 中列出的所有 CLI 插件。

插件可以作为项目创建过程的一部分,或在后期加入到项目中。它们也可以被归成一组可复用的 preset。我们会在插件和 preset 章节进行深入讨论。


关于旧版本

如果您已经安装了旧版本的vue-cli(1.x或2.x),您需要先通过npm卸载vue-cli -g或yarn global remove vue-cli卸载它。

节点版本要求

Vue CLI需要  Node.js  8.9或更高版本(推荐8.11.0+)。您可以使用  nvm  或  nvm-windows  在同一台计算机中管理多个Node版本。

可以使用以下任一命令安装这个新的包:

npm install -g @vue/cli# ORyarn global add @vue/cli

安装之后,您就可以在命令行中访问vue命令。您可以通过简单运行vue,看看是否展示出了一份所有可用命令的帮助信息,来验证它是否安装成功。

你还可以用这个命令来检查其版本是否正确:

vue --version


你可以使用 vue serve 和 vue build 命令对单个 *.vue 文件进行快速原型开发,不过这需要先额外安装一个全局的扩展:

npm install -g @vue/cli-service-global

vue serve 的缺点就是它需要安装全局依赖,这使得它在不同机器上的一致性不能得到保证。因此这只适用于快速原型开发。

vue serve

Usage: serve [options] [entry]在开发环境模式下零配置为 .js 或 .vue 文件启动一个服务器Options:  -o, --open  打开浏览器  -c, --copy  将本地 URL 复制到剪切板  -h, --help  输出用法信息

你所需要的仅仅是一个 App.vue 文件:

<template>  <h1>Hello!</h1></template>

然后在这个 App.vue 文件所在的目录下运行:

vue serve

vue serve 使用了和 vue create 创建的项目相同的默认设置 (webpack、Babel、PostCSS 和 ESLint)。它会在当前目录自动推导入口文件——入口可以是 main.js、index.js、App.vue 或 app.vue 中的一个。你也可以显式地指定入口文件:

vue serve MyComponent.vue

如果需要,你还可以提供一个 index.html、package.json、安装并使用本地依赖、甚至通过相应的配置文件配置 Babel、PostCSS 和 ESLint。

vue build

Usage: build [options] [entry]在生产环境模式下零配置构建一个 .js 或 .vue 文件Options:  -t, --target <target>  构建目标 (app | lib | wc | wc-async, 默认值:app)  -n, --name <name>      库的名字或 Web Components 组件的名字 (默认值:入口文件名)  -d, --dest <dir>       输出目录 (默认值:dist)  -h, --help             输出用法信息

你也可以使用 vue build 将目标文件构建成一个生产环境的包并用来部署:

vue build MyComponent.vue

vue build 也提供了将组件构建成为一个库或一个 Web Components 组件的能力。查阅构建目标了解更多。


vue create

运行以下命令来创建一个新项目:

vue create hello-world

警告

如果你在 Windows 上通过 minTTY 使用 Git Bash,交互提示符并不工作。你必须通过 winpty vue.cmd create hello-world 启动这个命令。不过,如果你仍想使用 vue create hello-world,则可以通过在 ~/.bashrc 文件中添加以下行来为命令添加别名。 alias vue='winpty vue.cmd' 你需要重新启动 Git Bash 终端会话以使更新后的 bashrc 文件生效。

你会被提示选取一个 preset。你可以选默认的包含了基本的 Babel + ESLint 设置的 preset,也可以选“手动选择特性”来选取需要的特性。

CLI 预览

这个默认的设置非常适合快速创建一个新项目的原型,而手动设置则提供了更多的选项,它们是面向生产的项目更加需要的。

CLI 预览

如果你决定手动选择特性,在操作提示的最后你可以选择将已选项保存为一个将来可复用的 preset。我们会在下一个章节讨论 preset 和插件。

~/.vuerc

被保存的 preset 将会存在用户的 home 目录下一个名为 .vuerc 的 JSON 文件里。如果你想要修改被保存的 preset / 选项,可以编辑这个文件。

在项目创建的过程中,你也会被提示选择喜欢的包管理器或使用淘宝 npm 镜像源以更快地安装依赖。这些选择也将会存入 ~/.vuerc。

vue create 命令有一些可选项,你可以通过运行以下命令进行探索:

vue create --help
用法:create [options] <app-name>创建一个由 `vue-cli-service` 提供支持的新项目选项:  -p, --preset <presetName>       忽略提示符并使用已保存的或远程的预设选项  -d, --default                   忽略提示符并使用默认预设选项  -i, --inlinePreset <json>       忽略提示符并使用内联的 JSON 字符串预设选项  -m, --packageManager <command>  在安装依赖时使用指定的 npm 客户端  -r, --registry <url>            在安装依赖时使用指定的 npm registry  -g, --git [message]             强制 / 跳过 git 初始化,并可选的指定初始化提交信息  -n, --no-git                    跳过 git 初始化  -f, --force                     覆写目标目录可能存在的配置  -c, --clone                     使用 git clone 获取远程预设选项  -x, --proxy                     使用指定的代理创建项目  -b, --bare                      创建项目时省略默认组件中的新手指导信息  -h, --help                      输出使用帮助信息

使用图形化界面

你也可以通过 vue ui 命令以图形化界面创建和管理项目:

vue ui

上述命令会打开一个浏览器窗口,并以图形化界面将你引导至项目创建的流程。

图形化界面预览

拉取 2.x 模板 (旧版本)

Vue CLI >= 3 和旧版使用了相同的 vue 命令,所以 Vue CLI 2 (vue-cli) 被覆盖了。如果你仍然需要使用旧版本的 vue init 功能,你可以全局安装一个桥接工具:

npm install -g @vue/cli-init# `vue init` 的运行效果将会跟 `vue-cli@2.x` 相同vue init webpack my-project


插件

Vue CLI 使用了一套基于插件的架构。如果你查阅一个新创建项目的 package.json,就会发现依赖都是以 @vue/cli-plugin- 开头的。插件可以修改 webpack 的内部配置,也可以向 vue-cli-service 注入命令。在项目创建的过程中,绝大部分列出的特性都是通过插件来实现的。

基于插件的架构使得 Vue CLI 灵活且可扩展。如果你对开发一个插件感兴趣,请翻阅插件开发指南

提示

你可以通过 vue ui 命令使用 GUI 安装和管理插件。

在现有的项目中安装插件

每个 CLI 插件都会包含一个 (用来创建文件的) 生成器和一个 (用来调整 webpack 核心配置和注入命令的) 运行时插件。当你使用 vue create 来创建一个新项目的时候,有些插件会根据你选择的特性被预安装好。如果你想在一个已经被创建好的项目中安装一个插件,可以使用 vue add 命令:

vue add eslint

提示

vue add 的设计意图是为了安装和调用 Vue CLI 插件。这不意味着替换掉普通的 npm 包。对于这些普通的 npm 包,你仍然需要选用包管理器。

警告

我们推荐在运行 vue add 之前将项目的最新状态提交,因为该命令可能调用插件的文件生成器并很有可能更改你现有的文件。

这个命令将 @vue/eslint 解析为完整的包名 @vue/cli-plugin-eslint,然后从 npm 安装它,调用它的生成器。

# 这个和之前的用法等价vue add cli-plugin-eslint

如果不带 @vue 前缀,该命令会换作解析一个 unscoped 的包。例如以下命令会安装第三方插件 vue-cli-plugin-apollo:

# 安装并调用 vue-cli-plugin-apollovue add apollo

你也可以基于一个指定的 scope 使用第三方插件。例如如果一个插件名为 @foo/vue-cli-plugin-bar,你可以这样添加它:

vue add @foo/bar

你可以向被安装的插件传递生成器选项 (这样做会跳过命令提示):

vue add eslint --config airbnb --lintOn save

如果一个插件已经被安装,你可以使用 vue invoke 命令跳过安装过程,只调用它的生成器。这个命令会接受和 vue add 相同的参数。

提示

如果出于一些原因你的插件列在了该项目之外的其它 package.json 文件里,你可以在自己项目的 package.json 里设置 vuePlugins.resolveFrom 选项指向包含其它 package.json 的文件夹。

例如,如果你有一个 .config/package.json 文件:

{  "vuePlugins": {    "resolveFrom": ".config"  }}

项目本地的插件

如果你需要在项目里直接访问插件 API 而不需要创建一个完整的插件,你可以在 package.json 文件中使用 vuePlugins.service 选项:

{  "vuePlugins": {    "service": ["my-commands.js"]  }}

每个文件都需要暴露一个函数,接受插件 API 作为第一个参数。关于插件 API 的更多信息可以查阅插件开发指南

你也可以通过 vuePlugins.ui 选项添加像 UI 插件一样工作的文件:

{  "vuePlugins": {    "ui": ["my-ui.js"]  }}

更多信息请阅读 UI 插件 API

Preset

一个 Vue CLI preset 是一个包含创建新项目所需预定义选项和插件的 JSON 对象,让用户无需在命令提示中选择它们。

在 vue create 过程中保存的 preset 会被放在你的 home 目录下的一个配置文件中 (~/.vuerc)。你可以通过直接编辑这个文件来调整、添加、删除保存好的 preset。

这里有一个 preset 的示例:

{  "useConfigFiles": true,  "cssPreprocessor": "sass",  "plugins": {    "@vue/cli-plugin-babel": {},    "@vue/cli-plugin-eslint": {      "config": "airbnb",      "lintOn": ["save", "commit"]    },    "@vue/cli-plugin-router": {},    "@vue/cli-plugin-vuex": {}  }}

Preset 的数据会被插件生成器用来生成相应的项目文件。除了上述这些字段,你也可以为集成工具添加配置:

{  "useConfigFiles": true,  "plugins": {...},  "configs": {    "vue": {...},    "postcss": {...},    "eslintConfig": {...},    "jest": {...}  }}

这些额外的配置将会根据 useConfigFiles 的值被合并到 package.json 或相应的配置文件中。例如,当 "useConfigFiles": true 的时候,configs 的值将会被合并到 vue.config.js 中。

Preset 插件的版本管理

你可以显式地指定用到的插件的版本:

{  "plugins": {    "@vue/cli-plugin-eslint": {      "version": "^3.0.0",      // ... 该插件的其它选项    }  }}

注意对于官方插件来说这不是必须的——当被忽略时,CLI 会自动使用 registry 中最新的版本。不过我们推荐为 preset 列出的所有第三方插件提供显式的版本范围。

允许插件的命令提示

每个插件在项目创建的过程中都可以注入它自己的命令提示,不过当你使用了一个 preset,这些命令提示就会被跳过,因为 Vue CLI 假设所有的插件选项都已经在 preset 中声明过了。

在有些情况下你可能希望 preset 只声明需要的插件,同时让用户通过插件注入的命令提示来保留一些灵活性。

对于这种场景你可以在插件选项中指定 "prompts": true 来允许注入命令提示:

{  "plugins": {    "@vue/cli-plugin-eslint": {      // 让用户选取他们自己的 ESLint config      "prompts": true    }  }}

远程 Preset

你可以通过发布 git repo 将一个 preset 分享给其他开发者。这个 repo 应该包含以下文件:

  • preset.json: 包含 preset 数据的主要文件(必需)。
  • generator.js: 一个可以注入或是修改项目中文件的 Generator
  • prompts.js 一个可以通过命令行对话为 generator 收集选项的 prompts 文件

发布 repo 后,你就可以在创建项目的时候通过 --preset 选项使用这个远程的 preset 了:

# 从 GitHub repo 使用 presetvue create --preset username/repo my-project

GitLab 和 BitBucket 也是支持的。如果要从私有 repo 获取,请确保使用 --clone 选项:

vue create --preset gitlab:username/repo --clone my-projectvue create --preset bitbucket:username/repo --clone my-project

加载文件系统中的 Preset

当开发一个远程 preset 的时候,你必须不厌其烦的向远程 repo 发出 push 进行反复测试。为了简化这个流程,你也可以直接在本地测试 preset。如果 --preset 选项的值是一个相对或绝对文件路径,或是以 .json 结尾,则 Vue CLI 会加载本地的 preset:

# ./my-preset 应当是一个包含 preset.json 的文件夹vue create --preset ./my-preset my-project# 或者,直接使用当前工作目录下的 json 文件:vue create --preset my-preset.json my-project


使用命令

在一个 Vue CLI 项目中,@vue/cli-service 安装了一个名为 vue-cli-service 的命令。你可以在 npm scripts 中以 vue-cli-service、或者从终端中以 ./node_modules/.bin/vue-cli-service 访问这个命令。

这是你使用默认 preset 的项目的 package.json:

{  "scripts": {    "serve": "vue-cli-service serve",    "build": "vue-cli-service build"  }}

你可以通过 npm 或 Yarn 调用这些 script:

npm run serve# ORyarn serve

如果你可以使用 npx (最新版的 npm 应该已经自带),也可以直接这样调用命令:

npx vue-cli-service serve

提示

你可以通过 vue ui 命令使用 GUI 运行更多的特性脚本。

这里有一个 GUI 的 webpack Analyzer:

UI webpack Analyzer

vue-cli-service serve

用法:vue-cli-service serve [options] [entry]选项:  --open    在服务器启动时打开浏览器  --copy    在服务器启动时将 URL 复制到剪切版  --mode    指定环境模式 (默认值:development)  --host    指定 host (默认值:0.0.0.0)  --port    指定 port (默认值:8080)  --https   使用 https (默认值:false)

vue-cli-service serve 命令会启动一个开发服务器 (基于 webpack-dev-server) 并附带开箱即用的模块热重载 (Hot-Module-Replacement)。

除了通过命令行参数,你也可以使用 vue.config.js 里的 devServer 字段配置开发服务器。

命令行参数 [entry] 将被指定为唯一入口,而非额外的追加入口。尝试使用 [entry] 覆盖 config.pages 中的 entry 将可能引发错误。

vue-cli-service build

用法:vue-cli-service build [options] [entry|pattern]选项:  --mode        指定环境模式 (默认值:production)  --dest        指定输出目录 (默认值:dist)  --modern      面向现代浏览器带自动回退地构建应用  --target      app | lib | wc | wc-async (默认值:app)  --name        库或 Web Components 模式下的名字 (默认值:package.json 中的 "name" 字段或入口文件名)  --no-clean    在构建项目之前不清除目标目录  --report      生成 report.html 以帮助分析包内容  --report-json 生成 report.json 以帮助分析包内容  --watch       监听文件变化

vue-cli-service build 会在 dist/ 目录产生一个可用于生产环境的包,带有 JS/CSS/HTML 的压缩,和为更好的缓存而做的自动的 vendor chunk splitting。它的 chunk manifest 会内联在 HTML 里。

这里还有一些有用的命令参数:

  • --modern 使用现代模式构建应用,为现代浏览器交付原生支持的 ES2015 代码,并生成一个兼容老浏览器的包用来自动回退。
  • --target 允许你将项目中的任何组件以一个库或 Web Components 组件的方式进行构建。更多细节请查阅构建目标
  • --report 和 --report-json 会根据构建统计生成报告,它会帮助你分析包中包含的模块们的大小。

vue-cli-service inspect

用法:vue-cli-service inspect [options] [...paths]选项:  --mode    指定环境模式 (默认值:development)

你可以使用 vue-cli-service inspect 来审查一个 Vue CLI 项目的 webpack config。更多细节请查阅审查 webpack config

#查看所有的可用命令

有些 CLI 插件会向 vue-cli-service 注入额外的命令。例如 @vue/cli-plugin-eslint 会注入 vue-cli-service lint 命令。你可以运行以下命令查看所有注入的命令:

npx vue-cli-service help

你也可以这样学习每个命令可用的选项:

npx vue-cli-service help [command]

缓存和并行处理

  • cache-loader 会默认为 Vue/Babel/TypeScript 编译开启。文件会缓存在 node_modules/.cache 中——如果你遇到了编译方面的问题,记得先删掉缓存目录之后再试试看。
  • thread-loader 会在多核 CPU 的机器上为 Babel/TypeScript 转译开启。

Git Hook

在安装之后,@vue/cli-service 也会安装 yorkie,它会让你在 package.json 的 gitHooks 字段中方便地指定 Git hook:

{  "gitHooks": {    "pre-commit": "lint-staged"  },   "lint-staged": {    "*.{js,vue}": [      "vue-cli-service lint",      "git add"    ]  }}

注意

yorkie fork 自 husky 且并与后者不兼容。

配置时无需 Eject

通过 vue create 创建的项目无需额外的配置就已经可以跑起来了。插件的设计也是可以相互共存的,所以绝大多数情况下,你只需要在交互式命令提示中选取需要的功能即可。

不过我们也知道满足每一个需求是不太可能的,而且一个项目的需求也会不断改变。通过 Vue CLI 创建的项目让你无需 eject 就能够配置工具的几乎每个角落。更多细节请查阅配置参考


browserslist

你会发现有 package.json 文件里的 browserslist 字段 (或一个单独的 .browserslistrc 文件),指定了项目的目标浏览器的范围。这个值会被 @babel/preset-env 和 Autoprefixer 用来确定需要转译的 JavaScript 特性和需要添加的 CSS 浏览器前缀。

现在查阅这里了解如何指定浏览器范围。

Polyfill

useBuiltIns: 'usage'

一个默认的 Vue CLI 项目会使用 @vue/babel-preset-app,它通过 @babel/preset-env 和 browserslist 配置来决定项目需要的 polyfill。

默认情况下,它会把 useBuiltIns: 'usage' 传递给 @babel/preset-env,这样它会根据源代码中出现的语言特性自动检测需要的 polyfill。这确保了最终包里 polyfill 数量的最小化。然而,这也意味着如果其中一个依赖需要特殊的 polyfill,默认情况下 Babel 无法将其检测出来。

如果有依赖需要 polyfill,你有几种选择:

  1. 如果该依赖基于一个目标环境不支持的 ES 版本撰写: 将其添加到 vue.config.js 中的 transpileDependencies 选项。这会为该依赖同时开启语法转换和根据使用情况检测 polyfill。
  2. 如果该依赖交付了 ES5 代码并显式地列出了需要的 polyfill: 你可以使用 @vue/babel-preset-app 的 polyfills 选项预包含所需要的 polyfill。注意 es6.promise 将被默认包含,因为现在的库依赖 Promise 是非常普遍的。// babel.config.jsmodule.exports = { presets: [ ['@vue/app', { polyfills: [ 'es6.promise', 'es6.symbol' ] }] ]}提示我们推荐以这种方式添加 polyfill 而不是在源代码中直接导入它们,因为如果这里列出的 polyfill 在 browserslist 的目标中不需要,则它会被自动排除。
  3. 如果该依赖交付 ES5 代码,但使用了 ES6+ 特性且没有显式地列出需要的 polyfill (例如 Vuetify):请使用 useBuiltIns: 'entry' 然后在入口文件添加 import 'core-js/stable'; import 'regenerator-runtime/runtime';。这会根据 browserslist 目标导入所有 polyfill,这样你就不用再担心依赖的 polyfill 问题了,但是因为包含了一些没有用到的 polyfill 所以最终的包大小可能会增加。

更多细节可查阅 @babel-preset/env 文档

构建库或是 Web Component 时的 Polyfills

当使用 Vue CLI 来构建一个库或是 Web Component 时,推荐给 @vue/babel-preset-app 传入 useBuiltIns: false 选项。这能够确保你的库或是组件不包含不必要的 polyfills。通常来说,打包 polyfills 应当是最终使用你的库的应用的责任。

现代模式

有了 Babel 我们可以兼顾所有最新的 ES2015+ 语言特性,但也意味着我们需要交付转译和 polyfill 后的包以支持旧浏览器。这些转译后的包通常都比原生的 ES2015+ 代码会更冗长,运行更慢。现如今绝大多数现代浏览器都已经支持了原生的 ES2015,所以因为要支持更老的浏览器而为它们交付笨重的代码是一种浪费。

Vue CLI 提供了一个“现代模式”帮你解决这个问题。以如下命令为生产环境构建:

vue-cli-service build --modern

Vue CLI 会产生两个应用的版本:一个现代版的包,面向支持 ES modules 的现代浏览器,另一个旧版的包,面向不支持的旧浏览器。

最酷的是这里没有特殊的部署要求。其生成的 HTML 文件会自动使用 Phillip Walton 精彩的博文中讨论到的技术:

  • 现代版的包会通过 <script type="module"> 在被支持的浏览器中加载;它们还会使用 <link rel="modulepreload"> 进行预加载。
  • 旧版的包会通过 <script nomodule> 加载,并会被支持 ES modules 的浏览器忽略。
  • 一个针对 Safari 10 中 <script nomodule> 的修复会被自动注入。

对于一个 Hello World 应用来说,现代版的包已经小了 16%。在生产环境下,现代版的包通常都会表现出显著的解析速度和运算速度,从而改善应用的加载性能。

提示

<script type="module"> 需要配合始终开启的 CORS 进行加载。这意味着你的服务器必须返回诸如 Access-Control-Allow-Origin: * 的有效的 CORS 头。如果你想要通过认证来获取脚本,可使将 crossorigin 选项设置为 use-credentials。

同时,现代浏览器使用一段内联脚本来避免 Safari 10 重复加载脚本包,所以如果你在使用一套严格的 CSP,你需要这样显性地允许内联脚本:

Content-Security-Policy: script-src 'self' 'sha256-4RS22DYeB7U14dra4KcQYxmwt5HkOInieXK1NUMB


HTML

Index 文件

public/index.html 文件是一个会被 html-webpack-plugin 处理的模板。在构建过程中,资源链接会被自动注入。另外,Vue CLI 也会自动注入 resource hint (preload/prefetch、manifest 和图标链接 (当用到 PWA 插件时) 以及构建过程中处理的 JavaScript 和 CSS 文件的资源链接。

插值

因为 index 文件被用作模板,所以你可以使用 lodash template 语法插入内容:

  • <%= VALUE %> 用来做不转义插值;
  • <%- VALUE %> 用来做 HTML 转义插值;
  • <% expression %> 用来描述 JavaScript 流程控制。

除了被 html-webpack-plugin 暴露的默认值之外,所有客户端环境变量也可以直接使用。例如,BASE_URL 的用法:

<link rel="icon" href="<%= BASE_URL %>favicon.ico">

更多内容可以查阅:

Preload

<link rel="preload"> 是一种 resource hint,用来指定页面加载后很快会被用到的资源,所以在页面加载的过程中,我们希望在浏览器开始主体渲染之前尽早 preload。

默认情况下,一个 Vue CLI 应用会为所有初始化渲染需要的文件自动生成 preload 提示。

这些提示会被 @vue/preload-webpack-plugin 注入,并且可以通过 chainWebpack 的 config.plugin('preload') 进行修改和删除。

Prefetch

<link rel="prefetch"> 是一种 resource hint,用来告诉浏览器在页面加载完成后,利用空闲时间提前获取用户未来可能会访问的内容。

默认情况下,一个 Vue CLI 应用会为所有作为 async chunk 生成的 JavaScript 文件 (通过动态 import() 按需 code splitting 的产物) 自动生成 prefetch 提示。

这些提示会被 @vue/preload-webpack-plugin 注入,并且可以通过 chainWebpack 的 config.plugin('prefetch') 进行修改和删除。

示例:

// vue.config.jsmodule.exports = {  chainWebpack: config => {    // 移除 prefetch 插件    config.plugins.delete('prefetch')    // 或者    // 修改它的选项:    config.plugin('prefetch').tap(options => {      options[0].fileBlacklist = options[0].fileBlacklist || []      options[0].fileBlacklist.push(/myasyncRoute(.)+?.js$/)      return options    })  }}

当 prefetch 插件被禁用时,你可以通过 webpack 的内联注释手动选定要提前获取的代码区块:

import(/* webpackPrefetch: true */ './someAsyncComponent.vue')

webpack 的运行时会在父级区块被加载之后注入 prefetch 链接。

提示

Prefetch 链接将会消耗带宽。如果你的应用很大且有很多 async chunk,而用户主要使用的是对带宽较敏感的移动端,那么你可能需要关掉 prefetch 链接并手动选择要提前获取的代码区块。

不生成 index

当基于已有的后端使用 Vue CLI 时,你可能不需要生成 index.html,这样生成的资源可以用于一个服务端渲染的页面。这时可以向 vue.config.js 加入下列代码:

// vue.config.jsmodule.exports = {  // 去掉文件名中的 hash  filenameHashing: false,  // 删除 HTML 相关的 webpack 插件  chainWebpack: config => {    config.plugins.delete('html')    config.plugins.delete('preload')    config.plugins.delete('prefetch')  }}

然而这样做并不是很推荐,因为:

  • 硬编码的文件名不利于实现高效率的缓存控制。
  • 硬编码的文件名也无法很好的进行 code-splitting (代码分段),因为无法用变化的文件名生成额外的 JavaScript 文件。
  • 硬编码的文件名无法在现代模式下工作。

你应该考虑换用 indexPath 选项将生成的 HTML 用作一个服务端框架的视图模板。

构建一个多页应用

不是每个应用都需要是一个单页应用。Vue CLI 支持使用 vue.config.js 中的 pages 选项构建一个多页面的应用。构建好的应用将会在不同的入口之间高效共享通用的 chunk 以获得最佳的加载性能。

处理静态资源

静态资源可以通过两种方式进行处理:

  • 在 JavaScript 被导入或在 template/CSS 中通过相对路径被引用。这类引用会被 webpack 处理。
  • 放置在 public 目录下或通过绝对路径被引用。这类资源将会直接被拷贝,而不会经过 webpack 的处理。

从相对路径导入

当你在 JavaScript、CSS 或 *.vue 文件中使用相对路径 (必须以 . 开头) 引用一个静态资源时,该资源将会被包含进入 webpack 的依赖图中。在其编译过程中,所有诸如 <img src="...">、background: url(...) 和 CSS @import 的资源 URL 都会被解析为一个模块依赖。

例如,url(./image.png) 会被翻译为 require('./image.png'),而:

<img src="./image.png">

将会被编译到:

h('img', { attrs: { src: require('./image.png') }})

在其内部,我们通过 file-loader 用版本哈希值和正确的公共基础路径来决定最终的文件路径,再用 url-loader 将小于 4kb 的资源内联,以减少 HTTP 请求的数量。

你可以通过 chainWebpack 调整内联文件的大小限制。例如,下列代码会将其限制设置为 10kb:

// vue.config.jsmodule.exports = {  chainWebpack: config => {    config.module      .rule('images')        .use('url-loader')          .loader('url-loader')          .tap(options => Object.assign(options, { limit: 10240 }))  }}

URL 转换规则

  • 如果 URL 是一个绝对路径 (例如 /images/foo.png),它将会被保留不变。
  • 如果 URL 以 . 开头,它会作为一个相对模块请求被解释且基于你的文件系统中的目录结构进行解析。
  • 如果 URL 以 ~ 开头,其后的任何内容都会作为一个模块请求被解析。这意味着你甚至可以引用 Node 模块中的资源:<img src="~some-npm-package/foo.png">
  • 如果 URL 以 @ 开头,它也会作为一个模块请求被解析。它的用处在于 Vue CLI 默认会设置一个指向 <projectRoot>/src 的别名 @。(仅作用于模版中)

public 文件夹

任何放置在 public 文件夹的静态资源都会被简单的复制,而不经过 webpack。你需要通过绝对路径来引用它们。

注意我们推荐将资源作为你的模块依赖图的一部分导入,这样它们会通过 webpack 的处理并获得如下好处:

  • 脚本和样式表会被压缩且打包在一起,从而避免额外的网络请求。
  • 文件丢失会直接在编译时报错,而不是到了用户端才产生 404 错误。
  • 最终生成的文件名包含了内容哈希,因此你不必担心浏览器会缓存它们的老版本。

public 目录提供的是一个应急手段,当你通过绝对路径引用它时,留意应用将会部署到哪里。如果你的应用没有部署在域名的根部,那么你需要为你的 URL 配置 publicPath 前缀:

  • 在 public/index.html 或其它通过 html-webpack-plugin 用作模板的 HTML 文件中,你需要通过 <%= BASE_URL %> 设置链接前缀:<link rel="icon" href="<%= BASE_URL %>favicon.ico">
  • 在模板中,你首先需要向你的组件传入基础 URL:data () { return { publicPath: process.env.BASE_URL }}然后:<img :src="`${publicPath}my-image.png`">

何时使用 public 文件夹

  • 你需要在构建输出中指定一个文件的名字。
  • 你有上千个图片,需要动态引用它们的路径。
  • 有些库可能和 webpack 不兼容,这时你除了将其用一个独立的 <script> 标签引入没有别的选择。


Vue CLI 项目天生支持 PostCSSCSS Modules 和包含 SassLessStylus 在内的预处理器。

引用静态资源

所有编译后的 CSS 都会通过 css-loader 来解析其中的 url() 引用,并将这些引用作为模块请求来处理。这意味着你可以根据本地的文件结构用相对路径来引用静态资源。另外要注意的是如果你想要引用一个 npm 依赖中的文件,或是想要用 webpack alias,则需要在路径前加上 ~ 的前缀来避免歧义。更多细节请参考处理静态资源

预处理器

你可以在创建项目的时候选择预处理器 (Sass/Less/Stylus)。如果当时没有选好,内置的 webpack 仍然会被预配置为可以完成所有的处理。你也可以手动安装相应的 webpack loader:

# Sassnpm install -D sass-loader node-sass# Lessnpm install -D less-loader less# Stylusnpm install -D stylus-loader stylus

然后你就可以导入相应的文件类型,或在 *.vue 文件中这样来使用:

<style lang="scss">$color: red;</style>

自动化导入

如果你想自动化导入文件 (用于颜色、变量、mixin……),你可以使用 style-resources-loader。这里有一个关于 Stylus 的在每个单文件组件和 Stylus 文件中导入 ./src/styles/imports.styl 的例子:

// vue.config.jsconst path = require('path')module.exports = {  chainWebpack: config => {    const types = ['vue-modules', 'vue', 'normal-modules', 'normal']    types.forEach(type => addStyleResource(config.module.rule('stylus').oneOf(type)))  },}function addStyleResource (rule) {  rule.use('style-resource')    .loader('style-resources-loader')    .options({      patterns: [        path.resolve(__dirname, './src/styles/imports.styl'),      ],    })}

你也可以选择使用 vue-cli-plugin-style-resources-loader

PostCSS

Vue CLI 内部使用了 PostCSS。

你可以通过 .postcssrc 或任何 postcss-load-config 支持的配置源来配置 PostCSS。也可以通过 vue.config.js 中的 css.loaderOptions.postcss 配置 postcss-loader

我们默认开启了 autoprefixer。如果要配置目标浏览器,可使用 package.json 的 browserslist 字段。

关于 CSS 中浏览器前缀规则的注意事项

在生产环境构建中,Vue CLI 会优化 CSS 并基于目标浏览器抛弃不必要的浏览器前缀规则。因为默认开启了 autoprefixer,你只使用无前缀的 CSS 规则即可。

CSS Modules

你可以通过 <style module> 以开箱即用的方式在 *.vue 文件中使用 CSS Modules

如果想在 JavaScript 中作为 CSS Modules 导入 CSS 或其它预处理文件,该文件应该以 .module.(css|less|sass|scss|styl) 结尾:

import styles from './foo.module.css'// 所有支持的预处理器都一样工作import sassStyles from './foo.module.scss'

如果你想去掉文件名中的 .module,可以设置 vue.config.js 中的 css.requireModuleExtension 为 false:

// vue.config.jsmodule.exports = {  css: {    requireModuleExtension: false  }}

如果你希望自定义生成的 CSS Modules 模块的类名,可以通过 vue.config.js 中的 css.loaderOptions.css 选项来实现。所有的 css-loader 选项在这里都是支持的,例如 localIdentName 和 camelCase:

// vue.config.jsmodule.exports = {  css: {    loaderOptions: {      css: {        // 注意:以下配置在 Vue CLI v4 与 v3 之间存在差异。        // Vue CLI v3 用户可参考 css-loader v1 文档        // https://github.com/webpack-contrib/css-loader/tree/v1.0.1        modules: {          localIdentName: '[name]-[hash]'        },        localsConvention: 'camelCaseOnly'      }    }  }}

向预处理器 Loader 传递选项

有的时候你想要向 webpack 的预处理器 loader 传递选项。你可以使用 vue.config.js 中的 css.loaderOptions 选项。比如你可以这样向所有 Sass/Less 样式传入共享的全局变量:

// vue.config.jsmodule.exports = {  css: {    loaderOptions: {      // 给 sass-loader 传递选项      sass: {        // @/ 是 src/ 的别名        // 所以这里假设你有 `src/variables.sass` 这个文件        // 注意:在 sass-loader v7 中,这个选项名是 "data"        prependData: `@import "~@/variables.sass"`      },      // 默认情况下 `sass` 选项会同时对 `sass` 和 `scss` 语法同时生效      // 因为 `scss` 语法在内部也是由 sass-loader 处理的      // 但是在配置 `data` 选项的时候      // `scss` 语法会要求语句结尾必须有分号,`sass` 则要求必须没有分号      // 在这种情况下,我们可以使用 `scss` 选项,对 `scss` 语法进行单独配置      scss: {        prependData: `@import "~@/variables.scss";`      },      // 给 less-loader 传递 Less.js 相关选项      less:{        // http://lesscss.org/usage/#less-options-strict-units `Global Variables`        // `primary` is global variables fields name        globalVars: {          primary: '#fff'        }      }    }  }}

Loader 可以通过 loaderOptions 配置,包括:

提示

这样做比使用 chainWebpack 手动指定 loader 更推荐,因为这些选项需要应用在使用了相应 loader 的多个地方。


简单的配置方式

调整 webpack 配置最简单的方式就是在 vue.config.js 中的 configureWebpack 选项提供一个对象:

// vue.config.jsmodule.exports = {  configureWebpack: {    plugins: [      new MyAwesomeWebpackPlugin()    ]  }}

该对象将会被 webpack-merge 合并入最终的 webpack 配置。

警告

有些 webpack 选项是基于 vue.config.js 中的值设置的,所以不能直接修改。例如你应该修改 vue.config.js 中的 outputDir 选项而不是修改 output.path;你应该修改 vue.config.js 中的 publicPath 选项而不是修改 output.publicPath。这样做是因为 vue.config.js 中的值会被用在配置里的多个地方,以确保所有的部分都能正常工作在一起。

如果你需要基于环境有条件地配置行为,或者想要直接修改配置,那就换成一个函数 (该函数会在环境变量被设置之后懒执行)。该方法的第一个参数会收到已经解析好的配置。在函数内,你可以直接修改配置,或者返回一个将会被合并的对象:

// vue.config.jsmodule.exports = {  configureWebpack: config => {    if (process.env.NODE_ENV === 'production') {      // 为生产环境修改配置...    } else {      // 为开发环境修改配置...    }  }}

链式操作 (高级)

Vue CLI 内部的 webpack 配置是通过 webpack-chain 维护的。这个库提供了一个 webpack 原始配置的上层抽象,使其可以定义具名的 loader 规则和具名插件,并有机会在后期进入这些规则并对它们的选项进行修改。

它允许我们更细粒度的控制其内部配置。接下来有一些常见的在 vue.config.js 中的 chainWebpack 修改的例子。

提示

当你打算链式访问特定的 loader 时,vue inspect 会非常有帮助。

修改 Loader 选项

// vue.config.jsmodule.exports = {  chainWebpack: config => {    config.module      .rule('vue')      .use('vue-loader')        .loader('vue-loader')        .tap(options => {          // 修改它的选项...          return options        })  }}

提示

对于 CSS 相关 loader 来说,我们推荐使用 css.loaderOptions 而不是直接链式指定 loader。这是因为每种 CSS 文件类型都有多个规则,而 css.loaderOptions 可以确保你通过一个地方影响所有的规则。

添加一个新的 Loader

// vue.config.jsmodule.exports = {  chainWebpack: config => {    // GraphQL Loader    config.module      .rule('graphql')      .test(/.graphql$/)      .use('graphql-tag/loader')        .loader('graphql-tag/loader')        .end()      // 你还可以再添加一个 loader      .use('other-loader')        .loader('other-loader')        .end()  }}

替换一个规则里的 Loader

如果你想要替换一个已有的基础 loader,例如为内联的 SVG 文件使用 vue-svg-loader 而不是加载这个文件:

// vue.config.jsmodule.exports = {  chainWebpack: config => {    const svgRule = config.module.rule('svg')    // 清除已有的所有 loader。    // 如果你不这样做,接下来的 loader 会附加在该规则现有的 loader 之后。    svgRule.uses.clear()    // 添加要替换的 loader    svgRule      .use('vue-svg-loader')        .loader('vue-svg-loader')  }}

修改插件选项

// vue.config.jsmodule.exports = {  chainWebpack: config => {    config      .plugin('html')      .tap(args => {        return [/* 传递给 html-webpack-plugin's 构造函数的新参数 */]      })  }}

你需要熟悉 webpack-chain 的 API 并阅读一些源码以便了解如何最大程度利用好这个选项,但是比起直接修改 webpack 配置,它的表达能力更强,也更为安全。

比方说你想要将 index.html 默认的路径从 /Users/username/proj/public/index.html 改为 /Users/username/proj/app/templates/index.html。通过参考 html-webpack-plugin 你能看到一个可以传入的选项列表。我们可以在下列配置中传入一个新的模板路径来改变它:

// vue.config.jsmodule.exports = {  chainWebpack: config => {    config      .plugin('html')      .tap(args => {        args[0].template = '/Users/username/proj/app/templates/index.html'        return args      })  }}

你可以通过接下来要讨论的工具 vue inspect 来确认变更。

审查项目的 webpack 配置

因为 @vue/cli-service 对 webpack 配置进行了抽象,所以理解配置中包含的东西会比较困难,尤其是当你打算自行对其调整的时候。

vue-cli-service 暴露了 inspect 命令用于审查解析好的 webpack 配置。那个全局的 vue 可执行程序同样提供了 inspect 命令,这个命令只是简单的把 vue-cli-service inspect 代理到了你的项目中。

该命令会将解析出来的 webpack 配置、包括链式访问规则和插件的提示打印到 stdout。

你可以将其输出重定向到一个文件以便进行查阅:

vue inspect > output.js

注意它输出的并不是一个有效的 webpack 配置文件,而是一个用于审查的被序列化的格式。

你也可以通过指定一个路径来审查配置的一小部分:

# 只审查第一条规则vue inspect module.rules.0

或者指向一个规则或插件的名字:

vue inspect --rule vuevue inspect --plugin html

最后,你可以列出所有规则和插件的名字:

vue inspect --rulesvue inspect --plugins

以一个文件的方式使用解析好的配置

有些外部工具可能需要通过一个文件访问解析好的 webpack 配置,比如那些需要提供 webpack 配置路径的 IDE 或 CLI。在这种情况下你可以使用如下路径:

<projectRoot>/node_modules/@vue/cli-service/webpack.config.js

该文件会动态解析并输出 vue-cli-service 命令中使用的相同的 webpack 配置,包括那些来自插件甚至是你自定义的配置。


你可以替换你的项目根目录中的下列文件来指定环境变量:

.env                # 在所有的环境中被载入.env.local          # 在所有的环境中被载入,但会被 git 忽略.env.[mode]         # 只在指定的模式中被载入.env.[mode].local   # 只在指定的模式中被载入,但会被 git 忽略

一个环境文件只包含环境变量的“键=值”对:

FOO=barVUE_APP_SECRET=secret

被载入的变量将会对 vue-cli-service 的所有命令、插件和依赖可用。

环境加载属性

为一个特定模式准备的环境文件的 (例如 .env.production) 将会比一般的环境文件 (例如 .env) 拥有更高的优先级。

此外,Vue CLI 启动时已经存在的环境变量拥有最高优先级,并不会被 .env 文件覆写。

NODE_ENV

如果在环境中有默认的 NODE_ENV,你应该移除它或在运行 vue-cli-service 命令的时候明确地设置 NODE_ENV。

模式

模式是 Vue CLI 项目中一个重要的概念。默认情况下,一个 Vue CLI 项目有三个模式:

  • development 模式用于 vue-cli-service serve
  • production 模式用于 vue-cli-service build 和 vue-cli-service test:e2e
  • test 模式用于 vue-cli-service test:unit

注意模式不同于 NODE_ENV,一个模式可以包含多个环境变量。也就是说,每个模式都会将 NODE_ENV 的值设置为模式的名称——比如在 development 模式下 NODE_ENV 的值会被设置为 "development"。

你可以通过为 .env 文件增加后缀来设置某个模式下特有的环境变量。比如,如果你在项目根目录创建一个名为 .env.development 的文件,那么在这个文件里声明过的变量就只会在 development 模式下被载入。

你可以通过传递 --mode 选项参数为命令行覆写默认的模式。例如,如果你想要在构建命令中使用开发环境变量,请在你的 package.json 脚本中加入:

"dev-build": "vue-cli-service build --mode development",

示例:Staging 模式

假设我们有一个应用包含以下 .env 文件:

VUE_APP_TITLE=My App

和 .env.staging 文件:

NODE_ENV=productionVUE_APP_TITLE=My App (staging)
  • vue-cli-service build 会加载可能存在的 .env、.env.production 和 .env.production.local 文件然后构建出生产环境应用;
  • vue-cli-service build --mode staging 会在 staging 模式下加载可能存在的 .env、.env.staging 和 .env.staging.local 文件然后构建出生产环境应用。

这两种情况下,根据 NODE_ENV,构建出的应用都是生产环境应用,但是在 staging 版本中,process.env.VUE_APP_TITLE 被覆写成了另一个值。

在客户端侧代码中使用环境变量

只有以 VUE_APP_ 开头的变量会被 webpack.DefinePlugin 静态嵌入到客户端侧的包中。你可以在应用的代码中这样访问它们:

console.log(process.env.VUE_APP_SECRET)

在构建过程中,process.env.VUE_APP_SECRET 将会被相应的值所取代。在 VUE_APP_SECRET=secret 的情况下,它会被替换为 "secret"。

除了 VUE_APP_* 变量之外,在你的应用代码中始终可用的还有两个特殊的变量:

  • NODE_ENV - 会是 "development"、"production" 或 "test" 中的一个。具体的值取决于应用运行的模式
  • BASE_URL - 会和 vue.config.js 中的 publicPath 选项相符,即你的应用会部署到的基础路径。

所有解析出来的环境变量都可以在 public/index.html 中以 HTML 插值中介绍的方式使用。

提示

你可以在 vue.config.js 文件中计算环境变量。它们仍然需要以 VUE_APP_ 前缀开头。这可以用于版本信息 process.env.VUE_APP_VERSION = require('./package.json').version。

只在本地有效的变量

有的时候你可能有一些不应该提交到代码仓库中的变量,尤其是当你的项目托管在公共仓库时。这种情况下你应该使用一个 .env.local 文件取而代之。本地环境文件默认会被忽略,且出现在 .gitignore 中。

.local 也可以加在指定模式的环境文件上,比如 .env.development.local 将会在 development 模式下被载入,且被 git 忽略。


当你运行 vue-cli-service build 时,你可以通过 --target 选项指定不同的构建目标。它允许你将相同的源代码根据不同的用例生成不同的构建。

应用

应用模式是默认的模式。在这个模式中:

  • index.html 会带有注入的资源和 resource hint
  • 第三方库会被分到一个独立包以便更好的缓存
  • 小于 4kb 的静态资源会被内联在 JavaScript 中
  • public 中的静态资源会被复制到输出目录中

关于 IE 兼容性的提醒

在库模式中,项目的 publicPath 是根据主文件的加载路径动态设置的(用以支持动态的资源加载能力)。但是这个功能用到了 document.currentScript,而 IE 浏览器并不支持这一特性。所以如果网站需要支持 IE 的话,建议使用库之前先在页面上引入 current-script-polyfill

注意对 Vue 的依赖

在库模式中,Vue 是外置的。这意味着包中不会有 Vue,即便你在代码中导入了 Vue。如果这个库会通过一个打包器使用,它将尝试通过打包器以依赖的方式加载 Vue;否则就会回退到一个全局的 Vue 变量。

要避免此行为,可以在build命令中添加--inline-vue标志。

vue-cli-service build --target lib --inline-vue

你可以通过下面的命令将一个单独的入口构建为一个库:

vue-cli-service build --target lib --name myLib [entry]
File                     Size                     Gzippeddist/myLib.umd.min.js    13.28 kb                 8.42 kbdist/myLib.umd.js        20.95 kb                 10.22 kbdist/myLib.common.js     20.57 kb                 10.09 kbdist/myLib.css           0.33 kb                  0.23 kb

这个入口可以是一个 .js 或一个 .vue 文件。如果没有指定入口,则会使用 src/App.vue。

构建一个库会输出:

  • dist/myLib.common.js:一个给打包器用的 CommonJS 包 (不幸的是,webpack 目前还并没有支持 ES modules 输出格式的包)
  • dist/myLib.umd.js:一个直接给浏览器或 AMD loader 使用的 UMD 包
  • dist/myLib.umd.min.js:压缩后的 UMD 构建版本
  • dist/myLib.css:提取出来的 CSS 文件 (可以通过在 vue.config.js 中设置 css: { extract: false } 强制内联)

警告

如果你在开发一个库或多项目仓库 (monorepo),请注意导入 CSS 是具有副作用的。请确保在 package.json 中移除 "sideEffects": false,否则 CSS 代码块会在生产环境构建时被 webpack 丢掉。

Vue vs. JS/TS 入口文件

当使用一个 .vue 文件作为入口时,你的库会直接暴露这个 Vue 组件本身,因为组件始终是默认导出的内容。

然而,当你使用一个 .js 或 .ts 文件作为入口时,它可能会包含具名导出,所以库会暴露为一个模块。也就是说你的库必须在 UMD 构建中通过 window.yourLib.default 访问,或在 CommonJS 构建中通过 const myLib = require('mylib').default 访问。如果你没有任何具名导出并希望直接暴露默认导出,你可以在 vue.config.js 中使用以下 webpack 配置:

module.exports = {  configureWebpack: {    output: {      libraryExport: 'default'    }  }}

Web Components 组件

兼容性提示

Web Components 模式不支持 IE11 及更低版本。更多细节

注意对 Vue 的依赖

在 Web Components 模式中,Vue 是外置的。这意味着包中不会有 Vue,即便你在代码中导入了 Vue。这里的包会假设在页面中已经有一个可用的全局变量 Vue。

你可以通过下面的命令将一个单独的入口构建为一个 Web Components 组件:

vue-cli-service build --target wc --name my-element [entry]

注意这里的入口应该是一个 *.vue 文件。Vue CLI 将会把这个组件自动包裹并注册为 Web Components 组件,无需在 main.js 里自行注册。也可以在开发时把 main.js 作为 demo app 单独使用。

该构建将会产生一个单独的 JavaScript 文件 (及其压缩后的版本) 将所有的东西都内联起来。当这个脚本被引入网页时,会注册自定义组件 <my-element>,其使用 @vue/web-component-wrapper 包裹了目标的 Vue 组件。这个包裹器会自动代理属性、特性、事件和插槽。请查阅 @vue/web-component-wrapper 的文档了解更多细节。

注意这个包依赖了在页面上全局可用的 Vue。

这个模式允许你的组件的使用者以一个普通 DOM 元素的方式使用这个 Vue 组件:

<script src="https://unpkg.com/vue" rel="external nofollow"  rel="external nofollow" ></script><script src="path/to/my-element.js"></script><!-- 可在普通 HTML 中或者其它任何框架中使用 --><my-element></my-element>

注册多个 Web Components 组件的包

当你构建一个 Web Components 组件包的时候,你也可以使用一个 glob 表达式作为入口指定多个组件目标:

vue-cli-service build --target wc --name foo 'src/components/*.vue'

当你构建多个 web component 时,--name 将会用于设置前缀,同时自定义元素的名称会由组件的文件名推导得出。比如一个名为 HelloWorld.vue 的组件携带 --name foo 将会生成的自定义元素名为 <foo-hello-world>。

异步 Web Components 组件

当指定多个 Web Components 组件作为目标时,这个包可能会变得非常大,并且用户可能只想使用你的包中注册的一部分组件。这时异步 Web Components 模式会生成一个 code-split 的包,带一个只提供所有组件共享的运行时,并预先注册所有的自定义组件小入口文件。一个组件真正的实现只会在页面中用到自定义元素相应的一个实例时按需获取:

vue-cli-service build --target wc-async --name foo 'src/components/*.vue'
File                Size                        Gzippeddist/foo.0.min.js    12.80 kb                    8.09 kbdist/foo.min.js      7.45 kb                     3.17 kbdist/foo.1.min.js    2.91 kb                     1.02 kbdist/foo.js          22.51 kb                    6.67 kbdist/foo.0.js        17.27 kb                    8.83 kbdist/foo.1.js        5.24 kb                     1.64 kb

现在用户在该页面上只需要引入 Vue 和这个入口文件即可:

<script src="https://unpkg.com/vue" rel="external nofollow"  rel="external nofollow" ></script><script src="path/to/foo.min.js"></script><!-- foo-one 的实现的 chunk 会在用到的时候自动获取 --><foo-one></foo-one>


通用指南

如果你用 Vue CLI 处理静态资源并和后端框架一起作为部署的一部分,那么你需要的仅仅是确保 Vue CLI 生成的构建文件在正确的位置,并遵循后端框架的发布方式即可。

如果你独立于后端部署前端应用——也就是说后端暴露一个前端可访问的 API,然后前端实际上是纯静态应用。那么你可以将 dist 目录里构建的内容部署到任何静态文件服务器中,但要确保正确的 publicPath

本地预览

dist 目录需要启动一个 HTTP 服务器来访问 (除非你已经将 publicPath 配置为了一个相对的值),所以以 file:// 协议直接打开 dist/index.html 是不会工作的。在本地预览生产环境构建最简单的方式就是使用一个 Node.js 静态文件服务器,例如 serve

npm install -g serve# -s 参数的意思是将其架设在 Single-Page Application 模式下# 这个模式会处理即将提到的路由问题serve -s dist

使用 history.pushState 的路由

如果你在 history 模式下使用 Vue Router,是无法搭配简单的静态文件服务器的。例如,如果你使用 Vue Router 为 /todos/42/ 定义了一个路由,开发服务器已经配置了相应的 localhost:3000/todos/42 响应,但是一个为生产环境构建架设的简单的静态服务器会却会返回 404。

为了解决这个问题,你需要配置生产环境服务器,将任何没有匹配到静态文件的请求回退到 index.html。Vue Router 的文档提供了常用服务器配置指引

CORS

如果前端静态内容是部署在与后端 API 不同的域名上,你需要适当地配置 CORS

PWA

如果你使用了 PWA 插件,那么应用必须架设在 HTTPS 上,这样 Service Worker 才能被正确注册。

平台指南

GitHub Pages

手动推送更新

  1. 在 vue.config.js 中设置正确的 publicPath。如果打算将项目部署到 https://<USERNAME>.github.io/ 上, publicPath 将默认被设为 "/",你可以忽略这个参数。如果打算将项目部署到 https://<USERNAME>.github.io/<REPO>/ 上 (即仓库地址为 https://github.com/<USERNAME>/<REPO>),可将 publicPath 设为 "/<REPO>/"。举个例子,如果仓库名字为“my-project”,那么 vue.config.js 的内容应如下所示:module.exports = { publicPath: process.env.NODE_ENV === 'production' ? '/my-project/' : '/'}
  2. 在项目目录下,创建内容如下的 deploy.sh (可以适当地取消注释) 并运行它以进行部署:   #!/usr/bin/env sh# 当发生错误时中止脚本set -e# 构建npm run build# cd 到构建输出的目录下 cd dist# 部署到自定义域域名# echo 'www.example.com' > CNAMEgit initgit add -Agit commit -m 'deploy'# 部署到 https://<USERNAME>.github.io# git push -f git@github.com:<USERNAME>/<USERNAME>.github.io.git master# 部署到 https://<USERNAME>.github.io/<REPO># git push -f git@github.com:<USERNAME>/<REPO>.git master:gh-pagescd -

使用 Travis CI 自动更新

  1. 仿照上面在 vue.config.js 中设置正确的 publicPath。
  2. 安装 Travis CLI 客户端:gem install travis && travis --login
  3. 生成一个拥有“repo”权限的 GitHub 访问令牌
  4. 授予 Travis 访问仓库的权限:travis set GITHUB_TOKEN=xxx (xxx 是第三步中的个人访问令牌)
  5. 在项目根目录下创建一个 .travis.yml 文件。language: node_jsnode_js: - "node"cache: npmscript: npm run builddeploy:provider: pagesskip_cleanup: truegithub_token: $GITHUB_TOKENlocal_dir: diston: branch: master
  6. 将 .travis.yml 文件推送到仓库来触发第一次构建。

GitLab Pages

根据 GitLab Pages 文档的描述,所有的配置都在根目录中的.gitlab-ci.yml 文件中。下面的范例是一个很好的入门:

# .gitlab-ci.yml 文件应放在你仓库的根目录下 pages: # 必须定义一个名为 pages 的 job  image: node:latest  stage: deploy  script:    - npm ci    - npm run build    - mv public public-vue # GitLab Pages 的钩子设置在 public 文件夹    - mv dist public # 重命名 dist 文件夹 (npm run build 之后的输出位置)  artifacts:    paths:      - public # artifact path 一定要在 /public , 这样 GitLab Pages 才能获取  only:    - master

通常, 你的静态页面将托管在 https://yourUserName.gitlab.io/yourProjectName 上, 所以你可以创建一个 initial vue.config.js 文件去 更新 BASE_URL 要匹配的值 :

// vue.config.js 位于仓库的根目录下// 确保用 GitLab 项目的名称替换了 `YourProjectName`module.exports = {  publicPath: process.env.NODE_ENV === 'production'    ? '/yourProjectName/'    : '/'}

请阅读在 GitLab Pages domains 的文档来学习更多关于页面部署 URL 的信息。注意,你也可以使用自定义域名

在推送到仓库之前提交 .gitlab-ci.yml 和 vue.config.js 文件。GitLab CI 的管道将会被触发: 当成功时候, 到 Settings > Pages 查看关于网站的链接。

Netlify

  1. 在 Netlify 上,使用以下设置从 GitHub 创建新项目:构建命令: npm run build 或 yarn build发布目录: dist
  2. 点击“deploy”按钮!

也可以查看 vue-cli-plugin-netlify-lambda

如果使用 Vue Router 的 history 模式,你需要在 /public 目录下创建一个 _redirects 文件:

# 单页应用的 Netlify 设置/*    /index.html   200

详细信息请查看 Netlify 重定向文档

Render

Render 提供带有全托管 SSL,全球 CDN 和 GitHub 持续自动部署的免费静态站点托管服务。

  1. 在 Render 上创建一个新的 Web Service,并授予 Render 的 GitHub 应用访问你的 Vue 仓库的权限。
  2. 在创建过程中使用以下设置:环境:Static Site构建命令:npm run build 或者 yarn build发布目录:dist

大功告成!构建结束时你的应用便会在你的 Render URL 上线。

如果使用 Vue Router 的 history 模式,你需要在站点的 Redirects/Rewrites 设置中添加以下改写规则:

  • Source: /*
  • Destination: /index.html
  • Status: Rewrite

详细信息请查看 Render 的重定向和改写自定义域名文档。

Amazon S3

参见 vue-cli-plugin-s3-deploy

Firebase

创建一个新的 Firebase 项目 Firebase console。 请参考文档

确保已经全局安装了 firebase-tools :

npm install -g firebase-tools

在项目的根目录下, 用以下命令初始化 firebase :

firebase init

Firebase 将会询问有关初始化项目的一些问题。

  • 选择需要 Firebase CLI 的功能。 一定要选择 hosting 。
  • 选择默认的 Firebase 项目。
  • 将 public 目录设为 dist (或构建输出的位置) 这将会上传到 Firebase Hosting。
// firebase.json{  "hosting": {    "public": "dist"  }}
  • 选择 yes 设置项目为一个单页应用。 这将会创建一个 index.html 在 dist 文件夹并且配置 hosting 信息。
// firebase.json{  "hosting": {    "rewrites": [      {        "source": "**",        "destination": "/index.html"      }    ]  }}

执行 npm run build 去构建项目。

在 Firebase Hosting 部署项目,执行以下命令 :

firebase deploy --only hosting

如果需要在部署的项目中使用的其他 Firebase CLI 功能, 执行 firebase deploy 去掉 --only 参数。

现在可以到 https://<YOUR-PROJECT-ID>.firebaseapp.com 访问你的项目了。

请参考 Firebase 文档 来获取更多细节。

ZEIT Now

ZEIT Now 是一个网站和无服务器 (Serverless) API 云平台,你可以使用你的个人域名 (或是免费的 .now.sh URL) 部署你的 Vue 项目。

步骤一:安装 Now CLI

要使用 npm 安装其命令行界面,运行以下命令:

npm install -g now

步骤二:部署

在项目根目录运行以下命令部署你的应用:

now

此外,你还可以使用他们的 GitHub 或 GitLab 集成服务。

大功告成!

你的站点会开始部署,你将获得一个形如 https://vue.now-examples.now.sh/ 的链接。

开箱即用地,请求会被自动改写到 index.html (除了自定义的静态文件) 并带有合适的缓存请求头。你可以改写这些规则。

Stdlib

未完成 | 欢迎参与贡献。

Heroku

  1. 安装 Heroku CLI
  2. 创建 static.json 文件:{ "root": "dist", "clean_urls": true, "routes": { "/**": "index.html" }}
  3. 将 static.json 加入 Gitgit add static.jsongit commit -m "add static configuration"
  4. 部署到 Herokuheroku loginheroku createheroku buildpacks:add heroku/nodejsheroku buildpacks:add https://github.com/heroku/heroku-buildpack-staticgit push heroku master

详细信息:https://gist.github.com/hone/24b06869b4c1eca701f9

Surge

要使用 Surge 进行部署,步骤非常简单。

首先,通过运行 npm run build 来构建项目。如果还没有安装 Surge 的命令行工具,可以通过运行命令来执行此操作:

npm install --global surge

然后 cd 进入项目的 dist/ 文件夹,然后运行 surge 并按照屏幕提示操作 。如果是第一次使用 Surge,它会要求设置电子邮件和密码。确认项目文件夹以及输入首选域来查看正在部署的项目,如下所示。

            project: /Users/user/Documents/myawesomeproject/dist/         domain: myawesomeproject.surge.sh         upload: [====================] 100% eta: 0.0s (31 files, 494256 bytes)            CDN: [====================] 100%             IP: **.**.***.***   Success! - Published to myawesomeproject.surge.sh

通过访问 myawesomeproject.surge.sh 来确保你的项目已经成功的用 Surge 发布,有关自定义域名等更多设置详细信息,可以到 Surge's help page 查看。

Bitbucket Cloud

  1. 如 Bitbucket 文档 创建一个命名为 <USERNAME>.bitbucket.io 的仓库。
  2. 如果你想拥有多个网站, 想要发布到主仓库的子文件夹中。这种情况下就要在 vue.config.js 设置 publicPath。如果部署到 https://<USERNAME>.bitbucket.io/, publicPath 默认将被设为 "/",你可以选择忽略它。如果要部署到 https://<USERNAME>.bitbucket.io/<SUBFOLDER>/,设置 publicPath 为 "/<SUBFOLDER>/"。在这种情况下,仓库的目录结构应该反映 url 结构,例如仓库应该有 /<SUBFOLDER> 目录。
  3. 在项目中, deploy.sh 使用以下内容创建并运行它以进行部署: #!/usr/bin/env sh# 当发生错误时中止脚本set -e# 构建npm run build# cd 到构建输出的目录cd distgit initgit add -Agit commit -m 'deploy'git push -f git@bitbucket.org:<USERNAME>/<USERNAME>.bitbucket.io.git mastercd -

Docker (Nginx)

在 Docker 容器中使用 Nginx 部署你的应用。

  1. 安装 Docker
  2. 在项目根目录创建 Dockerfile 文件FROM node:10COPY ./ /appWORKDIR /appRUN npm install && npm run buildFROM nginxRUN mkdir /appCOPY --from=0 /app/dist /appCOPY nginx.conf /etc/nginx/nginx.conf
  3. 在项目根目录创建 .dockerignore 文件设置 .dockerignore 文件能防止 node_modules 和其他中间构建产物被复制到镜像中导致构建问题。**/node_modules**/dist
  4. 在项目根目录创建 nginx.conf 文件Nginx 是一个能在 Docker 容器中运行的 HTTP(s) 服务器。它使用配置文件决定如何提供内容、要监听的端口等。参阅 Nginx 设置文档 以了解所有可能的设置选项。下面是一个简单的 Nginx 设置文件,它会在 80 端口上提供你的 Vue 项目。页面未找到 / 404 错误使用的是 index.html,这让我们可以使用基于 pushState() 的路由。user nginx;worker_processes 1;error_log /var/log/nginx/error.log warn;pid /var/run/nginx.pid;events { worker_connections 1024;}http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; keepalive_timeout 65; server { listen 80; server_name localhost; location / { root /app; index index.html; try_files $uri $uri/ /index.html; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } }}
  5. 构建你的 Docker 镜像docker build . -t my-app# Sending build context to Docker daemon 884.7kB# ...# Successfully built 4b00e5ee82ae# Successfully tagged my-app:latest
  6. 运行你的 Docker 镜像这个例子基于官方 Nginx 镜像,因此已经设置了日志重定向并关闭了自我守护进程。它也提供了其他有利于 Nginx 在 Docker 容器中运行的默认设置。更多信息参阅 Nginx Docker 仓库。docker run -d -p 8080:80 my-appcurl localhost:8080# <!DOCTYPE html><html lang=en>...</html>


全局 CLI 配置

有些针对 @vue/cli 的全局配置,例如你惯用的包管理器和你本地保存的 preset,都保存在 home 目录下一个名叫 .vuerc 的 JSON 文件。你可以用编辑器直接编辑这个文件来更改已保存的选项。

你也可以使用 vue config 命令来审查或修改全局的 CLI 配置。

目标浏览器

请查阅指南中的浏览器兼容性章节。

vue.config.js

vue.config.js 是一个可选的配置文件,如果项目的 (和 package.json 同级的) 根目录中存在这个文件,那么它会被 @vue/cli-service 自动加载。你也可以使用 package.json 中的 vue 字段,但是注意这种写法需要你严格遵照 JSON 的格式来写。

这个文件应该导出一个包含了选项的对象:

// vue.config.jsmodule.exports = {  // 选项...}

baseUrl

从 Vue CLI 3.3 起已弃用,请使用publicPath

publicPath

  • Type: string
  • Default: '/'
  • 部署应用包时的基本 URL。用法和 webpack 本身的 output.publicPath 一致,但是 Vue CLI 在一些其他地方也需要用到这个值,所以请始终使用 publicPath 而不要直接修改 webpack 的 output.publicPath。默认情况下,Vue CLI 会假设你的应用是被部署在一个域名的根路径上,例如 https://www.my-app.com/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.my-app.com/my-app/,则设置 publicPath 为 /my-app/。这个值也可以被设置为空字符串 ('') 或是相对路径 ('./'),这样所有的资源都会被链接为相对路径,这样打出来的包可以被部署在任意路径,也可以用在类似 Cordova hybrid 应用的文件系统中。相对 publicPath 的限制相对路径的 publicPath 有一些使用上的限制。在以下情况下,应当避免使用相对 publicPath:当使用基于 HTML5 history.pushState 的路由时;当使用 pages 选项构建多页面应用时。这个值在开发环境下同样生效。如果你想把开发服务器架设在根路径,你可以使用一个条件式的值:module.exports = { publicPath: process.env.NODE_ENV === 'production' ? '/production-sub-path/' : '/'}

outputDir

  • Type: string
  • Default: 'dist'
  • 当运行 vue-cli-service build 时生成的生产环境构建文件的目录。注意目标目录在构建之前会被清除 (构建时传入 --no-clean 可关闭该行为)。提示请始终使用 outputDir 而不要修改 webpack 的 output.path。

assetsDir

  • Type: string
  • Default: ''
  • 放置生成的静态资源 (js、css、img、fonts) 的 (相对于 outputDir 的) 目录。提示从生成的资源覆写 filename 或 chunkFilename 时,assetsDir 会被忽略。

indexPath

  • Type: string
  • Default: 'index.html'
  • 指定生成的 index.html 的输出路径 (相对于 outputDir)。也可以是一个绝对路径。

filenameHashing

  • Type: boolean
  • Default: true
  • 默认情况下,生成的静态资源在它们的文件名中包含了 hash 以便更好的控制缓存。然而,这也要求 index 的 HTML 是被 Vue CLI 自动生成的。如果你无法使用 Vue CLI 生成的 index HTML,你可以通过将这个选项设为 false 来关闭文件名哈希。

pages

  • Type: Object
  • Default: undefined
  • 在 multi-page 模式下构建应用。每个“page”应该有一个对应的 JavaScript 入口文件。其值应该是一个对象,对象的 key 是入口的名字,value 是:一个指定了 entry, template, filename, title 和 chunks 的对象 (除了 entry 之外都是可选的);或一个指定其 entry 的字符串。
  • module.exports = {  pages: {    index: {      // page 的入口      entry: 'src/index/main.js',      // 模板来源      template: 'public/index.html',      // 在 dist/index.html 的输出      filename: 'index.html',      // 当使用 title 选项时,      // template 中的 title 标签需要是 <title><%= htmlWebpackPlugin.options.title %></title>      title: 'Index Page',      // 在这个页面中包含的块,默认情况下会包含      // 提取出来的通用 chunk 和 vendor chunk。      chunks: ['chunk-vendors', 'chunk-common', 'index']    },    // 当使用只有入口的字符串格式时,    // 模板会被推导为 `public/subpage.html`    // 并且如果找不到的话,就回退到 `public/index.html`。    // 输出文件名会被推导为 `subpage.html`。    subpage: 'src/subpage/main.js'  }}
  •  提示当在 multi-page 模式下构建时,webpack 配置会包含不一样的插件 (这时会存在多个 html-webpack-plugin 和 preload-webpack-plugin 的实例)。如果你试图修改这些插件的选项,请确认运行 vue inspect。

lintOnSave

  • Type: boolean | 'warning' | 'default' | 'error'
  • Default: true
  • 是否在开发环境下通过 eslint-loader 在每次保存时 lint 代码。这个值会在 @vue/cli-plugin-eslint 被安装之后生效。设置为 true 或 'warning' 时,eslint-loader 会将 lint 错误输出为编译警告。默认情况下,警告仅仅会被输出到命令行,且不会使得编译失败。如果你希望让 lint 错误在开发时直接显示在浏览器中,你可以使用 lintOnSave: 'error'。这会强制 eslint-loader 将 lint 错误输出为编译错误,同时也意味着 lint 错误将会导致编译失败。或者,你也可以通过设置让浏览器 overlay 同时显示警告和错误:// vue.config.jsmodule.exports = { devServer: { overlay: { warnings: true, errors: true } }}当 lintOnSave 是一个 truthy 的值时,eslint-loader 在开发和生产构建下都会被启用。如果你想要在生产构建时禁用 eslint-loader,你可以用如下配置:// vue.config.jsmodule.exports = { lintOnSave: process.env.NODE_ENV !== 'production'}

runtimeCompiler

  • Type: boolean
  • Default: false
  • 是否使用包含运行时编译器的 Vue 构建版本。设置为 true 后你就可以在 Vue 组件中使用 template 选项了,但是这会让你的应用额外增加 10kb 左右。更多细节可查阅:Runtime + Compiler vs. Runtime only

transpileDependencies

  • Type: Array<string | RegExp>
  • Default: []
  • 默认情况下 babel-loader 会忽略所有 node_modules 中的文件。如果你想要通过 Babel 显式转译一个依赖,可以在这个选项中列出来。

productionSourceMap

  • Type: boolean
  • Default: true
  • 如果你不需要生产环境的 source map,可以将其设置为 false 以加速生产环境构建。

crossorigin

  • Type: string
  • Default: undefined
  • 设置生成的 HTML 中 <link rel="stylesheet"> 和 <script> 标签的 crossorigin 属性。需要注意的是该选项仅影响由 html-webpack-plugin 在构建时注入的标签 - 直接写在模版 (public/index.html) 中的标签不受影响。更多细节可查阅: CORS settings attributes

integrity

  • Type: boolean
  • Default: false
  • 在生成的 HTML 中的 <link rel="stylesheet"> 和 <script> 标签上启用 Subresource Integrity (SRI)。如果你构建后的文件是部署在 CDN 上的,启用该选项可以提供额外的安全性。需要注意的是该选项仅影响由 html-webpack-plugin 在构建时注入的标签 - 直接写在模版 (public/index.html) 中的标签不受影响。另外,当启用 SRI 时,preload resource hints 会被禁用,因为 Chrome 的一个 bug 会导致文件被下载两次。

configureWebpack

  • Type: Object | Function
  • 如果这个值是一个对象,则会通过 webpack-merge 合并到最终的配置中。如果这个值是一个函数,则会接收被解析的配置作为参数。该函数及可以修改配置并不返回任何东西,也可以返回一个被克隆或合并过的配置版本。更多细节可查阅:配合 webpack > 简单的配置方式

chainWebpack

  • Type: Function
  • 是一个函数,会接收一个基于 webpack-chain 的 ChainableConfig 实例。允许对内部的 webpack 配置进行更细粒度的修改。更多细节可查阅:配合 webpack > 链式操作

css.modules

从 v4 起已弃用,请使用css.requireModuleExtension。 在 v3 中,这个选项含义与 css.requireModuleExtension 相反。

css.requireModuleExtension

  • Type: boolean
  • Default: true默认情况下,只有 *.module.[ext] 结尾的文件才会被视作 CSS Modules 模块。设置为 false 后你就可以去掉文件名中的 .module 并将所有的 *.(css|scss|sass|less|styl(us)?) 文件视为 CSS Modules 模块。提示如果你在 css.loaderOptions.css 里配置了自定义的 CSS Module 选项,则 css.requireModuleExtension 必须被显式地指定为 true 或者 false,否则我们无法确定你是否希望将这些自定义配置应用到所有 CSS 文件中。更多细节可查阅:配合 CSS > CSS Modules

css.extract

  • Type: boolean | Object
  • Default: 生产环境下是 true,开发环境下是 false是否将组件中的 CSS 提取至一个独立的 CSS 文件中 (而不是动态注入到 JavaScript 中的 inline 代码)。同样当构建 Web Components 组件时它总是会被禁用 (样式是 inline 的并注入到了 shadowRoot 中)。当作为一个库构建时,你也可以将其设置为 false 免得用户自己导入 CSS。提取 CSS 在开发环境模式下是默认不开启的,因为它和 CSS 热重载不兼容。然而,你仍然可以将这个值显性地设置为 true 在所有情况下都强制提取。

css.sourceMap

  • Type: boolean
  • Default: false
  • 是否为 CSS 开启 source map。设置为 true 之后可能会影响构建的性能。

css.loaderOptions

  • Type: Object
  • Default: {}
  • 向 CSS 相关的 loader 传递选项。例如:module.exports = { css: { loaderOptions: { css: { // 这里的选项会传递给 css-loader }, postcss: { // 这里的选项会传递给 postcss-loader } } }}支持的 loader 有:css-loaderpostcss-loadersass-loaderless-loaderstylus-loader另外,也可以使用 scss 选项,针对 scss 语法进行单独配置(区别于 sass 语法)。更多细节可查阅:向预处理器 Loader 传递选项提示相比于使用 chainWebpack 手动指定 loader 更推荐上面这样做,因为这些选项需要应用在使用了相应 loader 的多个地方。

devServer

  • Type: Object
  • 所有 webpack-dev-server 的选项都支持。注意:有些值像 host、port 和 https 可能会被命令行参数覆写。有些值像 publicPath 和 historyApiFallback 不应该被修改,因为它们需要和开发服务器的 publicPath 同步以保障正常的工作。

devServer.proxy

  • Type: string | Object
  • 如果你的前端应用和后端 API 服务器没有运行在同一个主机上,你需要在开发环境下将 API 请求代理到 API 服务器。这个问题可以通过 vue.config.js 中的 devServer.proxy 选项来配置。devServer.proxy 可以是一个指向开发环境 API 服务器的字符串:module.exports = { devServer: { proxy: 'http://localhost:4000' }}这会告诉开发服务器将任何未知请求 (没有匹配到静态文件的请求) 代理到http://localhost:4000。如果你想要更多的代理控制行为,也可以使用一个 path: options 成对的对象。完整的选项可以查阅 http-proxy-middleware 。module.exports = { devServer: { proxy: { '/api': { target: '<url>', ws: true, changeOrigin: true }, '/foo': { target: '<other_url>' } } }}

parallel

  • Type: boolean
  • Default: require('os').cpus().length > 1是否为 Babel 或 TypeScript 使用 thread-loader。该选项在系统的 CPU 有多于一个内核时自动启用,仅作用于生产构建。

pwa

pluginOptions

  • Type: Object
  • 这是一个不进行任何 schema 验证的对象,因此它可以用来传递任何第三方插件选项。例如:module.exports = { pluginOptions: { foo: { // 插件可以作为 `options.pluginOptions.foo` 访问这些选项。 } }}

Babel

Babel 可以通过 babel.config.js 进行配置。

提示

Vue CLI 使用了 Babel 7 中的新配置格式 babel.config.js。和 .babelrc 或 package.json 中的 babel 字段不同,这个配置文件不会使用基于文件位置的方案,而是会一致地运用到项目根目录以下的所有文件,包括 node_modules 内部的依赖。我们推荐在 Vue CLI 项目中始终使用 babel.config.js 取代其它格式。

所有的 Vue CLI 应用都使用 @vue/babel-preset-app,它包含了 babel-preset-env、JSX 支持以及为最小化包体积优化过的配置。通过它的文档可以查阅到更多细节和 preset 选项。

同时查阅指南中的 Polyfill 章节。

ESLint

ESLint 可以通过 .eslintrc 或 package.json 中的 eslintConfig 字段来配置。

更多细节可查阅 @vue/cli-plugin-eslint

TypeScript

TypeScript 可以通过 tsconfig.json 来配置。

更多细节可查阅 @vue/cli-plugin-typescript

单元测试

Jest

更多细节可查阅 @vue/cli-plugin-unit-jest

Mocha (配合 mocha-webpack)

更多细节可查阅 @vue/cli-plugin-unit-mocha

E2E 测试

Cypress

更多细节可查阅 @vue/cli-plugin-e2e-cypress

Nightwatch

更多细节可查阅 @vue/cli-plugin-e2e-nightwatch


核心概念

系统里有两个主要的部分:

  • @vue/cli:全局安装的,暴露 vue create <app> 命令;
  • @vue/cli-service:局部安装,暴露 vue-cli-service 命令。

两者皆应用了基于插件的架构。

Creator

Creator 是调用 vue create <app> 时创建的类。负责偏好对话、调用 generator 和安装依赖。

Service

Service 是调用 vue-cli-service <command> [...args] 时创建的类。负责管理内部的 webpack 配置、暴露服务和构建项目的命令等。

CLI 插件

CLI 插件是一个可以为 @vue/cli 项目添加额外特性的 npm 包。它应该始终包含一个 Service 插件作为其主要导出,且可选的包含一个 Generator 和一个 Prompt 文件

一个典型的 CLI 插件的目录结构看起来是这样的:

.├── README.md├── generator.js  # generator (可选)├── prompts.js    # prompt 文件 (可选)├── index.js      # service 插件└── package.json

Service 插件

Service 插件会在一个 Service 实例被创建时自动加载——比如每次 vue-cli-service 命令在项目中被调用时。

注意我们这里讨论的“service 插件”的概念要比发布为一个 npm 包的“CLI 插件”的要更窄。前者涉及一个会被 @vue/cli-service 在初始化时加载的模块,也经常是后者的一部分。

此外,@vue/cli-service 的内建命令配置模块也是全部以 service 插件实现的。

一个 service 插件应该导出一个函数,这个函数接受两个参数:

  • 一个 PluginAPI 实例
  • 一个包含 vue.config.js 内指定的项目本地选项的对象,或者在 package.json 内的 vue 字段。

这个 API 允许 service 插件针对不同的环境扩展/修改内部的 webpack 配置,并向 vue-cli-service 注入额外的命令。例如:

module.exports = (api, projectOptions) => {  api.chainWebpack(webpackConfig => {    // 通过 webpack-chain 修改 webpack 配置  })  api.configureWebpack(webpackConfig => {    // 修改 webpack 配置    // 或返回通过 webpack-merge 合并的配置对象  })  api.registerCommand('test', args => {    // 注册 `vue-cli-service test`  })}

为命令指定模式

注意:插件设置模式的方式从 beta.10 开始已经改变了。

如果一个已注册的插件命令需要运行在特定的默认模式下,则该插件需要通过 module.exports.defaultModes 以 { [commandName]: mode } 的形式来暴露:

module.exports = api => {  api.registerCommand('build', () => {    // ...  })}module.exports.defaultModes = {  build: 'production'}

这是因为我们需要在加载环境变量之前知道该命令的预期模式,所以需要提前加载用户选项/应用插件。

在插件中解析 webpack 配置

一个插件可以通过调用 api.resolveWebpackConfig() 取回解析好的 webpack 配置。每次调用都会新生成一个 webpack 配置用来在需要时进一步修改。

module.exports = api => {  api.registerCommand('my-build', args => {    const configA = api.resolveWebpackConfig()    const configB = api.resolveWebpackConfig()    // 针对不同的目的修改 `configA` 和 `configB`...  })}// 请确保为正确的环境变量指定默认模式module.exports.defaultModes = {  'my-build': 'production'}

或者,一个插件也可以通过调用 api.resolveChainableWebpackConfig() 获得一个新生成的链式配置

api.registerCommand('my-build', args => {  const configA = api.resolveChainableWebpackConfig()  const configB = api.resolveChainableWebpackConfig()  // 针对不同的目的链式修改 `configA` 和 `configB`...  const finalConfigA = configA.toConfig()  const finalConfigB = configB.toConfig()})

第三方插件的自定义选项

vue.config.js 的导出将会通过一个 schema 的验证以避免笔误和错误的配置值。然而,一个第三方插件仍然允许用户通过 pluginOptions 字段配置其行为。例如,对于下面的 vue.config.js:

module.exports = {  pluginOptions: {    foo: { /* ... */ }  }}

该第三方插件可以读取 projectOptions.pluginOptions.foo 来做条件式的决定配置。

Generator

一个发布为 npm 包的 CLI 插件可以包含一个 generator.js 或 generator/index.js 文件。插件内的 generator 将会在两种场景下被调用:

  • 在一个项目的初始化创建过程中,如果 CLI 插件作为项目创建 preset 的一部分被安装。
  • 插件在项目创建好之后通过 vue invoke 独立调用时被安装。

这里的 GeneratorAPI 允许一个 generator 向 package.json 注入额外的依赖或字段,并向项目中添加文件。

一个 generator 应该导出一个函数,这个函数接收三个参数:

  1. 一个 GeneratorAPI 实例:
  2. 这个插件的 generator 选项。这些选项会在项目创建对话过程中被解析,或从一个保存在 ~/.vuerc 中的 preset 中加载。例如,如果保存好的 ~/.vuerc 像如下的这样:{ "presets" : { "foo": { "plugins": { "@vue/cli-plugin-foo": { "option": "bar" } } } }}如果用户使用 preset foo 创建了一个项目,那么 @vue/cli-plugin-foo 的 generator 就会收到 { option: 'bar' } 作为第二个参数。对于一个第三方插件来说,该选项将会解析自对话或用户执行 vue invoke 时的命令行参数中 (详见第三方插件的对话)。
  3. 整个 preset (presets.foo) 将会作为第三个参数传入。

示例:

module.exports = (api, options, rootOptions) => {  // 修改 `package.json` 里的字段  api.extendPackage({    scripts: {      test: 'vue-cli-service test'    }  })  // 复制并用 ejs 渲染 `./template` 内所有的文件  api.render('./template')  if (options.foo) {    // 有条件地生成文件  }}

Generator 的模板处理

当你调用 api.render('./template') 时,该 generator 将会使用 EJS 渲染 ./template 中的文件 (相对于 generator 中的文件路径进行解析)

此外,你可以使用 YAML 前置元信息继承并替换已有的模板文件的一部分:

---extend: '@vue/cli-service/generator/template/src/App.vue'replace: !!js/regexp /<script>[^]*?</script>/---<script>export default {  // 替换默认脚本}</script>

你也可以完成多处替换,当然你需要将要替换的字符串用 <%# REPLACE %> 和 <%# END_REPLACE %> 块包裹起来:

---extend: '@vue/cli-service/generator/template/src/App.vue'replace:  - !!js/regexp /欢迎来到你的 Vue.js 应用/  - !!js/regexp /<script>[^]*?</script>/---<%# REPLACE %>替换欢迎信息<%# END_REPLACE %><%# REPLACE %><script>export default {  // 替换默认脚本}</script><%# END_REPLACE %>

文件名的极端情况

如果你想要渲染一个以点开头的模板文件 (例如 .env),则需要遵循一个特殊的命名约定,因为以点开头的文件会在插件发布到 npm 的时候被忽略:

# 以点开头的模板需要使用下划线取代那个点:/generator/template/_env# 调用 api.render('./template') 会在项目目录中渲染成为:.env

同时这也意味着当你想渲染以下划线开头的文件时,同样需要遵循一个特殊的命名约定:

# 这种模板需要使用两个下划线来取代单个下划线:/generator/template/__variables.scss# 调用 api.render('./template') 会在项目目录中渲染成为:_variables.scss

Prompts

内建插件的对话

只有内建插件可以定制创建新项目时的初始化对话,且这些对话模块放置在 @vue/cli 包的内部

一个对话模块应该导出一个函数,这个函数接收一个 PromptModuleAPI 实例。这些对话的底层使用 inquirer 进行展示:

module.exports = api => {  // 一个特性对象应该是一个有效的 inquirer 选择对象  api.injectFeature({    name: 'Some great feature',    value: 'my-feature'  })  // injectPrompt 期望接收一个有效的 inquirer 对话对象  api.injectPrompt({    name: 'someFlag',    // 确认对话只在用户已经选取了特性的时候展示    when: answers => answers.features.include('my-feature'),    message: 'Do you want to turn on flag foo?',    type: 'confirm'  })  // 当所有的对话都完成之后,将你的插件注入到  // 即将传递给 Generator 的 options 中  api.onPromptComplete((answers, options) => {    if (answers.features.includes('my-feature')) {      options.plugins['vue-cli-plugin-my-feature'] = {        someFlag: answers.someFlag      }    }  })}

第三方插件的对话

第三方插件通常会在一个项目创建完毕后被手动安装,且用户将会通过调用 vue invoke 来初始化这个插件。如果这个插件在其根目录包含一个 prompts.js,那么它将会用在该插件被初始化调用的时候。这个文件应该导出一个用于 Inquirer.js 的问题的数组。这些被解析的答案对象会作为选项被传递给插件的 generator。

或者,用户可以通过在命令行传递选项来跳过对话直接初始化插件,比如:

vue invoke my-plugin --mode awesome

发布插件

为了让一个 CLI 插件能够被其它开发者使用,你必须遵循 vue-cli-plugin-<name> 的命名约定将其发布到 npm 上。插件遵循命名约定之后就可以:

  • 被 @vue/cli-service 发现;
  • 被其它开发者搜索到;
  • 通过 vue add <name> 或 vue invoke <name> 安装下来。

开发核心插件的注意事项

注意

这个章节只用于 vuejs/vue-cli 仓库内部的内建插件工作。

一个带有为本仓库注入额外依赖的 generator 的插件 (比如 chai 会通过 @vue/cli-plugin-unit-mocha/generator/index.js 被注入) 应该将这些依赖列入其自身的 devDependencies 字段。这会确保:

  1. 这个包始终存在于该仓库的根 node_modules 中,因此我们不必在每次测试的时候重新安装它们。
  2. yarn.lock 会保持其一致性,因此 CI 程序可以更好地利用缓存。


你的插件在其 UI 中使用时,可以展示不同的附加信息,使其更容易被发现和辨认。

Logo

你可以在将要发布到 npm 上的目录的根上放置一个 logo.png。它会展示在以下几个地方:

  • 搜索插件以安装时
  • 在已安装的插件列表中

插件

这个 logo 应该是一个不透明的正方形图 (最好 84x84).

可发现性

为了提升你的插件在搜索时的可见度,请将描述插件的关键字放到插件的 package.json 文件的 description 字段。

示例:

{  "name": "vue-cli-plugin-apollo",  "version": "0.7.7",  "description": "vue cli plugin to add Apollo and GraphQL"}

你应该将插件网站的 URL 或仓库添加添加到 homepage 或 repository 字段,这样“More Info”按钮就会在你的插件描述中展示出来。

{  "repository": {    "type": "git",    "url": "git+https://github.com/Akryum/vue-cli-plugin-apollo.git"  },  "homepage": "https://github.com/Akryum/vue-cli-plugin-apollo#readme"}

搜索到的插件项目


这个 cli-ui 暴露一个 API,允许增强项目的配置和任务,也可以分享数据和在进程间进行通信。

UI 插件架构

UI 文件

在每个安装好的 vue-cli 插件里,cli-ui 都会尝试从其插件的根目录加载一个可选的 ui.js 文件。注意你也可以使用文件夹 (例如 ui/index.js)。

该文件应该导出一个函数,函数会以 API 对象作为第一个参数:

module.exports = api => {  // 在这里使用 API...}

警告

当试图在“项目插件 (Project plugins)”中获取插件列表时,这些文件将会被重新加载。点击 UI 左侧边栏导航“项目插件 (Project plugins)”按钮可以应用更改。

这里是一个使用 UI API 的 vue-cli 插件的文件夹结构示例:

- vue-cli-plugin-test  - package.json  - index.js  - generator.js  - prompts.js  - ui.js  - logo.png

项目本地的插件

如果你需要在项目里访问插件 API 而不需要创建一个完整的插件,你可以在 package.json 文件中使用 vuePlugins.ui 选项:

{  "vuePlugins": {    "ui": ["my-ui.js"]  }}

每个文件都需要暴露一个函数,将插件 API 作为第一个参数携带。

开发模式

当构建你自己的插件时,你可能想在开发环境下运行 cli-ui,所以这样运行会输出较为实用的日志:

vue ui --dev

或:

vue ui -D

项目的配置

配置 UI

你可以通过 api.describeConfig 方法添加一个项目配置。

首先你需要传入一些信息:

api.describeConfig({  // 唯一的配置 ID  id: 'org.vue.eslintrc',  // 展示名称  name: 'ESLint configuration',  // 展示在名称下方的描述  description: 'Error checking & Code quality',  // “更多信息 (More info)”链接  link: 'https://eslint.org'})

危险

请确定为 id 设置正确的命名空间,因为它需要跨所有插件保持唯一。我们推荐使用反向域名记号 (reverse domain name notation)

配置图标

可以是一个 Material 图标代码或一个自定义的图片 (详见公共静态文件):

api.describeConfig({  /* ... */  // 配置图标  icon: 'application_settings'})

如果你没有定义图标,那就展示该插件可能存在的 logo (详见 Logo)。

配置文件

默认情况下,配置 UI 可能会读写一个或多个配置文件,例如 .eslintrc 和 vue.config.js。

你可以提供可能需要在用户项目中检测的文件:

api.describeConfig({  /* ... */  // 该配置所有可能的文件  files: {    // eslintrc.js    eslint: {      js: ['.eslintrc.js'],      json: ['.eslintrc', '.eslintrc.json'],      // 会从 `package.json` 读取      package: 'eslintConfig'    },    // vue.config.js    vue: {      js: ['vue.config.js']    }  },})

支持的类型有:json、yaml、js、package。这个顺序是很重要的:如果这项配置不存在,则会创建列表中的第一个文件。

展示配置提示符

使用 onRead 钩子来返回一个提示符列表,用以配置展示:

api.describeConfig({  /* ... */  onRead: ({ data, cwd }) => ({    prompts: [      // 提示符对象    ]  })})

这些提示符会展示在配置的详情面板中。

查阅提示符了解更多信息。

这个 data 对象包含了每个配置文件内容的 JSON 结果。

例如,假设用户在其项目中的 vue.config.js 有以下内容:

module.exports = {  lintOnSave: false}

我们在插件中像这样声明配置文件:

api.describeConfig({  /* ... */  // 该配置所有可能的文件  files: {    // vue.config.js    vue: {      js: ['vue.config.js']    }  },})

则这个 data 对象会是:

{  // 文件  vue: {    // 文件数据    lintOnSave: false  }}

多个文件的例子:如果我们在用户的项目中添加以下 eslintrc.js 文件:

module.exports = {  root: true,  extends: [    'plugin:vue/essential',    '@vue/standard'  ]}

那么在我们的插件中将 files 选项改变成为:

api.describeConfig({  /* ... */  // 该配置所有可能的文件  files: {    // eslintrc.js    eslint: {      js: ['.eslintrc.js'],      json: ['.eslintrc', '.eslintrc.json'],      // 会从 `package.json` 读取      package: 'eslintConfig'    },    // vue.config.js    vue: {      js: ['vue.config.js']    }  },})

则这个 data 对象会是:

{  eslint: {    root: true,    extends: [      'plugin:vue/essential',      '@vue/standard'    ]  },  vue: {    lintOnSave: false  }}

配置选项卡

你可以将这些提示符组织成为几个选项卡:

api.describeConfig({  /* ... */  onRead: ({ data, cwd }) => ({    tabs: [      {        id: 'tab1',        label: 'My tab',        // 可选的        icon: 'application_settings',        prompts: [          // 提示符对象们        ]      },      {        id: 'tab2',        label: 'My other tab',        prompts: [          // 提示符对象们        ]      }    ]  })})

保存配置变更

使用 onWrite 钩子将数据写入配置文件 (或者执行任何 Node.js 代码):

api.describeConfig({  /* ... */  onWrite: ({ prompts, answers, data, files, cwd, api }) => {    // ...  }})

参数:

  • prompts: 当前提示符们的运行时对象 (详见下方)
  • answers: 来自用户输入的回答数据
  • data: 从配置文件读取的只读的初始化数据
  • files: 被找到的文件的描述器 ({ type: 'json', path: '...' })
  • cwd: 当前工作目录
  • api: onWrite API (详见下方)

提示符的运行时对象:

{  id: data.name,  type: data.type,  name: data.short || null,  message: data.message,  group: data.group || null,  description: data.description || null,  link: data.link || null,  choices: null,  visible: true,  enabled: true,  // 当前值 (未被过滤的)  value: null,  // 如果用户修改过了则为 true  valueChanged: false,  error: null,  tabId: null,  // 原始的 inquirer 提示符对象  raw: data}

onWrite API:

  • assignData(fileId, newData): 在写入前使用 Object.assign 来更新配置文件。
  • setData(fileId, newData): newData 的每个 key 在写入之前都将会被深设置在配置数据上 (或当值为 undefined 时被移除)。
  • async getAnswer(id, mapper): 为一个给定的提示符 id 获取答复并通过可能提供了的 mapper 函数 (例如 JSON.parse) 进行 map 处理。

示例 (来自 ESLint 插件):

api.describeConfig({  // ...  onWrite: async ({ api, prompts }) => {    // 更新 ESLint 规则    const result = {}    for (const prompt of prompts) {      result[`rules.${prompt.id}`] = await api.getAnswer(prompt.id, JSON.parse)    }    api.setData('eslint', result)  }})

项目的任务

任务 UI

任务是从项目 package.json 文件的 scripts 字段生成的。

因为有 api.describeTask 方法,你可以为任务“增强”额外的信息和钩子:

api.describeTask({  // 用于匹配脚本命令的 RegExp 对象,来选择要被描述的任务  match: /vue-cli-service serve/,  description: 'Compiles and hot-reloads for development',  // “More info”链接  link: 'https://github.com/vuejs/vue-cli/blob/dev/docs/cli-service.md#serve'})

任务图标

可以是一个 Material 图标代码或一个自定义的图片 (详见公共静态文件):

api.describeTask({  /* ... */  // 任务图标  icon: 'application_settings'})

如果你没有定义图标,那就展示该插件可能存在的 logo (详见 Logo)。

任务参数

你可以添加提示符来修改命令参数。它们会展示在一个“参数”模态框中。

Example:

api.describeTask({  // ...  // 选填参数 (inquirer 提示符)  prompts: [    {      name: 'open',      type: 'confirm',      default: false,      description: 'Open browser on server start'    },    {      name: 'mode',      type: 'list',      default: 'development',      choices: [        {          name: 'development',          value: 'development'        },        {          name: 'production',          value: 'production'        },        {          name: 'test',          value: 'test'        }      ],      description: 'Specify env mode'    }  ]})

详见提示符

任务钩子

有一些钩子是可用的:

  • onBeforeRun
  • onRun
  • onExit

例如,你可以将 (上述) 提示符的回答作为一个新参数添加到命令上:

api.describeTask({  // ...  // 钩子  // 在这里修改参数  onBeforeRun: async ({ answers, args }) => {    // 参数    if (answers.open) args.push('--open')    if (answers.mode) args.push('--mode', answers.mode)    args.push('--dashboard')  },  // 任务运行之后立即执行  onRun: async ({ args, child, cwd }) => {    // child: Node 子进程    // cwd: 进程所在目录  },  onExit: async ({ args, child, cwd, code, signal }) => {    // code: 退出码    // signal: 可能会被使用的杀进程信号  }})

任务视图

你可以在任务详情面板中使用 ClientAddon API 展示自定义视图:

api.describeTask({  // ...  // 额外的视图 (例如 webpack 仪表盘)  // 默认情况下,这里是展示终端输出的 `output` 视图  views: [    {      // 唯一 ID      id: 'vue-webpack-dashboard-client-addon',      // 按钮文字      label: 'Dashboard',      // 按钮图标      icon: 'dashboard',      // 要加载的动态组件 (详见下述“客户端 addon”章节)      component: 'vue-webpack-dashboard'    }  ],  // 展示任务详情时默认选择的视图 (默认是 `output`)  defaultView: 'vue-webpack-dashboard-client-addon'})

详见客户端 addon

新增任务

你也可以不使用 api.describeTask,而是通过 api.addTask 添加一个 package.json 脚本中没有的全新任务。这些任务只会出现在 cli UI 中。

你需要提供一个 command 选项替代掉 match 选项。

示例:

api.addTask({  // 必填  name: 'inspect',  command: 'vue-cli-service inspect',  // 选填  // 其余部分类似 `describeTask` 但是没有 `match` 选项  description: '...',  link: 'https://github.com/vuejs/vue-cli/...',  prompts: [ /* ... */ ],  onBeforeRun: () => {},  onRun: () => {},  onExit: () => {},  views: [ /* ... */ ],  defaultView: '...'})

警告

command 将会运行一个 Node 上下文。也就是说你可以像在 package.json 脚本中一样调用 Node 的 bin 命令。

提示符

提示符对象必须是合法的 inquirer 对象。

不过你也可以添加下列额外的字段 (只会被 UI 使用的可选项):

{  /* ... */  // 用来将提示符按章节分组  group: 'Strongly recommended',  // 附加描述  description: 'Enforce attribute naming style in template (`my-prop` or `myProp`)',  // “More info”链接  link: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/attribute-hyphenation.md',}

支持的 inquirer 类型有:checkbox、confirm、input、password、list、rawlist。

此外,UI 还支持了仅在这里工作的特殊类型:

  • color:展示一个取色器。

Switch 示例

{  name: 'open',  type: 'confirm',  default: false,  description: 'Open the app in the browser'}

Select 示例

{  name: 'mode',  type: 'list',  default: 'development',  choices: [    {      name: 'Development mode',      value: 'development'    },    {      name: 'Production mode',      value: 'production'    },    {      name: 'Test mode',      value: 'test'    }  ],  description: 'Build mode',  link: 'https://link-to-docs'}

Input 示例

{  name: 'host',  type: 'input',  default: '0.0.0.0',  description: 'Host for the development server'}

Checkbox 示例

展示多个 switch。

{  name: 'lintOn',  message: 'Pick additional lint features:',  when: answers => answers.features.includes('linter'),  type: 'checkbox',  choices: [    {      name: 'Lint on save',      value: 'save',      checked: true    },    {      name: 'Lint and fix on commit' + (hasGit() ? '' : chalk.red(' (requires Git)')),      value: 'commit'    }  ]}

取色器示例

{  name: 'themeColor',  type: 'color',  message: 'Theme color',  description: 'This is used to change the system UI color around the app',  default: '#4DBA87'}

提示符的改进

在 vue-cli 插件中,你可能已经有一个 prompts.js 文件,在 (用 CLI 或 UI) 安装该插件的时候询问用户一些问题。你可以向那些提示符对象额外添加只支持 UI 的上述字段,这样的话如果用户使用 UI 的话可以看到更多的信息。

警告

目前,那些不支持的 inquirer 类型不会在 UI 中正常工作。

客户端 addon

客户端 addon 是一个动态加载到 cli-ui 中的 JS 包。用于加载自定义组件和路由。

创建一个客户端 addon

推荐的创建一个客户端 addon 的方式是通过 vue cli 创建一个新项目。你也可以在插件的子目录或不同的 npm 包中这样做。

作为开发依赖安装 @vue/cli-ui。

然后添加一个 vue.config.js 文件并附带以下内容:

const { clientAddonConfig } = require('@vue/cli-ui')module.exports = {  ...clientAddonConfig({    id: 'org.vue.webpack.client-addon',    // 开发环境端口 (默认值 8042)    port: 8042  })}

这个 clientAddonConfig 方法将会生成需要的 vue-cli 配置。除此之外,它会禁用 CSS extraction 并将代码输出到在客户端 addon 目录的 ./dist/index.js。

危险

请确定为 id 设置正确的命名空间,因为它需要跨所有插件保持唯一。我们推荐使用反向域名记号 (reverse domain name notation)

然后修改 .eslintrc.json 文件以添加一些允许的全局对象:

{  // ...  "globals": {    "ClientAddonApi": false,    "mapSharedData": false,    "Vue": false  }}

你现在可以在开发环境下运行 serve 脚本,也可以在准备发布时运行 build 脚本。

ClientAddonApi

在客户端 addon 资源中打开 main.js 文件并删除所有代码。

警告

别在客户端 addon 源文件总导入 Vue ,请从浏览器 window 使用全局的 Vue 对象。

这里是一个 main.js 的示例代码:

import VueProgress from 'vue-progress-path'import WebpackDashboard from './components/WebpackDashboard.vue'import TestView from './components/TestView.vue'// 你可以安装额外的 Vue 插件// 使用全局的 'Vue' 变量Vue.use(VueProgress, {  defaultShape: 'circle'})// 注册一个自定义组件// (工作原理类似 'Vue.component')ClientAddonApi.component('org.vue.webpack.components.dashboard', WebpackDashboard)// 在 vue-router 中为 /addon/<id> 添加子路由。// 例如,addRoutes('foo', [ { path: '' }, { path: 'bar' } ])// 将会向路由器添加 /addon/foo/ 和 /addon/foo/bar。// 我们在此用 'test-webpack-route' 名称创建一个新的 '/addon/vue-webpack/' 路由ClientAddonApi.addRoutes('org.vue.webpack', [  { path: '', name: 'org.vue.webpack.routes.test', component: TestView }])// 你可以翻译插件组件// (通过使用 vue-i18n) 加载语言文件const locales = require.context('./locales', true, /[a-z0-9]+.json$/i)locales.keys().forEach(key => {  const locale = key.match(/([a-z0-9]+)./i)[1]  ClientAddonApi.addLocalization(locale, locales(key))})

危险

请确定为 id 设置正确的命名空间,因为它需要跨所有插件保持唯一。我们推荐使用反向域名记号 (reverse domain name notation)

cli-ui 在 window 作用域内注册了 Vue 和 ClientAddonApi 作为全局变量。

你可以在自己的组件里使用 @vue/ui 和 @vue/cli-ui 所有的组件和 CSS class 以保持样式和体验的一致性。你也可以用内置的 vue-i18n 翻译字符串。

注册客户端 addon

回到 ui.js 文件,使用 api.addClientAddon 方法并带一个指向构建后的文件夹的 require 字符串:

api.addClientAddon({  id: 'org.vue.webpack.client-addon',  // 包含构建出来的 JS 文件的文件夹  path: '@vue/cli-ui-addon-webpack/dist'})

这会使用 Node.js 的 require.resolve API 查找文件夹并为从客户端 addon 构建的文件 index.js 启动一个服务器。

或者当开发插件时指定一个 URL (理想中你需要在 Vue 的测试项目的 vue-cli-ui.js 中做这些)

// 用于开发环境// 如果已经在插件中定义过,则会覆写路径api.addClientAddon({  id: 'org.vue.webpack.client-addon',  // 使用你之前配置过低同样的端口  url: 'http://localhost:8042/index.js'})

使用客户端 addon

现在你可以在这些视图中使用客户端 addon 了。例如,你可以在一个被描述的任务中指定一个视图:

api.describeTask({  /* ... */  // 额外的视图 (例如 webpack dashboard)  // 默认情况下,这是展示终端输出的 'output' 视图  views: [    {      // 唯一的 ID      id: 'org.vue.webpack.views.dashboard',      // 按钮文字      label: 'Dashboard',      // 按钮图标 (material-icons)      icon: 'dashboard',      // 加载的动态组件,会用 ClientAddonApi 进行注册      component: 'org.vue.webpack.components.dashboard'    }  ],  // 展示任务详情时默认选择的视图 (默认情况下就是 output)  defaultView: 'org.vue.webpack.views.dashboard'})

这是一个客户端 addon 代码,注册了 `'org.vue.webpack.components.dashboard' 组件 (像我们之前看到的一样):

/* 在 `main.js` 中 */// 导入组件import WebpackDashboard from './components/WebpackDashboard.vue'// 注册自定义组件// (工作原理类似 'Vue.component')ClientAddonApi.component('org.vue.webpack.components.dashboard', WebpackDashboard)

任务视图示例

自定义视图

你可以使用 api.addView 方法在标准的“Project plugins”、“Project configuration”和“Project tasks”之下添加一个新的视图:

api.addView({  // 唯一的 id  id: 'org.vue.webpack.views.test',  // 路由名称 (来自 Vue Router)  // 使用 'ClientAddonApi.addRoutes' 方法中相同的名字 (详见之前的客户端 addon 章节)  name: 'org.vue.webpack.routes.test',  // 按钮图标 (material-icons)  icon: 'pets',  // 你也可以指定一个自定义图片 (详见之前的公共静态文件章节):  // icon: 'http://localhost:4000/_plugin/%40vue%2Fcli-service/webpack-icon.svg',  // 按钮的提示文字  tooltip: 'Test view from webpack addon'})

这里是注册了 'org.vue.webpack.routes.test' 的客户端 addon 里的代码 (之前已经见过了):

/* 在 `main.js` 里 */// 导入组件import TestView from './components/TestView.vue'// 在 vue-router 中为 /addon/<id> 添加子路由// 例如,addRoutes('foo', [ { path: '' }, { path: 'bar' } ])// 将为 Vue Router 添加 /addon/foo/ 和 /addon/foo/bar 路由。// 我们这里创建一个新的 '/addon/vue-webpack/' 路由,并命名为 'test-webpack-route'。ClientAddonApi.addRoutes('org.vue.webpack', [  { path: '', name: 'org.vue.webpack.routes.test', component: TestView }])

自定义视图示例

共享的数据

一种简易的自定义组件之间通过共享的数据互通信息的方式。

例如,webpack 仪表盘在 UI 客户端和 UI 服务端之间通过这个 API 共享了构建的统计信息。

在插件 ui.js (Node.js) 中:

// 设置或更新api.setSharedData('com.my-name.my-variable', 'some-data')// 获取const sharedData = api.getSharedData('com.my-name.my-variable')if (sharedData) {  console.log(sharedData.value)}// 移除api.removeSharedData('com.my-name.my-variable')// 侦听变化const watcher = (value, id) => {  console.log(value, id)}api.watchSharedData('com.my-name.my-variable', watcher)// 取消侦听api.unwatchSharedData('com.my-name.my-variable', watcher)// 带命名空间的版本const {  setSharedData,  getSharedData,  removeSharedData,  watchSharedData,  unwatchSharedData} = api.namespace('com.my-name.')

危险

请确定为 id 设置正确的命名空间,因为它需要跨所有插件保持唯一。我们推荐使用反向域名记号 (reverse domain name notation)

在其自定义组件中:

// Vue 组件export default {  // 同步共享的数据  sharedData () {    return {      // 你可以在模板中使用 `myVariable`      myVariable: 'com.my-name.my-variable'      // 也可以映射带命名空间的共享数据      ...mapSharedData('com.my-name.', {        myVariable2: 'my-variable2'      })    }  },  // 手动方法  async created () {    const value = await this.$getSharedData('com.my-name.my-variable')    this.$watchSharedData(`com.my-name.my-variable`, value => {      console.log(value)    })    await this.$setSharedData('com.my-name.my-variable', 'new-value')  }}

如果你使用了 sharedData 选项,共享的数据就可以一个相应的属性被赋值时进行更新。

<template>  <VueInput v-model="message"/></template><script>export default {  sharedData: {    // 将会在服务端同步 'my-message' 共享的数据    message: 'com.my-name.my-message'  }}</script>

例如在创建一个设置组件时,这个特性是非常有用的。

插件的 action

插件的 action 就是在 cli-ui (浏览器) 和插件 (Node.js) 直接的调用。

例如,你可能有一个自定义组件里的按钮 (详见客户端 addon),这个按钮会通过这个 API 向服务端调用一些 Node.js 代码。

在插件 (Node.js) 的 ui.js 文件里,你可以从 PluginApi 使用两个方法:

// 调用一个 actionapi.callAction('com.my-name.other-action', { foo: 'bar' }).then(results => {  console.log(results)}).catch(errors => {  console.error(errors)})
// 监听一个 actionapi.onAction('com.my-name.test-action', params => {  console.log('test-action called', params)})

危险

请确定为 id 设置正确的命名空间,因为它需要跨所有插件保持唯一。我们推荐使用反向域名记号 (reverse domain name notation)

你可以通过 api.namespace 使用带命名空间的版本 (类似共享的数据):

const { onAction, callAction } = api.namespace('com.my-name.')

在客户端 addon 组件 (浏览器) 中,你可以访问 $onPluginActionCalled、$onPluginActionResolved 和 $callPluginAction:

// Vue 组件export default {  created () {    this.$onPluginActionCalled(action => {      // 当 action 被调用时      // 且在运行之前      console.log('called', action)    })    this.$onPluginActionResolved(action => {      // 当 action 运行完毕之后      console.log('resolved', action)    })  },  methods: {    testPluginAction () {      // 调用一个插件的 action      this.$callPluginAction('com.my-name.test-action', {        meow: 'meow'      })    }  }}

进程间通信 (IPC)

IPC 就是进程间通信 (Inter-Process Communication) 的缩写。该系统允许你轻松的从子进程 (例如任务) 发送消息,并且轻量快速。

为了在 webpack 仪表盘 UI 上展示数据,@vue/cli-service 的 serve 和 build 命令会在 --dashboard 参数被传入时向 cli-ui Node.js 服务器发送 IPC 消息。

在进程代码中 (可以是一个 webpack 插件或一个 Node.js 的任务脚本),你可以使用 @vue/cli-shared-utils 中的 IpcMessenger 类:

const { IpcMessenger } = require('@vue/cli-shared-utils')// 创建一个新的 IpcMessenger 实例const ipc = new IpcMessenger()function sendMessage (data) {  // 发送一条消息给 cli-ui 服务器  ipc.send({    'com.my-name.some-data': {      type: 'build',      value: data    }  })}function messageHandler (data) {  console.log(data)}// 监听消息ipc.on(messageHandler)// 不再监听ipc.off(messageHandler)function cleanup () {  // 从 IPC 网络断开连接  ipc.disconnect()}

手动连接:

const ipc = new IpcMessenger({  autoConnect: false})// 这条消息会被放入队列ipc.send({ ... })ipc.connect()

闲时自动断开连接 (在没有任何消息一段时间之后):

const ipc = new IpcMessenger({  disconnectOnIdle: true,  idleTimeout: 3000 // 默认值})ipc.send({ ... })setTimeout(() => {  console.log(ipc.connected) // false}, 3000)

连接到另一个 IPC 网络:

const ipc = new IpcMessenger({  networkId: 'com.my-name.my-ipc-network'})

在一个 vue-cli 插件的 ui.js 文件中,你可以使用 ipcOn、ipcOff 和 ipcSend 方法:

function onWebpackMessage ({ data: message }) {  if (message['com.my-name.some-data']) {    console.log(message['com.my-name.some-data'])  }}// 监听任何 IPC 消息api.ipcOn(onWebpackMessage)// 不监听任何消息api.ipcOff(onWebpackMessage)// 向所有已连接的 IpcMessenger 实例发送一条消息api.ipcSend({  webpackDashboardMessage: {    foo: 'bar'  }})

本地存储

一个插件可以从 UI 服务器本地的 lowdb 数据库保存和加载数据。

// 向本地的数据库存入一个值api.storageSet('com.my-name.an-id', { some: 'value' })// 从本地的数据库取回一个值console.log(api.storageGet('com.my-name.an-id'))// 完整的 lowdb 实例api.db.get('posts')  .find({ title: 'low!' })  .assign({ title: 'hi!'})  .write()// 带命名空间的辅助函数const { storageGet, storageSet } = api.namespace('my-plugin.')

危险

请确定为 id 设置正确的命名空间,因为它需要跨所有插件保持唯一。我们推荐使用反向域名记号 (reverse domain name notation)

Notification

你可以基于用户操作系统的通知系统展示通知:

api.notify({  title: 'Some title',  message: 'Some message',  icon: 'path-to-icon.png'})

这里有一些内建的图标;

  • 'done'
  • 'error'

进度界面

你可以用一些文字或进度条来展示进度界面:

api.setProgress({  status: 'Upgrading...',  error: null,  info: 'Step 2 of 4',  progress: 0.4 // 从 0 到 1, -1 表示隐藏进度条})

移除进度界面:

api.removeProgress()

钩子

钩子可以用来响应某些 cli-ui 的事件。

onProjectOpen

当插件在当前项目中第一次被加载时触发。

api.onProjectOpen((project, previousProject) => {  // 重置数据})

onPluginReload

当插件被重新加载时触发。

api.onPluginReload((project) => {  console.log('plugin reloaded')})

onConfigRead

当一个配置界面被打开或刷新时触发。

api.onConfigRead(({ config, data, onReadData, tabs, cwd }) => {  console.log(config.id)})

onConfigWrite

当用户在保存界面里保存时触发。

api.onConfigWrite(({ config, data, changedFields, cwd }) => {  // ...})

onTaskOpen

当用户打开一项任务的详情面板时触发。

api.onTaskOpen(({ task, cwd }) => {  console.log(task.id)})

onTaskRun

当用户运行一项任务时触发。

api.onTaskRun(({ task, args, child, cwd }) => {  // ...})

onTaskExit

当一项任务退出时触发。不论任务成功或失败它都会触发。

api.onTaskExit(({ task, args, child, signal, code, cwd }) => {  // ...})

onViewOpen

当用户打开一个视图 (如 'Plugins'、'Configurations' 或 'Tasks') 时触发。

api.onViewOpen(({ view, cwd }) => {  console.log(view.id)})

建议

这里的建议是指为用户提议执行 action 的按钮。它们展示在界面的顶栏上。例如我们可以放一个按钮,在应用里没有检测到 Vue Router 包的时候建议将其安装。

api.addSuggestion({  id: 'com.my-name.my-suggestion',  type: 'action', // 必填 (未来会加入更多类型)  label: 'Add vue-router',  // 该消息会展示在一个详情模态框里  message: 'A longer message for the modal',  link: 'http://link-to-docs-in-the-modal',  // 可选的图片  image: '/_plugin/my-package/screenshot.png',  // 当该项建议被用户激活时调用的函数  async handler () {    // ...    return {      // 默认移除这个按钮      keep: false    }  }})

危险

请确定为 id 设置正确的命名空间,因为它需要跨所有插件保持唯一。我们推荐使用反向域名记号 (reverse domain name notation)

UI 建议

之后你可以移除这项建议:

api.removeSuggestion('com.my-name.my-suggestion')

你也可以给建议附带 actionLink,当用户激活它时,会换做打开一个页面:

api.addSuggestion({  id: 'com.my-name.my-suggestion',  type: 'action', // Required  label: 'Add vue-router',  // 打开一个新标签  actionLink: 'https://vuejs.org/'})

通常情况下,你会选择适当的上下文用钩子来展示建议:

const ROUTER = 'vue-router-add'api.onViewOpen(({ view }) => {  if (view.id === 'vue-project-plugins') {    if (!api.hasPlugin('vue-router')) {      api.addSuggestion({        id: ROUTER,        type: 'action',        label: 'org.vue.cli-service.suggestions.vue-router-add.label',        message: 'org.vue.cli-service.suggestions.vue-router-add.message',        link: 'https://router.vuejs.org/',        async handler () {          await install(api, 'vue-router')        }      })    }  } else {    api.removeSuggestion(ROUTER)  }})

在这个例子中,如果 Vue Router 没有安装好,我们只会在插件视图中展示安装 Vue Router 的建议。

注意

addSuggestion 和 removeSuggestion 可以通过 api.namespace() 指定命名空间。

其它方法

hasPlugin

如果项目使用了该插件则返回 true。

api.hasPlugin('eslint')api.hasPlugin('apollo')api.hasPlugin('vue-cli-plugin-apollo')

getCwd

获取当前工作目录。

api.getCwd()

resolve

在当前工程下解析一个文件:

api.resolve('src/main.js')

getProject

得出当前打开的工程。

api.getProject()

公共静态文件

你可能需要在 cli-ui 内建的 HTTP 服务器上暴露一些静态文件 (通常是为自定义视图指定图标)。

在插件包根目录里可选的放置一个 ui-public 文件夹,这个文件夹里的任何文件都会暴露至 /_plugin/:id/* 的 HTTP 路由。

例如,如果你将 my-logo.png 文件放置到 vue-cli-plugin-hello/ui-public/ 文件夹,那么 cli-ui 加载插件的时候可以通过 /_plugin/vue-cli-plugin-hello/my-logo.png 这个 URL 来访问它。

api.describeConfig({  /* ... */  // 自定义图片  icon: '/_plugin/vue-cli-plugin-hello/my-logo.png'})


标准 UI

请遵循下列简单步骤来为 CLI UI 提交一种其它语言的翻译!

  1. 运行 navigator.languages 或 navigator.language 为新的地区获取语言代码。例如:'fr'。
  2. 搜索 npm 确认名为 vue-cli-locale-<language code> 的包是否已经存在。如果存在,则请通过 PR 为它贡献!如果没找到,则创建一个新的名为 vue-cli-locale-<language code> 的地区的包。例如:vue-cli-locale-fr.
  3. 将地区的 JSON 文件放置在一个 locales 文件夹并将这个文件命名为语言代码。例如:locales/fr.json。
  4. 在 package.json 文件中,设置 unpkg 字段为地区文件的路径。例如:"unpkg": "./locales/fr.json"。
  5. 将包发布到 npm 上。

可以参考这里的英文地区文件。

作为示例,参考一份法语的包

翻译插件

你也可以在插件的根目录的 locales 文件夹放置与 vue-i18n 兼容的地区文件。这样做会在项目打开的时候自动加载,然后你可以使用 $t 在你的组件和 vue-i18n 辅助函数里翻译字符串。同样的 UI API (像 describeTask) 用到的字符串将会进入 vue-i18n,这样你就可以对它们做本地化。

示例 locales 文件夹:

vue-cli-plugin/locales/en.jsonvue-cli-plugin/locales/fr.json

API 的用法示例:

api.describeConfig({  // vue-i18n 路径  description: 'com.my-name.my-plugin.config.foo'})

危险

请确定为 id 设置正确的命名空间,因为它需要跨所有插件保持唯一。我们推荐使用反向域名记号 (reverse domain name notation)

在组件中使用的示例:

<VueButton>{{ $t('com.my-name.my-plugin.actions.bar') }}</VueButton>

如果你愿意的话,可以使用 ClientAddonApi 在一个客户端 addon 加载地区文件:

// 加载本地文件 (使用 vue-i18n)const locales = require.context('./locales', true, /[a-z0-9]+.json$/i)locales.keys().forEach(key => {  const locale = key.match(/([a-z0-9]+)./i)[1]  ClientAddonApi.addLocalization(locale, locales(key))})