前端mock完美解決方案實戰

寫在前面,本文閱讀須要必定Nodejs的相關知識,由於會擴展webpack的相關功能,而且實現須要遵照必定約定和Ajax封裝。沉澱的腳手架也放到Github上供給同窗參考React-Starter, 使用手冊還沒寫完善, 總體思路和React仍是Vue無關,若是對你們有收穫記得Star下。
它有這些功能:html

  • 開發打包有不一樣配置
  • eslint 驗證
  • 代碼風格統一
  • commit 規範驗證
  • 接口mock
  • 熱更新
  • 異步組件

Mock功能介紹

市面上講前端mock怎麼作的文章不少,總體上閱讀下來的沒有一個真正站在前端角度上讓我以爲強大和易用的。下面就說下我指望的前端mock要有哪些功能:前端

  1. mock功能和前端代碼解耦
  2. 一個接口支持多種mock狀況
  3. 無需依賴另外的後端服務和第三方庫
  4. 能在network看到mock接口的請求且能區分
  5. mock數據、接口配置和頁面在同一個目錄下
  6. mock配置改變無需重啓前端dev
  7. 生產打包能夠把mock數據注入到打包的js中走前端mock
  8. 對於後端已有的接口也能快速把Response數據轉化爲mock數據

上面的這些功能我講其中幾點的做用:node

對於第7點的做用是後續項目開發完成,在徹底沒有開發後端服務的狀況下,也能夠進行演示。這對於一些ToB定製的項目來沉澱項目地圖(案例)頗有做用。
對於第8點在開發環境後端服務常常不穩定下,不依賴後端也能作頁面開發,核心是能實現一鍵生成mock數據。

配置解耦

耦合狀況

什麼是前端配置解耦,首先讓咱們看下平時配置耦合狀況有哪些:react

  • webpack-dev後端測試環境變了須要改git跟蹤的代碼
  • dev和build的時候 須要改git跟蹤的代碼
  • 開發的時候想這個接口mock 須要改git跟蹤的代碼 mockUrl ,mock?

如何解決

前端依賴的配置解耦的思路是配置文件conf.json是在dev或build的時候動態生成的,而後該文件在前端項目引用:webpack

├── config
│   ├── conf.json                                    # git 不跟蹤
│   ├── config.js                                    # git 不跟蹤
│   ├── config_default.js
│   ├── index.js
│   └── webpack.config.js
├── jsconfig.json
├── mock.json                                            # git 不跟蹤

webpack配置文件引入js的配置,生成conf.jsonios

// config/index.js
const _ = require("lodash");
let config = _.cloneDeep(require("./config_default"))
try {
  const envConfig = require('./config') // eslint-disable-line
  config = _.merge(config, envConfig);
} catch (e) {
    // 
}
module.exports = config;

默認使用config_default.js 的內容,若是有config.js 則覆蓋,開發的時候複製config_default.js 爲config.js 後續相關配置能夠修改config.js便可。git

// config/config_default.js
const pkg = require("../package.json");
module.exports = {
  projectName: pkg.name,
  version: pkg.version,
  port: 8888,
  proxy: {
    "/render-server/api/*": {
      target: `http://192.168.1.8:8888`,
      changeOrigin: true, // 支持跨域請求
      secure: true, // 支持 https
    },
  },
  ...
  conf: {
    dev: {
      title: "前端模板",
      pathPrefix: "/react-starter", // 統一前端路徑前綴
      apiPrefix: "/api/react-starter", //
      debug: true,
      delay: 500,    // mock數據模擬延遲
      mock: {
        // "global.login": "success",
        // "global.loginInfo": "success",
      }
    },
    build: {
      title: "前端模板",
      pathPrefix: "/react-starter",
      apiPrefix: "/api/react-starter",
      debug: false,
      mock: {}
    }
  }
};

在開發或打包的時候根據環境變量使用conf.dev或conf.build 生成conf.json文件內容github

