#ReactApp項目構建流程【2】

ReactApp項目構建流程【2】

React服務端渲染

  • 爲何會有服務端渲染?html

    • webapp開發模式不少框架都由瀏覽器渲染HTML內容,而seo抓取url內容時並不會執行js代碼,抓取webapp時獲得的是一個HTML[空內容]+js[內容寫在js中],seo難以進行推廣
    • 而且由瀏覽器渲染的webapp HTML內容必須等到js文件加載完成,首次請求時等待時間會比較長,體驗差
    • React團隊運用nodejs,使得用react構建的app可以在nodejs環境中進行渲染,以獲得內容,再返回給瀏覽器,此時的HTML就可以被seo以及爬蟲抓取,用戶等待時間也會變短

服務端渲染準備

工具 react-dom下的server模塊

reat-dom是React專門爲web端開發的渲染工具。
 
 在客戶端,咱們可使用react-dom的render方法渲染組件
 
 在服務端,react-dom/server提供咱們將react組件渲染成HTML的方法

基礎配置

  • 打開咱們以前作好的項目,這裏是以前的項目連接,未上傳至github
  • 首先打開app.js,發現以前是將<App />這個JSX標籤render(中文:傳遞)到document.body中的,而問題是在服務端中並無瀏覽器環境,天然也就沒有document這個dom對象node

    • 同級目錄下新建server-entry.js,將須要服務端渲染的內容export出去
      import React from 'react' //用到了JSX就須要import 'react'
      import App from './App.jsx' //須要添加 [./],由於默認的import目錄是node_modules
      export default <App /> //返回的應該是jsx語法再轉義
      至此一個簡單的server-entry.js文件就寫好了
      該文件的做用:在服務端渲染時使用該文件,要把該文件單獨打包出來,由於是JSX語法,須要用babel
    • webpack.config.js同級創建webpack.config.server.js
      內容拷貝自webpack.config.js,與entry選項同級添加react

      target:"node",webpack

      //指定webpack打包出來的文件是使用在哪一個執行環境中的,選項有且不只有web/node/...

      entry中的app修改成server-entry.jsgit

      output中的filename修改成'server-entry.js'而非hash化文件名github

      //此項可用於輸出版本號用於緩存更新,而服務端無瀏覽器緩存概念,而且這會加大import難度
      而且添加一項libraryTarget:"commonjs2" 
          //打包出js時使用的模塊方案,有多種global/cmd/amd/umd/commjs/...,commonjs2適用於node端

      刪除plugins:[new HTMLPlugin()],web

      同時刪除其引用const HTMLPlugin=require('html-webpack-plugin')
      由於服務端負責渲染而非輸出HTML文件
  • 配置完上步以後就能夠把上部分的js打包出來,爲方便後續打包,修改下package.json配置express

    * 刪除`scripts`下的`test`,當前暫時用不到
           "test": "echo \"Error: no test specified\" && exit 1",
           
       * 爲分別build`客戶端`和`服務端`以及區分不一樣端文件,在`scripts`添加兩個`script`並修改`build`用於,將`webpack.config.js`修改成下端代碼中的文件名
           "build:client":"webpack --config webpack.config.client.js",
           "build:server":"webpack --config webpack.config.server.js",
           "clear":"rimraf dist",
           //用於每次build時覆蓋dist目錄,該包專門用於刪除文件夾,須要安裝[該段第五步]
           "build":"npm run clear && npm run build:client && npm run build:server"
           //使用npm 按順序 run clear,build:client 和build:server 腳本
           
           $npm i rimraf -D
           //安裝完會自動更新package.json文件依賴關係
