Vue项目中env文件的作用和配置
在实际项目的开发中,我们一般会经历项目的开发阶段、测试阶段和最终上线阶段,每一个阶段对于项目代码的要求可能都不尽相同,那么我们如何能够游刃有余的在不同阶段下使我们的项目呈现不同的效果,使用不同的功能呢?这里就需要引入环境的概念。
一般一个项目都会有以下 3 种环境:
-
开发环境(开发阶段,本地开发版本,一般会使用一些调试工具或额外的辅助功能)
-
测试环境(测试阶段,上线前版本,除了一些 bug 的修复,基本不会和上线版本有很大差别)
-
生产环境(上线阶段,正式对外发布的版本,一般会进行优化,关掉错误报告)
作为一名开发人员,我们可能需要针对每一种环境编写一些不同的代码并且保证这些代码运行在正确的环境中,那么我们应该如何在代码中判断项目所处的环境同时执行不同的代码呢?这就需要我们进行正确的环境配置和管理。
1. 配置文件
正确的配置环境首先需要我们认识不同环境配置之间的关系,如图所示:
我们从上图中可以了解到每一个环境其实有其不同的配置,同时它们也存在着交集部分,交集便是它们都共有的配置项,那么在 Vue 中我们应该如何处理呢?
我们可以在根目录下创建以下形式的文件进行不同环境下变量的配置:
.env # 在所有的环境中被载入
.env.local # 在所有的环境中被载入,但会被 git 忽略
.env.[mode] # 只在指定的模式中被载入
.env.[mode].local # 只在指定的模式中被载入,但会被 git 忽略
比如我们创建一个名为 .env.stage 的文件,该文件表明其只在 stage 环境下被加载,在这个文件中,我们可以配置如下键值对的变量:
NODE_ENV=stage
VUE_APP_TITLE=stage mode
注意:环境变量的名称必须以
VUE_APP_
开头
这时候我们怎么在 vue.config.js 中访问这些变量呢?很简单,使用 process.env.[name]
进行访问就可以了,比如:
// vue.config.js
console.log(process.env.NODE_ENV); // development(在终端输出)
当你运行 yarn serve
命令后会发现输出的是 development,因为 vue-cli-service serve
命令默认设置的环境是 development,你需要修改 package.json 中的 serve 脚本的命令为:
"scripts": {
"serve": "vue-cli-service serve --mode stage",
}
--mode stage
其实就是修改了 webpack 4 中的 mode 配置项为 stage,同时其会读取对应 .env.[model] 文件下的配置,如果没找到对应配置文件,其会使用默认环境 development,同样 vue-cli-service build
会使用默认环境 production。
这时候如果你再创建一个 .env 的文件,再次配置重复的变量,但是值不同,如:
NODE_ENV=staging
VUE_APP_TITLE=staging mode
VUE_APP_NAME=project
因为 .env 文件会被所有环境加载,即公共配置,那么最终我们运行 vue-cli-service serve
打印出来的是哪个呢?答案是 stage,但是如果是 .env.stage.local 文件中配置成上方这样,答案便是 staging,所以 .env.[mode].local 会覆盖 .env.[mode] 下的相同配置。同理 .env.local 会覆盖 .env 下的相同配置。
由此可以得出结论,相同配置项的权重:
.env.[mode].local > .env.[mode] > .env.local > .env
如果看完上例还未理解优先级和覆盖规则的关系的话,这里有一个更容易理解的例子:
当你运行 vue-cli-service serve
命令时,Vue CLI 会根据你指定的模式(如 --mode stage
)加载不同的环境变量文件。以下是常见的环境变量文件及其优先级:
-
.env.local
: 该文件用于本地环境的配置,优先级最高,适用于所有模式。如果在该文件中定义了环境变量,它们将覆盖其他文件中的同名变量。 -
.env.[mode].local
: 这是特定于某个模式的本地配置文件,如.env.stage.local
。它的优先级仅次于.env.local
,并且可以覆盖.env.[mode]
中的相同变量。 -
.env.[mode]
: 这是特定于某个模式的环境变量文件,例如.env.stage
。它包含该模式下的公共配置。 -
.env
: 这是一个通用的环境变量文件,适用于所有模式。它的优先级最低。
实际效果
-
如果没有定义
.env.local
或.env.stage.local
:- 当你运行
vue-cli-service serve --mode stage
时,Vue CLI 会加载的环境变量是来自.env
,.env.stage
,结果将是.env.stage
中的变量。
- 当你运行
-
如果定义了
.env.stage.local
:- 如果你在
.env.stage.local
中定义了某些变量,这些变量将覆盖.env.stage
中的同名变量。例如,如果你在.env.stage.local
中定义VUE_APP_TITLE=Staging
,而在.env.stage
中定义了VUE_APP_TITLE=Stage App
,那么最终在应用中访问process.env.VUE_APP_TITLE
时,将会得到Staging
。
- 如果你在
示例
假设你的文件结构如下:
project-root/
│
├── .env
│ VUE_APP_TITLE=My Vue App
│
├── .env.stage
│ VUE_APP_TITLE=Stage App
│
├── .env.stage.local
│ VUE_APP_TITLE=Staging
执行命令
当你运行以下命令时:
npm run serve -- --mode stage
最终结果
process.env.VUE_APP_TITLE
的值将是Staging
,因为它是从.env.stage.local
加载的,并覆盖了.env.stage
和.env
文件中的同名变量。
需要注意的是,除了相同配置项权重大的覆盖小的,不同配置项它们会进行合并操作,类似于 Javascript 中的 Object.assign 的用法。
拓展1
1.为什么使用 VUE_APP_
前缀?
在 Vue.js 中,环境变量以 VUE_APP_
前缀开头是特别重要的。这是因为 Vue CLI 仅会将以 VUE_APP_
开头的环境变量暴露给客户端的 JavaScript 代码。它的主要目的是为了区分环境变量,使得这些变量能够被 Vue 应用程序访问,同时避免与其他环境变量的冲突
1. 明确标识
VUE_APP_
前缀清楚地标识这些变量是特定于 Vue 应用的环境变量。这有助于开发者快速识别哪些变量是可以在 Vue 组件和其他代码中直接使用的。
2. 隔离命名空间
- 在同一个项目中,可能会有多个不同的工具、库或模块,它们也可能定义环境变量。通过使用
VUE_APP_
前缀,Vue 应用的环境变量就被隔离在一个命名空间下,从而减少命名冲突的可能性。例如,其他工具可能使用NODE_ENV
、API_URL
等变量名,但由于 Vue 的环境变量使用了特定前缀,就能避免混淆。
3. 安全性
- 在 Vue CLI 中,只有以
VUE_APP_
前缀开头的环境变量会被嵌入到构建的应用中,其他的环境变量则不会被暴露到客户端。这意味着,如果你在环境变量中定义了敏感信息(比如数据库密码),只需确保这些变量不以VUE_APP_
开头,它们就不会被打包和暴露,从而提高了安全性。
4. 一致性
- 使用统一的前缀可以为团队开发提供一致的命名约定,有助于代码的可读性和可维护性。团队中的每个成员都知道哪些环境变量是可用的,以及它们的作用,从而减少了沟通成本。
5. 便于配置
- 在不同的环境(如开发、测试、生产环境)中,你可以使用相同的命名约定来配置不同的值。只需在不同的
.env
文件中设置相应的值(如.env.development
、.env.production
),即可在构建时自动加载正确的环境变量。
这里用一个例子说明:
在一个 Vue 应用中,你可能会看到以下环境变量的定义:
# .env.development
VUE_APP_TITLE=My Development App
VUE_APP_API_URL=https://dev.api.example.com
# .env.production
VUE_APP_TITLE=My Production App
VUE_APP_API_URL=https://api.example.com
这些变量在 Vue 组件中可以直接使用,比如:
console.log(process.env.VUE_APP_TITLE); // 输出 "My Development App" 或 "My Production App"
console.log(process.env.VUE_APP_API_URL); // 输出相应环境下的 API URL
2.APP
后缀的作用
1. 明确变量的作用
APP
后缀清晰地表明变量与应用程序的配置相关。这使得其他开发者在查看变量名时,可以迅速理解该变量的目的和使用场景。例如:VUE_APP_TITLE
:应用的标题。VUE_APP_API_URL
:API 的基本 URL。VUE_DB_CONNECTION_STRING
:数据库连接字符串。
2. 减少命名冲突
- 在大型项目中,可能会有多个不同模块或组件,每个模块可能都有自己的环境变量。如果每个模块都遵循相似的命名规则,你就可以有效地避免变量名的冲突。例如:
- 模块 A:
VUE_APP_MODULE_A_SETTING
- 模块 B:
VUE_APP_MODULE_B_SETTING
- 模块 A:
3. 提高可维护性
- 维护代码时,开发者可以更容易地识别和理解环境变量的来源和用途。尤其在团队协作中,这种清晰性可以显著减少沟通成本。
4. 便于环境切换
- 在某些情况下,可能需要为开发、测试和生产环境设置不同的变量。通过统一的命名约定,可以更方便地管理这些不同的环境变量。例如,开发环境可以使用
VUE_APP_DEV_TITLE
,而生产环境则可以使用VUE_APP_PROD_TITLE
。
5. 文档化和标准化
- 在项目文档中,清晰的命名约定可以作为标准操作程序的一部分,帮助新加入的开发者快速上手。文档中可以列出所有环境变量及其用途,形成良好的开发规范。
6. 版本控制和部署
- 在持续集成和持续部署(CI/CD)过程中,使用
APP
后缀可以帮助开发者快速识别与应用相关的配置,确保在不同环境下正确使用相应的变量。例如,你可以在不同的环境配置文件中定义各自的APP
相关变量,以便于版本控制和自动化部署。
示例:
假设你正在开发一个 Vue 应用,其中需要配置多个环境变量,可以使用 APP
后缀来组织它们:
# 应用基本设置
VUE_APP_TITLE=My Awesome App
VUE_APP_VERSION=1.0.0
# API 配置
VUE_APP_API_URL=https://api.example.com
VUE_APP_API_TIMEOUT=5000
# 用户设置
VUE_APP_USER_DEFAULT_LANGUAGE=en
VUE_APP_USER_ENABLE_NOTIFICATIONS=true
2. 环境注入
通过上述配置文件的创建,我们成功使用命令行的形式对项目环境进行了设置并可以自由切换,但是需要注意的是我们在 Vue 的前端代码中打印出的 process.env
与 vue.config.js 中输出的可能是不一样的,这需要普及一个知识点:webpack 通过 DefinePlugin 内置插件将 process.env 注入到客户端代码中。
// webpack 配置
{
...
plugins: [
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify(process.env.NODE_ENV)
}
}),
],
...
}
由于 vue-cli 3.x 封装的 webpack 配置中已经帮我们完成了这个功能,所以我们可以直接在客户端代码中打印出 process.env 的值,该对象可以包含多个键值对,也就是说可以注入多个值,但是经过 CLI 封装后仅支持注入环境配置文件中以 VUE_APP_
开头的变量,而 NODE_ENV
和 BASE_URL
这两个特殊变量除外。比如我们在权重最高的 .env.stage.local 文件中写入:
NODE_ENV=stage2
VUE_APP_TITLE=stage mode2
NAME=vue
然后我们尝试在 vue.config.js 中打印 process.env
,终端输出:
{
...
npm_config_ignore_scripts: '',
npm_config_version_git_sign: '',
npm_config_ignore_optional: '',
npm_config_init_version: '1.0.0',
npm_package_dependencies_vue_router: '^3.0.1',
npm_config_version_tag_prefix: 'v',
npm_node_execpath: '/usr/local/bin/node',
NODE_ENV: 'stage2',
VUE_APP_TITLE: 'stage mode2',
NAME: 'vue',
BABEL_ENV: 'development',
...
}
可以看到输出内容除了我们环境配置中的变量外还包含了很多 npm 的信息,但是我们在入口文件 main.js 中打印会发现输出:
{
"BASE_URL": "/vue/",
"NODE_ENV": "stage2",
"VUE_APP_TITLE": "stage mode2"
}
可见注入时过滤调了非 VUE_APP_
开头的变量,其中多出的 BASE_URL
为你在 vue.config.js 设置的值,默认为 /,其在环境配置文件中设置无效。
拓展2
DefinePlugin插件
DefinePlugin
是 Webpack 提供的一个插件,用于在编译时创建一个全局常量,并替换代码中的变量。这在处理环境变量和配置时非常有用,因为它允许你在客户端代码中使用不同的值,而这些值在编译时就已经确定了。
主要作用
- 定义全局常量:可以在代码中使用的全局变量,通过替换的方式来提供。
- 环境变量替换:常用于替换
process.env.NODE_ENV
,以便于在开发和生产环境中使用不同的配置。
基本用法
在 Webpack 配置文件中,你可以这样使用 DefinePlugin
:
const webpack = require('webpack');
module.exports = {
// 其他配置...
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
'API_URL': JSON.stringify('https://api.example.com'),
// 可以定义更多的环境变量
})
]
};
详细说明
-
创建全局常量: 你可以通过
DefinePlugin
创建任何你想要的全局常量。例如,在上面的示例中,API_URL
被定义为一个字符串。在代码其他地方,你可以直接使用API_URL
,Webpack 会在编译时将其替换为对应的字符串。 -
字符串化: 注意当定义常量时,通常需要使用
JSON.stringify
来确保常量在代码中是一个有效的 JavaScript 字符串。如果直接使用字符串,可能会导致语法错误。 -
环境变量: 在许多项目中,
process.env.NODE_ENV
是一个常见的用法。在开发环境下,它通常设置为'development'
,而在生产环境下则为'production'
。通过这种方式,你可以在代码中进行条件判断,比如:if (process.env.NODE_ENV === 'production') { // 生产环境的代码 } else { // 开发环境的代码 }
-
与其他工具结合:
DefinePlugin
通常与其他工具(如 Babel、Vue CLI 等)结合使用,以便在构建过程中正确处理环境变量。
示例
假设你有以下代码:
javascript
if (process.env.NODE_ENV === 'production') {
console.log('This is production mode');
} else {
console.log('This is development mode');
}
在 Webpack 配置中使用 DefinePlugin
:
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
})
]
};
在构建应用时,如果 NODE_ENV
为 'production'
,Webpack 会将代码中的 process.env.NODE_ENV
替换为 'production'
,这样在运行时就只会执行生产环境的代码。
3. 额外配置
以上我们通过新建配置文件的方式为项目不同环境配置不同的变量值,能够实现项目基本的环境管理,但是 .env 这样的配置文件中的参数目前只支持静态值,无法使用动态参数,在某些情况下无法实现特定需求,这时候我们可以在根目录下新建 config 文件夹用于存放一些额外的配置文件。
/* 配置文件 index.js */
// 公共变量
const com = {
IP: JSON.stringify('xxx')
};
module.exports = {
// 开发环境变量
dev: {
env: {
TYPE: JSON.stringify('dev'),
...com
}
},
// 生产环境变量
build: {
env: {
TYPE: JSON.stringify('prod'),
...com
}
}
}
上方代码我们把环境变量分为了公共变量、开发环境变量和生产环境变量,当然这些变量可能是动态的,比如用户的 ip 等。现在我们要在 vue.config.js 里注入这些变量,我们可以使用 chainWebpack 修改 DefinePlugin 中的值:
/* vue.config.js */
//从 config.js 文件中导入不同环境配置
const configs = require('./config');
// 引入 webpack-merge 库,用于合并不同的 Webpack 配置对象。这样可以灵活地组合不同的配置,而不丢失原有的配置。
const merge = require('webpack-merge');
// 根据环境判断使用哪份配置
const cfg = process.env.NODE_ENV === 'production' ? configs.build.env : configs.dev.env;
module.exports = {
...
chainWebpack: config => {
//plugin('define')访问 Webpack 中 DefinePlugin 插件通过修改这个插件的配置,可以更改全局变量的值
config.plugin('define')
.tap(args => {
let name = 'process.env';
// 使用 merge方法将 原有的process.env 和根据环境选择的 cfg 进行合并这样可以确保
在原有的环境变量基础上添加新的变量,而不覆盖原有的值。
args[0][name] = merge(args[0][name], cfg);
return args
})
},
...
}
args[0][name] = merge(args[0][name], cfg);
args[0]
: 这个数组的第一个元素是传递给DefinePlugin
的配置对象。merge(args[0][name], cfg)
: 这里的merge
函数用于将当前的process.env
对象与cfg
进行合并。- 合并: 将
cfg
中的环境变量添加到现有的process.env
对象中,确保在编译时可以访问这些变量。
最后我们可以在客户端成功打印出包含动态配置的对象:
{
"NODE_ENV": "stage2",
"VUE_APP_TITLE": "stage mode2",
"BASE_URL": "/vue/",
"TYPE": "dev",
"IP": "xxx"
}
4. 实际场景
结合以上环境变量的配置,我们项目中一般会遇到一些实际场景: 比如在非线上环境我们可以给自己的移动端项目开启 vConsole 调试,但是在线上环境肯定不需要开启这一功能,我们可以在入口文件中进行设置,代码如下:
/* main.js */
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
Vue.config.productionTip = false
// 如果是非正式环境,加载 VConsole
if (process.env.NODE_ENV !== 'production') {
var VConsole = require('vconsole/dist/vconsole.min.js');
var vConsole = new VConsole();
}
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
vConsole 是一款用于移动网页的轻量级,可扩展的前端开发工具,可以看作是移动端浏览器的控制台。
另外我们还可以使用配置中的 BASE_URL 来设置路由的 base 参数:
/* router.js */
import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'
import About from './views/About.vue'
Vue.use(Router)
let base = `${process.env.BASE_URL}`; // 获取二级目录
export default new Router({
mode: 'history',
base: base, // 设置 base 值
routes: [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/about',
name: 'about',
component: About
}
]
})
每一个环境变量你都可以用于项目的一些地方,它提供给了我们一种全局的可访问形式,也是基于 Node 开发的特性所在。
拓展3
1.webpack 通过 DefinePlugin 内置插件将 process.env 注入到客户端代码中时,`process.env.NODE_ENV` 为什么要进行 JSON.stringify 处理?
原因分析
-
JavaScript 语法: 当你在 JavaScript 代码中使用字符串时,这些字符串必须被正确地包裹在引号中。例如,
"production"
或者"development"
必须是字符串类型。而DefinePlugin
允许我们在编译时定义全局常量,如果不使用JSON.stringify
,将会导致生成的代码不符合 JavaScript 语法。// 如果没有 JSON.stringify,可能会变成这样 process.env.NODE_ENV === production // 这里的 production 会被认为是一个变量 // 正确的写法应该是 process.env.NODE_ENV === 'production' // 这里的 'production' 是一个字符串
-
确保类型一致性:
JSON.stringify
可以确保传递给DefinePlugin
的值在任何情况下都是字符串。即使process.env.NODE_ENV
的值为undefined
或者其他类型(如数字),JSON.stringify
也会将其转换为字符串格式,并避免潜在的类型错误。 -
防止意外的值: 如果你直接将某个变量(例如
process.env.NODE_ENV
)传递给DefinePlugin
,而这个变量在某些情况下可能是未定义的或不符合预期的类型,直接使用可能导致运行时错误或逻辑错误。通过JSON.stringify
,你可以确保一切都是严格的字符串,从而避免这些问题。
示例
下面是一个简单的示例来展示没有 JSON.stringify
的可能后果:
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': process.env.NODE_ENV || 'development' // 没有字符串化
})
]
};
// 结果可能是:
if (process.env.NODE_ENV === production) {
// 这里会报错,因为 production 没有被定义为字符串
}
而使用 JSON.stringify
的情况下:
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development') // 使用字符串化
})
]
};
// 结果是:
if (process.env.NODE_ENV === 'production') {
// 这里是正确的,逻辑不会出错
}
总结
使用 JSON.stringify
在 DefinePlugin
中确保 process.env.NODE_ENV
被定义为一个合法的字符串,从而避免了潜在的语法错误和运行时错误。这是一个好的实践,确保你在客户端代码中处理常量时的安全性和一致性。
2.如何在 package.json 中的 scripts 字段中定义一些自定义脚本来切换不同的环境?
假设你有一个 Node.js 项目,并且希望根据不同的环境(如开发、测试和生产)来执行不同的脚本。你可以在 package.json
中定义如下脚本:
{
"name": "your-project",
"version": "1.0.0",
"scripts": {
"start": "NODE_ENV=production node server.js",
"dev": "NODE_ENV=development nodemon server.js",
"test": "NODE_ENV=test mocha",
"build": "webpack --mode production",
"build:dev": "webpack --mode development"
}
}
-
start
: 用于生产环境启动应用,将NODE_ENV
设置为production
。在 Unix 系统中,环境变量可以通过KEY=value command
的方式设置。 -
dev
: 用于开发环境启动应用,将NODE_ENV
设置为development
。这里使用了nodemon
,它会自动重启 Node.js 应用程序。 -
test
: 用于测试环境,运行测试框架(如 Mocha),将NODE_ENV
设置为test
。 -
build
和build:dev
: 分别用于构建生产和开发版本,使用 Webpack 进行打包。
Windows 兼容性
在 Windows 上,设置环境变量的方式与 Unix 系统不同。为了确保你的脚本在不同操作系统上都能正常工作,你可以使用 cross-env
包。首先,你需要安装 cross-env
:
npm install --save-dev cross-env
然后修改 package.json
中的脚本如下:
{
"name": "your-project",
"version": "1.0.0",
"scripts": {
"start": "cross-env NODE_ENV=production node server.js",
"dev": "cross-env NODE_ENV=development nodemon server.js",
"test": "cross-env NODE_ENV=test mocha",
"build": "webpack --mode production",
"build:dev": "webpack --mode development"
}
}
运行脚本
可以使用以下命令来运行不同的脚本:
- 启动生产环境:
npm start
- 启动开发环境:
npm run dev
- 运行测试:
npm test
- 构建生产版本:
npm run build
- 构建开发版本:
npm run build:dev