项目中如何区分环境和设置环境变量

我心飞翔 分类:javascript

构建环境和运行环境

构建环境

在项目开发或者构建的时候,我们通常会将程序分为两种环境

  • 开发环境:我们正在开发的这个阶段所处的环境,也就是方便我们开发人员调试开发的一种环境构建,结果用于本地开发调试,不进行代码压缩、打印 debug 信息、包含 sourcemap 文件等。
  • 生产环境|线上环境:我们将程序开发完成经过测试之后无明显异常准备发布上线的环境构建,也可以理解为用户可以正常使用的就是生产环境。构建结果用于线上,即代码都是压缩过的、运行时不打印 debug 信息、静态文件不包括 sourcemap、通过tree-shaking去除开发环境特有代码等。

部署和部署环境

  • 部署:简单的来说部署指的就是将构建后的代码放到服务器上去,这个服务器可以是本地的也可以是远程的,所以相应地部署就分为远程部署和本地部署。
  • 部署环境|运行环境:一个软件产品从开发到用户可能经过一系列流程,开发->测试->上线,所以相应的环境也可以分为开发环境、测试环境、预发布环境、生产环境等。

构建环境和部署环境的关系

  • 构建环境是从代码打包层面上区分的,部署环境是从代码的运行层面上区分的
  • 不同的运行环境依赖于其对应构建环境打包出来的产物
  • 运行环境和构建环境并不是一一对应的,比如生产环境也可以运行开发环境打包出来的产物,但是实际上为了用户体验和方便测试,运行环境的生产、测试、预发布环境对应的是构建环境中的生产环境。
  • 对于最终运行在浏览器中的代码,构建过程是发生在node环境中的,运行是在浏览器环境中的,这里也会有一些差异。

如何区分环境以及设置环境变量

webpack中如何判断构建环境

客户端代码中的判断

  • 我们经常能看到前端利用 process.env.NODE_ENV 来判断当前处于什么构建环境,但其实process是node中独有的对象,实际上在前端运行环境或者window上中并没有process相关信息。

  • 那么如何在前端判断当前环境呢?有时候我们看到前端环境也在使用process.env.NODE_ENV来判断环境?那又是怎么做到的尼?

在webpack打包中,提供 mode 选项来配置构建环境

选项 描述
development 会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 development. 为模块和 chunk 启用有效的名。
production 会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 production。为模块和 chunk 启用确定性的混淆名称,FlagDependencyUsagePlugin,FlagIncludedChunksPlugin,ModuleConcatenationPlugin,NoEmitOnErrorsPlugin 和 TerserPlugin 。

设置了mode后,webpack将通过 webpack.DefinePlugin 插件将环境配置成对应的值。类似于下面这样:

plugins: [
    new webpack.DefinePlugin({
        "process.env": {
            NODE_ENV: JSON.stringify(mode),
         },
    })
]
 

这里的 process.env.NODE_ENV 跟node环境的 process.env.NODE_ENV 并不是一回事,是在编译代码前的字符串分析时候遇到 'process.env.NODE_ENV' 这个字符串时,将该字符串替换为设置的值,所以在设置值的时候需要执行一下JSON.stringify函数。过程如下:

let sourceCode = `console.log(process.env.NODE_ENV)`
sourceCode.replace('process.env.NODE_ENV', JSON.stringify('development'))

// 结果如下
resultCoude = `console.log('development')`

// 不添加 JOSN.stringify, 执行的时候会找不到 development 而报错
resultCoude = `console.log(development)`
 

其本质是字符串的替换,而不是添加了 process 这个全局数据。因此window下是没有挂process 这个字段的,包括 process.env['NODE_ENV']也是无法识别的。当然这里也可以用其他字符串,只要没有命名冲突都可。用process.env可能只是让看起来一致点😳,同时不污染全局变量环境,也可以在process.env后面上挂在其他变量process.env.BASE_URL等。

node端判断环境

上面所说的其实是为了给客户端代码运行时判断环境变量,那么构建时候的node如何区分环境尼?这里可能有点模糊,我代码展示下例子:

// webpack.config.js
console.log('process.env.NODE_ENV', process.env.NODE_ENV); // 1
module.exports = {
  mode: "development",
  plugins: [
    // 设置 mode 后,webpack 会自动配置下面
    new webpack.DefinePlugin({
      "process.env": {
        NODE_ENV: JSON.stringify('development'),
      },
    }),
  ],
};

