今天开会,同事突然说,我们的项目首屏渲染时间太久,引起客诉了。怎么办呢,需要把项目从客户端渲染转为服务端渲染,也就是SSR,大家一起想想研究一下怎么改才好。
我:啊?
于是,打开Google,郑重地打出了三个字母+三个汉字:什么是SSR
把学习笔记记录在这。
————————
最后我们使用了next框架改。
——————————
大佬们的demo大概是以下几个流程,我先以自己理解写点笔记,然后尝试着写一个简单demo出来。
使用ReactDOMServer.renderToString 或者ReactDOMServer.renderToNodeStream 方法,将写的JSX解析为html字符串。
什么是同构呢?因为服务端和客户端要获得相同的默认state数据,在初始化的时候,服务端获取了一遍数据,然后注入state,传入客户端,客户端就不用重新请求一遍了(还可能造成数据不统一),服务端和客户端的数据保持一致,就是同构。
同构和数据、js请求、路由都有关,所以我这认为同构是交互层面的。
因为我们项目原本就定了使用egg,所以我得在egg的基础上考虑SSR。
首先npm init egg --type=simple 创建一个空的egg项目。
参考
别人写好可使用的egg-react-ssr 脚手架。https://github.com/zhangyuang/egg-react-ssr
其他demo 参考:https://github.com/yjdjiayou/react-ssr-demo (从头创建)
1、node服务器返回静态HTML页面。
对Egg框架稍微了解一点之后,我需要把返回HTML的代码 放在controller/home里。
也就是说,首先建立一个写react代码的文件夹(我建了page文件夹),然后再在node服务器里引用react页面代码,这样才能做到服务器解析html渲染。
然后,因为我们写的都是JSX片段(虚拟DOM),肯定是无法渲染的,第二步就是将它在node端转换为html代码返回给浏览器。
// 新建一个page文件夹, /page/index.js
import React from 'react'
export default function HTML(props){
return <div>
<p>SSR</p>
<p>纯静态渲染</p>
</div>
}
const { renderToString } = require('react-dom/server');
renderToString就是react中提供用于编译虚拟DOM的方法,它可以将react的虚拟dom转为真实的html节点。我们使用这个方法帮我们处理一下引入的页面。
// /app/controller/home.js
'use strict';
const Controller = require('egg').Controller;
import Home from '../../page/index';
import { renderToString } from 'react-dom/server';
class HomeController extends Controller {
async index() {
const { ctx } = this;
ctx.type = 'text/html'
ctx.status = 200;
const content = renderToString(<Home />);
ctx.body = `
ssr
${content}
`;
}
}
module.exports = HomeController;
看上去好像和资料参考里的一模一样,肯定没问题了!自信满满的我在这个项目终端里打下npm start!
嗯!不出意料,根本启不起来!是因为import无法解析,egg中暂时不支持esm,所以需要把引入方式改成cmd模式,但是egg项目中使用esm没关系,我们的page页面里用cmd根本不好写,所以思来想去,难不成还是得用babel?
查询了一些资料,不少项目都是将node端和客户端代码分别打包,虽然我还是挺懵的,但是搞不懂的话就写bug试一下吧。
于是自信满满(x)的我又在百度打下了几个字:怎么使用webpack babel
总之咱们的pages文件夹下肯定是用webpack编译一下了。
这部分的参考:https://blog.csdn.net/u012443286/article/details/79577545
webpack文件内容参考https://github.com/yjdjiayou/react-ssr-demo
npm i webpack babel-loader babel-core babel-preset-env -D
npm i @babel/core @babel/preset-env @babel/preset-react @babel/plugin-proposal-class-properties -D
// /config/webpack.base.js
'use strict';
// const path = require('path');
module.exports = {
mode: 'development',
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
options: {
presets: [
'@babel/preset-env',
'@babel/preset-react',
],
plugins: [
'@babel/plugin-proposal-class-properties',
],
},
},
],
},
};
// /config/webpack.client
'use strict';
const path = require('path');
const merge = require('webpack-merge');
const base = require('./webpack.base');
module.exports = merge(base, {
entry: './src/client/index.js',
output: {
path: path.resolve('public'),
filename: 'client.js',
},
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: true,
},
},
],
},
],
},
});
// config/webpack.server
'use strict';
const path = require('path');
const nodeExternal = require('webpack-node-externals');
const merge = require('webpack-merge');
const base = require('./webpack.base');
module.exports = merge(base, {
// 注意这个值
target: 'node',
entry: './src/server/index.js',
output: {
path: path.resolve('build'),
filename: 'server.js',
},
externals: [ nodeExternal() ],
module: {
rules: [
{
test: /\.css$/,
use: [
// 服务端渲染不能用 style-loader,因为 node 没有 document 对象,无法插入 style 标签
// 服务端本来就不能渲染 dom,只是提供 html/css/js 代码给浏览器,交给浏览器去渲染
// 服务端返回的 html 源码里,没有 style 标签
// 而在浏览器中的 html 源码里,有 style 标签,是通过 js 插入进去的
'isomorphic-style-loader',
{
loader: 'css-loader',
options: {
modules: true,
},
},
],
},
],
},
});