報告老闆,我們的H5頁面在iOS11系統上白屏了!

dreamapplehappy發表於2020-08-04

0802.png

時間回到一週前,當時剛開發完公司A專案的一個新的版本,等待著測試完成就進行釋出。此時的我也準備從連續多日的緊張開發狀態中走出來,以為可以稍稍放鬆一下。而那時的我還不知道,我即將面臨一個強大的Bug選手,更不知道我要跟這個Bug來來回回進行多次的搏鬥。當然,我們能看到這篇文章也就說明了我最終解決了這個Bug,而且這個過程也是相當的精彩的。什麼?你不相信,那就讓我來帶你進入這個“跌宕起伏”的經歷中吧。

友情提示:接下來的文章也許有一點長,但是希望你能夠堅持讀下去。我相信我在解決這個Bug的過程中的一些思路會給你帶來一些思考。當然也希望你在這個過程中能夠像我一樣學習到一些新的知識,為以後排查類似的Bug積累一些經驗。好啦,話不多說,讓我們開始吧。

專案介紹

先來簡單介紹一下A專案,這是一個基於Vue框架的專案,專案使用的也是Vue CLI這個開發工具。這個專案是需要整合在別的APP中的,也就是頁面需要在APP中進瀏覽和操作。這個專案在我接手之前已經開發過一段時間了。所以專案中的一些依賴庫和工具庫版本相對比較低,這也給我後續的除錯以及解決Bug的過程增加了一些困難。

BUG初現

當時開發完成之後,就交給我們這邊的測試和另一個城市的相關同學去驗收這次開發的功能。在我們這邊一切都很正常,測試這邊也沒有反饋有什麼問題。但是在另一個城市的同學小C的iPhone手機上卻發現了白屏,開啟頁面之後什麼內容也沒有。

發現了這個問題之後,我再次跟我們這邊的測試同學確認了一下,看看我們這邊測試的iOS系統的iPhone手機有沒有這個問題。經過測試的測試,發現我們這邊的幾臺iPhone手機都沒有問題。然後就問了小C他使用的測試手機的系統版本是多少,當時感覺應該跟iOS的系統版本有關係。

小C反饋說他的iPhone是6S Plus,然後系統的版本是11.1.2。我問了我們這邊測試使用的iPhone版本都是多少,測試反饋說系統的版本都是12以上的。所以到這裡,我確定了這個白屏Bug的出現肯定跟iPhone手機的系統有關係

重現BUG之路

雖然確定了問題出現的環境,但是因為我身邊沒有系統是11的iPhone手機,所以想讓這個問題重現就變成了一個難題。詢問了身邊的同事,大家的系統版本也都普遍高於12,所以借用別人的手機進行除錯這個方法暫時也不可行。

在平時的開發中,如果網頁在iOS系統的APP中有一些問題的話,我們一般都會通過Safari瀏覽器進行除錯。但是因為這次出現問題的iPhone手機不在我這裡,並且我這邊也沒有相同系統的手機。所以想通過真機進行除錯就不太可能了。那怎麼辦呢?這個問題肯定是要解決的,我也相信辦法總比困難多

想要進行除錯,最簡單的辦法就是讓我有一個系統是11的iPhone手機。所以我就搜尋看看有沒有什麼辦法可以給iPhone手機安裝11的系統。一搜尋還真的有,過程也不算是很複雜。但是其中有一個步驟是需要到一些論壇或者第三方的助手網站下載跟自己手機型號相匹配的iOS系統,這個步驟讓我有點感覺不安全。畢竟不是官方的,不能夠保證安全性。而且也未必有版本是11的系統。所以這個方案就暫時作罷

在我搜尋的過程中,我發現有網友說可以使用Xcode安裝相應系統版本的iPhone模擬器來進行除錯。哎,你說我怎麼沒有想到這個辦法呢?這確實是一個不錯的辦法。因為之前跟公司的同事學習過Swift,也瞭解過Xcode的一些操作。突然感慨,真是技多不壓身,你不知道你什麼時候就會用上你學過的知識。所以有條件的話,還是多學習一些知識。額,有點跑題了。

安裝Xcode

我開啟公司的電腦,開始安裝Xcode,但是發現公司的電腦系統版本太低,安裝Xcode需要升級系統,所以沒辦法,先升級系統吧。因為升級的時間比較長,我想到自己家中的Mac電腦上是有安裝過Xcode,所以決定先回家。留下公司的電腦慢慢升級。

