Webpack 及 Vue CLI 中环境变量配置详解

关于项目环境变量配置的相关问题解答

我们通常会使用 CLI 模板创建初始项目,项目中的 .env 文件用于配置项目的环境变量,比如开发环境、测试环境、生产环境等。示例如下:

ini 复制代码
NODE_ENV=production

VUE_APP_API_URL=/api

在项目中可以通过 process.env.NODE_ENV 来获取当前的环境变量,示例代码:

ini 复制代码
const isProduction = process.env.NODE_ENV === 'production';

此外,也能在 vue.config.js 里配置项目的环境变量,示例如下:

arduino 复制代码
// 根据不同环境配置生产环境的资源路径

publicPath: process.env.NODE_ENV === 'production' ? '/prod/' : '/',

// 根据不同环境配置生产环境的输出目录

outputDir: process.env.NODE_ENV === 'production' ? 'dist-prod' : 'dist-dev'

但有时候,对其原理的理解不是很深入,所以就有了一些疑问。 下面来详细解答一些关于环境变量配置的疑问。

仅考虑 Webpack 配置文件的情况

1. process.env.NODE_ENV 是什么?

process 是 Node.js 提供的全局变量,其内部包含了一些全局对象,如 process.envprocess.argvprocess.stdinprocess.stdoutprocess.stderr 等。process.env 是其中一个对象,NODE_ENV 本身并不存在,需要我们自行赋值。

2. 如何设置这个属性,何时设置,使用什么方法?

Node 命令行中

ini 复制代码
process.env.NODE_ENV = 'development';

Webpack 项目里,在 package.json

json 复制代码
{

   "scripts": {

       "dev": "cross-env NODE_ENV=development webpack --config webpack.config.js"

   }

}

cross-env 用于管理项目中的环境变量,能解决不同环境系统间设置环境变量时命令行不同的问题。在 Windows 中使用 "SET NODE_ENV=development",在 Unix/Linux 系统中使用 "EXPORT NODE_ENV=development"

借助 dotenv 之类的工具,在 .env 文件中

ini 复制代码
NODE_ENV=development

3. 在 Webpack 中,如何获取这个属性?

ini 复制代码
const webpack = require('webpack');

const mode = process.env.NODE_ENV || 'development';

module.exports = {

   mode: mode,

   plugins: [

       new webpack.DefinePlugin({

           'process.env.NODE_ENV': JSON.stringify(mode)

       })

   ]

};

在项目中就可以通过 process.env.NODE_ENV 来获取当前的环境变量,示例代码:

arduino 复制代码
if (process.env.NODE_ENV === 'development') {

   console.log('当前处于开发环境');

} else if (process.env.NODE_ENV === 'production') {

   console.log('当前处于生产环境');

}

考虑 .env 文件的情况

其实就是将上述设置方法换成从文件中读取,主要分为两种情况:使用 dotenv 或者读取文件。

1. 使用 dotenv

**安装 **dotenv

css 复制代码
npm install dotenv --save-dev

package.json

json 复制代码
{

   "scripts": {

       "dev": "cross-env NODE_ENV=development webpack --config webpack.config.js",

       "prod": "cross-env NODE_ENV=production webpack --config webpack.config.js"

   }

}

webpack.config.js** 配置**:

ini 复制代码
const dotenv = require('dotenv');

const webpack = require('webpack');

// 根据 NODE_ENV 选择环境变量文件

const envFile = process.env.NODE_ENV === 'production' ? '.env.prod' : '.env.dev';

// 加载环境变量

const envConfig = dotenv.config({ path: envFile }).parsed;

// 将环境变量注入到 Webpack 中

