react 项目的一个ie8兼容性问题

2016/05/30

ie8React前端开发

相信现在很多人在使用react+webpack做项目,然后通过babel来解决ES6/7的兼容性问题,对于ie8的兼容也有一些经验和方案。不过今天在解决汇金账房的ie8兼容过程中仍然遇到一个坑,同时发现现有资料的一些问题。

首先说一下兼容ie8的通用方案,大家一般是按照 Make your React app work in IE8 中的方法去做,网上也会搜到一些方法,比如添加 transform-es3-property-literals,transform-es3-member-expression-literal , add-module-exports 插件等,不过它们可能是不同时期的不同解决方案,实际上是在解决同一类问题,即es3环境对es5语法的兼容。我们来分析一下 Make your React app work in IE8 中提到的问题,其实主要分三类。

一、es3ify解决es3环境兼容

对于这个问题,主要是解决es3的保留字在es3环境下的正确使用,default是暴露最多的问题,因为大家都在写export default xx。对于这个问题,目前比较快捷的方式就是使用es3ify,在webpack中就是添加es3ify-loader,代码如下:

    module: {loaders: [{test: /\.jsx?$/,loaders: ['es3ify-loader'],}]}

它主要做的事情就是对于一些保留字的使用做了es3兼容,以及一些额外的处理,比如去除数组尾部的多余逗号:

// babel转换前
var a = {class: "haha"
}
a.class = "bb";
var arr = [1,2,3,];//babel转换后
var a = {"class": "haha"
}
a["class"] = "bb";
var arr = [1,2,3];

有了它,其它的一些插件transform-es3-property-literals,transform-es3-member-expression-literal,add-module-exports 就没有必要了,你会发现这些插件就是在解决部分问题:

transform-es3-property-literals:保证在对象属性中的保留字正确

// babel转换前
var a = {class: "haha"  //变动处
}
a.class = "bb";//babel转换后
var a = {"class": "haha"
}
a.class = "bb";    //变动处

transform-es3-member-expression-literal:保证在对象属性访问中的保留字正确

// babel转换前
var a = {class: "haha"
}
a.class = "bb";   //变动处//babel转换后
var a = {class: "haha"
}
a["class"] = "bb";//变动处

所以也会把export.default转为export[“default”], 即解决了default不兼容问题。

add-module-exports:仅仅解决default的问题

二、babel-polyfill 解决缺失API问题

先跨过Object.defineProperty问题,因为那里是重要的坑点。而对于上面的这三个问题,实际上属于同一类问题,即对一些ES6 API缺失的模拟。比如常见的Object.assign,Promise对象,fetch等等,这些可以通过统一引用“babel-polyfill”来解决,如果感觉“babel-polyfill”太重,也可以针对所需要的API自行引用对应的polyfill。polyfill的应用可以有两种方式:

  1. npm包的方式,在编译入口文件通过require(“babel-polyfill”)引入执行。
  2. 也可以在页面上,业务js前引入babel的script标签。

这里最后一个问题:

以及console对象的兼容问题就比较简单,也都可以通过对应polyfill解决,就不多做解释。

三、最麻烦的Object.defineProperty

这里整个问题的说明已经滞后,且有错误:

首先,这里说的 “Object.defineProperty 在IE8中不存在” 是错的,而是IE8中有自己实现的Object.defineProperty,它的行为和标准不同,且只能接受DOM对象,如果传入普通javascript对象会抛异常。详细说明在这里Object.defineProperty。

其次,babel会把 export(非import) 编译成 Object.defineProperty的方式。相信添加这个问题的时候,babel确实存在这样的转换,具体的issues也有人提过 babel-export ,而提供的解决方案—-引入es5-shim和es5-sham在这种情况下是也确实是可行的。不过目前的babel版本已经不会有这种转换(却还存另一个转换的坑),但是es5-shim和es5-sham的引用是必要的,因为它是解决通用性的es3环境下es5 API的缺失问题,就像babel-polyfill一样,Object.defineProperty是其中的一个API。

以上是现有常规的兼容方案,很多人使用也没有存在太多问题。

遇到的问题和排查

本以为按照这样的指引,进行了babel转换,引入es3ify,babel-polyfill,es5-shim/es5-sham,console-polifill就大功告成了,可惜ie8运行起来还是崩了。先是错误定位到babel-polyfill中,去掉babel-polyfill又定位到es5-sham中,报的错误都是异常未捕获,且在IE8下调试很艰难。后来根据es5-sham压缩代码的抛异常位置,查看es5-sham的源码,结合es5-sham的文档说明,基本定位为Object.defineProperty的问题。

首先其抛异常的代码在这段:

