天天看點

Foxman, 基于微核架構的 Mock 解決方案

本文來自 網易雲社群 。

Foxman ⇗ 是一個使用 Node.js 開發的指令行工具,定位是一個可擴充的 Mock Server,幫助前端開發者輕松、獨立、高效地進行前端開發和完成後續的聯調工作。

他不是一款靜态檔案響應工具, 假如你隻需要一款輕量的 Node.js 開發伺服器,推薦你使用 puer ⇗ 或 webpack-dev-server ⇗。

github 位址: https://github.com/kaola-fed/foxman

Foxman, 基于微核架構的 Mock 解決方案

背景

作為前端開發的我們,在實際的開發場景中會遇到以下問題:

  1. 環境:進行本地開發,需要起後端環境(Tomcat),對于新人來說,需要大量時間熟悉;熟練的人遇到某些确實存在的問題,也要花時間去解決,耗費大量前端開發的時間;
  2. 流程:前端開發先開發 html,再将 html 改寫成指定的模闆文法,影響開發效率;
  3. 接口:
    • 接口定義使用聊天工具發送,前端開發時不好了解接口字段,影響開發效率;
    • 接口變更需要重新編寫文檔,并重新發送,影響開發效率;
    • 文檔散落,影響接口維護;
  4. 聯調:
    • 聯調過程很複雜,尤其是沒有做熱部署的Java工程,改視圖還需要重新開機Tomcat,影響前端聯調效率;
  5. 效益:
    • 前後端對接的方式,期望純粹的 JSON 交換。不過現實情況,是依賴後端的模闆引擎,導緻前端了解接口存在一定的障礙;

以上問題的存在,才産生了 Foxman 這個項目。

影響

從 考拉前端 使用情況來看,在接入 Foxman 後開發效率得到一定提升,主要展現在以下方面:

  1. 前端開發者不再需要在本地起 Tomcat 服務,新人也無需熟悉本地啟動環境;而啟動一個 Foxman 所需要的時間,在 5s 以内;
  2. 前端開發者更加有意識地去與後端定義接口,因為接口定義會落實到具體的 mock 資料上;
  3. Mock 功能,使得前端開發者在開發階段幾乎可以是自治、無打擾的情況(産品不改需求的前提下);
  4. Foxman 提供 Living Reload 的功能 - 頁面開發過程中,修改 html 和 js 會通知浏覽器 reload 頁面;修改 css 會通知浏覽器隻 reload 樣式,提升了開發體驗,節省了人肉重新整理耗費的時間。
  5. Foxman 提供 Processors 的功能 - 即時編譯的設定,更好地相容無 webpack 建構的場景;
  6. 聯調階段,由于 Foxman 提供 了 Proxy 功能,使前端開發可以再本地調試模闆和 javascript,避免了修改送出,再重新部署伺服器的時間耗費,大大提升聯調效率與體驗;

核心概念

容器 - Foxman 核心提供了一個挂載插件的容器,并且提供方法供插件提供或調用的服務。實作上,使用了IoC(依賴查找)、插件化等架構設計的思想。

插件 - Foxman 所有具體的功能都使用插件實作。插件的作用是實作本身需求,并提供服務供其他插件使用。

服務 - 服務是架設于 容器 與 插件 之上的概念,容器 提供方法供 插件 注冊或調用服務。

在這樣的體系下,你可以輕松地編寫 Foxman 的插件,并調用已有插件的服務。是以,完全不需要擔心,Foxman 會不适合你的項目,因為你完全可以根據自己的需求來定制你所需要的Foxman。

安裝

NPM

$ npm i -g foxman@lastest # 無梯子使用者,推薦使用 cnpm
      

  

⚠️ Foxman 采用 es6 文法的大部分特性編寫,要求使用 Node.js 版本不低于 v6.4.0

編寫配置檔案

module.exports = {
    port: 9000,
    secure: false,
    statics: [
        './src/'
    ],
    routes: [
        {
            method: 'GET',
            url: '/ajax/index.html',
            sync: false,
            filePath: 'foo.bar'
        }
    ],
    engineConfig: {},
    viewRoot: './views/',
    extension: 'ftl',
    syncData: './syncData/',
    asyncData: './ajax/',
    plugins: [],
    processors: [
        { match: '/src/css/*.css', pipeline: [], locate( reqUrl ) {} }
    ],
    proxy:  [
        { name: 'pre', host: 'm.kaola.com', ip: '1.1.1.1', protocol: 'http' }
    ]
}
      

這是一份基礎的 Foxman 的配置檔案,可以發現大部分字段都給 Server 用的,比如:

  • port - Server 監聽的端口
  • secure - 是否啟用 https
  • statics - 靜态資源配置
  • routes - 路由清單
  • engineConfig - 模闆引擎配置項
  • viewRoot - 模闆根目錄
  • extension - 模闆擴充名
  • syncData - 同步資料根目錄
  • asyncData - 異步資料根目錄

