node前后端同構的踩坑經歷


項目背景

nodejs項目,webpack打包,用axios請求,Promise封裝,nunjucks模板引擎;

之前已將nunjucks模板通過webpack打包策略,做成前后端共用;

目前需要將網絡請求以及數據處理封裝成service模塊;

目錄划分:

如上圖所示:

將公共代碼放到service中,整合兩端共同的一些網絡請求以及數據處理(node首屏,客戶端再次請求數據更新等操作)

 

這里碰到的兩個問題:

1. node模塊使用module.exports,而webpack我們使用的是import/export,兩者共用會報錯;

2.我們使用了Promise做了兩層封裝(service封裝、service中的fetch封裝:抹平node和客戶端的環境差異)

 

第一個問題,其實webpack也提供了module.exports的方法,所以兩端的模塊是可以共用的。

而第二個問題,我們使用了Promise,也在webpack全局引入了babel-polyfill,但是會報錯:

Uncaught TypeError: Cannot assign to read only property 'exports' of object '#<Object>'

導致前面的排查思路一直以為是module.exports出了問題;

 

解決方法

我們剛開始是通過引入es6-promise來解決的:

service/fetch.js:

var axios = require('axios');
var Promise = require('es6-promise').Promise;

module.exports = function(opts, request) {
  return new Promise(function(resolve, reject) {
    axios(opts).then(function(res) {
      res = res.data
      if (res.success) {
        resolve(res)
      } else {
        reject({ ___req: opts, ___res: res })
      }
    }).catch(function(err) {
      reject({ ___req: opts, ___res: err.data || err.stack || err })
    })
  })
}

 

service/wawa.js

var fetch = require('./fetch');
var Promise = require('es6-promise').Promise;

var getGamelist = function(params, req) {
  return new Promise(function(resolve, reject) {
    fetch({
      url: '/api/appeal/appealJoinOrderPage',
      type: 'get',
      params: params
    }).then(function(res) {
      resolve(res.data)
    }).catch(function(err) {
      reject(err)
    })
  })
}

module.exports = {
  getGamelist
}

 

並且我們也嘗試在全局引入es6-promise,仍然報錯;

這樣我們暫時得出結論,是原生Promise語法,直接與module.exports沖突報錯。目前只能通過在當前js中引入es6-promise來規避。

所幸的是,每個js中重復引入的es6-promise,在最終webpack打包的時候會去重,也避免了打包體積變大的問題。

至此,node前后端代碼共用的方案暫時通過。並且后面還可以寫除了service以外的共用代碼,提升了復用性和可維護性。

 

再次踩到坑

我們在遷移另一個公用service的時候,又碰到原來的問題,精簡后的代碼如下:

/**
 * 獲取紅包列表
 */
var getRedList = function(params, req) {
  return JSON.stringify({a:1})
}

module.exports = {
  getRedList
}

納尼?又報錯

Uncaught TypeError: Cannot assign to read only property 'exports' of object '#<Object>'

經過排查定位,發現是JSON.stringify不支持。。。但平時我們正常使用export/import從未碰到此問題。

所以猜測是module.exports出去的模塊,在webpack中默認是不會給全局方法加上window的。

 

最終解決:兼容global和window(其實還不是最終)

那解決方法就容易了,給全局方法手動加上全局對象,兼容處理global和window就可以了:

(function(global) {
  let isBrowser = global.toString() === '[object Window]';
  /**
   * 獲取紅包列表
   */
  var getRedList = function(params, req) {
    return JSON.stringify({a:1})
  }

  module.exports = {
    getRedList
  }
})(typeof exports === 'undefined' ? global = window : global);

 

以上,得出在node和瀏覽器webpack共用模塊化代碼的解決方案:

1. 使用 module.exports / require 做模塊化

2. 兼容處理global和window

 

打死不改終極奧義最終版

上周末線上突然報錯飆升,發現集中在安卓4.3以下,報錯:Promise is not defined

經過不斷試錯排查,發現在module.exports出去后,里邊的 'es6-promise'兼容包,在外面是不生效的

最后決定在外邊定義一個全局的Promise:

 

window.Promise = require('es6-promise').Promise;

 

此外,在低版本安卓中,判斷window環境還有一個坑:

需要這么寫

(function(global) {
  module.exports = function(opts, request) {
    var isBrowser = global.toString() === '[object Window]' || global.toString() === '[object DOMWindow]';

    return new global.Promise(function(resolve, reject) {
      if (isBrowser) {
        axios(opts).then(function(res) {

        }).catch(function(err) {
        })
      } else {
        axios(opts).then(function(res) {
          
        }).catch(function(err) {
        })
      }
    })
  }
})(typeof exports === 'undefined' ? global = window : global);

標紅的那段是重點,划下來!!!

End

寫到這里,已經做好了node項目代碼復用的基礎,那么整個接口的數據流程是怎么樣的呢,又會碰到什么樣的問題?

比如我們需要在兩端調用service的時候必須獲得同樣的數據格式,而瀏覽器的請求實際是經過一次node接口轉發,總共兩次fetch流程產生的。

而且fetch模塊,又需要支持瀏覽器和node的直接調用。所以我們整理出下面的接口請求流程圖:

 

具體項目架構會在下一期文章給出。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM