漸進式Web應用程序第1部分

漸進的網絡應用程序非常酷。但是,是否有可能構建一個真正的漸進式Web應用程序,而不需要任何JavaScripot?繼續閱讀以查看實驗。

我喜歡Progressive Web Apps。我喜歡它為您建立良好,穩定,可靠的網站和應用程序提供的模型。我喜歡使用PWA模型的原則平臺API - 服務工作者。

我們陷入的陷阱之一是“ App Shell”。App Shell模型表示,您的站點應該提供應用程序的完整外殼(以便即使在脫機狀態時也可以體驗某些內容),然後控制如何以及何時引入內容。

漸進式Web應用程序第1部分

App Shell

App Shell模型大致類似於“SPA”(單頁面應用程序) - 加載外殼程序,然後每個後續導航都由JS直接在頁面中處理。它在許多情況下運作良好。

我不認為App Shell是唯一的也不是最好的模型,並且一如既往,您的選擇因情況而異; 例如,我自己的博客使用簡單的“Stale-While-Revalidate”模式,在您瀏覽網站時緩存每個頁面,並在稍後的刷新中顯示更新; 在這篇文章中,我想探索一個我最近嘗試過的模型。

要將應用程序Shell或不要應用程序Shell

在App Shell的經典模型中,支持漸進渲染幾乎是不可能的,我希望實現真正的“漸進式”模型,用於構建具有以下屬性的服務人員的網站:

  • 它沒有JS工作。

  • 它在沒有服務工作者支持的情況下工作。

  • 它很快。

我著手通過創建一個我一直想要構建的項目來展示這一點:新聞之河+ TweetDeck Hybrid。對於給定的RSS提要集合,以列方式呈現它們。

漸進式Web應用程序第1部分

Feed Deck - 請忽略造型

“Feed Deck”對於服務人員和漸進式增強的實驗來說是一個很好的參考經驗。它有一個服務器渲染的組件,它需要一個“外殼”來快速向用戶顯示某些內容,並且它已動態生成需要定期更新的內容。最後,因為這是一個個人項目,所以我不需要太多的服務器基礎設施來保存用戶配置和身份驗證。

我完成了大部分工作,並在此過程中學到了很多東西。有些東西仍然需要JS,但理論上應用程序沒有JS功能; 我渴望Node.js與DOM API有更多共同之處; 我使用Glitch完全在Chrome OS上構建了它,但這個最後一部分是另一個故事。

我在項目的早期就“有效”的含義設定了一些定義。

  • “它沒有JS的作品” - 內容加載在屏幕上,並且對於沒有JS將來工作的所有東西(或者為什麼沒有啟用明確的理由),它都有一個明確的路徑。我不能只說“不”。

  • “它在沒有服務人員支持的情況下工作” - 所有事情都應該加載,運行並且速度非常快,但如果它無法在任何地方脫機工作,我很高興。

但這不是唯一的故事,如果我們擁有JS和對服務工作者的支持,我有權確保:

  • 它立即加載。

  • 這是可靠的,並具有可預測的性能特徵。

  • 它完全脫機工作。

Mea Culpa:如果你看看代碼,然後在較老的瀏覽器中運行它,這很可能會失效,但我確實選擇使用ES6,但這不是一個無法克服的障礙。

如果我們要專注於構建在未啟用JavaScript的情況下運行的體驗,那麼它認為我們應該儘可能在服務器上進行渲染。

最後,我有一個次要目標:我想探討在你的服務工作者和你服務器之間共享邏輯的可行性......我說謊,這是最讓我興奮的事情,還有很多好處這個進步故事的背後落了。

首先是服務工作者的服務器?

這兩者都是在同一時間。我必須從服務器進行渲染,但由於服務工作人員坐在瀏覽器和網絡之間,我不得不考慮兩者之間的相互作用。

我處於一個幸運的位置,因為我沒有很多獨特的服務器邏輯,所以我可以同時全面解決問題。我遵循的原則是考慮我希望通過第一次呈現頁面(每個用戶都可以獲得的體驗)以及後續的頁面呈現(吸引用戶獲得的體驗)來實現我想要實現的目標。服務人員。

首先渲染 - 沒有可用的服務工作者,因此我需要確保第一個渲染包含儘可能多的頁面內容並在服務器上生成它。