以及一些特殊的字段,後面我們會重點介紹:

  • proxy - 聯調階段,同步資料與異步資料的轉發至後端主機或測試伺服器
  • processors - Runtime Compiler 的配置
  • plugins - 插件配置

更詳細的 Foxman 配置,點選此處 ⇗

啟動

在編寫完 foxman.config 的目錄下,執行指令即可啟動 Foxman :

$ foxman      

設計理念

插件體系

Foxman 的外置插件可以在配置檔案中靈活載入:

...
plugins: [
        new RouteDisplay(),
        new MockControl({}),
        new Automount({}),
        new WebpackDevServer({}),
]     
...      

而所有的内置功能,其實也是依托于插件展開。每個 Foxman 插件,需要實作一些方法,用于裝載入 Foxman 容器時,做一些登記工作:

class Plugin {
    constructor() {
        // 初始化自身需要的屬性
    }

    name() { // 定義插件的名字,如果沒有該字段,會使用 constructor.name
        return 'name';
    }

    service() { // 提供給其他插件的服務
        return {
            foo() {
                return 'bar';
            }
        }
    }

    init({getter, service}) {
        const use = service('service.use'); 
    }
}      

LivereloadPlugin ⇗

容器與依賴查找

容器的設定,離不開 IoC(控制反轉)的概念。

實作 IoC,慣用的一種方案是依賴注入 (Dependency Injection) ,用于運作時被動地接收依賴的對象,早期的 Foxman 是根據 DI 的方式實作插件化的;

另一種方案是依賴查找 (Dependency Lookup) - 與 DI 相比更加主動,主動得調用架構提供的方法來擷取依賴,擷取時提供相關的配置檔案路徑 或 keypath 等資訊。

Foxman 核心提供了 use 和 start 兩個方法:

  • use - 注冊 Plugin 及 service
  • start - 執行 Plugin 的 init 方法,傳入 service/getter 方法,供其依賴查找
// core.js
class Core {
    use() {
        // 1. 注冊 Plugin 進入容器;
        // 2. 在容器中登記 Plugin 提供的 service 
    } 

    start() {
        // 1. 循環 Plugin 執行 init 方法, 注入 getters, service 等方法,用于擷取其他插件的配置或是服務
        // 2. 如果插件執行了 this.pending 方法,則等待異步操作完成。
    } 
}
// app.js
const core = new Core();

core.use(new Plugin({})); 
// 1. 執行 Plugin constructor
// 2. 注冊 Plugin 進入容器
// 3. 在容器中登記 Plugin 提供的 service 

core.start(); 
// 執行 Plugin 的 init 方法,會在參數中注入的 getters 和 service 方法,用于插件依賴查找,      

具體的實作細節,感興趣的同學可以 檢視源碼 ⇗

功能子產品

Server子產品

基于 Node.js Server 架構 [email protected] 建構,Server 的職責便是渲染模闆、響應異步資料,以及在頁面插入一些特定的腳本。

整個 Server 的啟動分為三個階段:

  1. 初始化 - 設定配置,設定路由,以及初始化 Koa 對象;
  2. 裝載中間件 - 初始化中間件隊列;
  3. 啟動服務 - 啟動 Server,并建立 websocket 伺服器,用于與浏覽器的通信。

在 Server 啟動後,請求進入 Server 時,會經曆中間件的處理,這個過程又能分為 3 個階段:

  1. 請求分析,及确定響應方式,在請求的 context 上,生成 dispatcher 對象,用于在步驟 3 中确定以何種方式進行響應(同步 or 異步,模闆路徑 or mock 資料路徑);
  2. 由插件裝載的中間件對請求進行處理(取決于具體使用的插件),這個階段可以對 dispatcher 對象進行修改,以完成插件所期望的渲染方式;
  3. 請求響應,根據請求的類型,分為以下幾種方式
    • 同步請求 - 交給 Foxman-Engine 進行渲染,(注入一些 script 腳本,并且在頁面上追加同步資料,使得浏覽器 console 中輸入 window.FOXMAN_SYNC_DATA 即可獲得 )
    • 異步請求 - 預設 json 方式展示,如需要 jsonp 響應,或是要自由控制響應方式,請使用插件 @foxman/plugin-mockcontrol ⇗
    • 檔案夾請求 - 展示檔案夾内的檔案清單
    • 靜态資源 - 響應靜态資源

Server子產品 提供其他插件一些關于 Server 相關的服務,可以供其他插件調用,比如:

  • injectScript - 允許其他插件在同步接口中插入 javascript 腳本
  • eval - 允許其他插件執行 js 代碼
  • livereload - 允許其他插件通知浏覽器 reload
  • use - 允許其他插件給 server 加入中間件
  • registerRouterNamespace - 允許其他插件新增路由,使用命名空間可以保證不同的插件的路由不會互相幹擾

