所有源代码、文档和图片都在 github 的仓库里,点击进入仓库
相关阅读
- React服务端渲染之路01——项目基础架构搭建
- React服务端渲染之路02——最简单的服务端渲染
- React服务端渲染之路03——路由
- React服务端渲染之路04——redux-01
- React服务端渲染之路05——redux-02
- React服务端渲染之路06——优化
- React服务端渲染之路07——添加CSS样式
- React服务端渲染之路08——404和重定向
- React服务端渲染之路09——SEO优化
1. 客户端渲染与服务端渲染
1.1 客户端渲染
- 客户端渲染,实际上就是客户端向服务端请求页面,服务端返回的是一个非常简单的 HTML 页面,在这个页面里,只有很少的一些 HTML 标签
- 客户端渲染时,页面中有两个比较重要的点,第一个是 script 标签,可能会有好几个 script 标签,这个标签是打包后的 js 代码,用来生成 DOM 元素,发送请求,事件绑定等。但是,生成的 DOM 元素需要有一个在页面上展示的容器,所以另外一个点就是容器,一般是一个 id 名为 root 或 app 的 div 标签,类似于这样
<div id="root"></div>
或<div id="app"></div>
客户端渲染的特点
- 客户端加载所有的 js 资源,渲染 DOM 元素
- 在浏览器页面上的所有资源,都由客户端主动去获取,服务端只负责静态资源的提供和 API 接口,不再负责页面的渲染,如果采用 CDN 的话,服务端仅仅需要提供 API 接口
- 优点: 前后端分离,前端专注页面的开发,后端专注接口的开发
- 缺点: 首屏加载资源多,首屏加载时响应慢。页面上没有 DOM 元素,不利于 SEO 优化
1.2 服务端渲染
- 服务端渲染,就是客户端页面上的 HTML 元素,都要由服务端负责渲染。服务端利用模板引擎,把数据填充到模板中,生成 HTML 字符串,最终把 HTML 字符串返回到浏览器,浏览器接收到 HTML 字符串,通过 HTML 引擎解析,最终生成 DOM 元素,显示在页面上
- 比如 Node.js 可以渲染的模板引擎有 ejs,nunjucks,pug 等。Java 最常见的是 JSP 模板引擎。
服务端渲染的特点
- 优点: 页面资源大多由服务端负责处理,所以页面加载速度快,缩短首屏加载时间。有利于 SEO 优化。无需占用客户端资源
- 缺点: 不利于前后端分离,开发效率低。占用服务器资源
1.3 区分与选择
- 客户端渲染和服务端渲染本质的区别就是,是谁负责 HTML 页面的拼接,那么就是谁渲染的页面
- 如果对首屏加载时间有非常高的需求,或者是需要 SEO 优化,那么就选择服务端渲染
- 如果对首屏加载时间没有要求,也不需要做 SEO 优化,类似于做后台管理系列的业务,那么就可以选择客户端渲染
- 具体选择客户端渲染还是服务端渲染,没有强制的要求,具体要根据项目的需求来区分选择
2. 项目基础架构搭建
- 我们现在搭建的项目架构,是一个整体的项目架构,现在创建的一些目录,可能暂时会用不上,但是为了了解每一个目录每一个模块具体都是做什么的,我们提前先知道,方便以后使用
- 我们把项目叫做 react-ssr-webpack
2.1 创建项目
- 新建一个 react-ssr-webpack 的文件夹,这个文件夹存放我们所有的源代码,也就是根目录。
- 使用
npm init
初始化项目 下载 webpack 的依赖包
npm i webpack webpack-cli -D
- webpack 和 webpack-cli 是 wepack 打包的主要模块
下载 Babel 的依赖包
npm i @babel/core @babel/preset-env @babel/preset-react babel-loader @babel/plugin-proposal-class-properties -D
- 我们采用的是 babel@7 版本,不再采用 babel@6 版本,因为 babel@7 使用起来更加的方便,简单
- @babel/core,Babel 编译的核心库
- @babel/preset-env,Babel 官方预置的一个库,一系列插件的集合
- @babel/preset-react,主要是用来编译 jsx 语法
- babel-loader,webpack 处理 js 文件所需要的加载器
- @babel/plugin-proposal-class-properties,编译 class 的一些新的特性
- 下载 React 的依赖包
npm i react react-dom react-router-dom -S
- 下载 express,
npm i express -S
,我们采用 express 做后端服务,也可以采用 koa,hapi,egg 等 - 根目录的文件目录结构
├── node_modules/ 第三方依赖包
|── build/ 服务端打包后生成的代码
| └── server.js
├── public/ 客户端打包后生成的代码
│ └── client.js
├── src
│ ├── client/ 客户端源代码
│ ├── components/ React 组件
│ ├── containers/ React 容器组件
│ ├── server/ 服务端源代码
│ ├── store/ redux
| └── routes.js 路由
├── .babelrc babel 编译
├── .gitignore git 忽略文件
├── package.json
├── webpack.base.js webpack 基础配置
├── webpack.client.js webpack 客户端配置
└── webpack.server.js webpack 服务端配置
2.2 配置服务端的 wbpack.server.js
- 配置服务端的 webpack,是因为我们要在服务端使用 jsx 语法,需要借助 babel 编译
- 既然服务端使用了 babel ,那么服务端也可以使用新的 ES6/7/8 语法
- 还有一点需要注意的是,在 webpack.server.js 进行编译的时候,仅仅是将 jsx 和 ES6/7/8 高级语法转为 ES5 语法,生成一个 build/server.js 文件。所以我们还需要用 build/server.js 创建一个服务,这个服务就是我们最终访问的服务
- webpack.server.js
// webpack.server.js
const path = require('path');
// webpack-node-externals 模块是为了不打包 node 的模块,比如 path, fs 等。因为我们的 Node 已经内置了这些模块,所以没有必要打包
const WebpackNodeExternals = require('webpack-node-externals');
module.exports = {
target: 'node',
// 服务端的入口文件,是 src/server/index.js
mode: 'development',
entry: './src/server/index.js',
output: {
// 打包后生成的文件的路径与文件名
filename: 'server.js',
path: path.resolve(__dirname, 'build/')
},
externals: [nodeExternals()],
module: {
rules: [
{
test: /\.js?$/,
// 可以在这里通过 option 配置 Babel,也可以使用 .babelrc 文件配置 Babel
loader: 'babel-loader',
exclude: /node_modules/
}
]
}
};
2.3 配置 package.json
- 前边我们说了,我们一共需要做两件事,一件事是使用 webpack 编译服务端的代码,另一件事是把服务端编译后生成的代码作为一个服务启动
所以我们先下载两个全局的包,npm-run-all 和 nodemon
npm i npm-run-all -g
,这个工具可以在一个 Terminal 里同时启动多个服务,便于我们开发npm i nodemon -g
,热重启 Node 服务,nodemon 的使用方法和 Node 是一样的,nodemon 监听到 build/server.js 文件的变动,就会自动重启服务
- 我们在 scripts 里添加两条命令
命令1
dev:build:server
,这个命令是调用 webpack.server.js 进行打包"dev:build:server": "webpack --config webpack.server.js --watch"
--config
参数的意思是指定webpack.server.js
为配置文件,如果没有--config
参数,那么 webpack 会默认调用webpack.config.js
配置文件,如果没有指定配置文件,也没有webpack.config.js
文件,那么会报错--watch
参数的意思是开启监听,每次修改代码,都会自动打包
命令2
dev:start
,这个命令是使用打包后代码开启服务"dev:start": "nodemon build/server.js"
- 每次
dev:build:server
打包后,都会生成新的 build/server.js 文件 - nodemon 监测到文件的改动,就会重新开启服务
我们使用 npm-run-all 再添加一条命令,用来启动这两个服务
"dev": "npm-run-all --parallel dev:**"
dev
命令是使用npm-run-all
工具开启多个服务,--parallel
参数的意思是并行开启服务,dev:**
的意思是npm-run-all
会启动scripts
里所有的以dev:
开头的命令。dev
只是一个代称,也可以使用其他的字符
- 建议使用 npm-run-all 和 nodemon,这样我们可以在一个 Terminal 里进行所有的操作,也可以开启多个 Terminal,每个 Terminal 开启不同的服务
{
"dev": "npm-run-all --parallel dev:**",
"dev:build:server": "webpack --config webpack.server.js --watch",
"dev:start": "nodemon build/server.js"
}
- 预览 package.json
{
"name": "react-ssr-docs",
"version": "1.0.0",
"description": "完全解读 react 服务端渲染",
"main": "index.js",
"scripts": {
"dev": "npm-run-all --parallel dev:**",
"dev:build:server": "webpack --config webpack.server.js --watch",
"dev:start": "nodemon build/server.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/dawnight/react-ssr-docs.git"
},
"keywords": [
"react",
"redux",
"react-ssr"
],
"author": "dawnight",
"license": "MIT",
"bugs": {
"url": "https://github.com/dawnight/react-ssr-docs/issues"
},
"homepage": "https://github.com/dawnight/react-ssr-docs#readme",
"devDependencies": {
"@babel/core": "^7.4.3",
"@babel/plugin-proposal-class-properties": "^7.4.0",
"@babel/preset-env": "^7.4.3",
"@babel/preset-react": "^7.0.0",
"babel-loader": "^8.0.5",
"webpack": "^4.30.0",
"webpack-cli": "^3.3.1",
"webpack-node-externals": "^1.7.2"
},
"dependencies": {
"express": "^4.16.4",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-redux": "^7.0.2",
"react-router-dom": "^5.0.0",
"redux": "^4.0.1",
"redux-logger": "^3.0.6",
"redux-thunk": "^2.3.0"
}
}
2.4 配置 .babelrc
- @babel/core, @babel/preset-env 和 @babel/preset-react 库是必须要有的
- @babel/plugin-proposal-class-properties 库并不是必须的,因为在后边我们使用了 class 的新语法特性,所以需要使用这个库,如果不安装这个库,也可以使用传统的 class 的属性方法语法,效果是一样的。
{
"presets": [
"@babel/preset-env",
"@babel/preset-react"
],
"plugins": [
"@babel/plugin-proposal-class-properties"
]
}