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"
]
}