博客 / 詳情

返回

前後端未分離項目檢視ES6和Web API兼容性方案

背景

近半年,已產生幾起FreeMarker項目(後面統一簡稱FM項目)在IE瀏覽器或者360瀏覽器兼容模式環境下下因使用 ES6+ 高級語法特性而運行出錯的線上問題,導致業務流程無法執行下去。雖然一直在強調開發同學在做FM項目的需求時不要使用ES6,但是口頭上的的團隊公約約束性不強,加上開發同學早已習慣性使用ES6,使之問題層出不窮,另外,還有些Web Apis和樣式在IE上存在兼容性問題(比如:Element.scrollIntoView()Element.scrollTo()),這些API和樣式的使用也需要強制禁用,因此亟需用工具在編程階段約束。

因使用ES6或者使用兼容性較差的Web Apis導致的缺陷:

image-20210812142554665.png

因使用ES6或者使用兼容性較差的Web Apis導致的線上問題(都是三級事件):

image-20210812143136040.png

另外,因jira單備註不夠清晰,樣式兼容性導致的問題未作統計。

方案

下面方案只針對ES6+語法特性和兼容性較差的Web Apis的處理,對於樣式兼容性處理另作方案。

Babel轉譯

webpack(使用其他構建工具也可以,比如 gulp)中引入babelES6語法轉譯成ES5,但是在構建之前需要將源文件做如下改造:

var managePage = {};

function  debounc() {}

改造後:

window.managePage = {}

window.debounc =  function  debounc() {} 

為什麼不設置libraryTarget: 'window'以全局變量輸出?

因為libraryTarget: 'window'的打包方式只能將export導出的對象以全局對象的方式輸出,需將源代碼做如下改造,改造後在線上運行不會有問題,在本地啓動項目會報“xxx is not defined”,因為export包裹後的變量會變成了局部變量。

export var managePage = {};

export function  debounc() {}

babel-polyfill

babel 默認只轉換 js 語法,而不轉換新的 API,比如 Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise 等全局對象,以及一些定義在全局對象上的方法(比如 Object.assign)都不會轉碼。

舉例來説,es2015Array 對象上新增了 Array.from 方法。babel 就不會轉碼這個方法。如果想讓這個方法運行,必須使用 babel-polyfill。(內部集成了 core-jsregenerator)

使用時,在所有代碼運行之前增加 require('babel-polyfill')。或者更常規的操作是在 webpack.config.js 中將 babel-polyfill 作為第一個 entry。因此必須把 babel-polyfill 作為 dependencies 而不是 devDependencies

babel-polyfill 主要有兩個缺點:

  1. 使用 babel-polyfill 會導致打出來的包非常大,因為 babel-polyfill 是一個整體,把所有方法都加到原型鏈上。比如我們只使用了 Array.from,但它把 Object.defineProperty 也給加上了,這就是一種浪費了。這個問題可以通過單獨使用 core-js 的某個類庫來解決,core-js 都是分開的。
  2. babel-polyfill 會污染全局變量,給很多類的原型鏈上都作了修改,如果我們開發的也是一個類庫供其他開發者使用,這種情況就會變得非常不可控。

因此在實際使用中,如果我們無法忍受這兩個缺點(尤其是第二個),通常我們會傾向於使用 babel-plugin-transform-runtime

但如果代碼中包含高版本 js 中類型的實例方法 (例如 [1,2,3].includes(1)),這還是要使用 polyfill。下圖是官網描述:

image-20210817145550319.png

方案的優點:

  • 任何JavaScript高級語法特性都能很好的支持;
  • 可以對代碼做壓縮、混淆處理,減小文件大小,提高靜態文件加載性能,提高源代碼安全性;

方案的缺點:

  • 在構建之前需要對源文件進行改造,工作量比較大,容易出錯;
  • 需要引入構建工具,添加構建配置,可能還需要修改Jenkinsfile CI腳本;

ESlint警告