// package.json
{
  "name": "react-starter",
  "version": "1.0.0",
  "description": "react前端開發腳手架",
  "main": "index.js",
  "scripts": {
    "start": "webpack-dev-server --config './config/webpack.config.js' --open --mode development",
    "build": "cross-env BUILD_ENV=VERSION webpack --config './config/webpack.config.js' --mode production --progress --display-modules && npm run tar",
    "build-mock": "node ./scripts/build-mock.js "
  },
  ...
}

指定webpack路徑是./config/webpack.config.jsweb

而後在webpack.config.js中引入配置並生成conf.json文件ajax

// config/webpack.config.js
const config = require('.')
const env = process.env.BUILD_ENV ? 'build' : 'dev'
const confJson = env === 'build' ? config.conf.build : config.conf.dev
fs.writeFileSync(path.join(__dirname, './conf.json'),  JSON.stringify(confGlobal, null, '\t'))

引用配置

src/common/utils.jsx文件中暴露出配置項,配置也能夠經過window.conf來覆蓋

// src/common/utils.jsx
import conf from '@/config/conf.json'
export const config = Object.assign(conf, window.conf)

而後就能夠在各個頁面中使用

import {config} from '@src/common/utils'
class App extends Component {
  render() {
    return (
      <Router history={history}>
        <Switch>
          <Route path={`${config.pathPrefix}`} component={Home} />
          <Redirect from="/" to={`${config.pathPrefix}`} />
        </Switch>
      </Router>
    )
  }
}
ReactDOM.render(
     <App />,
  document.getElementById('root'),
)

Mock實現

效果

爲了實現咱們想要的mock的相關功能,首先是否開啓mock的配置解耦能夠經過上面說的方式來實現,咱們通常在頁面異步請求的時候都會目錄定義一個io.js的文件, 裏面定義了當前頁面須要調用的相關後端接口:

// src/pages/login/login-io.js
import {createIo} from '@src/io'

const apis = {
  // 登陸
  login: {
    method: 'POST',
    url: '/dtwave-boot/sys/login',
  },
  // 登出
  logout: {
    method: 'POST',
    url: '/dtwave-boot/sys/logout',
  },
}
export default createIo(apis, 'login') // 對應login-mock.json

上面定義了登陸和登出接口,咱們但願對應開啓的mock請求能使用當前目錄下的login-mock.json文件的內容

// src/pages/login/login-mock.json
{
    "login": {
        "failed": {
            "success": false,
            "code": "ERROR_PASS_ERROR",
            "content": null,
            "message": "帳號或密碼錯誤!"
        },
        "success": {
            "success": true,
            "code": 0,
            "content": {
                "name": "admin",
                "nickname": "超級管理員",
                "permission": 15
            },
            "message": ""
        }
    },
    "logout": {
        "success": {
            "success": true,
            "code": 0,
            "content": null,
            "message": ""
        }
    }
}

在調用logout登出這個Ajax請求的時候且咱們的conf.json中配置的是"login.logout": "success" 就返回login-mock.json中的login.success 的內容,配置沒有匹配到就請求轉發到後端服務。

// config/conf.json
{
    "title": "前端後臺模板",
    "pathPrefix": "/react-starter",
    "apiPrefix": "/api/react-starter",
    "debug": true,
    "delay": 500,
    "mock": {
        "login.logout": "success"
    }
}

這是咱們最終要實現的效果,這裏有一個約定:項目目錄下全部以-mock.jsom文件結尾的文件爲mock文件,且文件名不能重複

思路

在webpack配置項中devServer的proxy配置接口的轉發設置,接口轉發使用了功能強大的 http-proxy-middleware 軟件包, 咱們約定proxy的配置格式是:

proxy: {
    "/api/react-starter/*": {
      target: `http://192.168.90.68:8888`,
      changeOrigin: true,
      secure: true,
      // onError: (),
      // onProxyRes,
      // onProxyReq  
    },
  },

它有幾個事件觸發的配置:

  • option.onError 出現錯誤
  • option.onProxyRes 後端響應後
  • option.onProxyReq 請求轉發前
  • option.onProxyReqWs
  • option.onOpen
  • option.onClose

因此咱們須要定製這幾個事情的處理,主要是請求轉發前和請求處理後

