如何根据请求路径返回不同 JSON 文件
解读
在国内前端面试中,这道题表面问“用 Grunt 怎么按路径返回不同 JSON”,实质是考察候选人能否把“本地静态文件服务 + 路由映射 + 动态响应”这三件事用 Grunt 插件串起来,并兼顾开发效率与可维护性。
很多候选人直接答“用 grunt-contrib-connect 开静态服务器”,却忽略了“不同路径 → 不同 JSON”这一动态逻辑,导致失分。
面试官想听到的是:
- 你知道 Grunt 只做“任务编排”,真正的“路由逻辑”要靠中间件;
- 你能选对插件(grunt-contrib-connect + 自定义 middleware),并把路由规则写成可配置;
- 你考虑了中国团队的真实场景:后端接口滞后、前端需要并行开发,因此这套方案必须零后端依赖、一键启动、可快速切换 mock 数据。
知识点
- grunt-contrib-connect:官方维护的静态服务器插件,支持 middleware 数组,可像 Express 一样插拔逻辑。
- middleware 执行顺序:Grunt 侧配置
middleware: function(connect, options, middlewares){ … },越晚 push 的中间件越靠近“兜底”,因此自定义路由需插在connect.static之前。 - mime 设置:返回 JSON 时必须写
res.setHeader('Content-Type', 'application/json; charset=utf-8'),否则浏览器会把响应当纯文本,导致中文乱码或被 axios 拒绝解析。 - 路径匹配策略:
- 精确匹配:
/api/user/info→./mock/user/info.json - 带参数匹配:
/api/order/:id→ 用正则或path-to-regexp解析后映射到./mock/order/123.json - 降级策略:找不到对应文件时返回
./mock/empty.json并附带200状态,避免 webpack-dev-proxy 因 404 而触发错误回调。
- 精确匹配:
- 热更新:配合
grunt-contrib-watch监听./mock/**/*.json变化,触发 livereload,让 UI 同学改完 JSON 立即看到效果,无需重启 node 进程。 - 跨域与安全:国内很多项目仍跑在
http://localhost:8000而接口域名是https://xxx.com.cn,因此要在 middleware 里手动写res.setHeader('Access-Control-Allow-Origin', '*'),否则联调时会被 Chrome 拦截。 - 性能与体积:mock 目录需加入
.gitignore,并在grunt copy阶段排除,防止 5M 的 JSON 被打包进 dist,造成 CDN 流量浪费。
答案
- 安装依赖
npm i -D grunt-contrib-connect grunt-contrib-watch
- 在 Gruntfile.js 中声明任务
module.exports = function(grunt) {
grunt.initConfig({
connect: {
mock: {
options: {
port: 9001,
hostname: '0.0.0.0', // 方便同事用手机扫码访问
open: true,
middleware: function(connect, options, middlewares) {
// 关键:把自定义路由插在静态资源之前
middlewares.unshift(function(req, res, next) {
const url = req.url.split('?')[0]; // 去掉 queryString
const fs = require('fs');
const path = require('path');
// 路由映射表,可抽离成单独 config
const map = {
'/api/user/info': './mock/user/info.json',
'/api/order/list': './mock/order/list.json'
};
const file = map[url];
if (file && fs.existsSync(file)) {
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.setHeader('Access-Control-Allow-Origin', '*');
return res.end(fs.readFileSync(file));
}
// 未命中则继续往下走,交给静态资源或 404
return next();
});
return middlewares;
}
}
}
},
watch: {
mock: {
files: ['mock/**/*.json'],
options: { livereload: true }
}
}
});
grunt.loadNpmTasks('grunt-contrib-connect');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.registerTask('serve', ['connect:mock', 'watch']);
};
- 启动
npx grunt serve
访问 http://localhost:9001/api/user/info 即可拿到对应 JSON,路径与文件一一对应,新增接口只需在 map 里加一行即可,无需改代码。
拓展思考
- 动态路由参数:如果接口路径是
/api/project/:projectId/task/:taskId,可在 middleware 里用path-to-regexp解析,把匹配到的参数拼成文件路径./mock/project/123/task/456.json,实现“无限级 RESTFul mock”。 - 延迟与错误模拟:在中间件里加
setTimeout返回 500ms 延迟,或根据req.headers['x-mock-error']返回 502,方便前端做 loading 与异常兜底逻辑。 - 与 BFF 层共存:上线前把 Grunt 的 mock 中间件抽成独立 npm 包,供 Node BFF 层引用,实现“开发用 Grunt,线上用同一份 mock 数据跑单元测试”,保证前后端契约一致。
- 性能压测:利用
grunt-contrib-connect的keepalive模式,把 mock 服务当轻量级挡板,在阿里云 PTS 压测时先打到本地,验证前端并发极限而不消耗后端资源。