原文連接:https://developers.google.com/web/tools/puppeteer/articles/ssr
注:因爲英文水平有限,沒有逐字翻譯,能夠選擇直接閱讀原文
tips:Headless瀏覽器徹底能夠做爲服務端渲染的一個替代方案,在服務端轉化js 站點爲靜態html頁面;在webserver 上運行Headless 瀏覽器徹底能夠預渲染現代js 模式的應用,增長響應速度,對SEO也更加友好javascript
本篇涉及到的技術展現瞭如何經過Google Headless 框架(puppteer)向一個Express web server 添加服務端渲染能力,對應用對友好的是,基本上不須要修改任何代碼;全部的工做基本都有puppteer承擔,經過簡單的幾行代碼你就能夠在服務端渲染幾乎全部頁面。html
下面是將要涉及到的一小段代碼:java
1 import puppeteer from 'puppeteer'; 2 3 async function ssr(url) { 4 const browser = await puppeteer.launch({headless: true}); 5 const page = await browser.newPage(); 6 await page.goto(url, {waitUntil: 'networkidle0'}); 7 const html = await page.content(); // 頁面的html內容 8 await browser.close(); 9 return html; 10 }
注意:本篇文章代碼基於es modules,須要node 8.5+ 並開啓--experimental-modulesnode
若是你須要seo,你登陸進來閱讀這篇文章無外乎兩種緣由:第一,你已建立了一個web 應用,可是它沒有被搜索引擎索引到,你的應用多是一個SPA、PWA應用。或者其實技術棧建立的應用,實際上你使用的技術棧也無關重要;重要的是,你花費了大量的時間建立了很棒應用,可是用戶卻沒法發現它。第二,你多是從其它網站注意到服務端渲染能提升必定的性能。你在這能夠能夠收穫如何減小javascript 啓動成本以及如何提升首屏渲染。react
tips:一些框架如(Preact)已經支持服務端渲染了,若是你使用的框架有服務端渲染的解決方案,那麼堅持使用就行了,沒有必要引入一個新的工具。git
搜索引擎主要是爬取靜態html標籤來工做,可是現代的web 應用已經進化的比較複雜了。基於Javascript的應用,內容對網絡爬蟲來講是透明的,由於其內容可能是在客戶端經過js渲染的。一些爬蟲好比google的爬蟲也開始變得聰明瞭,google的爬蟲使用Chrome41 執行Javascript 來獲得最終頁面,可是這種方案仍是不太成熟、完美。好比,好比一些ES6的新特性在舊的瀏覽器中仍是會引發Js error的。對於其餘的搜索引擎,鬼知道他們怎麼作的?O(∩_∩)O哈!github
全部爬蟲都理解HTML,因此咱們須要解決的是如何執行JS,來生成HTML。若是我告訴你有這樣一個工具,你以爲如何?web
聽起來很不錯吧?這個工具就是瀏覽器!chrome
Headless Chrome 不關心使用什麼庫、框架、或者工具鏈;它早飯吃進去Javascript,午餐就會吐出來靜態的HTML。固然咱們但願會比這個過程快不少--Ericexpress
若是你使用Node,Puppteer是一種比較簡單的方式來操做headless Chrome.它提供的API 是一個客戶端應用支持服務端渲染功能。下面是一個簡單的例子。
咱們以一個經過js動態生成HTML的動態頁面的例子開始:
public/index.html
1 <html> 2 <body> 3 <div id="container"> 4 <!-- Populated by the JS below. --> 5 </div> 6 </body> 7 <script> 8 function renderPosts(posts, container) { 9 const html = posts.reduce((html, post) => { 10 return `${html} 11 <li class="post"> 12 <h2>${post.title}</h2> 13 <div class="summary">${post.summary}</div> 14 <p>${post.content}</p> 15 </li>`; 16 }, ''); 17 18 // CAREFUL: assumes html is sanitized. 19 container.innerHTML = `<ul id="posts">${html}</ul>`; 20 } 21 22 (async() => { 23 const container = document.querySelector('#container'); 24 const posts = await fetch('/posts').then(resp => resp.json()); 25 renderPosts(posts, container); 26 })(); 27 </script> 28 </html>
接下來,簡單實現一下ssr方法
ssr.mjs
import puppeteer from 'puppeteer'; //內存緩存,key:url value:html內容 const RENDER_CACHE = new Map(); async function ssr(url) { if (RENDER_CACHE.has(url)) { return {html: RENDER_CACHE.get(url), ttRenderMs: 0}; } const start = Date.now(); const browser = await puppeteer.launch(); const page = await browser.newPage(); try { // networkidle0 waits 500ms 沒有其餘請求時. // The page's JS has likely produced markup by this point, but wait longer // if your site lazy loads, etc. await page.goto(url, {waitUntil: 'networkidle0'}); await page.waitForSelector('#posts'); //等待並確認 #posts 已經存在於dom中,若是已經存在,則當即執行. } catch (err) { console.error(err); throw new Error('page.goto/waitForSelector timed out.'); } const html = await page.content(); // 被序列化後的HTML內容 await browser.close(); const ttRenderMs = Date.now() - start; console.info(`Headless rendered page in: ${ttRenderMs}ms`); RENDER_CACHE.set(url, html); // cache rendered page. return {html, ttRenderMs}; } export {ssr as default};
主要代碼邏輯:
page.waitForSelector('#posts')方法,確保id爲posts的元素在後續操做以前已經存在於DOM中(有多中waitForxxx方法)
最後,經過一個Express server 把全部內容聯繫到一塊兒。哎直接看代碼吧,代碼中加了註釋。
server.mjs
import express from 'express'; import ssr from './ssr.mjs'; const app = express(); app.get('/', async (req, res, next) => { //調用上面寫好的ssr方法,傳入url,經過headless chrome 渲染完畢後把渲染結果返回 const {html, ttRenderMs} = await ssr(`${req.protocol}://${req.get('host')}/index.html`); // Add Server-Timing! See https://w3c.github.io/server-timing/. res.set('Server-Timing', `Prerender;dur=${ttRenderMs};desc="Headless render time (ms)"`); return res.status(200).send(html); // Serve prerendered page as response. }); app.listen(8080, () => console.log('Server started. Press Ctrl+C to quit'));
那麼,獲得的響應HTML應該是這樣的:
<html> <body> <div id="container"> <ul id="posts"> <li class="post"> <h2>Title 1</h2> <div class="summary">Summary 1</div> <p>post content 1</p> </li> <li class="post"> <h2>Title 2</h2> <div class="summary">Summary 2</div> <p>post content 2</p> </li> ... </ul> </div> </body> <script> ... </script> </html>
上篇結束,後續中篇 和 下篇 請繼續關注