阿里飞冰 icejs 如何实现 Mock 前端数据的功能

1,669 阅读2分钟

 这是我参与更文挑战的第 2 天,活动详情查看: 更文挑战

这段时间在一直在使用飞冰开发,开发过程中觉得本地 Mock 数据的特性非常好用。于是就去学习了下具体的实现方法。

Mock 数据的基本使用

飞冰官方关于 Mock 数据的文档介绍的很详细了,我这里给尚未了解的小伙伴简单介绍下。

现有一个目录结构如下的项目

├── mock
│   └── index.js
├── package.json
└── sr
    └── api.js

其中 mock/index.js 代码如下

export default {
  // 同时支持 GET 和 POST
  '/api/users/1': { data: {} },
  '/api/foo/bar': { data: {} },

  // 支持标准 HTTP
  'GET /api/users': { users: [1, 2] },
  'DELETE /api/users': { users: [1, 2] },

  // 支持参数
  'POST /api/users/:id': (req, res) => {
    const { id } = req.params;
    res.send({ id: id });
  },
};

在我们启用 Mock 服务后,请求 mock/index.js 中定义的接口就会得到响应的返回。

Mock 数据的原理分析

当前飞冰的 Mock 功能是基于 Webpack DevServer 开发的,原理是:在 Webpack DevServer 创建的 Express 服务里增加一个中间件,通过这个中间件来拦截和处理请求。

飞冰官方已经把这个 Express 中间件发布为名称 webpack-dev-mock 的 npm package。 webpack-dev-mock 具体拦截请求的代码见下方:

app.use((req, res, next) => {
    const match = mockConfig.length && matchPath(req, mockConfig);
    if (match) {
      debug(`mock matched: [${match.method}] ${match.path}`);
      return match.handler(req, res, next);
    } else {
      return next();
    }
  });

其中 MockConfig 就是上文介绍的基于项目 mock 目录下的文件生成的配置。生成配置的 getConfig 代码片段如下:

function getConfig(rootDir: string, ignore: IIgnoreFolders) {
  // get mock files
  const mockFiles = glob.sync('mock/**/*.[jt]s', {
    cwd: rootDir,
    ignore,
  }).map(file => path.join(rootDir, file));
  // 省略。。。
  const mockConfig = {};
  mockFiles.forEach(mockFile => {
    if (fse.existsSync(mockFile)) {
      // disable require cache
      Object.keys(require.cache).forEach(file => {
        const winPathFile = winPath(file);

        if (winPathFile === mockFile || winPathFile.indexOf(mockDir) > -1) {
          debug(`delete cache ${file}`);
          delete require.cache[file];
        }
      });
      try {
        // eslint-disable-next-line import/no-dynamic-require, global-require
        const mockModule = require(mockFile);
        const mockData = mockModule.default || mockModule || {};
        Object.assign(mockConfig, mockData);
      } catch (err) {
        console.log(err);
      }
    }
  });
  return mockConfig;
}

看到这里,我们应该已经能够了解本地数据 Mock 的基本原理了。但是肯定很多人对 webpack-dev-mock 是如何在 Webpack DevServer 中被使用还是有疑问的。

其实这是通过 devServer.before 来实现的,代码如下:

const webpackDevMock = require('webpack-dev-mock');

module.exports = (config, mock, context) => {
  // dev mock
  const { commandArgs, command } = context;
  if (!commandArgs.disableMock && command === 'start' && mock) {
    const originalDevServeBefore = config.devServer.get('before');
    // replace devServer before function
    config.merge({ devServer: {
      before(app, server) {
        // set cors before all served files
        app.use((req, res, next) => {
          res.set('Access-Control-Allow-Origin', '*');
          next();
        });
        const mockIgnore = Object.prototype.toString.call(mock) === '[object Object]' && mock.exclude;
        // keep mock server ahead of devServer.before
        webpackDevMock(app, mockIgnore || []);
        if (typeof originalDevServeBefore === 'function') {
          originalDevServeBefore(app, server);
        }
      },
    }});
  }
};

我们可以看到,在 devServer 的 before 配置里用到了 webpack-dev-mock 对 Express 的请求做了处理。

实际应用

在了解原理后,我们也可以在自己的开发项目中添加本地 Mock 数据的功能。具体方法就是在 devServer.before 配置中使用 webpack-dev-mock 来处理 Express 服务器的请求。

使用 Vue Cli 的小伙伴甚至可以基于 webpack-dev-mock 来封装一个 Mock 数据的插件

其它资料

  1. 前端mock完美解决方案实战