vue项目多环境配置方案
简介
项目:使用vue-cli-service作为启动devServer以及打包构建工具的vue项目。
需求场景:
- 开发:启动各环境的devServer,联调时可以在本地调用不同环境的后端接口
- 构建:打包各环境的代码,不同环境对应的后端服务器地址不同,部署后各环境的前端项目会调用对应环境(开发、测试、uat、生产)的后端接口
开发过程中经常会出现以上使用场景,基于该背景,以下是从vue-cli-service源码出发梳理一套vue项目多环境配置方案过程中的一些记录(查看具体方案的可跳到最后的"方案实现"一节)
Mode
vue项目的多环境配置中需要重点关注的是vue-cli-service中的Mode(环境模式)的概念,vue-cli-service中有三种模式:
-
development:
vue-cli-service serve
执行时的默认模式。 -
test:
vue-cli-service test:unit
执行时的默认模式,安装@vue/test-utils插件后会注入该模式。 -
production:
vue-cli-service build
执行时的默认模式。 在执行命令时没有通过--mode
命令行标识去指定模式的情况下,vue-cli-service会根据执行命令时不同的参数来推断一个默认的模式。
Mode与Environment
Mode
的几个枚举值很容易与环境(Environment)
的概念混淆,Mode有development/test/production
几种,Environment可以有很多种,常见的有开发(development)/测试(test)/uat/生产(production)
。
-
Mode
- 不同Mode下vue-cli-service会执行不同的操作,如development模式可以理解为当前执行vue-cli-service命令是在开发场景中执行的,vue-cli-service会自动生成一份针对开发优化的webpack配置并启用devServer,使得可以在本地打开项目以供调试。
- development和production模式下各自生成的webpack配置会不一样,production模式会针对打包的代码进行优化,如production模式会添加代码压缩、分包、文件名添加hash值等配置。
-
Environment
- 不同环境指的是打包出来的代码的运行环境,使用场景有:在不同环境中的代码调用的后端接口地址可能不同,如在开发服务器上的前端项目调用后端接口域名为
https://dev.xxx.com/apis
,测试服务器上部署的前端项目调用的后端接口域名为https://test.xxx.com/apis
若概念混淆,当执行
vue-cli-service build
时Mode设置为了development而不是production,那么部署后的代码可能会出现没有代码压缩(体积很大)、文件名后没有hash值(每次更新都需要用户清空缓存刷新才能生效)等问题。 - 不同环境指的是打包出来的代码的运行环境,使用场景有:在不同环境中的代码调用的后端接口地址可能不同,如在开发服务器上的前端项目调用后端接口域名为
执行vue-cli-service xxx后发生了什么
该节只介绍mode相关的内容,其他部分不过多赘述。
PS:本节内容搭配@vue/cli-service
的源码进行食用口感最佳。
1、入口
vue-cli-service命令的入口文件为@/vue/cli-service/bin/vue-cli-service.js
,即执行vue-cli-service
命令背后逻辑是执行该文件,查看可知通过vue-cli-service xxx
传入的参数经过minimist
处理后会传入service.run
方法中。
const rawArgv = process.argv.slice(2)
const args = require('minimist')(rawArgv, {
boolean: [
// build
'modern',
'report',
'report-json',
'inline-vue',
'watch',
// serve
'open',
'copy',
'https',
// inspect
'verbose'
]
})
// vue-cli-service xxx命令后第一个参数,正常情况下为serve/build/test:unit
const command = args._[0]
service.run(command, args, rawArgv).catch(err => {
error(err)
process.exit(1)
})
2、确定mode
# @vue/cli-service/lib/Service.js
class Service{
// modes为初始化过程中通过resolvePlugins方法加载@vue/cli-service/lib/commands/下的文件提供,具体可细看constructor中的内容,此处直接将加载后的结果展示以供理解mode的确定逻辑
modes={
"serve": "development",
"test:unit": "test",
"build": "production"
}
// name=serve|build|test:unit
run(name, args = {}, rawArgv = []) {
const mode = args.mode || (name === 'build' && args.watch ? 'development' : this.modes[name])
}
}
如上可知mode的推断逻辑(按优先级排列):
vue-cli-service serve --mode development
:通过--mode
指定mode优先级最高vue-cli-service build --watch
:执行该命令mode默认指定为development- 其余场景根据
modes[name]
自动匹配
3、初始化环境变量
调用service.loadEnv方法并通过dotenv
库依次读取并加载项目根目录下的.env.[mode].local
和.env.[mode]
文件,将内部的变量一一分配到process.env中。
4、确定NODE_ENV
# @vue/cli-service/lib/Service.js Service.loadEnv()
const defaultNodeEnv = (mode === 'production' || mode === 'test') ?
mode : 'development'
if (process.env.NODE_ENV == null) {
process.env.NODE_ENV = defaultNodeEnv
}
if (process.env.BABEL_ENV == null) {
process.env.BABEL_ENV = defaultNodeEnv
}
以上为定义process.env.NODE_ENV
以及process.env.BABEL_ENV
的逻辑。此部分用到的mode为“确定mode”一节中所得到的值。
PS(PS又来了):由上可知mode会影响process.env.NODE_ENV
以及process.env.BABEL_ENV
值的确定。后续vue-cli-service生成webpack配置、使用babel的过程中会用到这两个变量,这就解释了为什么mode的正确使用这么重要。如只有当process.env.NODE_ENV=production
时打包后的代码才会有代码压缩、文件名添加hash等效果。
除非目的明确,否则不要在env文件中指定NODE_ENV
,否则在如“初始化环境变量”一节中所示,提前通过加载env文件指定了process.env.NODE_ENV
的话会影响默认NODE_ENV值的定义。
webpack config对比
通过vue-cli-service inspect --mode xxx
可以得到development和production模式下生成的webpack配置信息,部分区别如下:
方案实现
新目录结构如下:
环境相关变量文件从根目录移动至cli/environments/
文件夹下,vue-cli-service原有逻辑会根据mode读取根目录下env文件。
cli/environments/
下的env文件存放Environment
相关变量,如:
# cli/environments/.env.dev
# 用于识别当前环境
VUE_APP_ENV=dev
# 接口服务器地址
API_HOST=https://dev.xxx.com/apis
以下是cli/index.js
的内容,根据@vue/cli-service/bin/vue-cli-service.js
改造,添加了加载指定位置env文件的逻辑
# cli/index.js
const { semver, error } = require("@vue/cli-shared-utils");
const requiredVersion = require("@vue/cli-service/package.json").engines.node;
const dotenv = require("dotenv");
const dotenvExpand = require("dotenv-expand");
const path = require("path");
if (
!semver.satisfies(process.version, requiredVersion, {
includePrerelease: true,
})
) {
error(
`You are using Node ${process.version}, but vue-cli-service ` +
`requires Node ${requiredVersion}.\nPlease upgrade your Node version.`
);
process.exit(1);
}
const Service = require("@vue/cli-service/lib/Service");
const service = new Service(process.env.VUE_CLI_CONTEXT || process.cwd());
const rawArgv = process.argv.slice(2);
const args = require("minimist")(rawArgv, {
boolean: [
// build
"modern",
"report",
"report-json",
"inline-vue",
"watch",
// serve
"open",
"copy",
"https",
// inspect
"verbose",
],
});
const command = args._[0];
const env = args.env || "dev";
loadEnv(env);
service.run(command, args, rawArgv).catch((err) => {
error(err);
process.exit(1);
});
/**
* @description: 加载env文件
* @param {String} env 环境(dev|test|uat|prod)
*/
function loadEnv(env) {
try {
const envOptions = ["dev", "test", "uat", "prod"];
if (!envOptions.includes(env)) {
throw new Error(
`env: ${env} is invalid, options: ${JSON.stringify(envOptions)}`
);
}
const envPath = path.resolve(process.cwd(), `cli/environments/.env.${env}`);
const localPath = `${envPath}.local`;
// 加载.env.local文件
const localEnvConfig = dotenv.config({
path: localPath,
debug: process.env.DEBUG,
});
dotenvExpand(localEnvConfig);
// 加载env文件
const envConfig = dotenv.config({ path: envPath, debug: process.env.DEBUG });
dotenvExpand(envConfig);
} catch (err) {
// 忽略文件不存在错误
if (err.toString().indexOf("ENOENT") < 0) {
error(err);
process.exit(1)
}
}
}
最后修改package.json文件中的scripts:
# package.json
{
"scripts": {
"serve:dev": "node ./cli serve --env dev",
"serve:test": "node ./cli serve --env test",
"serve:uat": "node ./cli serve --env uat",
"serve:prod": "node ./cli serve --env prod",
"build:dev": "node ./cli build --env dev",
"build:test": "node ./cli build --env test",
"build:uat": "node ./cli build --env uat",
"build:prod": "node ./cli build --env prod"
}
}
总结
总结一下这套方案,主要做了以下工作:
- 区分环境变量文件和模式变量文件,模式变量文件放在项目根目录,vue-cli-service会自动加载。环境变量文件移动到
/cli/environments/
目录下,由/cli/index.js
中添加的逻辑单独加载处理。 - 新增
/cli/index.js
文件,vue-cli-service的原有逻辑迁移至该文件,并添加加载/cli/environments/
目录下的环境变量文件的逻辑,手动处理需要的环境变量文件。
如有其他想法,欢迎指教
转载自:https://juejin.cn/post/7043384831366922277