const envKeys = Object.keys(envConfig).reduce((prev, next) => {

   prev[\`process.env.\${next}\`] = JSON.stringify(envConfig[next]);

   return prev;

}, {});

module.exports = {

   // 其他 Webpack 配置

   plugins: [

       new webpack.DefinePlugin(envKeys)

   ]

};

解释:这里将 NODE_ENV 属性交由 cross-env 来管理,从文件中读取其他属性,如 VUE_APP_API_URL 等。如果要在 .env 中设置 NODE_ENV,可以这样做:

json 复制代码
{

   "scripts": {

       "dev": "cross-env ENV_FILE=.env.dev webpack --config webpack.config.js",

       "prod": "cross-env ENV_FILE=.env.prod webpack --config webpack.config.js"

   }

}
arduino 复制代码
// 获取要读取的环境变量文件

const envFile = process.env.ENV_FILE || '.env';

// 加载环境变量

const envConfig = dotenv.config({ path: envFile }).parsed;

// 其他代码与上面一致

2. 读取文件

ini 复制代码
const fs = require('fs');

const path = require('path');

const webpack = require('webpack');

// 手动读取 .env 文件

const envFile = process.env.NODE_ENV === 'production' ? '.env.prod' : '.env.dev';

const envPath = path.resolve(__dirname, envFile);

const envFileContent = fs.readFileSync(envPath, 'utf8');

// 解析 .env 文件内容

const envVars = {};

envFileContent.split('\n').forEach(line => {

   const [key, value] = line.split('=');

   if (key && value) {

       envVars[key.trim()] = value.trim();

   }

});

// 使用 .env 文件中的 NODE_ENV 变量

const nodeEnv = envVars.NODE_ENV;

console.log('当前环境:', nodeEnv);

Vue CLI 项目中直接使用 .env 文件的实现原理

package.json 中:

json 复制代码
{

   "scripts": {

       "serve": "vue-cli-service serve --mode dev"

   }

}

启动服务时,会运行 vue-cli-service 命令,该命令会去 node_modules/.bin 目录下查找 vue-cli-service 命令并执行。

vue-cli-service 主要代码

arduino 复制代码
const Service = require('../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]

// 获取一些参数,调用 service.run 方法。

service.run(command, args, rawArgv).catch(err => {

   console.error(err)

   process.exit(1)

})

Service.js 中的 run 方法

kotlin 复制代码
async run (name, args = {}, rawArgv = []) {

   // 优先使用命令行内联的 --mode 参数

   // 如果定义了 --watch 参数,则回退到从插件解析出的默认模式,否则使用开发模式

   const mode = args.mode || (name === 'build' && args.watch ? 'development' : this.modes[name])

   // --skip-plugins 参数可能包含在初始化期间应跳过的插件

   this.setPluginsToSkip(args)

   // 加载环境变量,加载用户配置,应用插件

   this.init(mode)

   // 其他代码

}

Service.js 中的 init 方法

kotlin 复制代码
init (mode = process.env.VUE_CLI_MODE) {

   if (this.initialized) {

       return

   }

   this.initialized = true

   this.mode = mode

   // 主要看 loadEnv

   // load mode .env

   if (mode) {

       this.loadEnv(mode)

   }

   // load base .env

   this.loadEnv()

   // 其他代码

}

Service.js 中的 loadEnv 方法

arduino 复制代码
/\*\*

\* 加载指定模式的环境变量文件

\* @param {string} mode - 环境模式,如 'development', 'production' 等

\*/

loadEnv (mode) {

   // 创建一个调试日志记录器,用于记录环境变量加载相关信息

   const logger = debug('vue:env')

   // 解析基础环境变量文件的路径,根据模式决定文件名是否包含模式后缀

   const basePath = path.resolve(this.context, \`.env\${mode ? \`.\${mode}\` : \`\`}\`)

   // 解析本地环境变量文件的路径,即在基础路径后添加 .local 后缀

   const localPath = \`\${basePath}.local\`

   const load = envPath => {

       try {

           // 使用 dotenv 加载指定路径的环境变量文件,并开启调试模式(根据环境变量 DEBUG 决定)

           const env = dotenv.config({ path: envPath, debug: process.env.DEBUG })

           // 扩展环境变量,解析其中的变量引用

           dotenvExpand(env)

           // 使用日志记录器记录加载的环境变量文件路径和加载结果

           logger(envPath, env)

       } catch (err) {

           // 仅在文件不存在时忽略错误

           if (err.toString().indexOf('ENOENT') < 0) {

               // 若不是文件不存在的错误,则输出错误信息

               console.error(err)

           }

       }

   }

   // 优先加载本地环境变量文件

   load(localPath)

   // 再加载基础环境变量文件

   load(basePath)

   // 默认情况下,除非模式为 production 或 test,否则 NODE_ENV 和 BABEL_ENV 会被设置为 "development"。

   // 不过 .env 文件中的值优先级更高。

   if (mode) {

       // 始终在测试期间设置 NODE_ENV,因为这对于避免测试相互影响是必要的

       const shouldForceDefaultEnv = (

           process.env.VUE_CLI_TEST &&

           !process.env.VUE_CLI_TEST_TESTING_ENV

       )

       // 根据模式确定默认的 NODE_ENV 值

       const defaultNodeEnv = (mode === 'production' || mode === 'test')

         ? mode

         : 'development'

       // 如果需要强制使用默认环境变量,或者当前未设置 NODE_ENV,则设置 NODE_ENV

       if (shouldForceDefaultEnv || process.env.NODE_ENV == null) {

           process.env.NODE_ENV = defaultNodeEnv

       }

       // 如果需要强制使用默认环境变量,或者当前未设置 BABEL_ENV,则设置 BABEL_ENV

       if (shouldForceDefaultEnv || process.env.BABEL_ENV == null) {

           process.env.BABEL_ENV = defaultNodeEnv

       }

   }

}

其中,dotenvExpand 是一个库,用于解析 .env 文件中的变量引用并将其扩展到环境变量中。

dotenvExpand 函数定义

ini 复制代码
var dotenvExpand = function (config) {

   // 如果配置中设置了忽略 process.env,则使用空对象,否则使用 process.env

   var environment = config.ignoreProcessEnv ? {} : process.env;

   // 遍历 config.parsed 中的每个环境变量键

   for (var key in config.parsed) {

       if (config.parsed.hasOwnProperty(key)) {

           // 获取当前键对应的值

           var value = config.parsed[key];

           // 将该值赋给 config.parsed 中对应的键

           config.parsed[key] = value;

           // 将该值同步到 environment 中

           environment[key] = value;

       }

   }

   return config;

};

module.exports = dotenvExpand;

再回到命令 vue-cli-service serve --mode devmodedev,所以会加载 .env.dev 文件,然后将 .env.dev 文件中的属性注入到 process.env 中。

相关推荐
喜樂的CC13 分钟前
[react]Next.js之自适应布局和高清屏幕适配解决方案
javascript·react.js·postcss
天天扭码27 分钟前
零基础 | 入门前端必备技巧——使用 DOM 操作插入 HTML 元素
前端·javascript·dom
咖啡虫1 小时前
css中的3d使用:深入理解 CSS Perspective 与 Transform-Style
前端·css·3d
烛阴1 小时前
手把手教你搭建 Express 日志系统,告别线上事故!
javascript·后端·express
拉不动的猪1 小时前
设计模式之------策略模式
前端·javascript·面试
旭久1 小时前
react+Tesseract.js实现前端拍照获取/选择文件等文字识别OCR
前端·javascript·react.js
独行soc1 小时前
2025年常见渗透测试面试题-红队面试宝典下(题目+回答)
linux·运维·服务器·前端·面试·职场和发展·csrf
uhakadotcom2 小时前
Google Earth Engine 机器学习入门:基础知识与实用示例详解
前端·javascript·面试
麓殇⊙2 小时前
Vue--组件练习案例
前端·javascript·vue.js
outstanding木槿2 小时前
React中 点击事件写法 的注意(this、箭头函数)
前端·javascript·react.js