React服務端渲染之路01——項目基礎架構搭建

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"
  ]
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章