【NPM库】- 0x04 - Mock Data

2020-08-18 15:57:30 浏览数 (1)

  • 1. Mock Data 基础

1.1. 是什么?

Mock 数据是前端开发过程中必不可少的一环,是分离前后端开发的关键链路。通过预先跟服务器端约定好的接口,模拟请求数据甚至逻辑,能够让前端开发更加独立自主,不会被服务端的开发所阻塞。

1.2. 常见 Mock 方式?

2. Mock Data 实现

2.1. 技术要点?

  • 如何利用 webpack-dev-server 的 before 特性,进行请求拦截;
    • 如何利用 body-parser 解析请求体(request body)
    • 如何利用 pathToRegexp 匹配 URL 路径
  • 如何利用 chokidar 实现 Mock Data 热更新。
    • 如何利用 clear-module 清理 Mock Data 模块缓存。
  • 如何利用 Mock.js 生成随机数据。

2.2. 效果预览?

2.3. Mock 文件编码方式

Mock 文件的编码方式,参考自 Umijs:

  • 支持静态值
  • 支持动态函数

2.4. devServer.before

  • webpack-dev-server 底层基于 express。
    • 核心是其 middleware 机制。
  • devServer.before,会在其内部所有 middleware 执行之前触发。
    • 这给了我们拦截、分析请求,并返回自定义 Mock Data 的机会。

2.5. body-parser

  • Node.js body parsing middleware.
  • Parse incoming request bodies in a middleware before your handlers, available under the req.body property.
  • body-parser provides the following parsers:
    • JSON body parser
    • Raw body parser
    • Text body parser
    • URL-encoded form body parser

a. 搭建一个 Demo(此时没使用 body-parser):

代码语言:javascript复制

const express = require('express');
const app = express();

app.post('/login', (req, res) => {
    console.log('********************')
    console.log(req.body);
    res.send("Hello WEBJ2EE.");
});

app.listen(3000, () => {
    console.log('http://127.0.0.1:3000')
})

b. 使用 Postman 发送 POST 请求:

c. 不使用 body-parser 的情况下,直接获取 req.body,结果将是 undefined。

d. 配置 JSON 解析器。

代码语言:javascript复制

const express = require('express');
const bodyParser = require('body-parser');
const app = express();

app.post('/login', bodyParser.json(), (req, res) => {
    console.log('********************')
    console.log(req.body);
    res.send("Hello WEBJ2EE.");
});

app.listen(3000, () => {
    console.log('http://127.0.0.1:3000')
});

e. 使用 Postman 再次发送 JSON 数据,将得到执行结果。

备注:如果在模拟器上以非JSON格式发送,则会获得一个空的JSON对象

2.6. chokidar

A neat wrapper around node.js fs.watch / fs.watchFile / fsevents.

代码语言:javascript复制
a. 搭建一个 Demo:

const path = require("path");
const chokidar = require("chokidar");

chokidar.watch(path.resolve(__dirname, "ftp"))
    .on("all", (event, path)=>{
       console.log(event, path);
    });

b. 看看其监听能力:

  • 增加文件时,显示的事件名是add,并且显示对应的文件名;
  • 修改文件内容时,显示的事件名是change,并且显示对应的文件名;
  • 增加目录时,显示的事件名是addDir,并且显示对应的目录名;
  • 删除文件时,显示的事件名是unlink,并且显示对应的文件名;
  • 删除目录时,显示的事件名是unlinkDir,并且显示对应的目录名;

2.7. clear-module

Useful for testing purposes when you need to freshly import a module.

示例:

代码语言:javascript复制

// foo.js
let i = 0;
module.exports = () =>   i;

const clearModule = require('clear-module');
 
require('./foo')();
//=> 1
 
require('./foo')();
//=> 2
 
clearModule('./foo');
 
require('./foo')();
//=> 1

2.8. 综合示例:Mocker.ts

代码语言:javascript复制

import URL from 'url';
import PATH from 'path';
import { Request, Response, NextFunction, Application } from 'express';
import bodyParser from 'body-parser';
import * as toRegexp from 'path-to-regexp';
import { TokensToRegexpOptions, ParseOptions, Key } from 'path-to-regexp';
import clearModule from 'clear-module';
import chokidar from 'chokidar';
import color from 'colors-cli/safe';