寫到這裏咱們能夠發現npm run這個命令實際上是是運行package.json中的scripts中相應的script片斷的key-value中的value,好比npm run clear實際是運行npm run rimraf dist
  • npm run buildnpm

    編譯後能夠在output目錄dist中,分別看到客戶端js文件`[name].[hash].js`以及服務端js文件`server-entry.js`
    對比能夠發現server-entry.js在頂部使用了module.exports這一node語法,所以能夠發現是能夠被node執行各類操做包括渲染的
  • root下newserver文件夾用於添加node server,用到express,須要安裝,$npm i express -S,由於該服務是正常服務中須要用到的,因此要使用-S安裝到依賴模塊當中json

    安裝好後在server文件夾內新建server.js並加入如下代碼
    
    const express=require('express');                //node方式引入expeess
    const ReactSSR=require('react-dom/server');      //node方式引入react-dom/serve
    const serverEntry=require('../dist/server-entry').default;
    //此處爲什麼須要添加.default呢,由於若是不添加default時,下面的打印說明了問題
    const app=express();
    console.log(serverEntry);
    /*會發現打印出來的是這麼個東西,其中default中的東西纔是nodeServer須要渲染的東西
    { __esModule: true,
         default:
             { 
                '$$typeof': Symbol(react.element),
                type: [Function: App],
                key: null,
                ref: null,
                props: {},
                 _owner: null,
                _store: {} 
               }
      }
    */
    由於當前使用的是commonjs2模塊方案,而commonjs2默認使用export.default來export模塊,
    能夠回到server-entry.js看下代碼
    
    export default <APP /> 對應的引入方法 import app from './App.jsx'】
    export const app=App   對應的引入方法 import {app} from '..'     】->ES6中解構的寫法
    node中使用的是require引入,require默認不會讀取default內容
                 
     app.get('*',function (req,res) {
            const appString=ReactSSR.renderToString(serverEntry);
            res.send(appString)
         });
              
         app.listen(3000,function () {         //監聽端口,成功函數
             console.log('server is listening')
         })
  • $npm start

    進入http://localhost:3000端口,打開network查看Headers以及Response
    
    至此最簡單的服務端渲染已經完成,此時的返回內容html少以及未引用客戶端的業務代碼js
    爲解決該問題,須要把服務端渲染出來的內容(當前爲server-entry.js)插入到index.html
    (html-webpack-plugin插件經過編譯成的HTML)的body當中並總體返回body,這纔算打通一個整的
    服務端渲染過程
  • 添加模板文件template.html以及修改webpack.config.client.js配置

    在client目錄下新建`template.html`,在body中添加如下代碼
    
    <div id="root"><app></app></div>
    //這個名爲root的div就能夠替代以前app.js中App組件掛載的document.body了,而內部的app標籤則是用於覆蓋,下面講怎麼覆蓋
    
    在webpack.config.client.js的HTMLPlugin中添加剛纔新建的template模板
         {
           template:path.join(__dirname,'../client/template.html')
         }
         
     app.js中以前掛載的document.body修改成document.getElementById('root')
     ReactDOM.render(<App />,document.body)
     ->
     ReactDOM.render(<App />,document.getElementById('root'))
     //把渲染出來的內容render到root節點中
  • 配置好template後,在server.js中讀取template.html而且替換掉<app>標籤

    const fs=require('fs')            //node文件模塊
    const path=require('path')        //node路徑模塊
    const template=fs.readFileSync(path.join(__dirname,'../client/template.html'),'utf-8')    
    //同步讀取絕對路徑文件,而且以utf-8格式輸出,不指定的話回默認輸出buffer格式
               
    //修改app的get方法
    app.get('*',function (req,res) {
           const appString=ReactSSR.renderToString(serverEntry);
           res.send(template.replace('<app></app>',appString));//修改用於替換掉<app>標籤
    });
  • $npm run build ->$npm start

    進入http://localhost:3000,成功,可是network時發現請求localhost和請求js文件時返回結果同樣,
    也就是說有資源被重複返回的狀況出現
    由於咱們的服務接受到的全部請求都會返回服務端渲染的內容
    針對此問題,須要添加配置以肯定哪些是不須要屢次返回的靜態資源文件
                    
    app.use('/public',express.static(path.join(__dirname,'../dist')));
    //express中爲咱們提供的專門處理此問題的模塊,用於肯定哪些文件夾下的資源是靜態文件
    由於咱們的client下的全部內容都在webpack.config.client.js中配置編譯到dist(output->path)下,
    因此此處的靜態文件路徑就join dist,其中的publicPath在上次在跟走代碼時置爲空了,如今從新加回去,
    webpack.config.server.js中也須要加回去
                    
    #咱們能夠在編譯好的index.html中查看資源路徑是否正確
  • $npm run build ->$npm start

    瀏覽器查看,無異常,可是有個warning
        app.cad52c1053474fca53c1.js:14238 Warning: render(): Calling ReactDOM.render() to 
    hydrate server-rendered markup will stop working in React v17. Replace the ReactDOM.render() 
    call with ReactDOM.hydrate() if you want React to attach to the server HTML
                
    這是在react16中,本來是使用ReactDom.render方法去渲染,新加了一個方法,若是咱們使用了服務端渲染,那麼須要使用hydrate()在客戶端的js中去渲染客戶端中的內容,由於react會比對server和client生成的代碼。若是有差異,則會使用客戶端的代碼
                
    替換app.js中的render爲hydrate()
        ReactDOM.render(<App />,document.getElementById('root'));
        ->
        ReactDOM.hydrate(<App />,document.getElementById('root'));

小結

有時間再寫吧
問題:
    * 每次修改client後,都須要 npm run build
    * 每次修改server後,都須要npm start
    * 下篇文章再接觸此內容
相關文章
相關標籤/搜索