AMP 開發體驗洗白之路

本文做者:任家樂css

原創聲明:本文爲閱文前端團隊 YFE 成員出品,請尊重原創,轉載請聯繫公衆號 ( id: yuewen_YFE ) 獲取受權,並註明做者、出處和連接。前端

難用 or 姿式不對?

「用戶體驗 > 開發體驗」Google AMP 的設計準則絕不掩飾地標記了它的核心觀念,從一開始就認可了它對開發者的不友好。react

「Webnovel m 站」做爲國內首批使用 AMP 技術的產品「關於 AMP,Webnovel 都作了什麼?」之一,結論上創造了顯著提高的性能數據,過程當中正如谷歌核心觀點所說,開發體驗很是差。最近得來一次機會,咱們 Webnovel 面向非洲用戶的新產品「Ficool m 站」實踐了全站 AMP,從中找到了使用 AMP 的新姿式 - 「Next.js + AMP + Preact」,開發體驗提高了不止一個檔次,在此分享給你們。webpack

開啓洗白之路 - 重新項目入手

Ficool m站

做爲 Webnovel 面向非洲地區的站點,它不只覆蓋 web 渠道,其藉助 Google TWA 技術打包而成的輕量 APP 還將預裝到 Android Go 系統的手機上,此係統對 APP 內存、CPU 佔用有着嚴格的要求,現已成功入庫。目前「Ficool m 站」大多數頁面還暫未被 Google 搜索引擎收錄,後續能夠跟你們分享一下實際的效果以及咱們在服務端作的進階優化。git

Why AMP?

比起「Ficool m 站是什麼?」,對本文來講更重要的是「Again,Why AMP ?」github

一則演講的啓發

今年 AMP conf 中一位伊拉克小哥的演講使人印象深入。web

圖片

No poser, no internet, no support: How AMP bridges the app gap in Iraq and other war-impacked region後端

身處於戰亂、網絡條件落後的國家,他們選擇使用 AMP,並藉助 AMP 提供的一系列組件及優化方案一步步地填補了 APP 在體驗、兼容性方面的落差,從而創建起了 APP 和曾經他們主動放棄的一批落後用戶之間的橋樑。跨域

國內的咱們天天享受着 4G 可能某一天享受的就是 5G,很難想象遙遠的非洲兄弟們忍受着怎樣的網絡條件... 咱們的產品也終於有機會去了一趟非洲「肯尼亞」,並帶回了一些「前線」消息 「 那裏的網速雖然沒有想象中差,但廣泛使用 3G 網絡且速度有限,同時移動網絡套餐相比國內要貴不少。」sass

  • 「帶寬貴」
  • 「網速慢」
  • 「 Webnovel m 站」在 AMP 上嚐鮮成功

這 3 點已經足夠推動「Ficool m 站」全站 AMP 的想法了,不只如此,咱們還走了一條 AMP 的小衆路線 - 「Next.js + AMP + Preact」。

洗白的最佳姿式

Next.js + AMP + Preact

有了 「Webnovel m 站」的經驗教訓,咱們預留時間作了大量的前期調研,並設定了一些目標:

  1. 開發過程效率要提高
  2. 代碼後期可維護性要高
  3. 關注開發者身心:儘可能使用一些現有的主流技術,擺脫舊的開發模式

最終 get 了 Next.js + AMP + Preact 這樣的組合方式,聽起來好像比 AMP + HTML 洋氣了一些?這實際上是一個極少人嘗試過的 AMP 開發形式,在開發時間的壓迫下一步棋走錯全盤皆輸!其過程採坑無數,你們不妨看看咱們都經歷了哪些歷程。

2個歷程的採坑

一、從 HTML「硬寫 」 到 React

回顧「Webnovel m 站」 AMP 的舊開發方式:直接在 HTML 中引入 AMP 組件,以開發 HTML 的方式編寫 AMP 頁面 - 不可忍!

實際上,Next.js 在 8.1 版本以上就已經支持了 AMP 的開發,這意味着咱們徹底能夠直接用 React 開發 AMP 頁面。

抽組件

舊方式「圖 1」對於組件的提取較爲困難,藉助 EJS 模板當然能夠抽離 template 的部分並使用 include 語法將其引入,但 amp-list 的部分很難提取成組件(template 的部分是不肯定的),而在 React AMP 中,咱們能夠將其提取成組件,並經過增長失敗「fallback」、加載中「placeholder」等功能「圖3」,使 amp-list 用起來更靈活。

圖片

「圖 1:列表頁 - HTML 舊方式開發」

