你的博客用不著什么JavaScript框架

今年年初,我終于決定將自己的網(wǎng)站從基于 PHP 的 CMS 移植到基于 JavaScript 的靜態(tài)網(wǎng)站生成器(SSG)了。原因如下:
雖然一開始我是“全?!遍_發(fā)人員,但現(xiàn)在我只負(fù)責(zé)前端工作:如果我需要編寫自定義功能,那么能用 JavaScript 編寫的代碼我就不想用 PHP 來寫。
我不需要抽象層或 CMS 的復(fù)雜性——我最喜歡用 markdown 文件編寫內(nèi)容,并且希望永遠(yuǎn)不要再碰 Mysql 數(shù)據(jù)庫或所見即所得編輯器。
我想提高網(wǎng)站的性能:靜態(tài) HTML 文件在 99% 的時(shí)候都比動(dòng)態(tài)頁面更快。
最后還有成本優(yōu)勢(shì):LAMP stack 服務(wù)器得按月付費(fèi);Netlify 的免費(fèi)套餐(每月構(gòu)建 300 分鐘)應(yīng)該可以輕松覆蓋零成本個(gè)人博客的需求。
當(dāng)我決定使用靜態(tài)站點(diǎn)生成器和 JavaScript(排除了 Jekyll 和 Hugo)后,就只剩下兩個(gè)差別頗大的選項(xiàng)了:
根據(jù)官方網(wǎng)站的說法,“Gatsby 是一個(gè)基于 React 的免費(fèi)開源框架,可幫助開發(fā)人員構(gòu)建速度飛快的網(wǎng)站和應(yīng)用”。它有一個(gè)由 GraphQL 支持的數(shù)據(jù)層,并將所有內(nèi)容輸出到靜態(tài)文件,使你可以在幾乎任何地方托管它。
當(dāng)我第一次聽說我可以編寫 React 并使用這個(gè)很酷的 GraphQL 新玩意兒,同時(shí)還能輸出不需要 JavaScript 的靜態(tài)頁面時(shí),我很想嘗試它一下。我是這么想的:“這聽起來像是漸進(jìn)增強(qiáng),但用不著什么投入”。不幸的是,就像大多數(shù)聽起來過于美好的事情一樣,經(jīng)過一些調(diào)查我發(fā)現(xiàn)它就是個(gè)坑。用戶首次訪問 Gatsby 網(wǎng)站時(shí)會(huì)發(fā)生這些事情:
用戶請(qǐng)求一個(gè)頁面。
服務(wù)器將靜態(tài)生成的 HTML 文檔發(fā)送到用戶的瀏覽器,然后瀏覽器開始渲染頁面。
HTML 文檔到達(dá)后,JavaScript 包(包括 React 庫和渲染頁面所需的其他 JavaScript)開始在后臺(tái)下載、解析和編譯。
JavaScript 已準(zhǔn)備就緒,可以運(yùn)行——整個(gè) DOM 通過 React 組件“被水化”(hydrated)。
這里就有些不對(duì)勁——Gatsby 需要你以 React 組件的形式再加載一次頁面;在完成多出來的這一步之前,所有需要 JavaScript 的元素(例如按鈕、菜單、自定義輸入)實(shí)際上都不能交互。
哪怕你的網(wǎng)站沒有任何互動(dòng)元素(鏈接除外,即使沒有 Gatsby,它們也無需 JavaScript 即可工作),你的用戶也必須下載這部分 JavaScript,僅僅是為了將你的網(wǎng)站變成單頁應(yīng)用程序(SPA);SPA 是有自己的缺點(diǎn)的,我們稍后再提。
這種多出來的操作看來是違背我轉(zhuǎn)向 SSG 的初衷(提高頁面速度)的。華麗的 Gatsby 網(wǎng)站在 2,000 美元的 MacBook 上可能很快,但對(duì)于使用 3G 連接和廉價(jià)智能手機(jī)的用戶來說,它顯示是能顯示出來,但是沒有響應(yīng);用戶等待加載 JavaScript 的過程要持續(xù) 15 秒。電池和數(shù)據(jù)流量也得跟著往下掉了。
如果瀏覽器需要解析 296kb 的 JavaScript 代碼才能顯示出博客文章的列表,這就不是什么"漸進(jìn)增強(qiáng)”,而是用錯(cuò)了工具。從網(wǎng)站 /Web 應(yīng)用的大致區(qū)別來看,React 是用于構(gòu)建 Web 應(yīng)用的,這種應(yīng)用需要有響應(yīng)用戶輸入或?qū)崟r(shí)獲取數(shù)據(jù)的交互式 UI;而博客只是一個(gè)網(wǎng)站而已。
單頁應(yīng)用程序這種網(wǎng)站放棄了傳統(tǒng)的 Web 導(dǎo)航方法,即通過加載新的 HTML 文檔來加載新內(nèi)容;相反,它使用 AJAX 和 History API 之類的 JavaScript 特性來切換到新內(nèi)容上,而不會(huì)觸發(fā)頁面加載。它的目標(biāo)是提高感知的性能,并使網(wǎng)站看起來更像“原生”應(yīng)用(從應(yīng)用商店下載的那種)。不再需要整頁重新加載的問題在于,瀏覽器和輔助技術(shù)將頁面加載用作觸發(fā)某些有用行為的信號(hào),包括宣布新頁面的標(biāo)題或?qū)㈡I盤焦點(diǎn)重置到文檔的開頭。
如果你在開發(fā)關(guān)注可訪問性的單頁應(yīng)用程序,那么你可能會(huì)試著使用 JavaScript 來模擬瀏覽器的行為。Gatsby 試圖通過包含一個(gè) RouteAnnouncer 組件來為你解決這個(gè)問題。它使用一個(gè) ARIA live region 來宣布頁面的 title 或 h1,以對(duì)使用屏幕閱讀器軟件的用戶提示頁面跳轉(zhuǎn)行為。但這種方法也存在問題:它在配置和本地化方面仍然存在很多未解決的 issue。
我們已經(jīng)看到,單頁應(yīng)用程序在導(dǎo)航方面存在固有的可訪問性問題,但要注意的是,使用前端框架也會(huì)在其他方面帶來可訪問性問題。在 2020 年 2 月對(duì) 100 萬個(gè)首頁的調(diào)查中,WebAIM 發(fā)現(xiàn)使用 React 的網(wǎng)頁的可訪問性錯(cuò)誤比平均水平高 5.7%;而使用 Vue 的網(wǎng)頁則高出 25%。這并不一定意味著框架一定會(huì)導(dǎo)致這些錯(cuò)誤,但是更多的 JavaScript 與更差的可訪問性之間存在很強(qiáng)的相關(guān)性。
很有可能,你構(gòu)建的第一個(gè)網(wǎng)頁的性能要比之后構(gòu)建的許多頁面都要好得多——它由一個(gè) HTML 文件和一些 CSS 組成,也許還有一些未優(yōu)化的圖像,但它們并不會(huì)阻止頁面加載。如果你也有我這樣的經(jīng)歷,那么開始添加 JavaScript 的那一刻,你的網(wǎng)頁性能就開始急劇下降了。并非所有字節(jié)都是一樣的:與同等大小的 JavaScript 文件解析、編譯和執(zhí)行所需的時(shí)間相比,圖像解碼和渲染到屏幕所需的時(shí)間要少得多。
JavaScript 是一種強(qiáng)大的語言,可以完成一些令人難以置信的事情,但是在開發(fā)中你很容易過早開始使用它,其實(shí)本來用 HTML 和 CSS 就夠了。應(yīng)該看看最小功能原則:在你充分利用功能較弱的語言(HTML)之前,請(qǐng)不要使用功能更強(qiáng)大的語言(JavaScript)。在我看來,將博客變成 JavaScript 單頁應(yīng)用程序會(huì)帶來不必要的復(fù)雜性。
這篇文章并不是要批判 Gatsby 而寫的。它的背后有一些聰明的頭腦,他們已經(jīng)承認(rèn)了本文中提到的許多問題,并試圖解決它們。靜態(tài)渲染和水化的頁面還是比完全客戶端渲染的 React 應(yīng)用(如 create-react-app 生成的頁面)要好得多,后者沒有 JavaScript 就沒法用。我確實(shí)不太滿意 Gatsby 的宣傳手法,他們說 Gatsby 適合任何類型的網(wǎng)站??蛻舳?JavaScript 是有成本的,開發(fā)人員應(yīng)該意識(shí)到這一點(diǎn)。
這使我陷入了一個(gè)兩難境地:使用 Gatsby 開發(fā)網(wǎng)站是絕妙的體驗(yàn);但是開發(fā)體驗(yàn)(DX)應(yīng)該永遠(yuǎn)排在用戶體驗(yàn)(UX)之后。那么如何在構(gòu)建 Gatsby 網(wǎng)站時(shí)避免那些因?yàn)榇罅渴褂?JS 而帶來的固有問題呢?當(dāng)然,我們應(yīng)該盡量刪掉那些 JavaScript。所幸 Gatsby 社區(qū)內(nèi)做出了很多努力來構(gòu)建更、,更輕量級(jí)的網(wǎng)站:
首先,使用 gatsby-plugin-preact 將 React 換成 Preact 可以節(jié)省幾千字節(jié)。我在 Component Gallery 上用了它,立刻將 JavaScript 負(fù)載減少了約 30kb。
如果你想用更激進(jìn)的方法,可以使用一個(gè)插件來從你的 Gatsby 網(wǎng)站刪除所有 Gatsby JavaScript。
https://www.gatsbyjs.org/packages/gatsby-plugin-no-javascript/
你可以繼續(xù)編寫 react 組件和 GraphQL,甚至可以使用 CSS-in-JS 庫(只要它輸出 CSS 或內(nèi)聯(lián)樣式),而無需向?yàn)g覽器發(fā)送任何 JavaScript。只要扔掉所有客戶端 JavaScript 就可以解決 Gatsby 的大多數(shù)問題。Gatsby Starter Low Tech 博客使用 no-javascript 插件和其他一些技術(shù)(包括將所有圖像轉(zhuǎn)換為灰度),來幫助你創(chuàng)建一個(gè)輕量且節(jié)能的博客。
這時(shí)候我感覺有點(diǎn)不對(duì)勁——使用一個(gè)會(huì)大量推送客戶端 JavaScript 的框架,卻要?jiǎng)h除所有 JavaScript 代碼,這似乎是一種很復(fù)雜的網(wǎng)站構(gòu)建方式。我想看看是否可以不用客戶端 JavaScript 來構(gòu)建功能完善的博客,這樣就用不著什么插件來刪除它了。于是我轉(zhuǎn)向了另一個(gè)選項(xiàng):
Eleventy 鼓勵(lì)你按照自己的意愿構(gòu)建網(wǎng)站。你可以使用自己最熟悉的技術(shù),它只負(fù)責(zé)生成頁面。Eleventy 為你提供了十種可以任意搭配的模板語言選項(xiàng),包括 markdown、nunjucks 和 liquid;這意味著我可以從 Craft 中復(fù)制并粘貼舊的模板,更改文件擴(kuò)展名,并做一些細(xì)微的調(diào)整就能運(yùn)行在 Eleventy 中。用不著針對(duì)什么新的打包器來調(diào)整前端構(gòu)建流程,我只需放入現(xiàn)有的 webpack 文件和 src 文件夾即可。使用并發(fā)包,我可以在 Eleventy 的 serve 過程中同時(shí)運(yùn)行構(gòu)建腳本。
像 Gatsby 一樣,Eleventy 也有一個(gè)插件生態(tài)系統(tǒng)(雖然很小,但增長迅速)。我挑選了一些不需要添加客戶端 JavaScript 也能添加功能的插件:
在帖子中顯示代碼段時(shí),通常會(huì)包含特定于語言的語法高亮顯示。有一些 JavaScript 庫可以做到這一點(diǎn),其中最流行的似乎是 Prism——你可以在客戶端中運(yùn)行它,但由于我們使用的是 JavaScript SSG,因此可以在構(gòu)建時(shí)運(yùn)行它,并將語法高亮顯示所需的 HTML 元素和 CSS 類直接烘焙到文檔中——這樣就無需在瀏覽器中下載這個(gè)庫了。
eleventy-plugin-embed-tweet 也可以在構(gòu)建時(shí)而非客戶端運(yùn)行 JavaScript。Twitter 的默認(rèn)嵌入代碼迫使用戶下載大量 JavaScript 才能顯示一條推文。這個(gè)插件可以在構(gòu)建時(shí)獲取并渲染推文,這樣只需少量 HTML 和 CSS 即可,根本不需要額外的 JavaScript。
與其他新技術(shù)一樣,Eleventy 缺少某些更加成熟的工具所提供的功能。例如,在 Eleventy 中沒有一種優(yōu)雅的方法來生成響應(yīng)式圖像。相比之下,Gatsby 中出色的 gatsby-image 插件可以生成延遲加載和響應(yīng)式的圖片元素,并能在加載全分辨率文件后在低分辨率或 SVG 版本的圖像間平滑切換。Eleventry 還有一些讓我感到困惑的事情:我有一陣子一直搞不懂它的分頁功能,認(rèn)為它只是將帖子分頁到指定大小的一些組中,之后才意識(shí)到它可以動(dòng)態(tài)生成全新的頁面;我還發(fā)現(xiàn)自己在同一文件中混用了模板語言:你可以隨意在 markdown 文件中包含 nunjucks 標(biāo)簽,或?qū)⒒?yaml 的 frontmatter 換成 JavaScript,但這會(huì)破壞語法高亮顯示、linting 和自動(dòng)格式化。
如果你還是選擇了 Gatsby,我也不會(huì)怪你——有時(shí)候使用一個(gè) opinionated 的框架也不錯(cuò),并且如果你想要快速完成工作,這是一個(gè)可靠的解決方案。只是要注意它的性能成本,以及所有與 JavaScript 相關(guān)的潛在可訪問性問題。
我選擇使用 Eleventy 來構(gòu)建自己的網(wǎng)站,但我知道這種方法并不適合所有人——完全按照自己的意愿來構(gòu)建某些東西可能是很麻煩的事情。但你也用不著完全學(xué)我——與 Gatsby 類似,Eleventy 也有許多入門項(xiàng)目可以用作基礎(chǔ)。其中一些工具,例如 Andy Bell 的 Hylia 入門套件可以在幾分鐘內(nèi)搞定一個(gè)網(wǎng)站。它甚至預(yù)配置了 Netlify CMS,因此你無需編寫任何代碼即可編輯網(wǎng)站內(nèi)容。
我學(xué)到了什么呢?使用 Eleventy 可以輕松構(gòu)建不帶 JavaScript 的博客,但總會(huì)有一些功能需要客戶端 JavaScript 的:
我的網(wǎng)站拿掉了 Google Analytics,但它對(duì)用戶來說沒什么用途,所以我也不在乎——我會(huì)在另一篇文章中介紹它的服務(wù)端替代品。
我使用了 loading="lazy"屬性來延遲加載圖片,但它的瀏覽器支持不夠完整,并且在原生瀏覽器實(shí)現(xiàn)改進(jìn)之前,它無法在加載圖片時(shí)淡入淡出。
黑暗模式切換——雖然我可以只用 CSS 來實(shí)現(xiàn),無需訪問 cookies 或本地存儲(chǔ),但我沒辦法在頁面之間保持設(shè)定的值。
我是否會(huì)在不久的將來在網(wǎng)站上加入 JavaScript 呢?答案可能是否定的:我上面列出的功能并不是那么重要的。我并不是推薦大家都刪除自己網(wǎng)站上的所有 JavaScript 文件,但從現(xiàn)在開始,在構(gòu)建網(wǎng)站時(shí)我會(huì)嘗試將 JavaScript 視為可選的額外功能,而不是體驗(yàn)的基本組成部分。我鼓勵(lì)你也這樣做。
https://iainbean.com/posts/2020/your-blog-doesnt-need-a-javascript-framework/
