本文共--字 阅读约--分钟 | 浏览: -- Last Updated: 2021-02-26
由浏览器去解析执行JavaScript
,接受到的index.html
文件的DOM
树上只有1个div
,由JavaScript
向文档中append innerHTML
;
优点:服务端只用第一次返回一个index.html
,和后续的api
数据,服务器压力减小;
缺点:
HTML
需要去下载css
、js
文件,如果文件过大或出现错误,用户会看到白屏。SEO
(搜索引擎优化)由服务器渲染出带内容的HTML
后返回。 优缺点即相反,用户第一次拿到的HTML
文档已经进行了初步的内容渲染,虽然请求的数量并没有改变,只是把部分请求转移到了服务端,但是在服务器上进行数据拉取的成本要远远小于浏览器端,而且传输更加高效。
同构:服务端渲染应该表达出页面最主要、最核心、最基本的信息;而浏览器端则需要针对交互完成进一步的页面渲染、事件绑定等增强功能。所谓同构,就是指前后端共用一套代码或逻辑,而在这套代码或逻辑中,理想的状况是在浏览器端进一步渲染的过程中,判断已有的DOM
结构和即将渲染出的结构是否相同,若相同,则不重新渲染DOM
结构,只需要进行事件绑定即可。
优点:
SEO
优化支持。服务端接收到请求后,会返回一个相对完整、包含了初始内容的HTML
文档,所以更有利于搜索引擎爬虫获取信息,提高搜索结果展现排名。同时,更快的页面加载时间也有利于搜索结果展现排名的提升。
实现更加灵活。服务端渲染只是直出页面的初始内容,浏览器端仍然需要做后续工作,以完成页面的最终展现。这样服务端渲染和浏览器端渲染仍可以平衡,在很大程度上也能实现代码复用。
更好的用户体验。对于低端机型、恶劣的网络环境更加友好。因为内容的初步渲染是在服务端完成的,所以对于低端机型更加友好,不至于页面加载时出现白屏幕的状况。
缺点:
TTFB(Time To First Byte)
时间。TTFB
时间指的是从浏览器发起最初的网络请求,到从服务器接收到第一个字节的这段时间。它包含了TCP
连接时间、发送HTTP
请求的时间和获得响应消息的第一个字节的时间。因为对数据的获取和对页面初始内容的渲染,势必会降低服务端返回的速度。同构应用的核心在于虚拟DOM
,虚拟DOM
的优点如下:
DOM
树是高耗时的操作,所以应尽量减少DOM
树操作能优化网页的性能而通过 DOM Diff
算法能找出两个不同Object
的最小差异,得出最小的DOM
操作。DOM
在渲染时不仅可以通过操作DOM
树表示结果,也可能有其他表示,例如将虚拟DOM
渲染成字符串(服务器端渲染SSR
),或者渲染成手机App
原生的UI
组件(React Native
)构建同构应用的最终目的是从一份项目源码中构建出两份 JavaScript
代码,一份用于在浏览器端运行,一份用于在Node.js
环境中运行并渲染出HTML
。
对于要在Node
环境运行的JS
代码需要注意:
API
,例如DOM
、BOM
对象;CSS
代码,因为服务端渲染的目的是渲染出HTML
的内容,渲染CSS
会增加额外的计算,影响服务端渲染的性能。node_modules
里的第三方包和Node.js
的原生模块(例如fs
模块)打包进去,而是通过CommomJS
规范引入这些模块。CommonJS
规范导出一个渲染函数,用于在HTTP
服务器中执行这个渲染函数,渲染出HTML
的内容然后返回。关于 Webpack
的配置有两份,一份针对原本常用浏览器渲染,一份用于服务器渲染;
用于浏览器端的 webpack.config.js
:
// webpack.config.js
const path = require('path');
module.exports = {
// JS 执行入口文件
entry: './main_browser.js',
output: {
// 把所有依赖的模块合并输出到一个 bundle.js 文件
filename: 'bundle_browser.js',
// 输出文件都放到 dist 目录下
path: path.resolve(__dirname, './dist'),
},
module: {
rules: [
{
test: /\.js$/,
use: ['babel-loader'],
exclude: path.resolve(__dirname, 'node_modules'),
},
{
test: /\.css/,
use: ['style-loader', 'css-loader'],
},
]
},
devtool: 'source-map' // 输出 source-map 方便直接调试 ES6 源码
};
用于服务器端的webpack_server.config.js
// webpack_server.config.js
const path = require('path');
const nodeExternals = require('webpack-node-externals');
module.exports = {
// JS 执行入口文件
entry: './main_server.js',
// 为了不打包进 Nodejs 内置的模块,例如 fs net 模块等
target: 'node',
// 为了不打包进 node_modules 目录下的第三方模块
externals: [nodeExternals()],
output: {
// 为了以 CommonJS2 规范导出渲染函数,以给采用 Nodejs 编写的 HTTP 服务调用
libraryTarget: 'commonjs2',
// 把最终可在 Nodejs 中运行的代码输出到一个 bundle.js 文件
filename: 'bundle_server.js',
// 输出文件都放到 dist 目录下
path: path.resolve(__dirname, './dist'),
},
module: {
rules: [
{
test: /\.js$/,
use: ['babel-loader'],
exclude: path.resolve(__dirname, 'node_modules'),
},
{
// CSS 代码不能被打包进用于服务端的代码中去,忽略掉 CSS 文件
test: /\.css/,
use: ['ignore-loader'],
},
]
},
devtool: 'source-map' // 输出 source-map 方便直接调试 ES6 源码
};
页面的根组件 AppComponent.js
// AppComponent.js
import React, { Component } from 'react';
import './main.css';
export class AppComponent extends Component {
render() {
return <h1>Hello,Webpack</h1>
}
}
不同环境的主入口文件,也是上面对应webpack
设置的entry
。
用于浏览器端渲染的入口 main_browser.js
// main_browser.js
import React from 'react';
import { render } from 'react-dom';
import { AppComponent } from './AppComponent';
// 把根组件渲染到 DOM 树上
render(<AppComponent/>, window.document.getElementById('app'));
用于服务端端渲染的入口 main_server.js
// main_server.js
import React from 'react';
// React提供的用于服务器端渲染的
import { renderToString } from 'react-dom/server';
import { AppComponent } from './AppComponent';
// 导出渲染函数,以给采用 Nodejs 编写的 HTTP 服务器代码调用
export function render() {
// 把根组件渲染成 HTML 字符串
return renderToString(<AppComponent/>)
}
为了能够见更完整HTML
文件通过HTTP
服务返回给前段,还需要用Node.js
写一个HTTP
服务器。
// http_server.js 借助 express
const express = require('express');
const { render } = require('./dist/bundle_server'); // 就是从main_server中导出的 然后打包的文件
const app = express();
// 调用构建出的 bundle_server.js 中暴露出的渲染函数,再拼接下 HTML 模版,形成完整的 HTML 文件
app.get('/', function (req, res) {
res.send(`
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<div id="app">${render()}</div>
<!--导入 webpack 输出的用于浏览器端渲染的 JS 文件-->
<script src="./dist/bundle_browser.js"></script>
</body>
</html>
`);
});
// 其它请求路径返回对应的本地文件
app.use(express.static('.'));
app.listen(3000, function () {
console.log('app listening on port 3000!')
});
准备工作完成!
执行 webpack --config webpack_server.config.js
构建出用于服务端渲染的./dist/bundle_server.js
文件。
执行 webpack
(默认的配置文件就是webpack.config.js
)构建出用于浏览器环境运行的的./dist/bundle_browser.js
文件。
构建之后,将HTTP
服务器跑起来,node ./http_server.js
,然后访问localhost:3000
就能看到Hello,Webpack
了。