掘金文章归档 · 第 74 篇 工程化 · Vite 插件

自定义 VITE 插件,生成可修改配置项,用于不同项目部署

不同项目打包命令 + 环境变量 → 自动生成 setting.json → 运行期动态修改配置

原文作者:随意_(掘金) | juejin.cn/post/7490399021736837157 | 标签:前端

0背景与整体方案

我们做产品开发,经常遇见部署不同的项目,尤其是部署项目后后期免不了也要修改配置项。如果我们都写入环境变量中,每次都需要前端 npm run build:XXX 发版。

此文章基于 vite,就是解决这个痛点的。方法如下:

步骤说明
1根据不同的项目,创建不同的打包命令,在 package.json 中增加不同项目的打包命令
2根目录创建对应的环境变量配置文件,如 .env.130、.env.182 等
3vite 打包生成 setting.json,默认值就是上述的环境变量
4在页面初始化(在 axios 实例中)get 请求这个 setting.json,修改全局配置

11. 不同项目打包命令

"scripts": {
  "dev": "vite",
  "start": "vite --mode zhongyuan",
  "build:130": "vite build --mode 130",
  "build:182": "vite build --mode 182",
  "build:zhongyuan": "vite build --mode zhongyuan",
  "build:deyang": "vite build --mode deyang",
  "build:sjz": "vite build --mode sjz",
}

"build:130" 和 "build:182" 等就是不同项目的环境变量。

22. 根目录创建 .env 配置

根目录创建对应的环境变量配置文件,如 .env.130、.env.182 等:

截图占位:项目根目录下 .env.130 / .env.182 / .env.zhongyuan 等文件
# .env.130 示例
VITE_SYSTEM_TITLE=系统名称130
VITE_BASE_API=/api

33. 创建 global-api.ts 全局配置

创建 global-api.ts 用于获取全环境配置:

// 定义 Indexable 类型
type Indexable<T> = {
  [key: string]: T;
};

// 定义 GlobalAPI 对象
const GlobalAPI: Indexable<any> = {
  config: {}, // 用于存储环境变量
};

const devConfig: Indexable<any> = {};
for (const key in import.meta.env) {
  if (key.startsWith("VITE_")) {
    devConfig[key] = import.meta.env[key];
  }
}
GlobalAPI.config = devConfig;

export { GlobalAPI };

其他组件使用 GlobalAPI:

import { GlobalAPI } from "@/constants"; // 换成你的暴露地址
document.title = GlobalAPI?.config?.VITE_SYSTEM_TITLE || "政务知识助手";

44. 编写 vite 插件生成 setting.json

// vite-plugin-generate-settings.js
import fs from 'fs';
import path from 'path';
import { loadEnv } from 'vite';

/**
 * @param {object} options
 * @param {string} options.mode - Vite build mode
 * @param {string} options.envDir - Directory where .env files are located
 * @param {string | string[]} [options.prefixes=['VITE_']] - Prefixes to filter
 * @returns {import('vite').Plugin}
 */
function generateSettingsPlugin(options) { // 接收一个 options 对象
  const { mode, envDir, prefixes = ['VITE_'] } = options; // 解构参数

  if (!mode || !envDir) {
    // 在配置阶段就进行检查,确保必要参数已提供
    throw new Error('[Generate Settings] Missing required options: mode and envDir');
  }

  let viteConfig;
  let settings = {};

  return {
    name: 'vite-plugin-generate-settings',
    apply: 'build',

    configResolved(resolvedConfig) {
      viteConfig = resolvedConfig;
      // 使用传入的 mode 和 envDir
      const env = loadEnv(mode, envDir, '');

      settings = {};
      const VITE_PREFIXES = Array.isArray(prefixes) ? prefixes : [prefixes];

      for (const envKey in env) {
        if (VITE_PREFIXES.some(prefix => envKey.startsWith(prefix))) {
          settings[envKey] = env[envKey];
        }
      }
      console.log('\n[Generate Settings] Filtered settings:', settings);
    },

    writeBundle(options) {
      if (!viteConfig) {
        console.error('[Generate Settings] Vite config not resolved.');
        return;
      }
      if (Object.keys(settings).length === 0) {
        console.warn('[Generate Settings] No variables starting with VITE_ found. settings.json will be empty or not generated.');
      }

      const outDir = options.dir || viteConfig.build.outDir || 'dist';
      const filePath = path.resolve(outDir, 'setting.json');

      try {
        if (!fs.existsSync(outDir)) {
          fs.mkdirSync(outDir, { recursive: true });
        }
        fs.writeFileSync(filePath, JSON.stringify(settings, null, 2));
        console.log(`\n[Generate Settings] Successfully generated setting.json at: ${filePath}`);
      } catch (error) {
        console.error(`[Generate Settings] Error writing setting.json: ${error}`);
      }
    }
  };
}

export default generateSettingsPlugin;
📌 此插件的作用就是:获取当前的环境变量中以 VITE_ 开头的变量,并且生成对应的 setting.json(写入 dist 目录)。

55. vite.config.ts 引用插件

export default defineConfig(({ command, mode }) => {
  const root = process.cwd();
  return {
    plugins: [
      generateSettingsPlugin({
        mode: mode,        // 传递当前的 mode
        envDir: root,      // 显式传递 env 文件目录 (通常是项目根目录)
      })
    ],
  }
})
// ...你的其余配置

66. 执行构建生成 setting.json

执行 npm run build:xxx,默认会在 dist 文件目录中生成 setting.json:

{
  "VITE_SYSTEM_TITLE1": "系统名称1",
  "VITE_SYSTEM_TITLE2": "系统名称2",
  "VITE_SYSTEM_TITLE3": "系统名称3",
  "VITE_SYSTEM_TITLE4": "系统名称4"
}
截图占位:dist 目录下生成的 setting.json 文件

77. 初始化时重新获取配置(axios 中)

鉴于我们全局有请求接口,如获取用户信息的,我们在 axios 中重新设置配置项:

class Request<R = any, D = any> {
  constructor(config: InstanceRequestConfig<D, R>) {
    this.config = config;
    // 在此动态获取及配置
    if (config?.serverConfig && typeof config?.serverConfig === 'object') {
      const xhr = new XMLHttpRequest();
      // 此处注意路径
      xhr.open('get', `/setting.json`, false);
      xhr.onreadystatechange = () => {
        if (xhr.status === 200 && xhr.readyState === 4) {
          const responseJson = JSON.parse(xhr.responseText);
          Object.assign(GlobalAPI?.config, responseJson);
        }
      };
      xhr.send();
    }
    this.instance = axios.create(config);
    this.registerInterceptors();
    config.generateRequestKey &&
      (this.generateRequestKey = config.generateRequestKey);
  }
}

应用 class:

const request = new Request<Service.RequestResult>({
  timeout: 300000, // 超时五分钟
  baseURL: import.meta.env.VITE_BASE_API,
  withCredentials: false,
  // 取消重复请求
  cancelDuplicateRequest: false,

  // 开发环境不设置仅用默认值,生产环境设置动态设置
  serverConfig: import.meta.env.DEV ? undefined : GlobalAPI.config,
})
✅ 部署成功后,修改 setting.json,此时动态修改一个数据,刷新页面,页面的 GlobalAPI 已经生效。

8总结

以上是适用于多个项目部署,以及部署后有修改配置需求的方案。当然你也可以不自定义插件,将 setting.json 存放于服务器的任何一个目录,动态请求即可。

⚠️ 注意:请求 setting.json 的时机——对于静态的 import 极大概率在你获取到配置项之前就生效了;有好的交换 setting.json 时机,也可以在评论告知下,仅供参考。