vite打包构建时环境变量(env)生成可配置的js文件

现实需求

在vite开发过程中,一些变量可以放在.env(基础公共部分变量).env.dev(开发环境)、.env.production(生产环境)中管理,通常分成开发和生产两个不同的配置文件管理,方便调试和部署。但是在应用部署在若干个不同的运行环境,则需要修改.env.production中的部分变量(如api地址)重新打包会比较麻烦。

怎么样实现不重新打包的前提下修改配置呢?

答:在build打包时将.env.production和.env文件统一打包成额外的配置js文件,在通过外部挂载的方式做成全局变量即可。

文件结构如下:

1、构建脚本的文件夹结构

在这里插入图片描述

程序内部调用的文件结构

在这里插入图片描述
前提条件
.env文件

VITE_GLOB_APP_TITLE=测试平台
VITE_BASE_URL=/newpath/
VITE_GLOB_APP_SHORT_NAME=GIS_APP

.env.production文件

VITE_API_BASE_URL=/
VITE_LOGIN_API_URL=https://www.baidu.com/login

一、新建打包脚本(build.ts及依赖文件)

脚本目的是在打包目录下生成_app.config.js,效果如下:
在这里插入图片描述
_app.config.js结构如下:

window.__PRODUCTION__APP__CONF__ = {
	"VITE_GLOB_APP_TITLE": "测试平台",
    "VITE_BASE_URL":"/newpath/",
	"VITE_GLOB_APP_SHORT_NAME": "APP",
    "VITE_API_BASE_URL":"/",
	"VITE_LOGIN_API_URL": "https://www.baidu.com/login"
};
Object.freeze(window.__PRODUCTION__APP__CONF__);
Object.defineProperty(window, "__PRODUCTION__APP__CONF__", {
	configurable: false,
	writable: false,
});
1、build.ts
import { runBuildConfig } from "./buildConf"
import pkg from "../../package.json"

export const runBuild = async () => {
  try {
    const argvList = process.argv.splice(2)

    if (!argvList.includes("disabled-config")) {
      runBuildConfig()
    }

    console.log(`[${pkg.name}] - 构建成功!`)
  } catch (error) {
    console.log("虚拟构建错误:\n" + error)
    process.exit(1)
  }
}
runBuild()

2、buildConf.ts文件
/**
 * 用于打包时生成额外的配置文件。该文件可以配置一些全局变量,这样就可以直接在外部修改,而无需重新打包
 */
import fs, { writeFileSync } from "fs-extra"
import { getEnvConfig, getRootPath } from "../utils"
import { getConfigFileName } from "../getConfigFileName"
import pkg from "../../package.json"

// 打包脚本的名称
const GLOB_CONFIG_FILE_NAME = "_app.config.js"
// 输出文件的根目录
const OUTPUT_DIR = "dist"

interface CreateConfigParams {
  configName: string;
  config: any;
  configFileName?: string;
}

function createConfig(params: CreateConfigParams) {
  const { configName, config, configFileName } = params
  try {
    const windowConf = `window.${configName}`
   
    // 确保变量不会被修改
    const configStr = `${windowConf}=${JSON.stringify(config)};
      Object.freeze(${windowConf});
      Object.defineProperty(window, "${configName}", {
        configurable: false,
        writable: false,
      });
    `.replace(/\s/g, "")
    
    // 拼接新的输出根目录地址
    const filePath = `${OUTPUT_DIR}${config.VITE_BASE_URL || "/"}`

    // 创建根目录
    fs.mkdirp(getRootPath(filePath))
    writeFileSync(getRootPath(filePath + configFileName), configStr)
    
    console.log(`✨ [${pkg.name}]` + ` - 配置文件构建成功:`)
    console.log(filePath + "\n")
  } catch (error) {
    console.log("配置文件配置文件打包失败:\n" + error)
  }
}

export function runBuildConfig() {
  const config = getEnvConfig()
  const configFileName = getConfigFileName(config)
  createConfig({ config, configName: configFileName, configFileName: GLOB_CONFIG_FILE_NAME })
}
3、getConfigFileName.ts文件
/**
 * 获取配置文件变量名
 * @param env
 */
export const getConfigFileName = (env: Record<string, any>) => {
  return `__PRODUCTION__${env.VITE_GLOB_APP_SHORT_NAME || "__APP"}__CONF__`
    .toUpperCase()
    .replace(/\s/g, "")
}
4、utils.ts文件
import fs from "fs"
import path from "path"
import dotenv from "dotenv"

export function isDevFn(mode: string): boolean {
  return mode === "development"
}

