1. 程式人生 > >react 專案的一個ie8相容性問題

react 專案的一個ie8相容性問題

相信現在很多人在使用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: functionget() {
    return _reducer.LOCATION_CHANGE;
  }
});
Object.defineProperty(exports, 'routerReducer', {
  enumerable: true,
  get: functionget() {
    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的相容支援問題並不良好,且現在 [email protected] 版本已經放棄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相容性問題

相信現在很多人在使用react+webpack做專案,然後通過babel來解決ES6/7的相容性問題,對於ie8的相容也有一些經驗和方案。不過今天在解決匯金賬房的ie8相容過程中仍然遇到一個坑,同時發現現有資料的一些問題。 首先說一下相容ie8的通用方案,大家一般是按照 Make your React

react開啟一個專案

npx create-react-app my-app cd my-app npm start 在命令列裡執行以上語句就可(前兩天剛剛發現,最新版的react對webpack的版本要了新要求,大概是他新加的內容使用到了webpack高版本中的一些內容,所以使用時注意你的node版本和webpack

clone一個react專案怎麼執行

首先當你從git上面clone一個專案的時候怎麼讓專案跑起來, 首先看專案目錄結構,找到README.md上面有專案執行的步驟, 如果沒有可以看package.json檔案,找到scripts 上面有dev 所以跑起來專案就使用npm run dev 有start 就使用 np

react 搭建一個專案 (高版本)

1、先引用 react.js,redux,react-router 等基本檔案,建議用npm安裝,直接在檔案中引用。 npm install -g   create-react-app            &nbs

如何手動使用webpack搭建一個react專案

前言搭一個腳手架真不是一件容易的事,之前為了學習webpack是怎麼配置的選擇自己搭建開發環境,折騰了好幾天總算對入口檔案、打包輸出、JSX, es6編譯成es5、css載入、壓縮程式碼等這些基礎的東西有了一個大體的瞭解。 大體專案結構(模仿網上大佬)   然後就是正題了,當然最先要做的是

建立第一個React專案

一.環境準備:     2.將npm映象改為淘寶cnpm:            1.得到原本的映象地址:                    npm get registry                    > https://registry.npmj

React Native 一個專案其中一些主要功能實現 (頂部導航欄(可滑動),網路解析,上拉重新整理,下拉載入)

//網路解析 import React, { Component } from "react"; import { View, Text, TouchableOpacity } from "react-native"; import RefreshListView, { Re

今天遇到一個坑,react專案報錯 ,line 9 : Absolute imports should come before relative imports

首現看這個錯誤的名稱,第九行的匯入應該在相關匯入之前,這行程式碼是 import { HashRouter as Router, Switch, Redirect, Route } from 'react-router-dom'; 修改方式,將改行程式碼放在匯入react程式碼後面也是第二行:

如何開始一個react專案

1、新建目錄——>在dos視窗進入到目錄路徑下,輸入npm init   //進行初始化 初始化時packname和entry point(入口檔案)需要輸入值,其餘的可以不輸值 完成後1-1目錄下會生成一個package.json的配置檔案,裡面即是包的

一個React專案——使用官方腳手架create-react-app搭建第一個React專案

準備工作 1,node js環境 一、下載create-react-app           1)使用命令列的方式進入目標資料夾(shift+右鍵,此處開啟powershell視窗)(win10)           2)輸入命令  npx create-react

使用webpack搭建一個react專案

npm init package.json檔案配置 { "name": "serach-bar", "version": "1.0.0", "description": "", "main": "index.js", "scrip

react開發:從零開始搭建一個react專案

從頭開始建立一個React App - 專案基本配置 npm init 生成 package.json 檔案.安裝各種需要的依賴: npm install --save react - 安裝React.npm install --save reac

搭建一個react專案

從頭開始建立一個React App - 專案基本配置npm init 生成 package.json 檔案. 安裝各種需要的依賴:npm install --save react - 安裝React. npm install --save react-dom - 安裝React Dom,這個包是用來處理vi

使用creat-react-app快速新建一個react專案

Facebook 官方推出Create-React-App腳手架,基本可以零配置搭建基於webpack的React開發環境,內建了熱更新等功能。由於create-react-app命令預先安裝和配置了webpack和babel,同時也初始化了npm(可以通過npm init來

開始一個React專案(三)路由基礎(v4)

前言 前端路由 開始今天的話題之前,讓我們先來了解一下前端路由,Ajax誕生以後,解決了每次使用者操作都要向伺服器端發起請求重刷整個頁面的問題,但隨之而來的問題是無法儲存Ajax操作狀態,瀏覽器的前進後退功能也不可用,當下流行的兩種解決方法是:

開始一個React專案(一)一個最簡單的webpack配置

前言 搭一個腳手架真不是一件容易的事,之前為了學習webpack是怎麼配置的選擇自己搭建開發環境,折騰了好幾天總算對入口檔案、打包輸出、JSX, es6編譯成es5、css載入、壓縮程式碼等這些基礎的東西有了一個大體的瞭解。後來有一次組內分享技術,我作死的選擇

開始一個React專案(四)路由例項(v4)

前言 在開始一個React專案(三)路由基礎(v4)中我大概總結了一下web應用的路由,這一篇我會接著上一篇分享一些例子。 簡單的路由示例 一個最簡單的網站結構是首頁和幾個獨立的二級頁面,假如我們有三個獨立的二級頁面分別為:新聞頁、課程頁、加入我們,路

Github Actions簡單部署一個vue/react專案

大體介紹 本文對github actions部署前端專案做一個簡單的總結,總體來說,我感覺用它想要部署一個前端專案,可以說非常簡單,簡單得令人震驚

react專案總結

簡介 react 是一個基於 mvvm 模式的元件化開發的 web 框架。 做 react 專案需要掌握什麼 react 功能單一,用於 UI 渲染 redux 用來管理資料 react-router 用來管理路由 webpack 用來配置工程

如何在React專案中直接使用WebAssembly

前言 自從入坑WebAssembly以來,躺了很多坑,也瀏覽了很多資料,都沒有看到很多能夠直接在前端專案中使用WebAssembly的例子。即使有,我自己按照介紹的步驟一步一步來, 也會報各種錯誤,官方的文件也寫的比較模糊。於是,就決定自己擼一個,讓React專案能夠直接的藉助Webpack,在程式碼中引入