vue服务器端渲染
服务器端渲染的优势在于更好的seo以及更快的渲染速度,所以vue也开始支持服务器端渲染,即ssr。
基本知识
要使用服务器端渲染,需要使用server-entry.js和client-entry.js两个入口文件,两者都会使用到app.js进行打包,其中通过server-entry.js打包的代码是运行在node端,二通过client-entry.js打包代码运行在客户端。 具体的流程图如下所示。
从图上可以看出,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,这两个文件都是非常容易获取的
在纯前端渲染时,一般使用的是web-dev-server这个插件,它可以自动帮助我们开启一个node端,主要作用是监控并打包代码,但是实际上还是纯前端渲染,另外配合web-hot-middleware来进行HMR热更新,这样可以在我们改变了代码之后自动打包并更新view,以此来提高开发效率。
而在vue服务器端渲染时,就不能只是使用web-dev-server和web-hot-middle了,因为我们需要添加服务器渲染的node代码逻辑,这样,我们可以自己开一个node服务器,使用webpack-dev-middle中间件进行打包、使用webpack-hot-middle中间件进行热更新,并添加服务器端渲染逻辑,即node端通过引入vue-serverer-renderer插件来渲染服务器端打包的bundle文件到客户端。
而在服务器端的配置文件webpack.server.js如下所示:
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。
而之前所提到的webpack-dev-middleware和webpack-hot-middleware这两个插件可以配置在setup-dev-server.js中的,在index.js中配置服务器时,如果是开发环境,再引入setup-dev-server.js,否则不需要引入。
所以在bi项目中的两个服务器实际上都是node服务器,其中index.js创建的服务器是node服务器,提供ssr和其他的一些服务,而server下中src的index.js是一个node中间件服务器,起到的是代理服务的作用。
注意事项
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