likes
comments
collection
share

vite和webpack环境变量共存之法

作者站长头像
站长
· 阅读数 3

前言

笔者所在团队采用的是monorepo单仓+yarn workspace组织项目代码。各应用间通过软链引用,并抽取一个专门的业务组件库,供monorepo里的项目使用。 单仓里的历史项目采用的是create-react-app脚手架(基本上是webpack5+react17),最近来了个新的H5项目,组内同学想丰富一下小组的工具箱,决定采用vite作为项目工具链,于是仓库里的项目就出现了webpack和vite共存的情况。 于是,新的问题出现了...

起因

同学小A写一个业务组件,作用是获取到头部商户信息,商户信息是一组级联数据,所以采用了 Cascader 级联选择器作为基础组件,封装一层数据懒加载逻辑后得到了一个商户Cascader组件。 我们的系统有三套环境:测试、预发、生产,测试环境用于本地接口调试和测试同学测试,预发环境是部署生产环境前的一个过渡环境,用以进行最后的验证,生产环境直面用户。前、后端都独立部署并维护着这三套环境。 在经过专门的环境治理后,后端三套环境有三个独立的域名(test.xxx.com,pre.xxx.com,prod.xxx.com)。这就要求前端同学根据部署环境的不同切换不同的API域名。

这里可能会有读者疑惑为什么不用nginx做反向代理,这是因为我们的应用采用的是cdn轻量化部署,没有可以做反向代理的web容器,随之而来的跨域问题则是借助CORS协议克服

小A同学在写商户Cascader组件的时候,将API域名配置到了vite的环境变量里。具体配置如下:

// .env.development文件
VITE_API_URL=test.xxx.com

// .env.production
VITE_API_URL=prod.xxx.com

上述环境变量通过import.meta.env.VITE_API_URL就可以读取到。 上面的逻辑在vite项目里使用时是没有问题的... 然后有一天产品同学说这个组件这么好用,可不可以替换掉另一个系统里的组件呢? "当然可以",小A同学自信满满...考虑到原有的系统是webpack项目,并且也配置了API_URL,ta觉得只需要改动原组件获取API的那一行代码,其他都不需要改变。然后添加了如下逻辑:

  let apiUrl = "";
  if (process.env.REACT_API_URL) {
    apiUrl = process.env.REACT_APP_API_URL;
  } else {
    apiUrl = import.meta.env.VITE_API_URL;
  }

修改完后,启动webpack项目...运行正常。再启动vite项目,报错了:

vite和webpack环境变量共存之法

process不存在!!!

解决

上面的问题是在vite项目中使用到了webpack常用的环境变量写法,在webpack中对process.env.REACT_API_URL这类环境变量会采用整体替换的方式在最后的产物里直接用实际值代替,比如下面的代码:

const apiUrl = process.env.REACT_APP_API_URL;

最终会被编译成:

const apiUrl = "test.xxx.com";

vite和webpack环境变量共存之法 而在vite项目里,可不会做这样的替换,其会直接尝试在window对象下获取process,结果自然是undefined。 确定了问题原因,那就好办了,既然process没有定义,那就在window下定义一个呗,在vite项目的入口给window对象添加一个空的process:

// App.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import "./index.css";

// 注入process
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(window as any).process = { env: {} };

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

重新运行,依然报错:ReferenceError: process is not defined。 明明在App.tsx中为window对象注入了process呀,怎么在组件里使用还会报错呢?难道是vite的预构建在处理组件代码时,对于全局变量提前进行判断了?考虑到这一点,决定在组件中换一种写法,使其做到"运行时加载":

import React from "react";

const getApiUrl = () => {
  let apiUrl = "";
  if (process.env.REACT_APP_API_URL) {
    apiUrl = process.env.REACT_APP_API_URL;
  } else {
    apiUrl = import.meta.env.VITE_API_URL;
  }
  return apiUrl;
};

export default function Demo() {
  const apiUrl = getApiUrl();
  return (
    <div>
      <h2>
        From Demo Component <br />
        {apiUrl}
      </h2>
    </div>
  );
}

上面的代码重点改写了 apiUrl的获取方式,采用一个函数动态地返回,并在且组件调用初始化时调用。再次运行,correct!!! webpack项目里也运行一下,依然OK,下面贴上demo图:

vite和webpack环境变量共存之法

总结

关于vite和webpack环境变量的共存问题其实有很多解决办法,比如:

  1. 将变量提升到组件的props里,规避不同构建器的差异;
  2. 通过process这个库,让vite可以真正访问。process.env,达到统一vite和webpack环境变量访问方式的目的。

本文方案相比上面的技术路线不算得上是一种”优雅的“方案,这里抛出来希望能给大家一些新的思考。关于本文方案,有两个注意点:

  1. 需要在vite项目入口给window对象注入process属性;
  2. 组件中读取process.env.xxx的逻辑要保证在运行时执行(即用函数达到懒加载的效果),构建时执行会有问题。