前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【NPM库】- 0x04 - Mock Data

【NPM库】- 0x04 - Mock Data

作者头像
WEBJ2EE
发布2020-08-18 15:57:30
8030
发布2020-08-18 15:57:30
举报
文章被收录于专栏:WebJ2EEWebJ2EEWebJ2EE
  • 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):


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 解析器。


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.

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.

示例:


// 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


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 文件生效。
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-08-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 WebJ2EE 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档