ESlint是一款插件化的javascript代碼檢測工具,我們可以利用其在FM項目腳本中是否使用了ES6+語法,如果腳本中有使用,立即報錯處理,並提示哪些關鍵字的使用屬於ES6語法。另外,為防止開發同學強推使用了ES6+語法的代碼,在git hookspre-commit鈎子中執行eslint命令校驗,若通過,則代碼成功commit;若不通過,控制枱會打印錯誤日誌。

注意:請開發同學在commit時不要添加--no-verify參數,否則會跳過校驗

具體落地流程:

(1)在VScode編輯器中安裝ESlint拓展插件,並啓用

(2)在項目根目錄或者需要檢視的項目下手動或者執行npx eslint --init創建.eslintrc.json配置文件,並添加如下配置項:

{
  "root": true,
  "env": {
    "browser": true,
    "jquery": true
  },
  "parserOptions": {
      "ecmaVersion": 5,
      "sourceType": "script"
  },
  "rules": {
  }
}

(3)package.json配置目標環境

{
    "browserslist": [
        "defaults",
        "not ie <= 8"
    ]
}

(4)因為管理中心不需要支持IE,因此關於此端項目的js不需要ESlint檢視,我們在根目錄下創建.eslintignore配置文件,向其中添加需要跳過檢視的文件路徑,比如src/main/webapp/static/js/adjustPage.js不需要檢視:

// .eslintignore
src/main/webapp/static/js/adjustPage.js

(5)在根目錄執行npm init生成package.json,安裝lint-staged、husky,當執行git commit提交代碼之前觸發precommit鈎子進行eslint校驗

  • 安裝lint-staged、husky
$ yarn add lint-staged husky -D
  • 生成.husky文件夾以及pre-commit鈎子腳本
$ npx husky install // 生成.husky文件夾
$ npx husky add .husky/pre-commit 'npx lint-staged' // 添加pre-commit鈎子到.husky中
注意:因為這裏我用了npx,需要大家全局安裝npm i npx -g
  • 配置package.json
// 新增如下配置
{
  "lint-staged": {
    "*.js": [
      "eslint --ignore-path .gitignore"
    ]
  }
}

此時,執行git commit -m 'xxx'後檢視提交的js腳本,但是git push時會報錯沒有change-id,因為通過gerrit code review需要change-id,執行下述命令即可解決:

scp -p -P 29418 a02313@gerrit.casstime.net:hooks/commit-msg .husky
注意:a02313賬號部分改成自己的

執行效果圖:

image-20210813113825777.png

但是上述配置只能檢視到一些ES6語法,對於fetch、includes等不支持IE瀏覽器的api是檢視不到的,你就需要考慮禁止使用 fetch()API。在開發階段,人工保證 API 的兼容性是不可靠的,更可靠的方式是藉助工具來自動化掃描,例如下面要介紹的 eslint-plugin-compat

使用 eslint-plugin-compat

eslint-plugin-compatESLint 的一個插件,由前 uber 工程師 Amila Welihinda 開發。它可以幫助發現代碼中的不兼容 API

api-compat-1.png

下面介紹如何在工程中接入 eslint-plugin-compat

(1)安裝 eslint-plugin-compat

安裝 eslint-plugin-compat 和安裝其他 ESLint 插件類似:

$ npm install eslint-plugin-compat --save-dev

(2)修改 ESLint 配置

之後,我們需要修改 ESLint 的配置,加上該插件的使用:

// .eslintrc.json
{
  "extends": ["eslint:recommended", "plugin:compat/recommended"],
  "rules": {
    //...
  },
  "env": {
    "browser": true
    // ...
  },
  // ...
}

(3)配置目標運行環境

通過在 package.json 中增加 browserslist 字段來配置目標運行環境。示例:

{
  // ...
  "browserslist": ["chrome 70", "last 1 versions", "not ie <= 8"]
}

