配置对比实战揭秘那些你忽略的关键差异

UI甜雅 前端 阅读 615
赞 9 收藏
二维码
手机扫码查看
反馈

我的写法,亲测靠谱

配置对比这事儿,说白了就是不同环境(开发、测试、生产)下怎么管好变量。我之前在好几个项目里折腾过,从最开始的硬编码到后来用 .env 文件,再到后来搞 JSON 配置合并,踩过的坑能写一本小册子。

配置对比实战揭秘那些你忽略的关键差异

现在我一般这样处理:

根目录放几个配置文件:

  • .env.development
  • .env.production
  • .env.staging

然后通过 webpack.DefinePlugin 或者 Vite 的 envDir + mode 来注入全局变量。但这里有个关键点:我不会再把 API 地址这种动态的东西直接写死在代码里。

我现在更倾向的做法是——运行时加载配置文件,而不是构建时注入。

比如我现在的项目结构长这样:

// public/config.json
{
  "apiBaseUrl": "https://jztheme.com/api",
  "enableMock": false,
  "timeout": 10000
}

然后入口 JS 里先 fetch 这个 config.json,再启动整个应用:

// main.js
async function initApp() {
  const config = await fetch('/config.json').then(res => res.json());
  
  // 把配置挂到全局或传给 Vue/React 实例
  window.APP_CONFIG = config;

  // 启动你的框架
  new Vue({
    render: h => h(App)
  }).$mount('#app');
}
initApp();

好处太明显了:

  • 同一份 build 包可以扔到不同环境跑,不用每次构建都指定环境
  • 运维同学改个 Nginx 配置就能切换后端地址,不用求着前端重新打包
  • 紧急切接口调试?改个 JSON 就行,5 秒搞定

以前我们有个项目坚持用构建时注入,结果上线前临时要切到另一个后端做压测,得重新 build + deploy,等了一个小时……那次之后我就彻底转向运行时加载了。

这几种错误写法,别再踩坑了

见过太多离谱的操作,现在想起来还头皮发麻。

错误写法一:直接在代码里 if 判断域名

let apiBaseUrl;
if (location.hostname === 'localhost') {
  apiBaseUrl = 'http://localhost:3000';
} else if (location.hostname.includes('staging')) {
  apiBaseUrl = 'https://staging-api.jztheme.com';
} else {
  apiBaseUrl = 'https://jztheme.com/api';
}

看起来好像没问题?错!一旦你用了 CDN 或者多级代理,hostname 根本不一定是你想象的那个。而且这种逻辑散落在各处,后期维护简直是地狱模式。我接手过一个项目满地都是这种判断,改一个地址得搜半个工程。

错误写法二:全靠 CI/CD 替换字符串

有人图省事,在 CI 脚本里用 sed 直接替换 js 文件里的 placeholder,比如:

sed -i 's|__API_URL__|https://jztheme.com/api|g' dist/main.js

初看挺聪明,但问题是——万一这个字符串在别的地方也出现了呢?比如某个用户输入的内容里刚好有 __API_URL__,那就被误杀了。而且压缩后的代码可能格式变了,正则匹配失效。我们线上出过一次事故就是因为这个。

错误写法三:把所有配置写进 bundle

有些人为了“方便”,直接把 config 写成一个 JS 模块 export 出去,然后 import 使用。问题是这个文件一旦被打包进 chunk,你就没法外部修改了。哪怕只是换个地址都得重新发布版本。这种做法等于把 deployment 和 configuration 绑死了,违背了十二要素应用原则(虽然我不总遵守,但这条真得听)。

实际项目中的坑

第一个大坑是缓存。你以为 /config.json 每次都会重新拉?错了。如果没配好 cache-control,浏览器可能拿的是旧版配置,导致前端连错后端还不知道为啥。

解决方案很简单:加时间戳或者 hash 查询参数。

fetch(/config.json?t=${Date.now()})

或者更好的方式:让服务器返回 no-cache。

第二个坑是类型校验。JSON 是弱类型的,谁都能改,改错了应用就崩。所以我后来加上了一层校验:

function validateConfig(config) {
  const requiredFields = ['apiBaseUrl', 'timeout'];
  for (const field of requiredFields) {
    if (!config[field]) {
      console.error(Missing required config field: ${field});
      return false;
    }
  }
  return true;
}

// 使用
fetch('/config.json')
  .then(res => res.json())
  .then(config => {
    if (!validateConfig(config)) {
      throw new Error('Invalid config');
    }
    window.APP_CONFIG = config;
  });

第三个坑是本地开发体验。你总不能让每个新人 clone 下来还得自己写 config.json 吧?所以我在项目里保留 .env.development,用于本地开发时生成默认配置。

// scripts/generate-config.js
require('dotenv').config();

const fs = require('fs');

const config = {
  apiBaseUrl: process.env.API_BASE_URL || 'http://localhost:3000',
  enableMock: process.env.ENABLE_MOCK === 'true',
  timeout: parseInt(process.env.TIMEOUT) || 10000
};

fs.writeFileSync('./public/config.json', JSON.stringify(config, null, 2));

然后 package.json 加个脚本:

"scripts": {
  "dev:setup": "node scripts/generate-config.js && vite"
}

新人只需要 npm run dev:setup 就能跑起来,不影响正式部署流程。

核心技巧:支持远程配置中心(高级玩法)

到了中大型项目,你会发现连改 JSON 文件都不够用了。你需要支持从配置中心动态拉取。

这时候可以升级一下逻辑:

async function loadConfig() {
  // 先尝试从 URL 参数获取 configUrl
  const urlParams = new URLSearchParams(window.location.search);
  const remoteConfigUrl = urlParams.get('config');

  if (remoteConfigUrl) {
    return fetch(decodeURIComponent(remoteConfigUrl)).then(res => res.json());
  }

  // 否则走默认
  return fetch('/config.json?t=' + Date.now()).then(res => res.json());
}

这样一来,你可以通过访问 ?config=https://jztheme.com/custom-config.json 来动态指定配置源。特别适合客户私有化部署时自定义参数。

当然安全要考虑,建议加上域名白名单或者签名校验,别让人随便传个 URL 就执行了。

总结一下我的习惯

到现在为止,我的标准操作是:

  • 配置运行时加载,不塞进构建产物
  • 本地用 dotenv 生成默认 config.json
  • 线上由运维控制 config.json 内容
  • 加上基础字段校验和缓存规避机制
  • 必要时支持远程配置 URL 注入

这套方法不是最炫的,也不是理论最优解,但它稳定、透明、容易交接。最重要的是——出了问题不用翻三天代码才能改回来。

有一次半夜报警说接口超时,查了半天发现是某台预发机器上的 config.json 被误删了,恢复备份文件重启 Nginx 解决,全程不到两分钟。要是当年那种构建时注入的方式,至少得重新打包上传,半小时都算快的。

以上是我踩坑后的总结,希望对你有帮助。有更好的方案欢迎评论区交流,比如你们是不是已经在用 Apollo Config 或者 etcd 管理前端配置了?我也挺想看看别的路子。这个技巧的拓展用法还有很多,后续会继续分享这类实战博客。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论