onProxyReq

想在這裏來實現mock的處理, 若是匹配到了mock數據咱們就直接響應,就不轉發請求到後端。 怎麼作呢: 思路是依賴請求頭,dev狀況下前端在調用的時候可否注入約定好的請求頭 告訴我須要尋找哪一個mock數據項, 咱們約定Header:

  • mock-key 來匹配mock文件如login-mock.json的內容, 如login
  • mock-method 來匹配對應文件內容的方法項 如logout

而後conf.json中mock配置尋找到具體的響應項目如:"login.logout": "success/failed"的內容

onProxyRes

若是調用了真實的後端請求,就把請求的響應數據緩存下來,緩存到api-cache目錄下文件格式mock-key.mock-method.json

├── api-cache                                    # git 不跟蹤
│   ├── login.login.json
│   └── login.logout.json
// api-cache/global.logout.json
{
    "success": {
        "date": "2020-11-17 05:32:17",
        "method": "POST",
        "path": "/render-server/api/logout",
        "url": "/render-server/api/logout",
        "resHeader": {
            "content-type": "application/json; charset=utf-8",
      ...
        },
        "reqHeader": {
            "host": "127.0.0.1:8888",
            "mock-key": "login",
            "mock-method": "logout"
      ...
        },
        "query": {},
        "reqBody": {},
        "resBody": {
            "success": true,
            "code": 0,
            "content": null,
            "message": ""
        }
    }
}

這樣作的目的是爲了後續實現一鍵生成mock文件。

前端接口封裝

使用

上面咱們看到定義了接口的io配置:

// src/pages/login/login-io.js
import {createIo} from '@src/io'

const apis = {
  // 登陸
  login: {
    method: 'POST',
    url: '/dtwave-boot/sys/login',
  },
  // 登出
  logout: {
    method: 'POST',
    url: '/dtwave-boot/sys/logout',
  },
}
export default createIo(apis, 'login') // login註冊到header的mock-key

咱們在store中使用

// src/pages/login/login-store.js

import {observable, action, runInAction} from 'mobx'
import io from './login-io'
// import {config, log} from './utils'

export class LoginStore {
  // 用戶信息
  @observable userInfo
  // 登錄操做
  @action.bound
  async login(params) {
    const {success, content} = await io.login(params)
    if (!success) return
    runInAction(() => {
      this.userInfo = content
    })
  }
}
export default LoginStore

經過 createIo(apis, 'login') 的封裝在調用的時候就能夠很是簡單的來傳遞請求參數,簡單模式下會判斷參數是到body仍是到query中。 複雜的也能夠支持好比能夠header,query, body等這裏不演示了。

createIo 請求封裝

這個是前端接口封裝的關鍵地方,也是mock請求頭注入的地方

// src/io/index.jsx
import {message, Modal} from 'antd'
import {config, log, history} from '@src/common/utils'
import {ERROR_CODE} from '@src/common/constant'
import creatRequest from '@src/common/request'
let mockData = {}
try {
  // eslint-disable-next-line global-require, import/no-unresolved
  mockData = require('@/mock.json')
} catch (e) {
  log(e)
}

let reloginFlag = false
// 建立一個request
export const request = creatRequest({
  // 自定義的請求頭
  headers: {'Content-Type': 'application/json'},
  // 配置默認返回數據處理
  action: (data) => {
    // 統一處理未登陸的彈框
    if (data.success === false && data.code === ERROR_CODE.UN_LOGIN && !reloginFlag) {
      reloginFlag = true
      // TODO 這裏可能統一跳轉到 也能夠是彈窗點擊跳轉
      Modal.confirm({
        title: '從新登陸',
        content: '',
        onOk: () => {
          // location.reload()
          history.push(`${config.pathPrefix}/login?redirect=${window.location.pathname}${window.location.search}`)
          reloginFlag = false
        },
      })
    }
  },
  // 是否錯誤顯示message
  showError: true,
  message,
  // 是否以拋出異常的方式 默認false {success: boolean判斷}
  throwError: false,
  // mock 數據請求的等待時間
  delay: config.delay,
  // 日誌打印
  log,
})

