top-level await 动态加载防腐配置需满足三点:esm 模块环境、按环境动态导入配置模块、敏感参数闭包封装与错误兜底。必须使用 .mjs 或 type: "module",通过 import() 加载环境配置,用 getter 和 freeze 保护参数,并 try/catch 降级处理加载失败。

在顶层作用域中使用 Top-level await 动态加载不同环境的敏感防腐配置,关键在于三点:模块化隔离、环境感知加载、安全参数封装。它不是简单地“写个 await”,而是要让配置流可维护、不可裸露、按需生效。
必须用 ESM 模块上下文
Top-level await 只在 ES 模块中合法有效。普通 <script></script> 标签或 CommonJS 环境(如 .cjs)不支持,强行使用会报 SyntaxError: await is only valid in async functions and the top level bodies of modules。
- 浏览器端:HTML 中声明
<script type="module" src="app.mjs"></script> - Node.js 端:文件扩展名用
.mjs,或package.json中设"type": "module" - 确保构建工具(如 Vite、Webpack 5+)已启用 ESM 输出模式
按环境动态导入配置模块
避免在主模块里硬编码 fetch 或拼接 URL,而是把环境判断和加载逻辑下沉到独立配置模块中,实现关注点分离。
- 创建
config/index.mjs,导出一个异步默认对象:
const configModule = await import(`./${env}.mjs`);
export default configModule.default;
- 再为各环境建
config/production.mjs、config/staging.mjs,其中只含脱敏后的防腐参数(如加密密钥前缀、限流策略、白名单域名列表),不含明文密钥或 token - 主入口
main.mjs直接导入:import config from './config/index.mjs';—— 此时整个模块会等待配置加载完成后再执行后续逻辑
防腐参数需做运行时封装与访问控制
敏感参数不能直接 export const API_KEY = 'xxx',否则易被静态扫描或调试器读取。应通过闭包 + getter 封装,并加入环境校验。
- 在
config/production.mjs中:
apiBase: 'https://api.prod.example.com',
rateLimitWindowMs: 60_000,
cipherPrefix: 'prod_v2_'
};
// 防止开发环境误用生产密钥
if (process?.env?.NODE_ENV !== 'production') {
throw new Error('Production config loaded in non-production env');
}
export default Object.freeze({
get apiBase() { return raw.apiBase; },
get rateLimitWindowMs() { return raw.rateLimitWindowMs; },
// cipherPrefix 不暴露 getter,仅供内部加密函数调用
});
- 主逻辑中通过
config.apiBase安全读取,无法遍历或篡改原始字段 - 配合构建时 dead code elimination,未使用的 getter 在生产包中可被自动移除
兜底与错误隔离机制
网络请求失败或配置缺失不能导致整个应用白屏。顶层 await 的错误会阻塞模块初始化,因此必须主动捕获并降级。
- 在
config/index.mjs中包装 try/catch:
try {
const mod = await import(`./${env}.mjs`);
config = mod.default;
} catch (e) {
console.warn('[Config] Fallback to defaults due to load failure:', e);
config = { apiBase: '/api', rateLimitWindowMs: 30_000 };
}
export default config;
- 若需更严格风控,可在 catch 中触发 Sentry 上报或加载预埋的加密 fallback 配置(如从
localStorage解密读取)