export function isProdFn(mode: string): boolean {
  return mode === "production"
}

/**
 * 是否生成包预览
 */
export function isReportMode(): boolean {
  return process.env.REPORT === "true"
}

/**
 * 读取所有环境变量配置文件到process.env
 * @param envConf
 * @returns
 */
export function wrapperEnv(envConf: any) {
  const ret: any = {}

  for (const envName of Object.keys(envConf)) {
    let realName = envConf[envName].replace(/\\n/g, "\n")
    realName = realName === "true" ? true : realName === "false" ? false : realName

    if (envName === "VITE_PORT") {
      realName = Number(realName)
    }
    if (envName === "VITE_PROXY" && realName) {
      try {
        realName = JSON.parse(realName.replace(/'/g, '"'))
      } catch (error) {
        realName = ""
      }
    }
    ret[envName] = realName
    if (typeof realName === "string") {
      process.env[envName] = realName
    } else if (typeof realName === "object") {
      process.env[envName] = JSON.stringify(realName)
    }
  }
  return ret
}

/**
 * 获取当前环境下生效的配置文件名
 */
function getConfFiles() {
  const script = process.env.npm_lifecycle_script
  // eslint-disable-next-line prefer-regex-literals
  const reg = new RegExp("--mode ([a-z_\\d]+)")
  const result = reg.exec(script as string) as any
  if (result) {
    const mode = result[1] as string
    return [".env", `.env.${mode}`]
  }
  return [".env", ".env.production"]
}

/**
 * 获取以指定前缀开头的环境变量
 * @param match prefix
 * @param confFiles ext
 */
export function getEnvConfig(match = "VITE_", confFiles = getConfFiles()) {
  let envConfig = {}
  confFiles.forEach((item) => {
    try {
      const env = dotenv.parse(fs.readFileSync(path.resolve(process.cwd(), item)))
      envConfig = { ...envConfig, ...env }
    } catch (e) {
      console.error(`解析错误:${item}`, e)
    }
  })
  const reg = new RegExp(`^(${match})`)
  Object.keys(envConfig).forEach((key) => {
    if (!reg.test(key)) {
      Reflect.deleteProperty(envConfig, key)
    }
  })
  return envConfig
}

/**
 * 获取用户根目录
 * @param dir file path
 */
export function getRootPath(...dir: string[]) {
  return path.resolve(process.cwd(), ...dir)
}

二、修改vite.config.ts文件

脚本目的是将生成_app.config.js在index.html文件中添加引用,效果如下:
在这里插入图片描述

1、安装 vite-plugin-html
yarn add vite-plugin-html -D 

npm i vite-plugin-html -D
2、vite.config.ts中引用createHtmlPlugin和package.json,并添加createHtmlPlugin
import createHtmlPlugin from "vite-plugin-html"
import pkg from "./package.json"

在这里插入图片描述

  const getAppConfigSrc = () => {
    return `${ENV.VITE_BASE_URL || "/"}${GLOB_CONFIG_FILE_NAME}?v=${pkg.version}-${new Date().getTime()}`
  }

      createHtmlPlugin({
        minify: isBuild,
        inject: {
          data: {
            title: ""
          },
          // Embed the generated app.config.js file
          tags: isBuild
            ? [
              {
                tag: "script",
                attrs: {
                  src: getAppConfigSrc()
                }
              }
            ]
            : []
        }
      })

三、代码内部引用

1、env.ts文件
import { getConfigFileName } from "../../../build/getConfigFileName"
import pkg from "../../../package.json"

export function getCommonStoragePrefix() {
  const { VITE_GLOB_APP_SHORT_NAME } = getAppEnvConfig()
  return `${VITE_GLOB_APP_SHORT_NAME}__${getEnv()}`.toUpperCase()
}

/**
 * 根据版本生成缓存键
 * @returns
 */
export function getStorageShortName() {
  return `${getCommonStoragePrefix()}${`__${pkg.version}`}__`.toUpperCase()
}

export function getAppEnvConfig() {
  const ENV_NAME = getConfigFileName(import.meta.env)
  // 根据当前运行状态判断取值,如果是开发环境取.env.dev配置,如果是生产环境取_app.config.js引用的全局变量 
  const ENV = (import.meta.env.DEV ? (import.meta.env as unknown as any) : window[ENV_NAME as any]) as unknown as any
 
  // 此处可以进行过滤判断根据业务需求处理,示例未做任何处理
  const { VITE_GLOB_APP_SHORT_NAME } = ENV
  if (!/^[a-zA-Z\_]*$/.test(VITE_GLOB_APP_SHORT_NAME)) {
    console.log(`VITE_GLOB_APP_SHORT_NAME变量只能是字符/下划线,请在环境变量中修改后重新运行。`)
  }

  return ENV
}

/**
 * @description: Development mode
 */
export const devMode = "development"

/**
 * @description: Production mode
 */
export const prodMode = "production"

/**
 * @description: Get environment variables
 * @returns:
 * @example:
 */
export function getEnv(): string {
  return import.meta.env.MODE
}

/**
 * @description: Is it a development mode
 * @returns:
 * @example:
 */
export function isDevMode(): boolean {
  return import.meta.env.DEV
}

/**
 * @description: Is it a production mode
 * @returns:
 * @example:
 */
export function isProdMode(): boolean {
  return import.meta.env.PROD
}

2、业务调用index.ts文件
import { getAppEnvConfig } from "@mars/hooks/setting/env"

export const useGlobSetting = (): Readonly<any> => {
  const setting = getAppEnvConfig()
  return setting
}

四、修改package.json文件build命令

1、安装cross-env和esno
yarn add cross-env -D
yarn add esno -D 

npm i cross-env -D
npm i esno -D
1、修改build命令,增加"&& esno ./build/script/postBuild.ts"
"build": "cross-env npm run lint && vite build && esno ./build/script/postBuild.ts",

五、测试命令

在命令行执行下面的语句可以测试生成_app.config.js文件

yarn esno ./build/script/postBuild.ts

以上操作就能生成外部配置并加载了,使用时在开发环境、演示环境、生产环境部署修改_app.config.js中对应的api地址即可!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/428483.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MATLAB环境下基于区域椭圆拟合的细胞分割方法

使用图像分割技术可以找到图像中的目标区域&#xff0c;目标区域可以定义为具有特定值的单个区域&#xff0c;也可以定义为具有相同值的多个区域。目前图像分割已经融入到生活中的方方面面&#xff0c;在遥感领域&#xff0c;它应用于航拍图中的地形、地貌的分割&#xff1b;在…

【d35】【Java】【力扣】28. 找出字符串中第一个匹配项的下标

题目 给你两个字符串 haystack 和 needle &#xff0c;请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标&#xff08;下标从 0 开始&#xff09;。如果 needle 不是 haystack 的一部分&#xff0c;则返回 -1 。 示例 1&#xff1a; 输入&#xff1a;haystac…

YOLOv8从入门到入土使用教程!(一)训练模型

⭐⭐⭐瞧一瞧看一看&#xff0c;新鲜的YOLOv9魔改专栏来啦&#xff01;⭐⭐⭐ 专栏介绍&#xff1a;YOLOv9改进系列 | 包含深度学习最新创新&#xff0c;主力高效涨点&#xff01;&#xff01;&#xff01; 一、本文介绍 本文将演示如何使用YOLOv8进行训练及预测&#xff01; 二…

GitHub登不上:修改hosts文件来解决(GitHub520,window)

参考链接&#xff1a;GitHub520: 本项目无需安装任何程序&#xff0c;通过修改本地 hosts 文件&#xff0c;试图解决&#xff1a; GitHub 访问速度慢的问题 GitHub 项目中的图片显示不出的问题 花 5 分钟时间&#xff0c;让你"爱"上 GitHub。 (gitee.com) GitHub网站…

算法比赛|赛制介绍| ACM, IOI赛制, OI赛制

&#x1f525;博客介绍&#xff1a; 27dCnc &#x1f3a5;系列专栏&#xff1a; <<数据结构与算法>> << 算法入门>> << C项目>> &#x1f3a5; 当前专栏: << 算法入门>> 专题 : 数据结构帮助小白快速入门算法 &#x1f4…

【C++】二叉树进阶面试题(上)

目录 1. 二叉树创建字符串 题目 分析 代码 2. 二叉树的分层遍历1 题目 分析 代码 3. 二叉树的分层遍历2 题目 分析 代码 4. 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先 题目 分析 代码 5. 二叉树搜索树转换成排序双向链表 题目 分析 代码 1. …

鸿蒙开发实战【网络搜索】

概述 本示例通过eTS来展示电话服务中网络搜索功能&#xff0c;包含无线接入技术、网络状态、选网模式、ISO国家码、信号强度信息列表及Radio是否打开。 样例展示 涉及OpenHarmony技术特性 网络通信 基础信息 网络搜索 介绍 本示例通过[ohos.telephony.sim][ohos.telephon…

【算法分析与设计】组合

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;算法分析与设计 ⛺️稳中求进&#xff0c;晒太阳 题目 给定两个整数 n 和 k&#xff0c;返回范围 [1, n] 中所有可能的 k 个数的组合。 你可以按 任何顺序 返回答案。 示例 示例 1&…

【笔记版】docker常用指令---systemctl类、docker状态

systemctl [options] docker 启动&#xff1a;system start docker查看状态&#xff1a;systemctl status docker停止&#xff1a;systemctl stop docker有警告&#xff1a;service关闭了&#xff0c;但是docker.socket仍响应解决方法&#xff1a;systemctl stop docker.socket…

如何证明线性规划系统最优解存在性

先给定simplex所对应的算法的流程图: 添加图片注释,不超过 140 字(可选) 上图是线性规划算法的基本流程描述,但是给定的基本流程描述中的一些步骤还需要进一步的进行分解,第一步是如何将线性规划系统依靠算法的步骤现转换为标准型的线性规划系统,然后进行判断,主要是判…

递归实现指数型枚举

题目链接&#xff1a;92. 递归实现指数型枚举 - AcWing题库 解题思路&#xff1a; 递归思想&#xff0c;创建一个长度为n的数组&#xff0c;来存是否取当前的数&#xff0c;1代表取&#xff0c;2代表不取&#xff0c;先取&#xff0c;然后判断下一个数&#xff0c;直到大于n为…

transformer--编码器1(掩码张量、注意力机制、多头注意力机制)

编码器部分: 由N个编码器层堆叠而成每个编码器层由两个子层连接结构组成第一个子层连接结构包括一个多头自注意力子层和规范化层以及一个残差连接。第二个子层连接结构包括一个前馈全连接子层和规范化层以及一个残差连接 掩码张量 什么是掩码张量 掩代表遮掩&#xff0c;码…

【Maven】Maven 基础教程(四):搭建 Maven 私服 Nexus

《Maven 基础教程》系列&#xff0c;包含以下 4 篇文章&#xff1a; Maven 基础教程&#xff08;一&#xff09;&#xff1a;基础介绍、开发环境配置Maven 基础教程&#xff08;二&#xff09;&#xff1a;Maven 的使用Maven 基础教程&#xff08;三&#xff09;&#xff1a;b…

某大型制造企业数字化转型规划方案(附下载)

目录 一、项目背景和目标 二、业务现状 1. 总体应用现状 2. 各模块业务问题 2.1 设计 2.2 仿真 2.3 制造 2.4 服务 2.5 管理 三、业务需求及预期效果 1. 总体业务需求 2. 各模块业务需求 2.1 设计 2.2 仿真 2.3 制造 2.4 服务 2.5 管理 四、…

【前端寻宝之路】总结学习使用CSS的引入方式

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法|MySQL| ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-BNJBIEvpN0GHNeJ1 {font-family:"trebuchet ms",verdana,arial,sans-serif;f…

管家婆订货易在线商城 VshopProcess 任意文件上传漏洞复现

0x01 产品简介 管家婆订货易,帮助传统企业构建专属的订货平台,PC+微信+APP+小程序+h5商城5网合一,无缝对接线下的管家婆ERP系统,让用户订货更高效。支持业务员代客下单,支持多级推客分销,以客带客,拓展渠道。让企业的生意更轻松。 0x02 漏洞概述 管家婆订货易在线商城…

5G网络架构与组网部署01--5G网络架构的演进趋势

目录 1. 5G网络架构的演进趋势 1.1 5G移动通信系统整体架构 1.2 4G移动通信系统整体架构 1.3 4G与5G移动通信系统整体架构对比 1.4 核心网架构演进 1.5 无线接入网演进 1. 整体架构组成&#xff1a;接入网&#xff0c;核心网 2. 5G网络接入网和核心网对应的网元&#xff…

如何本地安装gemma

目录 通过ollama开源软件来一键安装目前主流的大模型&#xff0c;支持的开源模型包括以下内容&#xff1a; https://github.com/ollama/ollama

安卓驱动工程师 3年成长之路

大家好&#xff0c;我是杰哥 安卓驱动工程师 3年成长之路 最近和我的一个老朋友联系了一下&#xff0c;聊天中&#xff0c;透露了他目前已经达到30w的年薪 因为我自身是嵌入式的线下老师&#xff0c;所以就聊了他3年来的成长之路 正文 刚毕业不到1w的混子屌丝 是怎么3年后…

Java面试题总结8:springboot

Spring Boot自动配置原理 importConfigurationSpring spi 自动配置类由各个starter提供&#xff0c;使用ConfigurationBean定义配置类&#xff0c;放到META-INF/spring.factories下 使用Spring spi扫描META-INF/Spring.factories下的配置类 如何理解Spring Boot中Starter …