Open toFrankie opened 4 months ago
在 Node.js 中通常会使用 process.env 来获取环境变量。
process.env
它返回一个包含用户环境的对象。这里的用户环境是 Shell 进程,这个对象包含了当前进程的变量。注意,process.env 对象可以被修改,但其修改不会影响到此进程之外。
分类:
作用域:
local
export
Shell 环境是天然隔离的,在当前进程内设置或修改变量,都不会影响到其他非关联进程的环境变量。
function fn() { foo=1 # 作用域为当前进程 local bar=2 # 作用域为当前函数 export baz=3 # 作用域为当前进程及子进程 } fn echo $foo # 1 echo $bar # 空字符串 echo $baz # 3
在 Shell 中,如果引用的变量不存在,它不会报错,而是输出空字符。
假设有以下包:
{ "name": "node-env", "version": "1.0.0", "scripts": { "start": "node -e 'console.log(process.env)'" } }
执行 npm run start 时,会得到这些变量:
npm run start
{ // Shell 内置变量 SHELL: '/bin/zsh', USER: 'frankie', HOME: '/Users/frankie', // ... // zsh 自定义环境变量 NVM_DIR: '/Users/frankie/.nvm', // ... // npm config 相关变量 npm_config_sass_binary_site: 'https://npmmirror.com/mirrors/node-sass', npm_config_prefix: '/Users/frankie/.nvm/versions/node/v18.16.0', // ... // npm package 相关变量 npm_package_json: '/Users/frankie/Web/Git/html-demo/src/demo/node-env/package.json', npm_package_name: 'node-env', npm_package_version: '1.0.0', // ... }
process.env 的值都是字符串。如果赋值时不是字符串,会被隐式转换为字符串。
可以看到两类与 npm 相关的环境变量,在执行 npm run 命令时自动载入。
npm run
npm_config_
npm_package_
其中 npm_config_ 开头的环境变量源自 .npmrc 配置文件,优先级从上到下:
.npmrc
$HOME/.npmrc
$PREFIX/etc/npmrc
$PREFIX
npm config get prefix
/path/to/npm/npmrc
其中 key 大小写不敏感,它们都会被转换为小写形式,- 也会被转为 _。
-
_
其中 npm_package_ 则源自 package.json。比如使用 process.env.npm_package_version 获取包版本号。
package.json
process.env.npm_package_version
以上是 npm run 内部执行逻辑带入的环境变量,也可以自定义。
比如:
{ "name": "node-env", "version": "1.0.0", "scripts": { "start": "NODE_ENV=development node -e 'console.log(process.env.NODE_ENV)'" } }
这样就能在 Node 脚本里获取到这个 NODE_ENV 变量值了。
NODE_ENV
在命令前加上变量声明,它会传递给子进程。类似 export 的效果,但不完全相同,这种方式不会影响当前进程的同名变量。Simple Command Expansion
但它仅支持 Unix-like 操作系统,到 Windows 就不行了。后者需要使用 set 命令:
set
{ "name": "node-env", "version": "1.0.0", "scripts": { "start": "set NODE_ENV=development node -e 'console.log(process.env.NODE_ENV)'" } }
注意,Windows 操作系统的环境变量不区分大小写。
后来,出现了一些跨平台方案,比如 cross-env。用法变成了这样:
$ npm i cross-env -D
{ "name": "node-env", "version": "1.0.0", "scripts": { "start": "cross-env NODE_ENV=development node -e 'console.log(process.env.NODE_ENV)'" } }
cross-env is "finished" (now in maintenance mode)
如果项目的环境变量很多,script 就会很长很长,不好看也不好维护,后来又使用 dotenv 方案。
比如,项目根目录有 .env、.env.development 文件:
.env
.env.development
由于 .env 文件可能会包含像密钥这类敏感信息,它不在版本控制范围内,应该添加到 .gitignore 里。如果是多人协作的项目,可以考虑添加类似 .env.example 模板到仓库里,以便其他成员清楚了解用到哪些环境变量。
.gitignore
.env.example
# .env API_URL=https://example.com/api/
# .env.development API_URL=https://dev.example.com/api/
{ "name": "node-env", "version": "1.0.0", "scripts": { "start": "cross-env NODE_ENV=development node -e 'console.log(process.env.API_URL)'", "build": "cross-env NODE_ENV=production node -e 'console.log(process.env.API_URL)'" } }
这样本地开发和打包的时候,就能根据 NODE_ENV 的值从对应的 .env 文件中读取配置。
当然,以上环境变量仅可在编译时有效。要在业务代码中使用,还得借助类似 webpack.DefinePlugin、webpack.EnvironmentPlugin 等插件处理,它们将会在编译时被替换为相应的字符串。
webpack.DefinePlugin
webpack.EnvironmentPlugin
$ npm i dotenv
const webpack = require('webpack') require('dotenv').config() module.exports = { plugins: [ new webpack.DefinePlugin({ 'process.env.API_URL': JSON.stringify(process.env.API_URL), }), ], }
如果已有同名环境变量,dotenv 解析时将会忽略它。比如开发环境中先后加载 .env.development、.env,其中解析前者时已设置 API_URL 变量,当解析到后者时就会忽略 API_URL。 以上仅为示例,如果你是使用 webpack 的话,可以用 dotenv-webpack。 Node.js 20.6.0 原生支持 .env 文件,处于实验性阶段,当前还有很多功能上的缺失,不能完全替代 dotenv。更多请看 Node.js 20.6.0 includes built-in support for .env files
如果已有同名环境变量,dotenv 解析时将会忽略它。比如开发环境中先后加载 .env.development、.env,其中解析前者时已设置 API_URL 变量,当解析到后者时就会忽略 API_URL。
API_URL
以上仅为示例,如果你是使用 webpack 的话,可以用 dotenv-webpack。
Node.js 20.6.0 原生支持 .env 文件,处于实验性阶段,当前还有很多功能上的缺失,不能完全替代 dotenv。更多请看 Node.js 20.6.0 includes built-in support for .env files
注意,很多构建工具只有「特定前缀开头」以及像 NODE_ENV 这种很通用的环境变量才能在运行时(即业务代码)可用。
VITE_
VUE_APP_
REACT_APP_
TARO_APP_
任何不能对外公开的信息,都不要嵌入构建当中,因为它们都可以在构建产物中查到。
除此之外,还有其他一些方式可以提供。
可以通过 webpack-cli 的 --env 参数传递。比如:
--env
$ npm i webpack webpack-cli -D
{ "name": "node-env", "version": "1.0.0", "scripts": { "start": "webpack -w --env test", "build": "webpack --env prod", "build:pre": "webpack --env pre", } }
执行 npm run build:pre 时,可以这样获取到值:
npm run build:pre
// webpack.config.js module.exports = function (env, argv) { console.log(env.pre) // true // 可以结合 webpack.DefinePlugin 使用 // ... }
若使用 --env,webpack 配置需导出为函数。 更多请看 Environment Options。
若使用 --env,webpack 配置需导出为函数。
更多请看 Environment Options。
还想多说一下。
以 webpack 为例,其模式有 development、production 和 none 三种。当「显式」声明 mode 为前两者时,它会自动设置 process.env.NODE_ENV 为对应值(更多)。从这个角度看,process.env.NODE_ENV 通常用来区分开发模式、打包模式。比如,开发模式下启用 sourcemap、HMR 等以便于开发调试。打包模式下启用 minimizer、splitChunks 等以减少产物体积。
development
production
none
mode
process.env.NODE_ENV
但好像有些同学会将 process.env.NODE_ENV 用于区分「项目」的测试、生成环境,其实“不对”的。假设项目有测试环境、预生产环境和生产环境呢,那它就不够用了。而且,即使是部署到非正式环境,在打包时也应该使用 production 模式。
可以像上面那样不同项目环境传入不同的 --env 参数,然后结合 webpack.DefinePlugin 来定义特定变量,比如:
const webpack = require('webpack') module.exports = function (env, argv) { return { // ... plugins: [ new webpack.DefinePlugin({ 'process.env.TEST': env.test, 'process.env.PRE': env.pre, 'process.env.PROD': env.prod, }), ], } }
// 业务 export const IS_TEST = process.env.TEST export const IS_PRE = process.env.PRE export const IS_PROD = process.env.PROD export const API_URL = IS_TEST ? 'http://test.example.com/api/' : IS_PRE ? 'http://pre.example.com/api/' : 'http://example.com/api/'
说那么多,是为了不要混淆 --env、--mode 与 process.env.NODE_ENV 的关系。process.env.NODE_ENV 在各大构建工具频繁出现,算是一个约定俗成的变量了,它与项目环境是不同的概念。
--mode
未完待续...
前言
在 Node.js 中通常会使用
process.env
来获取环境变量。它返回一个包含用户环境的对象。这里的用户环境是 Shell 进程,这个对象包含了当前进程的变量。注意,
process.env
对象可以被修改,但其修改不会影响到此进程之外。Shell 变量
分类:
作用域:
local
显式声明的变量,其作用域仅在函数内。export
显式声明的变量,其作用域是当前进程及子进程。NPM 环境变量
假设有以下包:
执行
npm run start
时,会得到这些变量:可以看到两类与 npm 相关的环境变量,在执行
npm run
命令时自动载入。npm_config_
npm_package_
其中
npm_config_
开头的环境变量源自.npmrc
配置文件,优先级从上到下:.npmrc
$HOME/.npmrc
$PREFIX/etc/npmrc
(其中$PREFIX
为npm config get prefix
的路径)/path/to/npm/npmrc
其中
npm_package_
则源自package.json
。比如使用process.env.npm_package_version
获取包版本号。NPM Script 自定义环境变量
以上是
npm run
内部执行逻辑带入的环境变量,也可以自定义。比如:
这样就能在 Node 脚本里获取到这个
NODE_ENV
变量值了。但它仅支持 Unix-like 操作系统,到 Windows 就不行了。后者需要使用
set
命令:后来,出现了一些跨平台方案,比如 cross-env。用法变成了这样:
如果项目的环境变量很多,script 就会很长很长,不好看也不好维护,后来又使用 dotenv 方案。
比如,项目根目录有
.env
、.env.development
文件:这样本地开发和打包的时候,就能根据
NODE_ENV
的值从对应的.env
文件中读取配置。当然,以上环境变量仅可在编译时有效。要在业务代码中使用,还得借助类似
webpack.DefinePlugin
、webpack.EnvironmentPlugin
等插件处理,它们将会在编译时被替换为相应的字符串。注意,很多构建工具只有「特定前缀开头」以及像
NODE_ENV
这种很通用的环境变量才能在运行时(即业务代码)可用。VITE_
VUE_APP_
REACT_APP_
TARO_APP_
其他
除此之外,还有其他一些方式可以提供。
webpack
可以通过 webpack-cli 的
--env
参数传递。比如:执行
npm run build:pre
时,可以这样获取到值:还想多说一下。
以 webpack 为例,其模式有
development
、production
和none
三种。当「显式」声明mode
为前两者时,它会自动设置process.env.NODE_ENV
为对应值(更多)。从这个角度看,process.env.NODE_ENV
通常用来区分开发模式、打包模式。比如,开发模式下启用 sourcemap、HMR 等以便于开发调试。打包模式下启用 minimizer、splitChunks 等以减少产物体积。但好像有些同学会将
process.env.NODE_ENV
用于区分「项目」的测试、生成环境,其实“不对”的。假设项目有测试环境、预生产环境和生产环境呢,那它就不够用了。而且,即使是部署到非正式环境,在打包时也应该使用production
模式。可以像上面那样不同项目环境传入不同的
--env
参数,然后结合webpack.DefinePlugin
来定义特定变量,比如:未完待续...