5.開發時服務端渲染
由於配置了webpack-dev-server,客戶端啟動時,就不必再本地生成dist目錄。但是伺服器端的編譯還是需要本地的dist目錄,所以本節我們將會配置服務端的內容,使得服務端也不用依賴本地的dist目錄。
相關依賴
- npm i axios// http依賴,估計大家都知道
- npm i memery-fs -D// 相關介面和node的fs一樣,只不過是在記憶體中生成檔案
- npm i http-proxy-middleware -D// 伺服器端一個代理的中介軟體
本章節內容比較難,對於沒有接觸過node和webpack的同學,理解起來不是那麼容易。我也是不知道看了多少遍,才大概知道其流程。
開發時的服務端配置
以前server.js中要依賴本地dist中的檔案,所以首先要對其進行更改。
const static = require('./util/dev-static') const isDev = process.env.NODE_ENV === 'development'// 增加環境的判斷 const app =express() if(!isDev) { // 生產環境,和以前的處理方式一樣 const serverEntry = require('../dist/server-entry').default // 配置靜態檔案目錄 app.use('/public', express.static(path.join(__dirname, '../dist'))) const template = fs.readFileSync(path.join(__dirname, '../dist/index.html'), 'utf-8') // https://blog.csdn.net/qq_41648452/article/details/80630598 app.get('*', function(req, res) { const appString = ReactSSR.renderToString(serverEntry) res.send(template.replace('<!-- <app /> -->', appString)) }) } else { // 開發環境,進行單獨的處理 static(app) }
開發環境中的static方法,位於server/util/dev-static.js中,接受一個app引數。按照生產模式的處理邏輯,開發模式下的配置也分為如下幾點:
- 獲取打包好的入口檔案,即server-entry.js檔案
- 獲取模板檔案
- 將模板檔案中的內容替換為server-entry.js中的內容,返回給客戶端
- 對靜態檔案的請求進行處理。
獲取模板檔案
獲取模板檔案最簡單,所以最先解決這個問題。配置客戶端的devServer時,再http://localhost :8888下面就可以訪問到index.html檔案,呼叫下面getTemplate方法就可以拿到模板檔案。
const axios = require('axios') const getTemplate = () => { return new Promise((resolve, reject) => { axios.get('http://localhost:8888/public/index.html') .then(res => { resolve(res.data) }) .catch(reject) }) }
獲取server-entry.js檔案
獲取服務端的檔案,我們需要用到memory-fs包,直接再記憶體中生成打包好的檔案,讀取速度更快,那要怎麼配置呢?
const path = require('path') const webpack = require('webpack') const MemoryFs = require('memory-fs') const serverConfig = require('../../build/webpack.config.server') // 讀取配置檔案 // webpack(serverConfig)和我們的build:server命令類似 const serverCompile = webpack(serverConfig) // webpack處理 const mfs = new MemoryFs() serverCompile.outputFileSystem = mfs// 將檔案的輸出交給mfs;預設應該是node的fs模組 // 監聽檔案的變化 serverCompile.watch({}, (err, stats) => { if(err) throw err // stats物件有一些狀態資訊,如我們編譯過程中的一些錯誤或警告,我們直接將這些資訊打印出來 stats = stats.toJson() stats.errors.forEach(err => console.err(err)) stats.warnings.forEach(warn => console.warn(warn)) // 通過配置檔案獲取檔案的路徑和名稱 const bundlePath = path.join( serverConfig.output.path, serverConfig.output.filename ) // 讀取檔案的內容 const bundle = mfs.readFileSync(bundlePath, 'utf-8') })
所以服務端檔案也獲取到了?其實還是有問題的,我們獲取的僅僅是字串,並不是node中的一個模組(如果聽不懂,先去補補node中模組的概念),所以還需要做進一步的處理。
const Module = module.constructor// node中,每個檔案中都有一個Module變數,不懂的就要多學習了 const m = new Module() m._compile(bundle, serverConfig.output.filename) // 將字串編譯為一個模組 serverBundle = m.exports.default // 這才是我們需要的內容
替換內容
app.get('*', function (req, res) { getTemplate().then(template => { const content = ReactDomSSR.renderToString(serverBundle) res.send(template.replace('<!-- <app /> -->', content)) }) })
靜態資源處理
和模板檔案一樣,靜態資源我們將會代理到localhost:8888裡面去獲取
const proxy = require('http-proxy-middleware') app.use('/public', proxy({ target: 'http://localhost:8888' }))
到這裡,開發時服務端渲染就完成了。
本小節完整程式碼位於倉庫 的2-8分支,覺得有用的可以去start一下。