Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Roadhog 底层原理 #10

Open
3 of 4 tasks
dkvirus opened this issue Nov 23, 2018 · 1 comment
Open
3 of 4 tasks

Roadhog 底层原理 #10

dkvirus opened this issue Nov 23, 2018 · 1 comment
Labels
resource 源码

Comments

@dkvirus
Copy link
Owner

dkvirus commented Nov 23, 2018

  • mock 功能如何实现;
  • 按需加载如何实现;
  • 代理技术如何实现; <= http-proxy-middleware
  • 热更新;<= chokidar
@dkvirus dkvirus added the resource 源码 label Nov 23, 2018
@dkvirus
Copy link
Owner Author

dkvirus commented Nov 26, 2018

mock 功能如何实现?

src/dev.js

$ roadhog dev 启动项目时,如果项目根目录下有 .roadhog.mock.js 文件,就会启动 Mock 功能。

入口文件 bin/roadhog.js => src/roadhog.js => src/scripts/dev.js,开启一个子进程 => src/scripts/realDev.js => src/dev.js 文件。运行 $ roadhog dev 核心处理代码就是这个文件。

src/dev.js

import dev from 'af-webpack/dev';
import { applyMock } from './utils/mock';

export default function runDev(opts = {}) {
  const { cwd = process.cwd(), entry } = opts;

  // .......

  dev({
    webpackConfig,
    proxy: config.proxy || {},
    beforeServer(devServer) {
      try {
        applyMock(devServer);
      } catch (e) {
        console.log(e);
      }
    },
    afterServer(devServer) {
      returnedWatchConfig(devServer);
    },
    openBrowser: true,
  });
}

对外暴露一个接口 runDev,接口内部处理一些参数,最后调用 dev 方法,dev 是 af-webpack 底层库对于 webpack 的封装,其中 af 指的是 ali-financial(阿里金融)。dev 的 beforeServer 参数字面意思是启动服务前的一个钩子函数,可以看到就是这里加载的 Mock 相关功能。

src/utils/mock.js

这个文件由7个方法组成,一个个看。

入口方法

applyMock() 入口方法。但不处理核心逻辑,核心逻辑交给 realApplyMock() 处理,这一点和 scripts 目录下的 dev.js 和 realDev.js 颇为相似,看来是大佬的个人编码习惯~~

export function applyMock(devServer) {
  try {
    realApplyMock(devServer);
    error = null;
  } catch (e) {
    error = e;
    outputError();

    // 加载 Mock 失败,可能原因配置文件或者 mock 目录写的有问题,监听这两个文件的变化,
    // 只要有修改就重启 Mock 功能。chokidar.watch 功能和 fs.watch 类似,监听文件变化,但更稳定
    // ....
    const watcher = chokidar.watch([configFile, mockDir], {});
    watcher.on('change', path => {
        applyMock(devServer);
    });
  }
}

几个工具类方法

在看 realApplyMock() 方法之前,先看几个工具类方法。

  • getConfig():加载 .roadhog.mock.js 配置文件,获取 mock 目录下 mock 数据,返回一个对象;
  • parseKey(): 解析键值,获取请求方法(e.g. GET)和请求地址(e.g. /api/v1/users);
  • outputError():解析过程出现错误,打印这部分内容提示用户;
  • createMockHandler()Roadhog-mock 中描述 mock 即可以写成函数形式,也可以直接写一个对象字面量,如下图,createMockHandler() 方法实现该功能;

image

上图为 Roadhog-Mock 写法,其中键值除了对象自面量就是函数,压根没有字符串的示例。代理中只有键值为字符串,才会调用 createProxy 方法,对于这个方法不觉明历??而且 Mock 为什么会涉及到代理的东东??奇怪。