圖片

「圖 2:列表頁 - React 方式開發」

圖片

「圖 3:列表頁 amp-list 組件 - React」

一樣的,舊開發模式下能難作到服務端渲染(直出)部分、異步渲染部分的模板統一,這是因爲 HTML 直出模板「 EJS 模板」語法和 AMP 的 「mustache template 模板」語法是大相徑庭的,然而在 React AMP 中咱們卻能夠:

例如「圖 2」中的 <BookItemTtoB> 組件,直出部分天然是正常傳遞 props,而 amp-list 的模板部分只須要用 mustache 模板語法 {{ bookId }} 傳遞值便可 ,<BookItemTtoB> 也就達到了兩種情景下的複用。

編寫樣式

繼續回顧以前的舊開發方式:樣式必須內聯放於 HTML 中的 <style amp-custom> 標籤內,就當時的本地開發流程來講,要實現直接編譯 scss 文件並將生成的 css 內聯到 <style amp-custom> 標籤內,改造起來遠沒有添加一條 webpack 配置那麼簡單,最終咱們粗暴地將 css 文件用 EJS 引入模板的方式 include 到了 <style> 標籤內,這顯然不太優雅。

新的開發流程下,咱們只需在 next.config.js 中拓展 webpack 的配置便可:引入 sass-loader 以及 Next.js 內置的 styled-jsx 「圖4」,Next.js 會直接將編譯好的 css 內聯到 <style amp-custom> 標籤內「圖 5」,就是這麼簡單。

圖片

「圖 4:next.config.js 中編譯 css 相關配置」

圖片

「圖 5:組件中引入樣式」

總結:

一、用 React 方式開發 AMP 頁面,組件的提取更簡單。

二、頁面的邏輯相比冗長的 HTML 代碼更易維護且風格統一「單一 React 風格 VS HTML + EJS + mustache 混用」。

三、樣式的編寫更加容易、開發形式也吻合當前趨勢。

二、AMP 頁面的羽翼 - amp-script

第一階段咱們實現了用 React 愉快地進行 AMP 的開發,但侷限是:React 的生命週期函數不能用、一切自定義的 JS 交互依然不能實現(AMP 不容許引入咱們本身的 JavaScript )。

好消息!在今年 4 月中旬 AMP 發佈了 amp-script,藉助它就能夠寫咱們本身的腳本了!固然,它有必定侷限性:大小上限( uncompressed 150KB),數量限制(每一個頁面只容許引入 1 個 amp-script )以及 API 限制 ... 無論有多少限制,爲了實現更多的複雜交互,咱們須要使用這個能力。

amp-script 很是規用法 - 使用 Preact

AMP 官方提供的 amp-script demo 基本都是原生 Javascript 的寫法,而在咱們的 React AMP 項目中很不但願維護 2 種風格的代碼,所以咱們探索了用 React 開發 amp-script 的可能性。

首先是解決大小限制問題:

圖片

「各框架源碼 uncompressed 大小」

size ( uncompressed ) amp-script 剩餘空間
React 110kb 150 - 110 = 40kb
Preact 8.2kb 150 - 8.2 = 141.8kb

「圖 6:React vs Preact in amp-script」

React 源碼足足有 110kb,若是使用 React,那咱們本身的腳本只能寫 40kb,這顯然不夠,所以咱們選擇了既能保持 React 開發風格、又能控制 amp-script 大小的 「Preact,React 的輕量版」。

webpack 實現 amp-script 的編譯&打包

amp-script 的引入方法不一樣於普通的 React 組件「圖 七、圖 8」,其中 amp-script 組件必須有 src 屬性,其值爲你引入的腳本文件 url(絕對地址),因爲每一個頁面只能引 1 個腳本,咱們須要保證這個腳本已是打包後的最終腳本,此時毫無疑問要使用 webpack 進行依賴分析及打包了。

圖片
「圖 7:普通組件的引入方式」

圖片

「圖 8:amp-script 的引入腳本的方式」

咱們從新爲 amp-script 的打包寫了個獨立的 webpack 配置「webpack.amp.config」,使其編譯打包過程更加單一簡單,儘可能不和 next.config.js 的配置項搞混。

webpack.amp.config 的目標:對目標腳本進行依賴分析,最終通過 babel 編譯打包成咱們所需的 es5 腳本(此腳本就是 amp-script 最終引用的腳本)。

這個功能算是 webpack 比較基本的功能了,按 webpack 官網 demo 使用便可,重點配置項可參考「圖 9」,其中 getAllAmpScriptEntries() 會獲取全部須要編譯的 amp-script 腳本路徑,最終生成的腳本存放於 Next.js 託管的靜態資源文件夾下 ./static