如果用戶有支持服務人員的瀏覽器,那麼我可以做一些有趣的事情。我已經在服務器上創建了模板邏輯,而且它們沒有什麼特別之處,那麼它們應該與我直接在客戶端上使用的模板完全相同。服務人員可以在oninstall時間獲取模板並將其存儲起來以備後用。

漸進式Web應用程序第1部分

Feed Deck - 首次加載

沒有服務人員的第二個渲染 - 它應該像第一個渲染一樣。我們可能會受益於正常的HTTP緩存,但理論是一樣的:快速呈現體驗。

第二渲染與服務人員 -這應該採取行動正是像第一服務器渲染,但是,所有的服務人員內部。我沒有傳統的外殼。如果你看看網絡,所有你看到的都是完整的HTML:結構和內容。

漸進式Web應用程序第1部分

“渲染” - 流媒體是我們的朋友

我試圖儘可能地進步,這意味著我需要儘快在服務器上進行渲染。我有一個挑戰。如果我合併了所有RSS源的所有數據,則第一個渲染將被網絡對RSS源的請求阻止,因此我們會減慢第一個渲染速度。

我選擇了以下路徑:

  • 渲染頁面的頭部 - 這是相對靜態的,快速將屏幕快速顯示到屏幕上,並且可以感知性能。

  • 根據配置(列)呈現頁面結構 - 對於給定的用戶來說,這是目前靜態的,並且使其快速可見對於用戶來說很重要。

  • 如果我們有內容緩存並可用,則渲染列數據; 我們可以在服務器和服務人員上執行此操作

  • 渲染包含邏輯頁面的頁腳,以便定期動態更新頁面內容。

考慮到這些限制,一切都需要異步,並且我需要儘可能快地將網絡中的所有內容全部取出。

網絡上的流式模板庫真的很缺乏。我用我的好友和同事Surma使用了Stream -dot,它是模板框架doT的一個端口,但添加了生成器,因此它可以寫入節點或DOM流,而不會阻塞整個可用內容。

渲染列數據(即提要中的內容)是最重要的部分,目前這需要客戶端上的JavaScript用於第一次加載。系統設置為能夠在服務器上呈現第一次加載的所有內容,但我選擇不阻止網絡。

如果數據已經被提取並且在服務人員中可用,那麼即使它可能很快變得陳舊,我們也可以很快地將其提供給用戶。

在aysnc中呈現內容的代碼是相對程序化的,並遵循前面描述的模型:當模板準備就緒時,我們將頭部渲染到流中,然後將正文內容呈現給流,然後可能正在等待內容如果有的話,也將被衝到溪流。最後,當一切準備就緒時,我們添加頁腳並將其刷新到響應流。

以下是我在服務器和服務人員上使用的代碼。

const root = (dataPath, assetPath) => {

let columnData = loadData(`${dataPath}columns.json`).then(r => r.json());

let headTemplate = getCompiledTemplate(`${assetPath}templates/head.html`);

let bodyTemplate = getCompiledTemplate(`${assetPath}templates/body.html`);

let itemTemplate = getCompiledTemplate(`${assetPath}templates/item.html`);

let jsonFeedData = fetchCachedFeedData(columnData, itemTemplate);

/*

* Render the head from the cache or network

* Render the body.

* Body has template that brings in config to work out what to render

* If we have data cached let's bring that in.

* Render the footer - contains JS to data bind client request.

*/

const headStream = headTemplate.then(render => render({ columns: columnData }));

const bodyStream = jsonFeedData.then(columns => bodyTemplate.then(render => render({ columns: columns })));

const footStream = loadTemplate(`${assetPath}templates/foot.html`);

let concatStream = new ConcatStream;

headStream.then(stream => stream.pipeTo(concatStream.writable, { preventClose:true }))

.then(() => bodyStream)

.then(stream => stream.pipeTo(concatStream.writable, { preventClose: true }))

.then(() => footStream)

.then(stream => stream.pipeTo(concatStream.writable));

return Promise.resolve(new Response(concatStream.readable, { status: "200" }))

}

有了這個模型,在服務器和服務工作者上面獲得上述代碼和過程實際上相對簡單。


分享到:


相關文章: