共计 2804 个字符,预计需要花费 8 分钟才能阅读完成。
由于配置了 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 一下。