nodejs項目,webpack打包,用axios請求,Promise封裝,nunjucks模板引擎;javascript
以前已將nunjucks模板經過webpack打包策略,作成先後端共用;java
目前須要將網絡請求以及數據處理封裝成service模塊;node
目錄劃分:webpack
如上圖所示:ios
將公共代碼放到service中,整合兩端共同的一些網絡請求以及數據處理(node首屏,客戶端再次請求數據更新等操做)es6
1. node模塊使用module.exports,而webpack咱們使用的是import/export,二者共用會報錯;web
2.咱們使用了Promise作了兩層封裝(service封裝、service中的fetch封裝:抹平node和客戶端的環境差別)axios
第一個問題,其實webpack也提供了module.exports的方法,因此兩端的模塊是能夠共用的。後端
而第二個問題,咱們使用了Promise,也在webpack全局引入了babel-polyfill,可是會報錯:api
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就能夠了:
(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);
標紅的那段是重點,劃下來!!!
寫到這裏,已經作好了node項目代碼複用的基礎,那麼整個接口的數據流程是怎麼樣的呢,又會碰到什麼樣的問題?
好比咱們須要在兩端調用service的時候必須得到一樣的數據格式,而瀏覽器的請求實際是通過一次node接口轉發,總共兩次fetch流程產生的。
並且fetch模塊,又須要支持瀏覽器和node的直接調用。因此咱們整理出下面的接口請求流程圖:
具體項目架構會在下一期文章給出。