// 標識是不是簡單傳參數, 值爲true標識複雜封裝
export const rejectToData = Symbol('flag')

/**
 * 建立請求IO的封裝
 * @param ioContent {any { url: string method?: string mock?: any apiPrefix?: string}}
  }
 * @param name mock數據的對應文件去除-mock.json後的
 */
export const createIo = (ioContent, name = '') => {
  const content = {}
  Object.keys(ioContent).forEach((key) => {
    /**
     * @param {baseURL?: string, rejectToData?: boolean,  params?: {}, query?: {}, timeout?: number, action?(data: any): any, headers?: {},  body?: any, data?: any,   mock?: any}
     * @returns {message, content, code,success: boolean}
     */
    content[key] = async (options = {}) => {
      // 這裏判斷簡單請求封裝 rejectToData=true 表示複雜封裝
      if (!options[rejectToData]) {
        options = {
          data: options,
        }
      }
      delete options[rejectToData]
      if (
        config.debug === false &&
        name &&
        config.mock &&
        config.mock[`${name}.${key}`] &&
        mockData[name] &&
        mockData[name][key]
      ) { // 判斷是不是生產打包 mock注入到代碼中
        ioContent[key].mock = JSON.parse(JSON.stringify(mockData[name][key][config.mock[`${name}.${key}`]]))
      } else if (name && config.debug === true) { //注入 mock請求頭
        if (options.headers) {
          options.headers['mock-key'] = name
          options.headers['mock-method'] = key
        } else {
          options.headers = {'mock-key': name, 'mock-method': key}
        }
      }
      const option = {...ioContent[key], ...options}

      option.url = ((option.apiPrefix ? option.apiPrefix : config.apiPrefix) || '') + option.url

      return request(option)
    }
  })
  return content
}

這裏對request也作進一步的封裝,配置項設置了一些默認的處理設置。好比通用的請求響應失敗的是否有一個message, 未登陸的狀況是否有一個彈窗提示點擊跳轉登錄頁。若是你想定義多個通用處理能夠再建立一個request2和createIo2。

request封裝axios

是基於axios的二次封裝, 並非很是通用,主要是在約定的請求失敗和成功的處理有定製,若是須要能夠本身修改使用。

import axios from 'axios'

// 配置接口參數
// declare interface Options {
//   url: string
//   baseURL?: string
//   // 默認GET
//   method?: Method
//   // 標識是否注入到data參數
//   rejectToData?: boolean
//   // 是否直接彈出message 默認是
//   showError?: boolean
//   // 指定 回調操做 默認登陸處理
//   action?(data: any): any
//   headers?: {
//     [index: string]: string
//   }
//   timeout?: number
//   // 指定路由參數
//   params?: {
//     [index: string]: string
//   }
//   // 指定url參數
//   query?: any
//   // 指定body 參數
//   body?: any
//   // 混合處理 Get到url, delete post 到body, 也替換路由參數 在createIo封裝
//   data?: any
//   mock?: any
// }
// ajax 請求的統一封裝
// TODO 1. 對jsonp請求的封裝 2. 重複請求

/**
 * 返回ajax 請求的統一封裝
 * @param Object option 請求配置
 * @param {boolean} opts.showError 是否錯誤調用message的error方法
 * @param {object} opts.message  包含 .error方法 showError true的時候調用
 * @param {boolean} opts.throwError 是否出錯拋出異常
 * @param {function} opts.action  包含 自定義默認處理 好比未登陸的處理
 * @param {object} opts.headers  請求頭默認content-type: application/json
 * @param {number} opts.timeout  超時 默認60秒
 * @param {number} opts.delay   mock請求延遲
 * @returns {function} {params, url, headers, query, data, mock} data混合處理 Get到url, delete post 到body, 也替換路由參數 在createIo封裝
 */
export default function request(option = {}) {
  return async (optionData) => {
    const options = {
      url: '',
      method: 'GET',
      showError: option.showError !== false,
      timeout: option.timeout || 60 * 1000,
      action: option.action,
      ...optionData,
      headers: {'X-Requested-With': 'XMLHttpRequest', ...option.headers, ...optionData.headers},
    }
    // 簡單請求處理
    if (options.data) {
      if (typeof options.data === 'object') {
        Object.keys(options.data).forEach((key) => {
          if (key[0] === ':' && options.data) {
            options.url = options.url.replace(key, encodeURIComponent(options.data[key]))
            delete options.data[key]
          }
        })
      }
      if ((options.method || '').toLowerCase() === 'get' || (options.method || '').toLowerCase() === 'head') {
        options.query = Object.assign(options.data, options.query)
      } else {
        options.body = Object.assign(options.data, options.body)
      }
    }
    // 路由參數處理
    if (typeof options.params === 'object') {
      Object.keys(options.params).forEach((key) => {
        if (key[0] === ':' && options.params) {
          options.url = options.url.replace(key, encodeURIComponent(options.params[key]))
        }
      })
    }
    // query 參數處理
    if (options.query) {
      const paramsArray = []
      Object.keys(options.query).forEach((key) => {
        if (options.query[key] !== undefined) {
          paramsArray.push(`${key}=${encodeURIComponent(options.query[key])}`)
        }
      })
      if (paramsArray.length > 0 && options.url.search(/\?/) === -1) {
        options.url += `?${paramsArray.join('&')}`
      } else if (paramsArray.length > 0) {
        options.url += `&${paramsArray.join('&')}`
      }
    }
    if (option.log) {
      option.log('request  options', options.method, options.url)
      option.log(options)
    }
    if (options.headers['Content-Type'] === 'application/json' && options.body && typeof options.body !== 'string') {
      options.body = JSON.stringify(options.body)
    }
    let retData = {success: false}
    // mock 處理
    if (options.mock) {
      retData = await new Promise((resolve) =>
        setTimeout(() => {
          resolve(options.mock)
        }, option.delay || 500),
      )
    } else {
      try {
        const opts = {
          url: options.url,
          baseURL: options.baseURL,
          params: options.params,
          method: options.method,
          headers: options.headers,
          data: options.body,
          timeout: options.timeout,
        }
        const {data} = await axios(opts)
        retData = data
      } catch (err) {
        retData.success = false
        retData.message = err.message
        if (err.response) {
          retData.status = err.response.status
          retData.content = err.response.data
          retData.message = `瀏覽器請求非正常返回: 狀態碼 ${retData.status}`
        }
      }
    }

    // 自動處理錯誤消息
    if (options.showError && retData.success === false && retData.message && option.message) {
      option.message.error(retData.message)
    }
    // 處理Action
    if (options.action) {
      options.action(retData)
    }
    if (option.log && options.mock) {
      option.log('request response:', JSON.stringify(retData))
    }
    if (option.throwError && !retData.success) {
      const err = new Error(retData.message)
      err.code = retData.code
      err.content = retData.content
      err.status = retData.status
      throw err
    }
    return retData
  }
}
一鍵生成mock

根據api-cache下的接口緩存和定義的xxx-mock.json文件來生成。

# "build-mock": "node ./scripts/build-mock.js"
# 全部:
npm run build-mock mockAll 
# 單個mock文件:
npm run build-mock login
# 單個mock接口:
npm run build-mock login.logout
# 複雜 
npm run build-mock login.logout user

具體代碼參考build-mock.js

mock.json文件生成

爲了在build打包的時候把mock數據注入到前端代碼中去,使得mock.json文件內容儘量的小,會根據conf.json的配置項來動態生成mock.json的內容,若是build裏面沒有開啓mock項,內容就會是一個空json數據。 固然後端接口代理處理內存中也映射了一份該mock.json的內容。這裏須要作幾個事情:

  • 根據配置動態生成mock.json的內容
  • 監聽config文件夾 判斷關於mock的配置項是否有改變從新生成mock.json