回到家,二話不說就開始準備除錯,但是發現我的Xcode上面的iPhone模擬器的系統版本也都是12以上的,查了一下資料,Xcode是可以安裝不同系統版本的模擬器的,於是我就安裝了系統版本是11的模擬器。這個過程需要我們開啟Xcode的偏好設定,然後在Components選項中,選擇下載你要安裝的對應系統版本的模擬器。

安裝iOS11的模擬器

安裝成功之後,執行iPhone 6S Plus模擬器,使用模擬器的Safari開啟h5的頁面地址,果然是白屏。

iPhone 6S Plus模擬器出現白屏

小樣,終於把這個問題給復現了,這樣就距離解決這個Bug不遠了。我開啟MacSafari瀏覽器,進入開發者模式,發現瞭如下所示的報錯

Safari瀏覽器控制檯的報錯

我搜尋了一下這個錯誤,發現是因為專案中使用了...ES6擴充套件運算子,然後iOS 11系統不支援這個這個運算子。這麼容易就找到問題了,開心。想到這個問題還是比較好解決的,可以通過使用Babel的一些外掛,很容易就可以將這個問題解決掉。然後我就開心的睡覺去了,心想這個問題也不是什麼大問題,明天處理一下就好了。

安裝Safari Technology Preview

第二天到公司,我就在專案中的babel的配置檔案中新增了相應的外掛

{
  ...  // 省略原來的配置內容
  "plugins": ["@babel/plugin-proposal-object-rest-spread"]
}

然後釋出到測試環境中。告訴了小C同學再次測試一下,我也在等著解決這個Bug的好訊息。但是,出現的卻不是好訊息,小C給我回復說還是不可以。什麼,不可能呀,我就馬上用公司的電腦再次進行測試。當我用公司電腦的Safari除錯系統是iOS 11iPhone 6S PLus模擬器的時候,卻發現出現了下面這個情況:稽核警告:“data-custom”太新,無法在此檢查的頁面上執行

稽核警告:“data-custom”太新,無法在此檢查的頁面上執行

我就又搜尋了一下為什麼會出現這個問題,終於讓我找到了答案Safari瀏覽器的Web Inspector工程師也說這是一個Bug,不過他們已經修復了,在下個釋出的版本中就可以正常使用新的Safari瀏覽器去除錯比較老的iOS系統的模擬器了。知道現在這個版本的Safari除錯不了模擬的iOS 11系統的頁面。我有點沮喪,總不能我現在回家把我的電腦拿過來吧??當我想著該如何解決的時候,我發現了上面那個回答中提到了Safari Technology PreviewSafari技術預覽

stackoverflow上面Safari瀏覽器的Web檢查器的開發者的回覆

我看這個名字感覺有點希望,然後就搜尋了一下Safari Technology Preview是什麼。然後就發現它相對於Safari就跟Chromium相對於Chrome是一樣,都相當於是開發版本的瀏覽器。

Safari Technology Preview

這時,我覺得可以使用Safari Technology Preview進行除錯。所以就下載了Safari Technology Preview,當我開啟Safari Technology Preview然後進入開發者模式後,發現確實可以除錯iOS 11系統的頁面。然後我就看了一下為什麼還是白屏的問題。發現出現的錯誤還是上次的問題:

SyntaxError: Unexpected token '...'. Expected a property name.

也就是說這個問題還沒有解決掉,因為打包後的程式碼是沒有SourceMap的,所以要想看更詳細的報錯資訊,需要在本地進行除錯。本地的環境中是有SourceMap的,可以定位到更詳細的錯誤資訊,我在本地執行了專案,然後我開啟了控制檯的錯誤詳情,發現是使用的一個第三方的庫出現了問題。

找到了出現問題的使用的第三方庫

那麼到這裡為止,可以說明上面我們使用的Babel外掛沒有處理這個第三方的庫,所以現在我們的問題就變成了:如何解決第三方庫中出現的...擴充套件運算子沒有被編譯為ES5語法的問題

將第三方庫中的ES6語法進行編譯

檢視Vue CLI中相關的配置方法

這時我又仔細的看了一下Vue CLI的相關文件,發現確實在瀏覽器的相容性這個章節中,提到了一些處理的方法。原來我們在專案中寫的程式碼預設會幫我們轉換為ES5的語法的,但是如果專案中依賴的第三方庫需要polyfill的話,那需要我們手動進行配置。一看到這裡,我感覺黎明就要來了

Vue CLI瀏覽器相容性

我就開始嘗試這三種方法。我發現第一種方法是比較簡單的,也很好配置。於是我就嘗試了第一種方法。在專案的vue.config.js中新增如下的配置:

