"build": "cross-env NODE_ENV=production webpack --config build/webpack.config.js"
大家经常会看到这种代码,其中cross-env NODE_ENV=production,指定的环境变量key为NODE_ENV,值为production,这样调用之后呢,我们可以通过process.env.NODE_ENV 获取对应的环境变量。
process 是node 的一个进程环境,其中通过process.env可以获取进程环境中的环境变量,方便node在调用时处理各种环境信息。
这种是最基本的环境变量配置方式,NODE_ENV基本上常用的只有两种,development和production,用来做开发和生产的区分。
- "scripts": {
-
- "dev": "vite",
-
- "build": "tsc && vite build",
-
- "preview": "vite preview"
-
- },
我们在执行npm run dev的时NODE_ENV 会自动变成develop,为什么可以自动设置呢?只有了解了事情的本质我们才可以更放心大胆的使用各种命令。要想知道为什么会这么执行,我们首先要知道npm run dev 执行的vite方法干了什么,然后我们一步步的来扒源码。先找到vite这个文件夹。
首先我们要知道,当执行npm run 时会自动找到当前目录下的scripts对象,比如我们npm run dev,就会找到package.json下scripts 下的dev 这个指令。我相信可能有些同学会试过,直接在当前目录下执行vite,然后得到了一个:
或者大家会有这个疑问,为什么npm run dev 执行 script 下的dev触发vite可以执行,而我们直接执行不可以呢?这是因为在界面中执行vite是必须要全局安装的才可以的。而不全局安装的要用的话,使用scripts执行,而scripts会默认查找当前node_modules下对应的文件夹,比如vite。找到对应的vite文件夹之后会查找对应的package.json下的bin对应的执行文件,像vite如下:
然后承接上面的疑问,为什么会有默认的development 这个环境变量,在vite中的源代码如下:
他会有个默认值,如果不设置的话,就默认是development。
vite build的时候会默认传入production,所以就变成了production
所以在vite中,vite 和 vite build 这两个指令执行的时候,环境变量NODE_ENV的值默认为development和production 。
- import { defineConfig } from "vite";
-
- import react from "@vitejs/plugin-react";
-
- import * as path from "path";
-
- const target = "https://dev1.advai.cn”;
- export default defineConfig(({ command, mode }) => {
- return {
- plugins: [react()],
- server: {
- host: "0.0.0.0",
- proxy: {
- "/api/dashboard": {
- target,
- changeOrigin: true,
- secure: false,
- xfwd: false,
- },
- },
- },
- base: mode === "production" ? "./" : "",
- resolve: {
- alias: {
- "@": path.resolve(__dirname, "./src"),
- },
- },
- };
- });
这种方案好处是一看就懂,坏处就是你改了地址,代码会被提上去,把别人的本地地址也会覆盖掉。
我们本地要区分多环境时,这种方案就不行,所以,各种构建工具都提供了环境变量的配置及使用,我们这里着重介绍vite的使用,及vite为什么可以这么用,知己知彼,才能用得更好,玩得更溜。
再来看我们之前的那个script案例,
- "scripts": {
-
- "dev": "vite",
-
- "build": "tsc && vite build",
-
- "preview": "vite preview"
-
- },
执行vite的时候,会自动种下一个环境变量,叫NODE_ENV来区分我们当前的环境是什么。
那么,其实我们本地请求后端的各种环境也可以通过环境变量来设置。我们可以通过在根目录添加.env.development,.env.development.local两个文件。
其中.env.development 可以包含各个环境的所有配置项,更像是一个清单,大概内容如下:
- # dev1
-
- # VITE_TARGET=https://dev1.advai.cn
-
- # dev2
-
- #VITE_TARGET=https://dev2.advai.cn
-
- # prod
-
- VITE_TARGET=https://prod.advai.cn
.env.development.local文件中是要用到的具体的后端环境,例如:
- # dev1
-
- VITE_TARGET=https://dev1.advai.cn
这样做有个好处,正常的话,.env.development.local 这个文件是git ignore 要忽略的,这样代码提上去之后会有一个默认启用的VITE_TARGET。
配置完了,环境变量我们要怎么使用呢?
- export default defineConfig(({ command, mode }) => {
-
- const env = loadEnv(mode, path.resolve(process.cwd()));
-
- console.log('env', env);
-
- });
这时我们就能获取到自己的环境变量。
完整示例:
- import path from 'path';
-
- import fs from 'fs';
-
- import process from 'process';
-
- import { defineConfig, loadEnv } from 'vite';
-
- import legacy from '@vitejs/plugin-legacy';
-
- import react from '@vitejs/plugin-react';
-
- // https://vitejs.dev/config/
-
- export default defineConfig(({ command, mode }) => {
-
- const env = loadEnv(mode, path.resolve(process.cwd()));
-
- console.log('env', env);
-
- return {
-
- plugins: [
-
- react(),
-
- ],
-
- server: {
-
- proxy: {
-
- '/api/service': {
-
- target: env.VITE_TARGET,
-
- changeOrigin: true,
-
- secure: false,
-
- },
-
- },
-
- host: true,
-
- },
-
- };
-
- });
通过使用env.VITE_TARGET我们就可以动态的获取到本地环境变量中的后端地址了。
其中有的小伙伴可能觉得VITE_ 有点碍事,我们直接写TARGET 不好吗?那我们来试一下,我们将env.development.local改为:
- # dev1
- TARGET=https://dev1.advai.cn
然后重新执行npm run dev,会发现获取到了一个空对象:
为什么呢?要了解这个问题,首先要知道咱们调用的loadEnv这个方法是干啥用的,官网上也有介绍:
loadEnv接收三个参数,第一个是.env后面的名字,第二个是绝对路径,第三个参数是你环境变量名的前缀,在vite中默认是VITE_,为什么不传VITE_的环境变量就不显示了呢?源码在这里:
vite/env.ts at 134ce6817984bad0f5fb043481502531fee9b1db · vitejs/vite · GitHub ,我将其中几个重要的地方拿出来:
(1)、需要抓取的文件名称
- const envFiles = [
-
- /** default file */ `.env`,
-
- /** local file */ `.env.local`,
-
- /** mode file */ `.env.${mode}`,
-
- /** mode local file */ `.env.${mode}.local`,
-
- ]
这里定义vite会抓取的文件名称,并且顺序就是优先级,也就是`.env.${mode}.local` > `.env.${mode}` >`.env.local` > `.env` 。这里的环境变量的优先级的根据文件的优先级加载,当有重名的环境变量时,会按文件的优先级来覆盖环境变量的值。
(2)判断包含prefixes的key
- for (const key in process.env) {
-
- if (prefixes.some((prefix) => key.startsWith(prefix)) &&
-
- env[key] === undefined) {
-
- env[key] = process.env[key];
-
- }
-
- }
这段代码写的就是刚才为什么获取不到不带VITE_环境变量的原因,因为这个会判断key是否包含你写的prefixes,然后加载到env这个对象中。如果你没传prefixes 的话,那么默认就是VITE_。
(3)读取路径内容,并返回给process.env
- for (const file of envFiles) {
-
- const path = lookupFile(envDir, [file], { pathOnly: true, rootDir: envDir });
-
- if (path) {
-
- const parsed = dotenv.parse(fs__default.readFileSync(path), {
-
- debug: ((_a = process.env.DEBUG) === null || _a === void 0 ? void 0 : _a.includes('vite:dotenv')) || undefined
-
- });
-
- // let environment variables use each other
-
- main({
-
- parsed,
-
- // prevent process.env mutation
-
- ignoreProcessEnv: true
-
- });
-
- // only keys that start with prefix are exposed to client
-
- for (const [key, value] of Object.entries(parsed)) {
-
- if (prefixes.some((prefix) => key.startsWith(prefix)) &&
-
- env[key] === undefined) {
-
- env[key] = value;
-
- }
-
- else if (key === 'NODE_ENV' &&
-
- process.env.VITE_USER_NODE_ENV === undefined) {
-
- // NODE_ENV override in .env file
-
- process.env.VITE_USER_NODE_ENV = value;
-
- }
-
- }
-
- }
-
- }
遍历envFiles 文件列表,判断如果有对应路径的话,读取路径内容,并取出prefix 开头的环境变量,并返回给process.env。
还有的朋友可能要问,我为什么非要在根目录下创建两个文件?这样我看起来乱得很,我想创建一个文件夹来存放那些env环境变量。其实vite也提供了加载指定目录env文件的配置项,这是官网的介绍
最后我们的vite配置文件改成了:
- export default defineConfig(({ command, mode }) => {
-
- const env = loadEnv(mode, path.resolve(process.cwd()));
-
- return {
-
- envDir: './env',
-
- plugins: [
-
- react(),
-
- ],
-
- server: {
-
- proxy: {
-
- '/api/iam-service': {
-
- target: env.VITE_TARGET,
-
- changeOrigin: true,
-
- secure: false,
-
- },
-
- '/accounts/': {
-
- target: env.VITE_TARGET,
-
- changeOrigin: true,
-
- secure: false,
-
- },
-
- },
-
- host: true,
-
- },
-
- }
-
- });
环境变量的默认目录由当前路径改为当前路径下的env文件夹:
.env.development的内容:
- # dev1
-
- # VITE_TARGET=https://dev1.advai.cn
-
- # dev2
-
- #VITE_TARGET=https://dev2.advai.cn
-
- # prod
-
- VITE_TARGET=https://prod.advai.cn
.env.development.local的内容:
- # dev1
-
- VITE_TARGET=https://dev1.advai.cn
.git ignore别忘记修改,这样我们才可以让我们本地的环境变量只作用在本地,
- node_modules
- .DS_Store
- dist
- dist-ssr
- *.local
- node_modules/*
- .vscode/