补充:在 Roadhog PR 里找到答案了,roadhog#795 提出加代理需求,个人感觉一团糟就是个挖坑的过程,没想到 PR 还被通过了。mock 功能很清晰,就是本地造假数据,脱离真实服务器也可以启动项目进行项目演示,代理完全可以在 .webpackrc.js 中添加,功能分离,更优雅。

配置:['/api/v1/(.*)']: 'localhost:8008',键值可以是字符串,Roadhog 官方文档未更新相关特性。

/**
 * 引入 .roadhog.mock.js,该文件导出对象
 * { 
 *   [`GET /users`] (req, res) { res.json({  }) },
 *   [`GET /roles`] (req, res) { res.json({  }) },
 * }
 */
function getConfig() {
    // require.cache 第一次看到,了解一波:https://cnodejs.org/topic/5993c0d54e3c4e5a7021b112
}

/**
 * 解析键值,返回请求方法和请求地址
 * For Example:[`GET /api/v1/users`] (req, res) { res.json({  }) }
 * key 就是键值,上述列子中:key = `GET /api/v1/users`
 * return { method: 'GET', path: '/api/v1/users' }
 */
function parseKey(key) {
}

/**
 * 启动 Mock 功能报错处理方法
 */
export function outputError() {
  console.log('xxxxx');
}

/**
 * 创建 Mock 处理函数
 * https://github.com/sorrycc/roadhog#mock => roadhog 文档有介绍可以返回一个方法,
 * 也可以直接返回一个对象字面量,createMockHandler() 实现这一功能。
 */
function createMockHandler(method, path, value) {
    if (typeof value === 'function') {
      value(...args);
    } else {
      res.json(value);
    }
}

/**
 * 创建代理
 */
function createProxy(method, pathPattern, target) {
  return proxy(filter, { target: realTarget, pathRewrite });
}

核心处理逻辑

realApplyMock() 处理核心逻辑。

function realApplyMock(devServer) {
  // Step1 
  const config = getConfig();       // 获取 mock 造的假数据对象
  const { app } = devServer;

  // Step2:键值是字符串的放到 proxyRules,其它类型的键值放到 mockRules 中
  const proxyRules = [], mockRules = [];
  
  Object.keys(config).forEach(key => {
    const keyParsed = parseKey(key);    // 解析获得请求方法和请求地址
    
    if (typeof config[key] === 'string') {
      proxyRules.push({ path, method: keyParsed.method, target: config[key] });
    } else {
      mockRules.push({ path: keyParsed.path, method: keyParsed.method, target: config[key] });
    }
  });

  // Step3:question?what is app apis.
  proxyRules.forEach(proxy => {
    app.use(proxy.path, createProxy(proxy.method, proxy.path, proxy.target));
  });

  /**
   * body-parser must be placed after http-proxy-middleware
   * https://github.com/chimurai/http-proxy-middleware/blob/master/recipes/modify-post.md
   */
  devServer.use(bodyParser.json({ limit: '5mb', strict: false }));
  devServer.use(bodyParser.urlencoded({
      extended: true,
      limit: '5mb',
    }));

  mockRules.forEach(mock => {
    app[mock.method](
      mock.path,
      createMockHandler(mock.method, mock.path, mock.target),
    );
  });

  // Step4:调整 stack,把 historyApiFallback 放到最后,question??
  let lastIndex = null;
  app._router.stack.forEach((item, index) => {
    // .......
  });
  
  // Step5:监听 mock 目录和 .roadhog.mock.js 文件变化,有变化,自动重启
  const watcher = chokidar.watch([configFile, mockDir], {});
  watcher.on('change', path => {
    applyMock(devServer);
  });
}

Mock 核心代码读下来还算 ok,只是 realApplyMock 方法中 devServer 这东东是 af-webpack 里封装的对象,api 还不熟悉。Step3 中注册 mock 数据,涉及到 devServer 参数,这是 af-webpack 封装的。Step4 historyApiFallback 是 webpack 中的一个配置。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
resource 源码
Projects
None yet
Development

No branches or pull requests

1 participant