服务器端渲染的优点在于更好的seo以及更快的渲染速度,因此vue也开始支持服务器端渲染,即ssr。css
基本知识
要使用服务器端渲染,须要使用server-entry.js和client-entry.js两个入口文件,二者都会使用到app.js进行打包,其中经过server-entry.js打包的代码是运行在node端,二经过client-entry.js打包代码运行在客户端。 具体的流程图以下所示。html
从图上能够看出,SSR 有两个入口文件,client.js 和 server.js, 都包含了应用代码,webpack 经过两个入口文件分别打包成给服务端用的 server bundle 和给客户端用的 client bundle. 当服务器接收到了来自客户端的请求以后,会建立一个渲染器 bundleRenderer,这个 bundleRenderer 会读取上面生成的 server bundle 文件,而且执行它的代码, 而后发送一个生成好的 html 到浏览器,等到客户端加载了 client bundle 以后,会和服务端生成的DOM 进行 Hydration (判断这个 DOM 和本身即将生成的 DOM 是否相同,若是相同就将客户端的Vue实例挂载到这个 DOM 上, 不然会提示警告)。 前端
client bundle就是vue-ssr-client-manifest.json,而server bundle就是vue-ssr-server-bundle.json,这两个文件都是很是容易获取的
vue
在纯前端渲染时,通常使用的是web-dev-server这个插件,它能够自动帮助咱们开启一个node端,主要做用是监控并打包代码,可是实际上仍是纯前端渲染,另外配合web-hot-middleware来进行HMR热更新,这样能够在咱们改变了代码以后自动打包并更新view,以此来提升开发效率。node
而在vue服务器端渲染时,就不能只是使用web-dev-server和web-hot-middle了,由于咱们须要添加服务器渲染的node代码逻辑,这样,咱们能够本身开一个node服务器,使用webpack-dev-middle中间件进行打包、使用webpack-hot-middle中间件进行热更新,并添加服务器端渲染逻辑,即node端经过引入vue-serverer-renderer插件来渲染服务器端打包的bundle文件到客户端。 react
而在服务器端的配置文件webpack.server.js以下所示:webpack
const webpack = require('webpack') const merge = require('webpack-merge') const base = require('./webpack.base.config') const nodeExternals = require('webpack-node-externals') const VueSSRServerPlugin = require('vue-server-renderer/server-plugin') module.exports = merge(base, { target: 'node', devtool: '#source-map', entry: './client/entry-server.js', output: { filename: 'server-bundle.js', libraryTarget: 'commonjs2' }, externals: nodeExternals({ // do not externalize CSS files in case we need to import it from a dep whitelist: /\.css$/ }), plugins: [ new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'), 'process.env.VUE_ENV': '"server"', 'process.env.VUE_HOST': JSON.stringify(process.env.VUE_HOST || 'http://localhost:8888') }), new VueSSRServerPlugin() ] })
即经过webpack-merge来merge到webpack.base.config的文件,接着具体配置中,target: 'node'表示在node端运行,devtools: '#source-map'能够保证报错时定位到出错文件的行数,output.filename表示输出文件的名称,而output.libraryTarget表示模块入口形式,external表示外部不须要打包,最后就是使用到的plugins。git
而以前所提到的webpack-dev-middleware和webpack-hot-middleware这两个插件能够配置在setup-dev-server.js中的,在index.js中配置服务器时,若是是开发环境,再引入setup-dev-server.js,不然不须要引入。github
因此在bi项目中的两个服务器实际上都是node服务器,其中index.js建立的服务器是node服务器,提供ssr和其余的一些服务,而server下中src的index.js是一个node中间件服务器,起到的是代理服务的做用。web
注意事项
1. webpack打包是须要在服务器端进行的。即webpack在以前纯前端渲染时都是跑在浏览器端的,而若是要使用服务器端渲染,就须要让webpack打包这个过程代码跑在服务器端了。由于vue文件是用.vue形式组织的,因此必须在服务器端打包才能进行服务器端渲染,而且由于在客户端请求是单页的,因此服务器端打包也应该打包为单个文件。
2. 服务器端index.js流程是如何的? 即npm run dev以后,就会进入index.js,而后引入express做为node服务器,并引入vue-server-renderer来集成,进一步将vue的app来服务器渲染,可是如何在服务器端获取这个打包以后的app呢? 即经过entry-server.js便可,这个入口文件就会打包node端运行的vue,打包以后,node端会生成了html标记,而后须要一层html外壳,即套用index.html模板template,这个模板中有一个<!--vue-ssr-outlet>注释,表示
3. 既然有了服务器端渲染,为何项目中还须要client-server进行客户端的webpack打包呢?
(1)服务器须要在服务器端打包而后进行渲染,而客户端打包bundle是须要将客户端bundle给浏览器进行混合静态标记。那么为何须要混合静态标记呢? 默认状况下,能够在浏览器中输出 Vue 组件,进行生成 DOM 和操做 DOM。然而,也能够将同一个组件渲染为服务器端的 HTML 字符串,将它们直接发送到浏览器,最后将静态标记"混合"为客户端上彻底交互的应用程序.如上,由于服务器端发送来的是html字符串,还不是应用程序,好比没有css等,这样,就须要在客户端进行打包,而后混合为应用程序。
(2)另外一个须要关注的问题是在客户端,在挂载(mount)到客户端应用程序以前,须要获取到与服务器端应用程序彻底相同的数据 - 不然,客户端应用程序会由于使用与服务器端应用程序不一样的状态,而后致使混合失败。
(3) 客户端只须要拿到简单的vue打包文件,这个文件是一个模板,而不须要获取到具体的数据。而服务器端是在获取到数据以后拼接html发送,因此须要在客户端进行整合。即服务器端代码打包是为了提供最开始的html页面,而客户端代码打包是为了后期的数据和交互,因此,并非只须要服务器端打包便可。
4. 配置webpack.server.config时为何externals中包含css文件?由于此功能webpack没法在node端执行。
5. 服务器端获取打包以后的代码是和客户端同样都是js文件吗? 不是的,通常来讲,服务器端在获取webpack打包的代码应该是 built-server-bundle.js,可是这样每次在编辑过应用程序代码以后都须要再从新重启,会影响开发效率,另外nodejs不支持source map。因此,咱们可使用 bundlerender,这种方式和render是相似的,但它支持sourcemap,热重载等。在webpack.server.config文件中配置了插件new VueSSRServerPlugin(),这个插件的做用是做为整个服务器的输出为json文件,而再也不是js文件,默认文件名为 vue-ssr-server-bundle.json。
6. 在app.js中,createApp函数的做用是什么?这是为了每一个用户获得一份新的实例,防止状态污染。
7. 总体过程究竟是怎样的?即首先写好各类组件、路由、store等,接着app.js中开始进行汇聚,而后entry-client.js和entry-server.js分别进行对二者的整合。接下来就能够build了,在build客户端代码的时候即经过webpack.client.js进入,入口文件为entry-client.js,最后会打包完整的代码;在build服务器端代码的时候经过webpack.server.js进入,入口文件为entry-server.js,会打包出vue-ssr-server-bundle.json文件;固然这些打包后的文件都会打包到dist文件夹下。build以后,就能够把代码放在服务器上运行了,即经过node建立一个服务器进行服务器端渲染。
8. 查看服务器端渲染代码? 使用查看源代码便可。 好比使用vue和react作出的网站是SPA,那么经过view source得到的代码必定是一个html框架,即<html><head></head><body> ' 这里是空的' </body></html>,即在body标签中是不存在html代码的,这样的结果就是很是不利于seo,且这表示它是没有作服务器端渲染的。 而若是一个作了服务器端渲染的vue网站,view source获得的代码中html是填满的,且包括了全部数据,这就表示这个数据是经过服务器端渲染获得的。或者直接在network中查看接受到的html页面便可 。若是作了ssr,那么获得的html是包含插入的css的,由于须要到客户端就显示,因此html和css都是须要的,而JavaScript对于显示而言并非必要的,因此服务器端渲染到的html中不须要引入额外的JavaScript(除了自身写入的)。
9. vue ssr中,为何组件已经用preFetch在服务器端获取数据,客户端还须要再去fetch? 服务端已经 preFetch 的客户端固然不用 fetch。preFetch 的数据是存进了服务端 vuex store 里面,而后这些数据会直接内联在直出的 HTML 里面。客户端的 vuex store 启动的时候就直接以这些数据为初始数据了。客户端组件调用 actions 的时候,vuex store 起到一层缓存的做用,已经有的数据不会再 fetch。
!! 因此能够认为是服务器端在接收到url以后,就开始router.push(url),而后由于服务器端也有对应的打包,因此会请求数据并拼接获得完整的html页面,并把服务器端vuex的state赋值给context.state并传递给前端,前端拿到这个state以后,就会用这个数据,而不会继续请求新的数据。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <link rel="stylesheet" href="./test1.css"> <link rel="preload" href="./test2.css" as="style"> <link rel="preload" href="./test1.js" as="script"> </head> <body> <h1>title</h1> <p>some contents</p> <script src="./test2.js"></script> </body> </html>
如上,preload使用须要rel中声明,而且须要使用as标记格式。加载顺序为 test2.css -> test1.js -> test1.css -> test2.js,即对于preload的文件会最早加载。可是在浏览器执行时能够发现,preload的文件只是加载可是没有使用,可是若是在下面又但愿引入,这时引入会很快,不会从新下载,即后面但愿用到的时候立马有效,能够解决不少问题。 注意: preload是在当前页面中使用的。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <link rel="stylesheet" href="./test1.css"> <link rel="prefetch" href="./test2.css"> <link rel="prefetch" href="./test1.js"> </head> <body> <h1>title</h1> <p>some contents</p> <script src="./test2.js"></script> </body> </html>
如上而prefetch的使用,它不须要添加as来标记类型,加载顺序为 test1.css -> test2.js -> test2.css -> test1.js,通常prefetch会最后加载,因此,使用prefetch每每是用在下一页可能会用到,这种状况也比较常见,这样用户在点入下一个可能的页面时,因为数据已经加载到,那么速度就很是快了。
18. 在服务器端渲染获得页面view source时,能够看到 data-server-rendered = "true",是什么意思?data-server-rendered
特殊属性,让客户端 Vue 知道这部分 HTML 是由 Vue 在服务端渲染的,而且应该以激活模式进行挂载。注意,这里并无添加 id="app"
,而是添加 data-server-rendered
属性:你须要自行添加 ID 或其余可以选取到应用程序根元素的选择器,不然应用程序将没法正常激活。在开发模式下,Vue 将推断客户端生成的虚拟 DOM 树(virtual DOM tree),是否与从服务器渲染的 DOM 结构(DOM structure)匹配。若是没法匹配,它将退出混合模式,丢弃现有的 DOM 并从头开始渲染。在生产模式下,此检测会被跳过,以免性能损耗。
即咱们能够区分是不是服务器端渲染,只要存在data-server-renderer="true"便可判断。
可参考这篇文章: https://zhuanlan.zhihu.com/p/25936718
而代码大部分是参考: https://github.com/vuejs/vue-hackernews-2.0