Foxman 的内置的 Mock Data 編寫方式使用最原始的 JSON 字元串。

沒有使用 MockJS 等庫的原因是,原始的 JSON字元串,使用者可以對模拟資料的完全掌控。

有特殊需求可以使用插件 @foxman/plugin-mockcontrol ⇗ 對響應進行額外控制。

整合 NEI

NEI ⇗ 是我們網易開發的一個接口定義平台。

foxman 接入 nei 非常簡單, 在 foxman.config.js 中配置 nei key 即可:

...
nei: {
    key: 'xxx' // nei key
}
...      

首次運作會自動同步 NEI 接口。

當需要更新本地 nei 接口時,使用以下指令:

$ foxman -U      

Template 渲染引擎

模闆解析子產品,具有特定接口,完成模闆渲染需求。
var engine = require('@foxman/engine-arttemplate');

...
engine: engine,
engineConfig: { // 取決于具體的模闆引擎
    bail: true,
    compileDebug: true,
    imports: renderImports,
    debug: false,
    cache: false,
}
...      

目前支援的模闆引擎有:

  • Freemaker - @foxman/engine-freemarker ⇗
  • Art-template - @foxman/engine-arttemplate ⇗
  • Handlebars - @foxman/engine-handlebars ⇗

假如沒有你需要的,你也可以自行開發一款 Foxman 的模闆引擎解析器,隻需要實作一個特定的接口,基本結構如下。

const template = require('xxx-template');
class TemplateEngin {
    constructor(viewRoot, engineConfig) {
        // 初始化配置
    }

    parse(path, mockData) {
        // 傳回一個 Promise,Promise 的傳回是處理後的接口
        return Promise.resolve(template(path, mockData));
    }
}      

具體實作,參考 @foxman/engine-arttemplate ⇗

Proxy

使用本地的模闆,結合遠端端的資料來拼裝頁面。

代理的原理:

  1. Foxman 接收到使用者的代理需求時,将請求轉發給背景伺服器,并帶上特殊的請求頭(X-Special-Proxy-Header: foxman);
  2. 後端接收到 Foxman 的代理請求後,要求以 JSON 的方式将頁面的同步資料傳回;
  3. Foxman 接收到服務端的響應資料後,結合本地的模闆來實作模闆渲染的需求,并響應給使用者。

代理的設定,使得我們可以在本地的環境下調試測試環境的場景,發現存在前端的 bug 也能輕松修複,不再需要重複的部署測試伺服器。

來接觸下 Foxman Proxy 的實際配置:

...
proxy:  [{ 
    name: 'pre', 
    host: 'm.kaola.com',  // 用于 nginx 轉發到制定應用
    ip: '1.1.1.1',        // 目标的 IP 位址
    protocol: 'http'      // 協定
}]
...      

完成上述配置後,使用者輸入以下指令啟動 Foxman,即可代理至遠端伺服器

$ foxman -P pre # pre 為 配置的 proxy name      

Processors

Processors 是 Runtime Compiler 的設定,在接收到靜态資源請求時,才去即時地編譯前端資源(sass/less/mcss/autoprefixer),主要目的是相容無 webpack 建構的開發場景。如已使用 webpack,則推薦使用插件 @foxman/plugin-webapck-dev-server

舉例介紹 mcss 的即時編譯配置

const Mcss = require('@foxman/processor-mcss');
const AutoPrefixer = require('@foxman/processor-autoprefixer');

...
processors: [
    {
        match: '/src/css/**.css', // 攔截該請求
        pipeline: [ // pipe 式的處理
            new Mcss({
                paths: []
            }),
            new AutoPrefixer({
                cascade: false,
                browsers: '> 5%'
            })
        ],
        locate(reqPath) { // 根據請求路徑,定位到在系統中具體路徑
            return path.join(__dirname + reqPath.replace(/css/g, 'mcss'));
        }
    }
],
...      

假如沒有你需要的,你也可以自行開發一款 Foxman 的 Processor ,隻需要實作一個特定的接口,基本結構如下:

const mcss = require('mcss');

class Processor {
    constructor(options) {
        // 初始化解析器的參數
    }

    locate(reqPath) { // 根據請求路徑找到檔案在系統中的位置
        return reqPath.replace(/\.css$/g, '\.mcss');
    }

    *handler({ raw, filename }) {
        return yield new Promise((resolve, reject) => {
            // 在這裡 進行 parse 操作,如 sass | less 的解析操作
            return {
                dependencies, content 
                // 該檔案依賴,及内容
            }
        })
    }
}      

具體實作,參考 @foxman/processor-mcss ⇗

結束

最後,如果你對 Foxman 的開發感興趣,歡迎一起參與到開發當中。

感謝閱讀!

本文已由作者許駿宇授權網易雲社群釋出。