本文來自 網易雲社群 。
Foxman ⇗ 是一個使用 Node.js 開發的指令行工具,定位是一個可擴充的 Mock Server,幫助前端開發者輕松、獨立、高效地進行前端開發和完成後續的聯調工作。
他不是一款靜态檔案響應工具, 假如你隻需要一款輕量的 Node.js 開發伺服器,推薦你使用 puer ⇗ 或 webpack-dev-server ⇗。
github 位址: https://github.com/kaola-fed/foxman
背景
作為前端開發的我們,在實際的開發場景中會遇到以下問題:
- 環境:進行本地開發,需要起後端環境(Tomcat),對于新人來說,需要大量時間熟悉;熟練的人遇到某些确實存在的問題,也要花時間去解決,耗費大量前端開發的時間;
- 流程:前端開發先開發 html,再将 html 改寫成指定的模闆文法,影響開發效率;
- 接口:
- 接口定義使用聊天工具發送,前端開發時不好了解接口字段,影響開發效率;
- 接口變更需要重新編寫文檔,并重新發送,影響開發效率;
- 文檔散落,影響接口維護;
- 聯調:
- 聯調過程很複雜,尤其是沒有做熱部署的Java工程,改視圖還需要重新開機Tomcat,影響前端聯調效率;
- 效益:
- 前後端對接的方式,期望純粹的 JSON 交換。不過現實情況,是依賴後端的模闆引擎,導緻前端了解接口存在一定的障礙;
以上問題的存在,才産生了 Foxman 這個項目。
影響
從 考拉前端 使用情況來看,在接入 Foxman 後開發效率得到一定提升,主要展現在以下方面:
- 前端開發者不再需要在本地起 Tomcat 服務,新人也無需熟悉本地啟動環境;而啟動一個 Foxman 所需要的時間,在 5s 以内;
- 前端開發者更加有意識地去與後端定義接口,因為接口定義會落實到具體的 mock 資料上;
- Mock 功能,使得前端開發者在開發階段幾乎可以是自治、無打擾的情況(産品不改需求的前提下);
- Foxman 提供 Living Reload 的功能 - 頁面開發過程中,修改 html 和 js 會通知浏覽器 reload 頁面;修改 css 會通知浏覽器隻 reload 樣式,提升了開發體驗,節省了人肉重新整理耗費的時間。
- Foxman 提供 Processors 的功能 - 即時編譯的設定,更好地相容無 webpack 建構的場景;
- 聯調階段,由于 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 的啟動分為三個階段:
- 初始化 - 設定配置,設定路由,以及初始化 Koa 對象;
- 裝載中間件 - 初始化中間件隊列;
- 啟動服務 - 啟動 Server,并建立 websocket 伺服器,用于與浏覽器的通信。
在 Server 啟動後,請求進入 Server 時,會經曆中間件的處理,這個過程又能分為 3 個階段:
- 請求分析,及确定響應方式,在請求的 context 上,生成 dispatcher 對象,用于在步驟 3 中确定以何種方式進行響應(同步 or 異步,模闆路徑 or mock 資料路徑);
- 由插件裝載的中間件對請求進行處理(取決于具體使用的插件),這個階段可以對 dispatcher 對象進行修改,以完成插件所期望的渲染方式;
- 請求響應,根據請求的類型,分為以下幾種方式
- 同步請求 - 交給 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
使用本地的模闆,結合遠端端的資料來拼裝頁面。
代理的原理:
- Foxman 接收到使用者的代理需求時,将請求轉發給背景伺服器,并帶上特殊的請求頭(X-Special-Proxy-Header: foxman);
- 後端接收到 Foxman 的代理請求後,要求以 JSON 的方式将頁面的同步資料傳回;
- 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 的開發感興趣,歡迎一起參與到開發當中。
感謝閱讀!
本文已由作者許駿宇授權網易雲社群釋出。