上面的值表示 Chrome 版本 70 以上,或每種瀏覽器的最近一個版本,或者非 ie 8 及以下。這裏的填寫格式是遵循 browserslist (https://github.com/browsersli... )所定義的一套描述規範。browserslist 是一套描述產品目標運行環境的工具,它被廣泛用在各種涉及瀏覽器/移動端的兼容性支持工具中,例如 eslint-plugin-compat 、babel、Autoprefixer 等。下面我們來詳細瞭解一下 browserslist 的描述規範。

browserslist 支持指定目標瀏覽器類型,並且能夠靈活組合多種指定條件。

測試效果

完成了 browserslist 規則的配置後,我們就可以結合 ESLint 掃描工程中的 API 兼容問題。同時 VS Code 插件也可以即時提示不兼容的 API 調用。

image-20210818093845893.png

使用 eslint-plugin-builtin-compat

eslint-plugin-compat 的原理是針對確認的類型和屬性,使用 caniuse (http://caniuse.com) 的數據集 caniuse-db 以及 MDN(https://developer.mozilla.org... )的數據集 mdn-browser-compat-data 裏的數據來確認 API 的兼容性。但對於不確定的實例對象,由於難以判斷該實例的方法的兼容性,為了避免誤報,eslint-plugin-compat 選擇了跳過這類 API 的檢查。

例如,foo.includes 在不確定 foo 是否為數組類型的時候,就無法判斷 includes 方法的兼容性。在下圖中,我們在使用上面的 browserslint 配置的情況下,includes 方法的兼容問題並沒有被掃描出來:

api-compat-includes-1.png

然而,從 caniuse 上可以查知,Array.prototype.includes() 方法不能被 IE 兼容:

image-20210817151252336.png

為了避免漏報這種問題,我們可以結合另一個兼容檢查插件 eslint-plugin-builtin-compat 。該插件同樣藉助 mdn-browser-compat-data 來進行兼容掃描,與 eslint-plugin-compat 不同的是,該插件不會放過實例對象,因此它會把所有 foo.includesincludes 方法當成是 Array.prototype.includes() 方法來掃描。可想而知,這個插件可能會導致誤報。因此建議將其告警級別改為 warning 級別。

(1)安裝 eslint-plugin-builtin-compat

$ npm install eslint-plugin-builtin-compat --save-dev

(2)修改 ESLint 配置

eslint-plugin-compat 類似,我們可以修改 ESLint 的配置,加上該插件的使用。但由於該插件容易誤報,因此只建議將其告警級別改為 warning 級別:

// .eslintrc.json
{
  "extends": ["eslint:recommended", "plugin:compat/recommended"],
  "plugins": [
    "builtin-compat"
  ],
  "rules": {
    //...
    "builtin-compat/no-incompatible-builtins": 1
  },
  "env": {
    "browser": true
    // ...
  },
  // ...
}

加入該插件後,可以發現 Array.prototype.includes() 方法將會被該插件告警:

image-20210818094029925.png

該方案的優點:

  • 配置比較簡單,不用改造腳本代碼,不用修改Jenkinsfile CI腳本;

該方案的缺點:

  • 源代碼直接在控制枱能看到,不安全;

IDEA中引入ESlint

實際需求中,後端同學可能會負責FM項目的部分前端需求,因此還需要滿足IDEAESlint的使用。

(1)查看本地有沒有安裝node環境和全局安裝eslint,如果沒有就需要安裝,安裝之後重啓IDEA,然後勾選Enable啓動ESlint檢視

image-20210813163012398.png

(2)如果重啓IDEA後啓動eslint服務時報如下錯誤,這是因為IEADplugins/JavaScriptLanguage/languageService/eslint版本過低,存在插件兼容問題

image-20210813163045270.png

image-20210813163626125.png

解決方法,照如下修改plugins/JavaScriptLanguage/languageService/eslint/bin/eslint-plugin.js文件,然後重啓IDEA

//this.cliEngine = require(this.basicPath + "lib/cli-engine");
this.cliEngine = require(this.basicPath + "lib/cli-engine").CLIEngine;

正常效果:

image-20210813164107066.png

綜上所述,如果已有項目組在FM項目中已經引入構建工具,比如備貨組的商城頁面已經引入多種語言開發,再使用構建工具打包,此類項目不用考慮ES6+語法問題,其他場景下的FM項目可以選擇使用第二種方案。本文的方案作為拋磚引玉,大傢俱體場景具體分析。

組織實施


問題反饋與建議

一切使用問題或者更好的建議都可以向XX同學反饋,謝謝支持與配合!

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.