代码会在supportsAccessors为false且hasGetter或者hasSetter时抛异常,逻辑上讲就是如果当前js引擎不支持访问器属性,但是却在属性描述符中设置了get,set,那么就会抛出异常。supportsAccessors用于判断当前js引擎是否支持访问器属性,它的判断逻辑在这里:


实际就是用Object.prototype.hasOwnProperty(“defineGetter“)做判断,“defineGetter”的兼容情况是只兼容IE11,具体查看 defineGetter说明 ,虽然ie9,ie10同样不支持defineGetter,不过他们直接支持Object.defineProperty方法和get语法,无需sham,所以代码并不会走到异常这里。实际上es5-sham官方文档也提到对Object.defineProperty的polyfill会存在限制和fail的情况:

具体查看: es5-shim , 虽然其说明和代码实现存在些差异,不过结论是明确的:ie8下访问器属性不支持,会抛异常;

基本明确了是用Object.defineProperty()设置访问器属性的问题,那么就向上查找到底是哪里使用了访问器属性设置,在编译后的源代码里搜索“ :get”查到了这段代码:

根据上面关键字syncHistoryWithStore,routerReducer等初步判断是在react-router-redux中,node_modules查看react-router-redux源码,果然,其lib中index.js里有很多对export的访问器属性设置。再查看对应src/index.js文件,两份代码如下:

//src/index.js
export syncHistoryWithStore from './sync'
export { LOCATION_CHANGE, routerReducer } from './reducer'export {CALL_HISTORY_METHOD,push, replace, go, goBack, goForward,routerActions
} from './actions'
export routerMiddleware from './middleware'

babel编译后部分代码:

//lib/index.js
'use strict';Object.defineProperty(exports, "__esModule", {value: true
});
exports.routerMiddleware = exports.routerActions = exports.goForward = exports.goBack = exports.go = exports.replace = exports.push = exports.CALL_HISTORY_METHOD = exports.routerReducer = exports.LOCATION_CHANGE = exports.syncHistoryWithStore = undefined;var _reducer = require('./reducer');Object.defineProperty(exports, 'LOCATION_CHANGE', {enumerable: true,get: function get() {return _reducer.LOCATION_CHANGE;}
});
Object.defineProperty(exports, 'routerReducer', {enumerable: true,get: function get() {return _reducer.routerReducer;}
});

所以确定了是babel编译问题,又回到了上面提到的,babel对直接的export会转码为Object.defineProperty,但是它们修复了这个问题,不过好死不死的,对于export 和 import结合的写法—-export xx from ‘xxx’ 还是会转码为Object.defineProperty对exports进行属性设置,更加严重的是,之前的Object.defineProperty设置的是数据属性,直接指定的value,但是这次的Object.defineProperty 脑残般的设置了访问器属性,如此一来es5-sham已经没有办法解决(上面已经说明)。

所以最终的排查结果就是主要3点:

  1. ie8不支持设置访问器属性,即便是引了es5-shim;
  2. Babel 会把export xxx from ‘xx’ 语法转码为访问器属性设置的exports对象。
  3. 而react-router-react的index.js 偏偏用了export xxx from ‘xx’这样的语法。

解决方案

最直接的解决办法就是Babel修复这种转码方式,目前已经有人提过issue,但是尚未解决:issue

其次react-router-redux 不要用export xx from ‘xx’的方式,也有人提过issue: issue

不过目前为了解决线上bug,肯定没有办法等待它们的修复,且尚未找到一种ie8下兼容访问器属性设置的方法,所以最终此项目的解决方案是修改react-router-react的源码,把其中的export xx from ‘xx’的语法改成分开的方式,比如:

//修改前
export syncHistoryWithStore from './sync'//修改后
import syncHistoryWithStore from './sync';
export {syncHistoryWithStore} ;

然后所有引用react-router-react的地方改为对src/index.js的引用,在项目中自己重新编译,而不使用lib中编译好的版本。

总结

原本对webpack和babel了解的就不是很多,差不多用了一天的时间来排查这个问题,感觉react的项目想要支持ie8坑还会很多,其中包括babel,webpack,第三方库对ie8的兼容支持问题并不良好,且现在react@15.x.x版本已经放弃ie8。所以目前实践的ie8兼容方案是:

  1. webpack 进行babel对ES6,7语法的转码。
  2. webpack 引用es3ify-loader 解决es3语法兼容问题。
  3. 全局引用babel-polyfill,es5-shim/es5-sham,console-polyfill,JSON的polyfill等
  4. 不要在代码中用Object.defineProperty设置访问器属性,若第三方包中有,找到,改之。

各位还遇到哪些问题可以一起讨论,积累经验,整个排查过程也对很多知识理解的深刻了一点。