// scripts/webpack-init.js 在wenpack配置文件中初始化
const path = require('path')
const fs = require('fs')
const {syncWalkDir} = require('./util')
let confGlobal = {}
let mockJsonData = {}
exports.getConf = () => confGlobal
exports.getMockJson =() => mockJsonData

/**
 * 初始化項目的配置 動態生成mock.json和config/conf.json
 * @param {string} env  dev|build
 */
 exports.init = (env = process.env.BUILD_ENV ? 'build' : 'dev') => {
   
  delete require.cache[require.resolve('../config')]
  const config  = require('../config')
  const confJson = env === 'build' ? config.conf.build : config.conf.dev
  confGlobal = confJson
  // 1.根據環境變量來生成
  fs.writeFileSync(path.join(__dirname, '../config/conf.json'),  JSON.stringify(confGlobal, null, '\t'))
  buildMock(confJson)
 }
 
 // 生成mock文件數據
 const buildMock = (conf) => {
  // 2.動態生成mock數據 讀取src文件夾下面全部以 -mock.json結尾的文件 存儲到io/index.json文件當中
  let mockJson = {}
  const mockFiles = syncWalkDir(path.join(__dirname, '../src'), (file) => /-mock.json$/.test(file))
  console.log('build mocks: ->>>>>>>>>>>>>>>>>>>>>>>')
  mockFiles.forEach((filePath) => {
    const p = path.parse(filePath)
    const mockKey = p.name.substr(0, p.name.length - 5)
    console.log(mockKey, filePath)
    if (mockJson[mockKey]) {
      console.error(`有相同的mock文件名稱${p.name} 存在`, filePath)
    }
    delete require.cache[require.resolve(filePath)]
    mockJson[mockKey] = require(filePath)
  })
  // 若是是打包環境, 最小化mock資源數據
  const mockMap = conf.mock || {}
  const buildMockJson = {}
  Object.keys(mockMap).forEach((key) => {
    const [name, method] = key.split('.')
    if (mockJson[name][method] && mockJson[name][method][mockMap[key]]) {
      if (!buildMockJson[name]) buildMockJson[name] = {}
      if (!buildMockJson[name][method]) buildMockJson[name][method] = {}
      buildMockJson[name][method][mockMap[key]] = mockJson[name][method][mockMap[key]]
    }
  })
  mockJsonData = buildMockJson
  fs.writeFileSync(path.join(__dirname, '../mock.json'), JSON.stringify(buildMockJson, null, '\t'))
 }
 
 // 監聽配置文件目錄下的config.js和config_default.js
const confPath = path.join(__dirname, '../config')

if ((env = process.env.BUILD_ENV ? 'build' : 'dev') === 'dev') {
  fs.watch(confPath, async (event, filename) => {
    if (filename === 'config.js' || filename === 'config_default.js') {
      delete require.cache[path.join(confPath, filename)]
      delete require.cache[require.resolve('../config')]
      const config  = require('../config')
      // console.log('config', JSON.stringify(config))
      const env = process.env.BUILD_ENV ? 'build' : 'dev'
      const confJson = env === 'build' ? config.conf.build : config.conf.dev
      if (JSON.stringify(confJson) !== JSON.stringify(confGlobal)) {
        this.init()
      }
    }
  });
}

接口代理處理

onProxyReq和onProxyRes

實現上面思路里面說的onProxyReq和onProxyRes 響應處理

util.js

// scripts/api-proxy-cache 
const fs = require('fs')
const path = require('path')
const moment = require('moment')
const {getConf, getMockJson} = require('./webpack-init')
const API_CACHE_DIR = path.join(__dirname, '../api-cache')
const {jsonParse, getBody} = require('./util')

fs.mkdirSync(API_CACHE_DIR,{recursive: true})

