如何根据请求路径返回不同 JSON 文件

解读

在国内前端面试中,这道题表面问“用 Grunt 怎么按路径返回不同 JSON”,实质是考察候选人能否把“本地静态文件服务 + 路由映射 + 动态响应”这三件事用 Grunt 插件串起来,并兼顾开发效率可维护性
很多候选人直接答“用 grunt-contrib-connect 开静态服务器”,却忽略了“不同路径 → 不同 JSON”这一动态逻辑,导致失分。
面试官想听到的是:

  1. 知道 Grunt 只做“任务编排”,真正的“路由逻辑”要靠中间件;
  2. 你能选对插件(grunt-contrib-connect + 自定义 middleware),并把路由规则写成可配置
  3. 考虑了中国团队的真实场景:后端接口滞后、前端需要并行开发,因此这套方案必须零后端依赖、一键启动、可快速切换 mock 数据

知识点

  1. grunt-contrib-connect:官方维护的静态服务器插件,支持 middleware 数组,可像 Express 一样插拔逻辑。
  2. middleware 执行顺序:Grunt 侧配置 middleware: function(connect, options, middlewares){ … }越晚 push 的中间件越靠近“兜底”,因此自定义路由需插在 connect.static 之前。
  3. mime 设置:返回 JSON 时必须写 res.setHeader('Content-Type', 'application/json; charset=utf-8'),否则浏览器会把响应当纯文本,导致中文乱码或被 axios 拒绝解析
  4. 路径匹配策略
    • 精确匹配:/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 而触发错误回调
  5. 热更新:配合 grunt-contrib-watch 监听 ./mock/**/*.json 变化,触发 livereload,让 UI 同学改完 JSON 立即看到效果,无需重启 node 进程
  6. 跨域与安全:国内很多项目仍跑在 http://localhost:8000 而接口域名是 https://xxx.com.cn,因此要在 middleware 里手动写 res.setHeader('Access-Control-Allow-Origin', '*')否则联调时会被 Chrome 拦截
  7. 性能与体积:mock 目录需加入 .gitignore,并在 grunt copy 阶段排除,防止 5M 的 JSON 被打包进 dist,造成 CDN 流量浪费。

答案

  1. 安装依赖
npm i -D grunt-contrib-connect grunt-contrib-watch
  1. 在 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']);
};
  1. 启动
npx grunt serve

访问 http://localhost:9001/api/user/info 即可拿到对应 JSON,路径与文件一一对应,新增接口只需在 map 里加一行即可,无需改代码。

拓展思考

  1. 动态路由参数:如果接口路径是 /api/project/:projectId/task/:taskId,可在 middleware 里用 path-to-regexp 解析,把匹配到的参数拼成文件路径 ./mock/project/123/task/456.json,实现“无限级 RESTFul mock”。
  2. 延迟与错误模拟:在中间件里加 setTimeout 返回 500ms 延迟,或根据 req.headers['x-mock-error'] 返回 502,方便前端做 loading 与异常兜底逻辑
  3. 与 BFF 层共存:上线前把 Grunt 的 mock 中间件抽成独立 npm 包,供 Node BFF 层引用,实现“开发用 Grunt,线上用同一份 mock 数据跑单元测试”,保证前后端契约一致
  4. 性能压测:利用 grunt-contrib-connectkeepalive 模式,把 mock 服务当轻量级挡板,在阿里云 PTS 压测时先打到本地,验证前端并发极限而不消耗后端资源