react 项目的一个ie8兼容性问题相关推荐

  1. 对于一个IE8兼容性问题的反思

    近期做了一个需求,功能非常easy,把用户的优惠券数量读取出来,然后显示到"用户中心"上.开发完毕后.别的浏览器正常.可是到IE8上就不行了.并且,按下F12之后,就又能够载入出来 ...

  2. ie8不兼容java项目_常见IE8兼容性问题及解决

    1.css3媒体查询 IE8不支持媒体查询 解决:respond.js,在页面中所有css文件的引用位置之后引用Respond.js 2.HTML5新标签 IE8不支持H5新标签 解决:html5sh ...

  3. react项目中处理浏览器兼容性问题

    在index.html中添加如下代码 <meta http-equiv="X-UA-Compatible" content="IE=edge">

  4. 如何创建一个 react 项目

    目录 前言 一.create-react-app 脚手架快速搭建 react 项目 1.安装 create-react-app 2.检测 create-react-app 是否安装成功 3.创建 re ...

  5. 二手前端入门React项目

    个人对ReactJS这门技术比较感兴趣,在基友的帮助下成功创建了一个React标准前端工程,过程中遇到了不少麻烦,今天作为笔记一般记录一下遇到的问题和解决方案. 基础环境 手头一台Mac 使用OSX系 ...

  6. 如何创建React项目

    前言 构建React项目的几种方式: create-react-app 脚手架快速搭建 react 项目(推荐) yeoman 脚手架搭建 react 项目 webpack 一步一步构建 react ...

  7. 只需3分钟,就能轻松创建 一个SpreadJS的React项目

    概述 SpreadJS 纯前端表格控件 V11.2(SP2) 已经全面支持了 React 的拓展.接下来我们看下如何利用3分钟快速创建一个 SpreadJS 的 React 项目. 1.新建React ...

  8. 构建一个react项目_您想要了解更多有关React的内容吗? 让我们构建一个游戏,然后玩。...

    构建一个react项目 by Samer Buna 通过Samer Buna 您想要了解更多有关React的内容吗? 让我们构建一个游戏,然后玩. (Do you want to learn more ...

  9. react项目_保证一看就会 | 手把手教你创建一个React项目

    一.如何使用 git 在 GitHub 上创建一个项目 新建一个项目 首先,在github上注册自己的账号,然后登录后,可以看到如下界面,是你的主页.点击右边的➕号,选择第一个选项 填写项目信息 此时 ...

最新文章

  1. 第三课.使用简单的NN模拟fizzbuzz
  2. python【数据结构与算法】各进制转换-使用内置函数
  3. Hihocoder 1370 快乐数字
  4. Android 中文api (81)——InputMethod [输入法]
  5. php函数嵌套 作用域,javascript 嵌套的函数(作用域链)_javascript技巧
  6. Linux: 系统配置 crond 和 crontab(有图有代码有真相!!!)
  7. C语言求x和y的乘积,计算方程式,求x,C语言中怎么计算x,y的值?
  8. CORS漏洞的利用方式(精)
  9. CentOS7在/etc/grub2.conf引导中配置1G大页内存
  10. Word2Vec算法详解(相关背景介绍)
  11. 你需要启用steam社区界面功能以进行购买_钱包,危!Jump现在可以查看Steam游戏折扣了!更多功能进来看!(内有福利)...
  12. 匹配区县代码_省份、城市、区县三级联动Html代码
  13. 东大22春领导科学与决策X《领导科学与决策》在线平时作业3百分非答案
  14. package.json简介
  15. 2021年中国移动广告行业发展现状及趋势:发展趋势不断攀升,互动广告已逐渐成为主流[图]
  16. 图像处理算法之模糊检测
  17. 【面经】TP-LINK 图像算法工程师(提前批)
  18. 不同型号的二极管模块并联_原来我不懂二极管…
  19. uni-app引入阿里图标
  20. 关于使用VMware Workstation Pro16 创建Kali-Linux虚拟环境的操作办法以及库源的配置

热门文章

  1. “法外狂徒”为什么那么多?用程序来告诉你!
  2. 高等代数 线性映射(第9章)1 概念,运算,核与象
  3. 启动Nginx时报错:error while loading shared libraries: librdkafka.so.1: cannot open shared object file: No
  4. 介绍篇 决策引擎环节
  5. INTERCEPTOR DISCONNECTED 的问题
  6. android 百度网盘 播放器,最好用的安卓播放器,支持云盘播放,看电影必备
  7. 用mac原生的日历和automator,实现定时发微信
  8. [构造] Codeforces Gym 101173 CERC 16 K BZOJ 4796 Key Knocking
  9. leet code: Two Sum
  10. 使用苹果手机/PAD做树莓派的外接屏幕