module.exports = {
  // 代理前處理
  onProxyReq: async (_, req, res) => {
    req.reqBody = await getBody(req)
    const {'mock-method': mockMethod, 'mock-key': mockKey} = req.headers
    // eslint-disable-next-line no-console
    console.log(`Ajax 請求: ${mockKey}.${mockMethod}`,req.method, req.url)
    // eslint-disable-next-line no-console
    req.reqBody && console.log(JSON.stringify(req.reqBody, null, '\t'))
    if (mockKey && mockMethod) {
      req.mockKey = mockKey
      req.mockMethod = mockMethod
      const conf = getConf()
      const mockJson = getMockJson()
      if (conf.mock && conf.mock[`${mockKey}.${mockMethod}`] && mockJson[mockKey] && mockJson[mockKey][mockMethod]) {
        // eslint-disable-next-line no-console
        console.log(`use mock data ${mockKey}.${mockMethod}:`, conf.mock[`${mockKey}.${mockMethod}`], 'color: green')
        res.mock = true
        res.append('isMock','yes')
        res.send(mockJson[mockKey][mockMethod][conf.mock[`${mockKey}.${mockMethod}`]])
      }
     
    }
  },
  // 響應緩存接口
  onProxyRes: async (res, req) => {
    const {method, url, query, path: reqPath, mockKey, mockMethod} = req
    
    if (mockKey && mockMethod && res.statusCode === 200) {
      
      let resBody = await getBody(res)
      resBody = jsonParse(resBody)
      const filePath = path.join(API_CACHE_DIR, `${mockKey}.${mockMethod}.json`)
      let  data = {}
      if (fs.existsSync(filePath)) {
        data = jsonParse(fs.readFileSync(filePath).toString())
      }
      const cacheObj = {
        date: moment().format('YYYY-MM-DD hh:mm:ss'),
        method,
        path: reqPath,
        url,
        resHeader: res.headers,
        reqHeader: req.headers,
        query,
        reqBody: await jsonParse(req.reqBody),
        resBody: resBody
      }
      if (resBody.success === false) {
        data.failed = cacheObj
      } else {
        data.success = cacheObj
      }
      // eslint-disable-next-line no-console
      fs.writeFile(filePath, JSON.stringify(data,'', '\t'), (err) => { err && console.log('writeFile', err)})
    }
  },
  // 後端服務沒啓的異常處理
  onError(err, req, res) {
    setTimeout(() => {
     if (!res.mock) {
       res.writeHead(500, {
         'Content-Type': 'text/plain',
       });
       res.end('Something went wrong. And we are reporting a custom error message.');
     }
   }, 10)
  }
}
webpack配置

在webpack配置中引入使用

const config = require('.')
// config/webpack.config.js
const {init} = require('../scripts/webpack-init');
init();
// 接口請求本地緩存
const apiProxyCache = require('../scripts/api-proxy-cache')
for(let key in config.proxy) {
  config.proxy[key] = Object.assign(config.proxy[key], apiProxyCache);
}

const webpackConf = {
  devServer: {
    contentBase: path.join(__dirname, '..'), // 本地服務器所加載的頁面所在的目錄
    inline: true,
    port: config.port,
    publicPath: '/',
    historyApiFallback: {
      disableDotRule: true,
      // 指明哪些路徑映射到哪一個html
      rewrites: config.rewrites,
    },
    host: '127.0.0.1',
    hot: true,
    proxy: config.proxy,
  },
}

總結

mock作好其實在咱們前端實際中仍是頗有必要的,作過的項目若是後端被剷除了想要回憶就可使用mock讓項目跑起來,能夠尋找一些實現的效果來進行代碼複用。當前介紹的mock流程實現有不少定製的開發,可是真正完成後,團隊中的成員只是使用仍是比較簡單配置便可。

關於前端項目部署我也分享了一個BFF 層,當前作的還不是很完善,也分享給你們參考

Render-Server 主要功能包含:

  • 一鍵部署 npm run deploy
  • 支持集羣部署配置
  • 是一個文件服務
  • 是一個靜態資源服務
  • 在線可視化部署前端項目
  • 配置熱更新
  • 在線Postman及接口文檔
  • 支持前端路由渲染, 支持模板
  • 接口代理及路徑替換
  • Web安全支持 Ajax請求驗證,Referer 校驗
  • 支持插件開發和在線配置 可實現: 前端模板參數注入、請求頭注入、IP白名單、接口mock、會話、第三方登錄等等
相關文章
相關標籤/搜索