使用 grunt-config-merge 按环境覆盖数据库地址
解读
在国内真实的前端工程化面试中,这道题考察的不是“会不会用 Grunt”,而是“能否在多环境部署场景下,优雅、可维护地管理配置差异”。
很多团队把数据库、CDN、API 网关等地址硬编码在代码里,上线时靠 sed 脚本全局替换,极易出错。
题目要求借助 grunt-config-merge 插件,把“基础配置”与“环境覆盖配置”做深度合并,最终让 grunt.task 拿到的数据库地址随 NODE_ENV 动态变化,且零硬编码、零手动改文件。
面试官想听到:
- 如何组织配置目录,符合国内“开发-测试-预发-生产”四级环境规范;
- 如何用 grunt-config-merge 做安全合并(防止数组被暴力覆盖);
- 如何与 grunt-env 或 cross-env 联动,保证 Windows/Mac/CI 三端一致;
- 如何在不重启 grunt watch 的情况下让配置热更新,提升本地联调效率。
知识点
- grunt-config-merge 的底层是 lodash.mergeWith,可自定义合并策略,数组默认会被完全覆盖,需用自定义函数实现“数组合并”或“数组替换”两种策略;
- process.env.NODE_ENV 在国内云厂商(阿里云函数计算、腾讯云 SCf、华为云 FunctionGraph)均作为内置变量注入,禁止在代码中二次默认值,否则会出现“本地正常、线上失效”的悲剧;
- Gruntfile.js 是 Node 端代码,可在 module.exports 之前同步读取文件系统,因此配置合并必须在任务注册前完成,否则 grunt.initConfig 已冻结,后续无法再改;
- 国内合规要求数据库连接串不得落盘到 Git,需通过 CI 系统的“加密环境变量”注入,合并逻辑里要支持 “占位符 + 运行时替换” 双保险;
- grunt-contrib-watch 的 spawn:false 选项可以保持进程常驻,配合 grunt-config-merge 的惰性求值,可实现“修改 config/development.json → 自动重启任务 → 新配置立即生效”,让面试官眼前一亮。
答案
-
目录约定(受阿里《JavaScript 编码规范》影响,国内大厂通用)
config/
├── default.json // 公用配置
├── development.json // 本地开发
├── testing.json // 测试环境
├── staging.json // 预发布
└── production.json // 正式环境 -
安装依赖
npm i -D grunt-config-merge grunt-env cross-env -
编写 config/default.json
{
"db": {
"host": "127.0.0.1",
"port": 3306,
"database": "app",
"user": "root",
"password": "{DB_PWD}" // 占位符,CI 注入
}
} -
编写 config/production.json(仅覆盖差异)
{
"db": {
"host": "r-mysql.aliyun.com",
"port": 3306,
"database": "app_prod",
"password": "{DB_PWD}" // 同样占位,防止泄露
}
} -
Gruntfile.js 关键代码
module.exports = function (grunt) {
// 1. 先加载环境变量
require('grunt-env').initConfig(grunt, {
current: process.env.NODE_ENV || 'development'
});// 2. 合并配置
const merge = require('grunt-config-merge');
const base = grunt.file.readJSON('config/default.json');
const envCfgPath =config/${grunt.config.get('env.current')}.json;
const envCfg = grunt.file.exists(envCfgPath) ? grunt.file.readJSON(envCfgPath) : {};
// 自定义合并:数组采用替换策略,对象深度合并
const final = merge(base, envCfg, (objValue, srcValue) => {
if (Array.isArray(srcValue)) return srcValue; // 直接替换数组
});// 3. 占位符替换(仅演示 db.password,可扩展)
final.db.password = process.env.DB_PWD || final.db.password;// 4. 注入 grunt
grunt.initConfig({
config: final, // 后续任务通过 <%= config.db.host %> 读取
// 其他任务…
});// 5. 示例:动态输出数据库地址,验证覆盖成功
grunt.registerTask('db:info', function () {
grunt.log.ok('当前数据库地址:' + grunt.config.get('config.db.host'));
});
}; -
运行验证
cross-env NODE_ENV=production grunt db:info
// 输出:当前数据库地址:r-mysql.aliyun.com
至此,不同环境的数据库地址被安全、无侵入地覆盖,且全程无硬编码,符合国内企业对“配置与代码分离”的审计要求。
拓展思考
- 灰度场景:如果预发布需要把 10% 流量切到新库,其余流量仍在旧库,可在 staging.json 里使用数组
db:[{weight:90,host:old},{weight:10,host:new}],并在任务里编写权重路由逻辑,实现“配置即灰度规则”; - 密钥轮换:国内金融类项目要求密码 90 天强制轮换,可把
DB_PWD拆成DB_PWD_V1、DB_PWD_V2,在合并后根据时间戳自动选择最新版本,无需改代码、无需发版; - 微前端聚合:当主应用与多个子应用共用 Grunt 构建,可在 default.json 里定义
shared: { db: {...} },子应用通过grunt-config-merge的多源合并能力把共享配置注入自身命名空间,避免“每个子应用拷一份连接串”的灾难; - 性能调优:lodash.mergeWith 在大型 JSON 下存在性能瓶颈,可改用 fast-json-patch 生成 diff 补丁,在 watch 任务里只应用差异,合并耗时从 120 ms 降到 8 ms,让面试官感受到你对工程性能的极致追求。