写于 2016.08.23javascript
项目地址:github.com/jrainlau/vu…css
随着Vue 2.0的发布,服务端渲染一度成为了它的热卖点。在此以前,单页应用的首屏加载时长和SEO的问题,一直困扰着开发者们,也在必定程度上制约着前端框架的使用场景。React提出的服务端渲染方案,较好得解决了上述两个痛点,受到了开发者的青睐,但也所以多了一个抨击Vue的理由——Vue没有服务端渲染。为了解决这个问题,Vue的社区里也贡献了一个方案,名曰VueServer。然而这货并不是单纯的服务端渲染方案,而是至关于另一个一个服务端的Vue,看看它的readme就知道了:html
VueServer.js is designed for static HTML rendering. It has no real reactivity. Also, the module is not running original Vue.js on server. It has its own implementation. It means VueServer.js is just trying to perfectly reproduce the same result as Vue.js does.前端
因此有没有一种通用的解决方法,既可以让咱们使用原生的Vue 1.x,又能愉快地进行服务端渲染呢?下面请听我细细道来……vue
在文章开始以前,咱们有必要先了解一下什么是服务端渲染,以及为何须要服务端渲染(知道的同窗能够跳过)。 服务端渲染(Sever Side Render,简称SSR),听起来高大上,其实原理就是咱们最多见的“服务器直接吐出页面”。咱们知道,传统的网站都是后端经过拼接、模版填充等方式,把数据与html结合,再一块儿发送到客户端的。这个把数据与html结合的过程就是服务端渲染。java
服务端渲染的好处,首先是首屏加载时间。由于后端发送出来的html是完整的带有数据的html,因此浏览器直接拿来就能够用了。与之相反的,以Vue 1.x开发的单页应用为例,服务端发送过来的html只是一个空的模板,浏览器根据js异步地从后端请求数据,再渲染到html中。一个大型单页应用的js每每很大,异步请求的数量也不少,直接致使的结果就是首屏加载时间长,在网速很差的状况下,白屏或loading的漫长等待过程对于用户体验来讲真的很不友好。node
另一点,通常的搜索引擎爬虫因为没法执行html里面的js代码(我大Google除外),因此对于单页应用,爬虫所获取到的仅仅是空的html,所以须要作SEO的网站极少采用单页应用的方案。咱们能够看看例子——react
首先咱们来写一个经过js生成内容的html文件:webpack
<!-- SPA.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>SPA-DEMO</title>
</head>
<body>
<script>
var div = document.createElement('div')
div.innerHTML = 'Hello World!'
document.body.appendChild(div)
</script>
</body>
</html>
复制代码
浏览器打开,输出“Hello World!”,很好没有问题。git
接下来咱们来写一个小爬虫:
'use strict'
const superagent = require('superagent')
const cheerio = require('cheerio')
var theUrl = 'http://localhost:3000/spa.html'
const spider = (link) => {
let promise = new Promise( (resolve, reject) => {
superagent.get(link)
.end((err, res) => {
if (err) return console.log(err)
let $ = cheerio.load(res.text)
console.log($('html').html())
resolve($)
})
})
return promise
}
spider(theUrl)
复制代码
运行,输出结果以下:
能够看到,在<body></body>
标签以内并无生成对应的div
,爬虫没法解析页面当中的js代码。
为了实现服务端渲染,咱们的主角PhantomJS登场了。
PhantomJS is a headless WebKit scriptable with a JavaScript API. It has fast and native support for various web standards: DOM handling, CSS selector, JSON, Canvas, and SVG.
简单来讲,PhantomJS封装了一个webkit内核,所以能够用它来解析js代码,除此之外它也有着其余很是实用的用法,具体使用方法能够到它的官网进行查看。因为PhantomJS是一个二进制文件,须要安装使用,比较麻烦,因此我找到了另一个封装了PhantomJS的NodeJS模块——phantomjs-node
PhantomJS integration module for NodeJS
有了它,就能够结合node愉快地使用PhantomJS啦!
npm install phantom --save
复制代码
新建一个phantom-demo.js
文件,写入以下内容:
var phantom = require('phantom');
var sitepage = null;
var phInstance = null;
phantom.create()
.then(instance => {
phInstance = instance;
return instance.createPage();
})
.then(page => {
sitepage = page;
return page.open('http://localhost:3000/spa.html');
})
.then(status => {
console.log(status);
return sitepage.property('content');
})
.then(content => {
console.log(content);
sitepage.close();
phInstance.exit();
})
.catch(error => {
console.log(error);
phInstance.exit();
});
复制代码
你会在控制台看到完整的http://localhost:3000/spa.html的内容<div>Hello World!</div>
接下来开始实战了。首先咱们要创建一个Vue 1.x的项目,在这里使用vue-cli
生成:
npm install vue-cli -g
vue init webpack vue-ssr
复制代码
在生成的项目中执行下列代码:
npm install
npm run build
复制代码
能够看到在根目录下生成了一个\dist
目录,里面就是构建好的Vue 1.x的项目:
|__ index.html
|__ static
|__ css
|__ app.b5a0280c4465a06f7978ec4d12a0e364.css
|__ app.b5a0280c4465a06f7978ec4d12a0e364.css.map
|__ js
|__ app.efe50318ee82ab81606b.js
|__ app.efe50318ee82ab81606b.js.map
|__ manifest.e2e455c7f6523a9f4859.js
|__ manifest.e2e455c7f6523a9f4859.js.map
|__ vendor.13a0cfff63c57c979bbc.js
|__ vendor.13a0cfff63c57c979bbc.js.map
复制代码
接下来咱们随便找个地方创建Express项目:
express Node-SSR -e
cd Node-SSR && npm install
npm install phantom --save
复制代码
而后,咱们把以前\dist
目录下的\static\css
和\static\js
中的所有代码,分别复制粘贴到刚刚生成的Express项目的\public\stylesheets
和\public\javascripts
文件夹当中(注意,必定要包括全部*.map
文件),同时把\dist
目录下的index.html
更名为vue-index.ejs
,放置到Express项目的\view
文件夹当中,改写一下,把里面全部的引用路径改成以/stylesheets/
或/javascripts/
开头。
接下来,咱们打开Express项目中的\routes\index.js
文件,改写为以下内容:
const express = require('express')
const router = express.Router()
const phantom = require('phantom')
/* GET home page. */
router.get('/render-vue', (req, res, next) => {
res.render('vue-index')
})
router.get('/vue', (req, res, next) => {
let sitepage = null
let phInstance = null
let response = res
phantom.create()
.then(instance => {
phInstance = instance
return instance.createPage()
})
.then(page => {
sitepage = page
return page.open('http://localhost:3000/render-vue')
})
.then(status => {
console.log('status is: ' + status)
return sitepage.property('content')
})
.then(content => {
// console.log(content)
response.send(content)
sitepage.close()
phInstance.exit()
})
.catch(error => {
console.log(error)
phInstance.exit()
})
})
module.exports = router
复制代码
如今咱们用以前的爬虫爬取http://localhost:3000/render-vue的内容,其结果以下:
能够看到是一些未被执行的js。
而后咱们爬取一下http://localhost:3000/vue,看看结果是什么:
满满的内容。
咱们也能够在浏览器打开上面两个地址,虽然结果都是以下图,可是经过开发者工具的Network
选项,能够看到所请求的html内容是不一样的。
至此,基于PhantomJS + Node + Express + VueJS 1.x的服务端渲染实践就告一段落了。
因为PhantomJS打开页面并解析当中的js代码也须要必定时间,咱们不该该在用户每次请求的时候都从新执行一次服务端渲染,而是应该让服务端把PhantomJS渲染的结果缓存起来,这样用户的每次请求只须要返回缓存的结果便可,大大减小服务器压力并节省时间。
本文仅做抛砖引玉学习之用,并未进行深刻的研究。同时此文章所研究的方法不只仅适用于Vue的项目,理论上任何构建事后的单页应用项目均可以使用。若是读者发现文章有任何错漏烦请指点一二,感激涕零。如有更好的服务端渲染的方法,也欢迎和我分享。
感谢你的阅读。我是Jrain,欢迎关注个人专栏,将不按期分享本身的学习体验,开发心得,搬运墙外的干货。下次见啦!