彻底理解服务端渲染 - SSR原理( 三 )


同构的条件为了实现同构,我们需要满足什么条件呢?首先,我们思考一个应用中一个页面的组成,假如我们使用的是Vue.js,当我们打开一个页面时,首先是打开这个页面的URL,这个URL,可以通过应用的路由匹配,找到具体的页面,不同的页面有不同的视图,那么,视图是什么?从应用的角度来看,视图 = 模板 + 数据,那么在 Vue.js 中, 模板可以理解成组件,数据可以理解为数据模型,即响应式数据 。所以,对于同构应用来说,我们必须实现客户端与服务端的路由、模型组件、数据模型的共享 。

彻底理解服务端渲染 - SSR原理

文章插图
 
实践知道了服务端渲染、同构的原理之后,下面从头开始,一步一步完成一次同构,通过实践来了解SSR 。
实现基础的NODEJS服务端渲染首先,模拟一个最简单的服务器渲染,只需要向页面返回我们需要的html文件 。
const express = require('express');const app = express();app.get('/', function(req, res) {res.send(`<html><head><title>SSR</title></head><body><p>hello world</p></body></html>`);});app.listen(3001, function() {console.log('listen:3001');});启动之后打开localhost:3001可以看到页面显示了hello world 。而且打开网页源代码:
彻底理解服务端渲染 - SSR原理

文章插图
 
也就是说,当浏览器拿到服务器返回的这一段HTML源代码的时候,不需要加载任何JavaScript脚本,就可以直接将hello world显示出来 。
实现基础的VUE客户端渲染我们用 vue-cli新建一个vue项目,修改一个App.vue组件:
<template><div><p>hello world</p><button @click="sayHello">say hello</button></div></template><script>export default {methods: {sayHello() {alert('hello ssr');}}}</script>然后运行npm run serve启动项目,打开浏览器,一样可以看到页面显示了 hello world,但是打开我们开网页源代码:
彻底理解服务端渲染 - SSR原理

文章插图
 
除了简单的兼容性处理 noscript 标签以外,只有一个简单的id为app的div标签,没有关于hello world的任何字眼,可以说这是一个空的页面(白屏),而当加载了下面的 script 标签的 JavaScript 脚本之后,页面开始这行这些脚本,执行结束,hello world 正常显示 。也就是说真正渲染 hello world 的是 JavaScript 脚本 。
同构VUE项目构建配置模板组件的共享,其实就是使用同一套组件代码,为了实现 Vue 组件可以在服务端中运行,首先我们需要解决代码编译问题 。一般情况,vue项目使用的是webpack进行代码构建,同样,服务端代码的构建,也可以使用webpack,借用官方的一张 。
彻底理解服务端渲染 - SSR原理

文章插图
 
第一步:构建服务端代码由前面的图可以看到,在服务端代码构建结束后,需要将构建结果运行在nodejs服务器上,但是,对于服务端代码的构建,有一下内容需要注意:
  • 不需要编译CSS,样式表只有在浏览器(客户端)运行时需要 。
  • 构建的目标的运行环境是commonjs,nodejs的模块化模式为commonjs
  • 不需要代码切割,nodejs将所有代码一次性加载到内存中更有利于运行效率
于是,我们得到一个服务端的 webpack 构建配置文件 vue.server.config.js
const nodeExternals = require("webpack-node-externals");const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')module.exports = {css: {extract: false // 不提取 CSS},configureWebpack: () => ({entry: `./src/server-entry.js`, // 服务器入口文件devtool: 'source-map',target: 'node', // 构建目标为nodejs环境output: {libraryTarget: 'commonjs2' // 构建目标加载模式 commonjs},// 跳过 node_mdoules,运行时会自动加载,不需要编译externals: nodeExternals({allowlist: [/.css$/] // 允许css文件,方便css module}),optimization: {splitChunks: false // 关闭代码切割},plugins: [new VueSSRServerPlugin()]})};使用 vue-server-renderer提供的server-plugin,这个插件主要配合下面讲到的client-plugin使用,作用主要是用来实现nodejs在开发过程中的热加载、source-map、生成html文件 。
第二步:构建客户端代码在构建客户端代码时,使用的是客户端的执行入口文件,构建结束后,将构建结果在浏览器运行即可,但是在服务端渲染中,HTML是由服务端渲染的,也就是说,我们要加载那些JavaScript脚本,是服务端决定的,因为HTML中的script标签是由服务端拼接的,所以在客户端代码构建的时候,我们需要使用插件,生成一个构建结果清单,这个清单是用来告诉服务端,当前页面需要加载哪些JS脚本和CSS样式表 。


推荐阅读