...  // 省略的配置
transpileDependencies: [
  'module-name/library-name' // 出現問題的那個庫
],
...  // 省略的配置

重新執行專案,當我將要為即將到來的成功歡呼鼓掌時,控制檯突然報告瞭如下的錯誤:
Uncaught TypeError: Cannot assign to read only property 'exports' of object '#<Object>'

Uncaught TypeError: Cannot assign to read only property ...

這個報錯是在Chrome瀏覽器的控制檯出現的,因為專案在本地重新執行之後會首先開啟Chrome瀏覽器。真是的,一個問題還沒有解決,又出來了一個新的問題。然後再次查詢資料後發現,原來是因為這個第三方的庫是一個CommonJS型別的庫,而Babel預設處理的是ES6module型別的庫,所以這裡就又出現了新的問題。

https://github.com/webpack/webpack/issues/4039 sokra的回覆

第一種方法遇到了阻礙,先暫停一下。我準備繼續嘗試下面兩種方法。但是因為後面兩種方法對原來的專案改動有點大,所以我直接通過Vue CLI建立了一個新的專案,在package.json中加入專案中使用的那個第三方包的依賴,使用公司的包管理工具安裝了依賴。然後執行專案,開啟控制檯確實發現了相同的錯誤。但是開啟詳情以後,發現出錯的路徑跟我原來專案不一致。然後我這次抱著試一試的心態,繼續使用了第一種方法嘗試看看可不可以。然後複製了出錯路徑的包名稱,在vue.config.js檔案中的對應位置新增了如下的配置程式碼:

...  // 省略的配置
transpileDependencies: [
  'module-name-new/library-name-new' // 出現問題的那個庫
],
...  // 省略的配置

然後重新執行專案,發現居然可以了。啊,居然可以了。為什麼我在原來的專案中這樣卻不可以呢?我看了一下原來專案的依賴以及現在新的測試專案的依賴,發現它們的vue, babel版本差了好多。我猜測可能是因為這個原因。但是現在肯定不可以貿然升級這些依賴的版本,因為為了解決這個問題再次帶來新的問題就得不償失了。

還有一個問題就是為什麼同樣的第三方庫,在原來的專案中和現在的專案中報錯的路徑不一樣。而且看著像是使用了兩個不一樣的第三方庫。這裡先留個懸念,我會在後面的文章中進行解釋。

接下來,我開始在測試專案中繼續嘗試剩下的兩種方法,對於第二種方法,因為老專案中使用的presets是沒有polyfills這個配置選項的,到現在為止出問題的這個第三方庫我不知道除了這個...物件擴充套件操作符之外還有沒有別的依賴。所以這個方法我暫時也放棄了。

對於第三個方法,我覺得可以嘗試,首先我將測試專案中的一些關鍵依賴進行了手動降級,然後按照上面的第三個方法的步驟在測試專案中使用。但是發現測試專案執行之後,提示需要安裝core-js,安裝core-js之後還報錯,再次提示需要安裝es.module.regex.match等等很多依賴,繼續查資料,發現需要把配置中的 useBuiltIns修改,但是因為我接手的這個專案是老專案,依賴比較多,不確定修改useBuiltIns這個配置選項後會不會出現新的問題。所以也不敢貿然修改這個配置選項,所以也暫時放棄了這個方法。

我後來想了一下,對於...擴充套件運算子來說,這是一個新的語法。是不能夠通過一些polyfills去解決的。需要Babel對這個語法進行編譯,然後才可以在低版本的系統中使用,所以解決的辦法還是要讓Babel對這個庫再次進行編譯。

尋找新的突破口

當進行到了這裡的時候,似乎沒有了出路。一時間我感覺我要被這個Bug打敗了,我似乎聽到了它無情的嘲笑,“小夥子,是不是被我折磨的沒有脾氣啦;放棄吧,你是沒辦法打倒我的。哈哈哈。。。

Photo by sebastiaan stam on Unsplash

但是,它看錯我了,Bug越是難解決,我對它就越有興趣。所以我決定好好理一下思路,準備再次揚帆起航。

我發現第一種辦法其實是起作用的,只不過是因為一個是CommonJS型別的,一個需要是ES6 module型別的。所以我決定從這個地方入手,於是我決定查查相關的資料,看看Babel有沒有辦法可以即能夠處理CommonJS模組,又能夠處理ES6 module模組呢?終於,功夫不負有心人,我發現了Babel裡面有這麼一個配置sourceType,如果把sourceType設定為unambiguous就可以解決這個問題

