原文连接: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>
上篇结束,后续中篇 和 下篇 请继续关注