如何在 connect 中注入 Mock Service Worker 拦截 API

解读

这道题表面问的是“把 MSW 塞进 connect”,实质考察三点:

  1. 你是否理解 connect 只是中间件栈,本身不生成 Service Worker 文件,也不负责运行时注入;
  2. 你是否知道 MSW 的浏览器端需要一段注册脚本+mockServiceWorker.js 文件,而这两个资源必须能被浏览器 200 拿到;
  3. 你是否能把 Grunt 的“静态托管”能力与 MSW 的“开发阶段拦截”能力拼成一条闭环,让“前端页面一刷新就自动注册、接口一调用就被拦截”,且 不改一行生产代码
    国内面试场景里,这道题常作为“本地联调没后端”的解决方案出现,答不出“静态文件怎么透传”会直接被判定为“只会 npm run,不会工程化”。

知识点

  • connect:Node 生态最轻量的中间件内核,grunt-contrib-connect 基于它暴露静态托管、端口监听、livereload 等功能。
  • MSW 双模式
    – browser 模式靠 Service Worker 拦截 fetch;
    – node 模式靠拦截 http/https 模块,用于 Jest/Storybook。
  • Service Worker 注册限制
    – 只能在 HTTPS 或 localhost 生效;
    – 文件必须与页面同源且 通过网络请求 200 返回,不能是 dataURL 或 inline 脚本。
  • grunt-contrib-connect 关键配置项
    – base:数组,决定静态根目录;
    – middleware:函数,可插入任意 connect 中间件;
    – livereload:布尔,决定是否注入 lr 脚本。
  • MSW 初始化步骤
    1. npx msw init <PUBLIC_DIR> --save;
    2. 在页面入口调用 worker.start();
    3. 保证 mockServiceWorker.js 落在 PUBLIC_DIR 下并能 200 访问。

答案

  1. 安装依赖
    npm i -D msw grunt-contrib-connect

  2. 把 MSW 的 worker 文件托管到 connect 能访问的位置
    npx msw init static --save
    这会在项目根生成 static/mockServiceWorker.js

  3. 在 Gruntfile 里配置 connect,把 static 目录挂到根路径,并插入一段“兜底中间件”保证 *.js 返回正确 MIME:

    grunt.initConfig({
      connect: {
        options: {
          port: 8000,
          hostname: 'localhost',
          base: ['.', './static'],   // 关键:static 目录被合并到根
          middleware: function(connect, options, middlewares) {
            // 把 MSW 的 worker 文件优先级提到最前,防止被其他规则覆盖
            middlewares.unshift(function(req, res, next) {
              if (req.url === '/mockServiceWorker.js') {
                res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
                return require('fs')
                  .createReadStream(require('path').join(__dirname, 'static/mockServiceWorker.js'))
                  .pipe(res);
              }
              next();
            });
            return middlewares;
          }
        }
      }
    });
    
  4. 在页面入口(例如 src/main.js)启动 MSW,仅开发环境生效:

    if (process.env.NODE_ENV === 'development') {
      const { worker } = require('./mocks/browser');
      worker.start({ onUnhandledRequest: 'bypass' });
    }
    
  5. 运行 Grunt 任务
    grunt connect:keepalive
    浏览器访问 http://localhost:8000,F12 → Application → Service Workers 可见 mockServiceWorker.js 已注册,接口调用被成功拦截。

要点回顾

  • static 目录必须出现在 base 数组里,否则 404;
  • middleware 顺序决定优先级,把 worker 文件中间件放到最前;
  • localhost 协议无证书限制,正是国内开发机最常见场景;
  • mockServiceWorker.js 返回的 MIME 必须是 application/javascript,部分国产浏览器(360 兼容模式)对 text/plain 会拒绝注册。

拓展思考

  1. 如果团队把 Grunt 换成 Vite/Webpack5,思路完全一致:
    把 mockServiceWorker.js 丢进 public 或 webpack static dir,保证构建 devServer 能 200 返回即可;MSW 与构建工具解耦,真正的门槛是“静态文件可达”而非“谁托管”
  2. 在 HTTPS 内网环境(如 https://dev.company.com)使用 MSW,需要:
    – 给 devServer 配置合法证书(公司内网 CA 签发);
    – 或者让浏览器信任自签证书,否则 Service Worker 注册会被 DOMException: Failed to register a ServiceWorker: An SSL certificate error occurred 打断。
  3. 若想在 Grunt 驱动的单元测试(karma/jasmine)里用 MSW,必须切到 node 模式,因为 karma 启动的是无头浏览器,Service Worker 无法拦截本地 file:// 请求;此时用 server.listen() 即可,无需 connect 介入。
  4. 大型项目往往把 mock 数据按模块拆分,可以写一份 grunt-contrib-watch 任务:
    当 mocks/*.js 变动时,热替换 MSW 的 handler 列表,无需重启 connect,实现“ mock 数据热更新”,这是国内中台项目面试的加分亮点。