Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Node.js 环境变量 #338

Open
toFrankie opened this issue May 1, 2024 · 0 comments
Open

Node.js 环境变量 #338

toFrankie opened this issue May 1, 2024 · 0 comments
Labels
2024 2024 年撰写 Node.js 与 Node.js、npm、yarn、pnpm 等相关的文章 前端 与 JavaScript、ECMAScript、Web 前端相关的文章

Comments

@toFrankie
Copy link
Owner

toFrankie commented May 1, 2024

配图源自 Freepik

前言

在 Node.js 中通常会使用 process.env 来获取环境变量。

process.env

它返回一个包含用户环境的对象。这里的用户环境是 Shell 进程,这个对象包含了当前进程的变量。注意,process.env 对象可以被修改,但其修改不会影响到此进程之外。

Shell 变量

分类:

  • 环境变量:通常是指 Shell 内置变量或者 Shell 配置文件中声明的变量。
  • 自定义变量:通常是在 Shell 脚本或命令行中声明的变量。

作用域:

  • 环境变量:创建 Shell 进程时会自动加载,这些变量可以被当前进程以及子进程访问。
  • 自定义变量:
    • 函数体内:在函数体内使用 local 显式声明的变量,其作用域仅在函数内。
    • 当前进程:当前 Shell 进程内可访问,但子进程不能访问。默认作用域。
    • 当前进程及其子进程:使用 export 显式声明的变量,其作用域是当前进程及子进程。

Shell 环境是天然隔离的,在当前进程内设置或修改变量,都不会影响到其他非关联进程的环境变量。

function fn() {
  foo=1         # 作用域为当前进程
  local bar=2   # 作用域为当前函数
  export baz=3  # 作用域为当前进程及子进程
}

fn

echo $foo    # 1
echo $bar    # 空字符串
echo $baz    # 3

在 Shell 中,如果引用的变量不存在,它不会报错,而是输出空字符。

NPM 环境变量

假设有以下包:

{
  "name": "node-env",
  "version": "1.0.0",
  "scripts": {
    "start": "node -e 'console.log(process.env)'"
  }
}

执行 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_config_
  • npm_package_

其中 npm_config_ 开头的环境变量源自 .npmrc 配置文件,优先级从上到下:

  • 项目级别 .npmrc
  • 用户级别 $HOME/.npmrc
  • 全局级别 $PREFIX/etc/npmrc(其中 $PREFIXnpm config get prefix 的路径)
  • npm 内置配置文件 /path/to/npm/npmrc

其中 key 大小写不敏感,它们都会被转换为小写形式,- 也会被转为 _

其中 npm_package_ 则源自 package.json。比如使用 process.env.npm_package_version 获取包版本号。

NPM Script 自定义环境变量

以上是 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 变量值了。

在命令前加上变量声明,它会传递给子进程。类似 export 的效果,但不完全相同,这种方式不会影响当前进程的同名变量。Simple Command Expansion

但它仅支持 Unix-like 操作系统,到 Windows 就不行了。后者需要使用 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 文件可能会包含像密钥这类敏感信息,它不在版本控制范围内,应该添加到 .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.DefinePluginwebpack.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_ENV 这种很通用的环境变量才能在运行时(即业务代码)可用。

  • vite:VITE_
  • vue-cli:VUE_APP_
  • create-react-app:REACT_APP_
  • Taro:TARO_APP_
  • ...

任何不能对外公开的信息,都不要嵌入构建当中,因为它们都可以在构建产物中查到。

其他

除此之外,还有其他一些方式可以提供。

webpack

可以通过 webpack-cli 的 --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 时,可以这样获取到值:

// webpack.config.js
module.exports = function (env, argv) {
  console.log(env.pre) // true
  // 可以结合 webpack.DefinePlugin 使用
  // ...
}

若使用 --env,webpack 配置需导出为函数。

更多请看 Environment Options

还想多说一下。

以 webpack 为例,其模式有 developmentproductionnone 三种。当「显式」声明 mode 为前两者时,它会自动设置 process.env.NODE_ENV 为对应值(更多)。从这个角度看,process.env.NODE_ENV 通常用来区分开发模式、打包模式。比如,开发模式下启用 sourcemap、HMR 等以便于开发调试。打包模式下启用 minimizer、splitChunks 等以减少产物体积。

但好像用些同学会将 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--modeprocess.env.NODE_ENV 的关系。process.env.NODE_ENV 在各大构建工具频繁出现,算是一个约定俗成的变量了,它与项目环境是不同的概念。

未完待续...

@toFrankie toFrankie added 2024 2024 年撰写 Node.js 与 Node.js、npm、yarn、pnpm 等相关的文章 前端 与 JavaScript、ECMAScript、Web 前端相关的文章 labels May 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
2024 2024 年撰写 Node.js 与 Node.js、npm、yarn、pnpm 等相关的文章 前端 与 JavaScript、ECMAScript、Web 前端相关的文章
Projects
None yet
Development

No branches or pull requests

1 participant