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的直接调用。因此咱们整理出下面的接口请求流程图:
具体项目架构会在下一期文章给出。