export type MockerResultFunction = ((req: Request, res: Response, next?: NextFunction) => void);
export type MockerResult = string | number| Array<any> | Record<string, any> | MockerResultFunction;
export type MockerProxyRoute = Record<string, MockerResult> & {}
export type WatchFiles = Array<string>;

const pathToRegexp = toRegexp.pathToRegexp;
let mocker: MockerProxyRoute = {};

function pathMatch(options: TokensToRegexpOptions & ParseOptions) {
  options = options || {};
  return function (path: string) {
    var keys: (Key & TokensToRegexpOptions & ParseOptions & { repeat: boolean })[] = [];
    var re = pathToRegexp(path, keys, options);
    return function (pathname: string, params?: any) {
      var m = re.exec(pathname);
      if (!m) return false;
      params = params || {};
      var key, param;
      for (var i = 0; i < keys.length; i  ) {
        key = keys[i];
        param = m[i   1];
        if (!param) continue;
        params[key.name] = decodeURIComponent(param);
        if (key.repeat) params[key.name] = params[key.name].split(key.delimiter)
      }
      return params;
    }
  }
}

// Merge multiple Mockers
function getConfig(watchFiles: WatchFiles) {
  return watchFiles.reduce((mocker, file) => {
    const mockerItem = require(file);
    return Object.assign(mocker, mockerItem.default ? mockerItem.default : mockerItem);
  }, {})
}

function watchMockFiles(watchFiles: WatchFiles){
  const watcher = chokidar.watch(watchFiles.map(watchFile => PATH.dirname(require.resolve(watchFile))));
  watcher.on('all', (event, path) => {
    if (event === 'change' || event === 'add') {
      try {
        watchFiles.forEach(file => cleanCache(file));
        mocker = getConfig(watchFiles);
        console.log(`${color.green_b.black(' Done: ')} Hot Mocker ${color.green(path.replace(process.cwd(), ''))} file replacement success!`);
      } catch (ex) {
        console.error(`${color.red_b.black(' Failed: ')} Hot Mocker ${color.red(path.replace(process.cwd(), ''))} file replacement failed!!`);
      }
    }
  });
}

function cleanCache(modulePath: string) {
  try {
    modulePath = require.resolve(modulePath);
  } catch (e) {}
  var module = require.cache[modulePath];
  if (!module) return;

  clearModule(modulePath);
}

export default function (app: Application, watchFile: string | string[]) {
  const watchFiles = Array.isArray(watchFile) ? watchFile : [watchFile];
  if (watchFiles.some(file => !file)) {
    throw new Error('Mocker file does not exist!.');
  }

  mocker = getConfig(watchFiles);
  if (!mocker) {
    return (req: Request, res: Response, next: NextFunction) => {
      next();
    }
  }

  watchMockFiles(watchFiles);

  app.all('/*', (req: Request, res: Response, next: NextFunction) => {
    const mockerKey: string = Object.keys(mocker).find((kname) => {
      return !!pathToRegexp(kname.replace((new RegExp('^'   req.method   ' ')), '')).exec(req.path);
    });

    if (mocker[mockerKey]) {
      let bodyParserMethd = bodyParser.json(); // 默认使用json解析
      bodyParserMethd(req, res, function () {
        const result = mocker[mockerKey];
        if (typeof result === 'function') {
          const rgxStr = ~mockerKey.indexOf(' ') ? ' ' : '';
          req.params = pathMatch({ sensitive: false, strict: false, end: false })(mockerKey.split(new RegExp(rgxStr))[1])(URL.parse(req.url).pathname);
          result(req, res, next);
        } else {
          res.json(result);
        }
      });
    } else {
      next();
    }
  });

  return (req: Request, res: Response, next: NextFunction) => {
    next();
  }
}

核心逻辑截取自:mocker-api

  • 利用 chokidar 监听 mock 文件变更,实现热更新特性。
    • 监听到文件更新后,使用 clear-module 清理模块缓存,在下次请求拦截时,才能使更新后的 mock 文件生效。

0 人点赞