圖片

「圖 9:amp-script webpack 配置項重點部分」

amp-script 開發方式塵埃落定

至此,咱們已經能夠正常享用 Preact 版 amp-script 了。若是隻是簡單的 DOM 操做,能夠將須要被操做的 DOM 放在 amp-script 標籤內、經過 ID 選擇器獲取元素便可,但咱們既然使用了 Preact, 更推薦的作法是:須要操做的 DOM 全權由 amp-script 引用的 Preact 腳本渲染出來。

例如「圖 10」中藍色吸底欄,它涉及到的交互有:點擊後展現 confirm 彈窗、請求後端進行相關操做(加入/移除書架)、接口返回成功後進行前端回顯,普通頁面的交互不外乎如此,具體實現思路是:React AMP 頁面準備一個空的具備 ID 標識的 div 容器,藉助 Preact 的 render 方法渲染出吸底欄組件,直接在 Preact 中進行事件的綁定、setState 更新 UI 等邏輯。

圖片

圖片

「圖 10:詳情頁吸底欄使用 amp-script 腳本渲染」

amp-script 腳本 CDN 化

藉助 CDN 能夠減輕服務器的壓力、最快地響應用戶,所以全部的 JavaScript 資源都應該放於 CDN 上,amp-script 固然不能例外。

amp-script 的打包編譯流程是獨立於 Next.js 的,但當咱們打包 Next 腳本時,兩個獨立流程的融合不可避免,咱們但願執行 build 命令後,amp-script 的 src 地址將會被替換爲 CDN 域名,同時 amp-script 腳本文件名也會加上 md5 碼... 來繼續倒騰 next.config.js 吧!

amp-script 的 src 路徑替換 + 文件名 md5 化,這個需求和絕大多數圖片的引入基本同樣,所以用 webpack 的 file-loader 就能夠實現「圖 11」。

圖片

「圖 11:file-loader 替換 amp-script 路徑」

爲避免報錯,test 對應的正則建議只匹配 amp-script 的腳本,同時 publicPath 也需進行「本地/線上」的區分用以保證本地可以正常引用 amp-script 腳本(本地依舊訪問 /static/ 路徑下的腳本)。爲配合 file-loader 的使用,AMP 頁面中也需把 amp-script 腳本的引用方式替換爲 require() 方法引用。

一套流程下來,徹底解決了 amp-script 腳本路徑替換 + 文件名加 md5 的需求。

amp-script 腳本跨域問題完美解決

到了這一步,眼看着 amp-script 享受到了 CDN 的待遇、Next 服務減輕了一些壓力,然而卻爆出了跨域錯誤「圖 12」。

此跨域問題是 amp-script 自身的一套安全策略拋出的異常,官方說明可參考「圖 13」,大體意思是,若是咱們引用的 amp-script 腳本和頁面 URL 域名不一樣,須要在各頁面 meta 標籤內,添加 amp-script-src 對應的 hash code ,此 code 根據腳本內容、基於 Content Security Policy 「CSP」生成,是每一個腳本獨一無二的編碼。

圖片

「圖 12:amp-script 跨域報錯」

圖片

「圖 13:amp-script 安全策略」

幸虧 AMP 官方也提供了 CSP hash code 的生成工具@ampproject/toolbox-script-csp,咱們初步的思路定了下來:

  1. 開發 webpack 自定義 plugin,具體功能:獲取編譯後的 amp-script 腳本,同目錄下生成對應的 hash code 文件。
  2. 每一個 AMP 頁面添加 meta 標籤,其 content 爲引入的 hash code 文件。
  3. Next.js 配置 raw-loader ,實現 meta 標籤內 content 內容替換爲 hash code 文件內容。

Step 1: webpack 自定義 plugin

此 plugin 功能單一還算好寫,能夠參考以下「圖 14」,重點是獲取到編譯後的 amp-script 腳本內容、生成 hash code 、存放於同一路徑下的 hash.txt 文件內。最終在 webpack 配置文件中 plugins 中引入此插件並實例化便可。

圖片

「圖 14:webpack 自定義 plugin」

Step 2 & Step 3: next.config.js 配置 raw-loader,AMP 頁面引入 hash code 文件

頁面中的改動以下所示:

圖片

「圖 15:AMP 頁面中引入 hash code 文件,添加 meta 標籤」

next.config.js 中也須要配置 raw-loader,實現 meta 標籤內 content 值的替換。