// index.js
console.log('process.env.NODE_ENV', process.env.NODE_ENV); // 2
 

运行结果 1: undefined 2: 'development'

出现的原因是因为 webpack.config.js 是运行在node环境,index.js是客户端运行,DefinePlugin只负责客户端的替换,而并没有给process.env.NODE_ENV赋值。process.env 本身也是不包含NODE_ENV这个变量的,也是需要开发者赋值的,那么如何设置值尼?

image.png

答案就是通过cross-env设置环境变量

// package.json
{
    script: {
        // node 环境中 precess.env.NODE_ENV = development
        build1: 'cross-env NODE_ENV=development webpack xxx',
        
        // precess.env = {...precess.env, NODE_ENV: 'development', BASE_URL='api.xxx' }
        build2: 'cross-env NODE_ENV=development BASE_URL=api.xxx webpackxxx'
        
        // 同时配置构建环境和node环境
        "build3": "cross-env NODE_ENV='development' --mode development webpack xxx",
    }
}
 

当我们执行 npm run build3 后,上述的运行结果变为
1: 'development' 2: 'development',两端的环境判断以及获取就一致了。

如何判断运行环境

刚刚说了构建环境的判断,为什么还要说一下运行环境的判断尼?

上面也说了,运行环境和构建环境的不同,有时候并不是一一对应的,运行环境通常会多于构建环境,比如构建里面生产环境的代码可能同时对应于运行环境和预发布环境,这时候如果我们想要针对预发运行环境做一些环境变量的控制,就不得不判断是否处于预发环境了

通过webpack控制

再谈一谈 webpack 的 mode

mode 环境实际上只是webpack预定义的一套模式,配置了mode后webpack帮你预配置了某些插件以及定义了前端使用的变量,实际上你也可以不使用它提供的mode,将mode配置成none,进行个性化配置。

  1. 继续使用 mode 模式,设置node端环境,修改默认DefinePlugin
// webpack.config.js
// node 环境参数判断,node环境(构建环境)不需要感知 pre
const getEnvInNode = () => {
    return process.env.NODE_ENV === 'development' ? 'development' : 'production'
}
console.log('process.env.NODE_ENV', process.env.NODE_ENV); // pre
console.log(getEnvInNode()) // production
module.exports = {
  mode: "development",
  plugins: [
    // 覆盖 mode 默认配置,传递环境给客户端
    new webpack.DefinePlugin({
      "process.env": {
        NODE_ENV: JSON.stringify(process.env.NODE_ENV),
      },
    }),
  ],
};

// package.js
{
    script: {
        build: 'cross_env NODE_ENV=pre webpack'
    }
}

// index.js
console.log('当前运行环境', process.env.NODE_ENV) // pre
 

2、放弃使用 mode,自定义各种配置

可以搭配webpack的merge函数使用节减配置。

// webpack.pre.config.js
console.log('process.env.NODE_ENV', process.env.NODE_ENV); // development | production 
module.exports = {
  // 自定义配置 
  mode: "node",
  plugins: [
    // 不使用 mode 默认配置,传递环境给客户端
    new webpack.DefinePlugin({
      "process.env": {
        NODE_ENV: JSON.stringify('pre' | 'development' | 'production'),
      },
    }),
  ],
};

// package.js
{
    script: {
        build:dev: 'cross_env NODE_ENV=development webpack --config ./webpack.development.config.js',
        build:pre: 'cross_env NODE_ENV=production webpack --config ./webpack.pre.config.js'
        build:prod: 'cross_env NODE_ENV=production webpack --config ./webpack.production.config.js'
    }
}

// index.js
console.log('当前运行环境', process.env.NODE_ENV) // pre | development | production
 

3、根据运行环境域名控制

当我们代码部署在不同环境中,部署的域名是不一样的,我们可以通过域名来判断当前属于哪个环境

const domainHost = {
    'test.xxx.com': 'DEV',
    'pre.xxx.com': 'PRE',
    'xxx.com': 'PROD'
}
const env = domainHost[location.host] || 'DEV'
 

在react、Vue中设置环境变量

  • vue: cli.vuejs.org/zh/guide/mo…
  • react: create-react-app.dev/docs/adding…
  • dotenv: github.com/motdotla/do…

回复

我来回复
  • 暂无回复内容