來源:http://www.aliued.com/?p=3240javascript
相信如今不少人在使用react+webpack作項目,而後經過babel來解決ES6/7的兼容性問題,對於ie8的兼容也有一些經驗和方案。不過今天在解決匯金帳房的ie8兼容過程當中仍然遇到一個坑,同時發現現有資料的一些問題。css
首先說一下兼容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 中提到的問題,其實主要分三類。java
對於這個問題,主要是解決es3的保留字在es3環境下的正確使用,default是暴露最多的問題,由於你們都在寫export default xx。對於這個問題,目前比較快捷的方式就是使用es3ify,在webpack中就是添加es3ify-loader,代碼以下:node
module: { loaders: [{ test: /\.jsx?$/, loaders: ['es3ify-loader'], } ] }
它主要作的事情就是對於一些保留字的使用作了es3兼容,以及一些額外的處理,好比去除數組尾部的多餘逗號:react
// 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 就沒有必要了,你會發現這些插件就是在解決部分問題:webpack
// babel轉換前 var a = { class: "haha" //變更處 } a.class = "bb"; //babel轉換後 var a = { "class": "haha" } a.class = "bb"; //變更處
// babel轉換前 var a = { class: "haha" } a.class = "bb"; //變更處 //babel轉換後 var a = { class: "haha" } a["class"] = "bb";//變更處
因此也會把export.default轉爲export[「default」], 即解決了default不兼容問題。git
先跨過Object.defineProperty問題,由於那裏是重要的坑點。而對於上面的這三個問題,實際上屬於同一類問題,即對一些ES6 API缺失的模擬。好比常見的Object.assign,Promise對象,fetch等等,這些能夠經過統一引用「babel-polyfill」來解決,若是感受「babel-polyfill」過重,也能夠針對所須要的API自行引用對應的polyfill。polyfill的應用能夠有兩種方式:github
這裏最後一個問題:
以及console對象的兼容問題就比較簡單,也均可以經過對應polyfill解決,就很少作解釋。web
這裏整個問題的說明已經滯後,且有錯誤:express
首先,這裏說的 「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點:
最直接的解決辦法就是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兼容方案是:
各位還遇到哪些問題能夠一塊兒討論,積累經驗,整個排查過程也對不少知識理解的深入了一點。