圖片

「圖 16:next.config.js 中配置 raw-loader」

跨域問題得以解決!

總結:實現了 amp-script React 方式開發的全過程。有了自定義的腳本能力,AMP 頁面的開發更加靈活。

最後一層加持

至此,開發過程當中不免還會面臨一些 amp-script 都沒法實現的交互形式,這樣的交互着實挑戰了 AMP 的設計原則,換句話說,若是可以作到交互形式上徹底遵循 AMP 的規範,開發體驗則會暢通無阻!

AMP 設計原則

AMP 設計原則中,沒有規定你具體要怎樣設計、怎樣實施,但核心原則必須是:只作對用戶體驗有利的事情!其中有2條原則對咱們開發者來講會有必定的啓發「圖 17」

圖片

「圖 17:AMP 設計原則 2 條」

4.涉及到開發的各個層面應職責分明,在正確的層解決問題。

5.只作可讓網頁變快的事情。

邏輯後置

「涉及到開發的各個層面應職責分明,在正確的層解決問題」- 若是邏輯放於後端來講對用戶體驗較爲友好,請不要僅僅由於前端實現起來簡單,而把全部的邏輯都放在前端。

這裏有一些場景能夠參考:

  1. 須要使用 amp-mustache 組件(弱邏輯的模板語言,不支持運算、正則以及一些複雜判斷邏輯)時,不少邏輯應該儘可能讓後端同窗實現,例如:搜索頁關鍵詞高亮邏輯「圖 18」,後端能夠直接返回帶有高亮樣式的 HTML 元素。
  2. 對於長列表頁面,下拉加載功能可使用 amp-list 組件,但這須要後端協助加字段返回下一頁的請求 URL 並攜帶參數、同時須要判斷是不是最後一頁而作邏輯的變動。固然前端也能夠用 amp-script 實現下拉加載,可是其效果遠沒有直接引用 amp-list 好,此時邏輯後置頗有必要。

圖片

「圖 18:搜索頁關鍵詞藍色高亮」

AMP 實現不了的窘迫場景

只作可讓網頁變快的事情 - AMP 不推薦引入任何會致使動畫幀率變慢、頁面加載速度受限的組件、特性。

我對它的理解是,若是不能保證本身寫的組件徹底符合 AMP 標準,那就儘可能用 AMP 提供的組件吧!這裏不得再也不潑一下冷水了,amp-script 真的有不少限制,它並不能實現全部交互邏輯,只是給開發者開了個不大不小的窗戶而已。這種狀況只有 2 條路可選了:

  1. 調整需求和設計稿。
  2. 妥協、不使用 AMP。

項目組各部門 follow 一致的規範

若是你提早看到了此文,那麼狀況可能並無那麼糟,由於你能夠防範於未然,如下就是良方。

AMP 的設計原則有個很好的初衷:

「 These design principles are meant to guide the ongoing design and development of AMP. They should help us make internally consistent decisions. 」

(讓咱們作出一致的決定)

其中 「咱們」,指的不只是 AMP 頁面前端開發者,而是參與這個項目的全部人,這個出發點很是重要,這也是 AMP 2019 CONF 「AMP core mindset」提到的主要觀念。

產品須要瞭解 AMP 的限制,避免天馬行空的需求設計;設計也須要了解 AMP 的組件規範,進而節約設計成本、產出能夠用 AMP 技術實現出來的組件,最終避免開發成本的增長。例如, 圖片輪播組件 AMP 自身已經實現的很好了「圖 19」,設計若是沒有特殊要求,不須要再設計新的風格。

圖片

「圖 19:amp 輪播圖組件 amp-carousel

從開發層面來說,前端在拿到設計稿後,就能夠清楚的看出頁面能夠用到的 AMP 組件,對於後端同窗來講,開發前期就知道該補什麼字段、作哪些邏輯調整。

結束語

通過踩坑無數的坎坷歷程,「Next.js + AMP + Preact 」這個 AMP 的新嘗試在「Ficool m站」完美收工。儘管「Next.js + AMP + Preact」的組合在其餘非 AMP 項目中幾乎不會用到,但文中提到的每個坑想必其餘團隊也很難迴避,但願本文能讓你們少走點彎路。

最後,團隊的共識很重要,除了能節約溝通成本,更多的是你們都知道要作什麼、爲何去作,咱們是在實現同一個目標 - 更好的用戶體驗。

查看更多分享,請關注閱文前端團隊公衆號:

想要了解關於團隊的更多信息,能夠查看官網哦~ 😄

相關文章
相關標籤/搜索