https://babeljs.io/docs/en/options#sourcetype

這樣Babel就會根據模組檔案中有沒有import/export來決定使用哪種解析模組的方式。於是我再次使用了第一種方法,在vue.config.js中新增了transpileDependencies選項的配置,然後在專案中的Babel配置檔案中新增了如下的配置:

module.exports = {
  ...  // 省略的配置
  sourceType: 'unambiguous',
  ...  // 省略的配置
};

發現的確可以,這一刻成功的喜悅再次降臨。然後我再次打包,再次把程式碼部署到測試環境,趕忙讓小C同學再次測試一下,發現的確可以。歐耶,終於解決這個問題了。我終於可以鬆一口氣了,哈哈哈。。。小樣,這怎麼會難得到我呢?

但是,當我仔細閱讀將這個選項設定為unambiguous時,我發現了一些問題。因為這樣的話會有一些風險,因為就算不使用import/export語句的這些模組也可能是完全有效的ES6 module,所以這樣的話就有可能會出現一些意外的情況。怎麼辦,我似乎在一不留神的時候又被Bug卡住了脖子

https://babeljs.io/docs/en/options#sourcetype

我覺得老天總是給我開玩笑,當我從一個坑裡跳出來,以為沒有危險的時候。前面突然又多出來一個坑,我一不留心就又掉了進去。我感覺既然都走到了這裡,肯定要繼續走下去,一定有辦法可以優化我現在遇到的問題。我就很仔細的再次看了一下Babel的配置說明文件,這個時候就心想如果我對Babel再熟悉一些就好了。沒關係,繼續努力。終於,我似乎看到了什麼了不得的配置選項。

https://babeljs.io/docs/en/options#overrides

我在Config Merging options裡發現了overrides選項,這個配置選項不正是我需要的嗎?我可以利用這個配置選項將我需要的第三方包使用unambiguous的處理方式,然後其他的第三方庫都按照之前的方式處理不就可以了。哈哈哈,我真是個天才,我心裡這樣對自己說?。

Photo by bruce mars on Unsplash

所以只需要在專案的babel.config.js中寫下如下的配置就可以了:

module.exports = {
  ...  // 省略的配置
  overrides: [
    {
      include: './node_modules/module-name/library-name/name.common.js',  // 使用的第三方庫
      sourceType: 'unambiguous'
    }
  ],
  ...  // 省略的配置
};

對了,還有一件事情還沒有說,那就是上文提到的關於為什麼使用公司自己的包管理工具下載下來的node_modules包的名稱跟使用官方的npm包管理工具下載的包的名稱不一致的問題。原因是公司使用的包管理工具是cnpm的一個修改版本。又因為cnpm為了提高下載的速度,使用了cnpm/npminstall,所以才會出現下載的包名比較混亂的情況,詳情可以看這裡

到此完結撒花,總結一下:出現白屏的原因是因為使用的第三方庫的包中使用了...擴充套件運算子,然後因為第三方的包預設是沒有被Babel處理過的,所以在不支援...iOS 11系統上就出現了白屏。解決的方式就是通過給vue.config.js的配置檔案中transpileDependencies配置選項中新增上出問題的包的名稱就可以了。當然如果專案比較老,可能還需要像文章上面寫的那樣的處理方式。

解決這個Bug過程就像是升級打怪一樣,不斷失敗,不斷嘗試,只要不放棄,終有成功的那一天。如果你堅持看到了這裡,那說明你也很棒呀。在當今這個資訊爆炸的時代裡,能夠堅持看完一篇很長的文章已經很不錯了。

一點反思與思考:這個過程中我也發現了自己對BabelVue CLI其實沒有那麼熟練,如果我對它們比較熟練的話,那我解決這個Bug應該會花費更少的時間。當然,現在把它們學習好也不算晚。要抱著學習的態度,這次解決這個Bug的過程,就是我以後解決其它類似Bug的經驗。還有在解決Bug的這個過程中要有耐心,當然在嘗試之後也要學會放棄錯誤的方向

寫這篇文章也花費了我不少的時間,如果你有所收穫或者感悟,不妨點贊,轉發,收藏走一波,這個要求應該不算過分吧??

如果你對本篇文章有什麼意見和建議,都可以直接在文章下面留言,也可以在這裡提出來。也歡迎大家關注我的公眾號關山不難越,學習更多實用的前端知識,讓我們一起努力進步吧。

公眾號:關山不難越

相關文章