这就是服务端的启动代码了,只需处理获取几个打包过后的参数(template模板和clientManifest),传入createBundleRenderer函数。然后通过renderToString,展现给客户端。
七、热更新与本地调试
上面一步是启动服务,但是我们本地调试的时候,不可能每次build之后,再启动,然后再修改,再build吧?那也太麻烦了。所以我们借助webpack搞一个热更新。这里在build里面添加一个文件
server.dev.conf.js
//server.dev.conf.js const fs = require('fs') const path = require('path') const MFS = require('memory-fs') const webpack = require('webpack') const chokidar = require('chokidar') const clientConfig = require('./webpack.client.conf.js') const serverConfig = require('./webpack.server.conf.js') const readFile = (fs, file) => { try { return fs.readFileSync(path.join(clientConfig.output.path, file), 'utf-8') } catch (e) {} } module.exports = function setupDevServer (app, templatePath, cb) { let bundle let template let clientManifest let ready const readyPromise = new Promise(r => { ready = r }) const update = () => { if (bundle && clientManifest) { ready() cb(bundle, { template, clientManifest }) } } // read template from disk and watch template = fs.readFileSync(templatePath, 'utf-8') chokidar.watch(templatePath).on('change', () => { template = fs.readFileSync(templatePath, 'utf-8') console.log('index.html template updated.') update() }) // modify client config to work with hot middleware clientConfig.entry.app = ['webpack-hot-middleware/client', clientConfig.entry.app] clientConfig.output.filename = '[name].js' clientConfig.plugins.push( new webpack.HotModuleReplacementPlugin(), new webpack.NoEmitOnErrorsPlugin() ) // dev middleware const clientCompiler = webpack(clientConfig) const devMiddleware = require('webpack-dev-middleware')(clientCompiler, { publicPath: clientConfig.output.publicPath, noInfo: true }) app.use(devMiddleware) clientCompiler.plugin('done', stats => { stats = stats.toJson() stats.errors.forEach(err => console.error(err)) stats.warnings.forEach(err => console.warn(err)) if (stats.errors.length) return clientManifest = JSON.parse(readFile( devMiddleware.fileSystem, 'vue-ssr-client-manifest.json' )) update() }) // hot middleware app.use(require('webpack-hot-middleware')(clientCompiler, { heartbeat: 5000 })) // watch and update server renderer const serverCompiler = webpack(serverConfig) const mfs = new MFS() serverCompiler.outputFileSystem = mfs serverCompiler.watch({}, (err, stats) => { if (err) throw err stats = stats.toJson() if (stats.errors.length) return // read bundle generated by vue-ssr-webpack-plugin bundle = JSON.parse(readFile(mfs, 'vue-ssr-server-bundle.json')) update() }) return readyPromise }
这个代码基本上是从官方文档copy下来的,写的挺好的 哈哈。
怎么理解这段代码呢,这个代码封装了一个promise,因为代码更新后重新打包需要时间,所以我们在renderToString之前,需要等待一段处理的时间。这个代码对3部分进行了监控,template.html、vue业务代码、客户端配置代码。检测到有改动之后,就重新打包获取,然后返回。这里就是热更新部分代码,当然我们还要改动server.js部分代码,毕竟要处理开发模式和生成模式的不同。
//server.js const express = require("express"); const fs = require('fs'); let path = require("path"); const server = express() const { createBundleRenderer } = require('vue-server-renderer') const isProd = process.env.NODE_ENV === 'production' let renderer let readyPromise const resolve = file => path.resolve(__dirname, file) const templatePath = resolve('./src/index.template.html') function createRenderer (bundle, options) { return createBundleRenderer(bundle, Object.assign(options, { runInNewContext: false })) } if(isProd){ const template = fs.readFileSync(templatePath, 'utf-8') const bundle = require('./dist/vue-ssr-server-bundle.json') const clientManifest = require('./dist/vue-ssr-client-manifest.json') renderer = createRenderer(bundle, { template, clientManifest }) }else{ readyPromise = require('./build/server.dev.conf.js')( server, templatePath, (bundle, options) => { renderer = createRenderer(bundle, options) } ) } server.use(express.static('./dist')) // 在服务器处理函数中…… server.get('*', (req, res) => { const context = { url: req.url } // 这里无需传入一个应用程序,因为在执行 bundle 时已经自动创建过。 // 现在我们的服务器与应用程序已经解耦! if(isProd){ renderer.renderToString(context, (err, html) => { // 处理异常…… res.end(html) }) }else{ readyPromise.then(()=>{ renderer.renderToString(context, (err, html) => { // 处理异常…… res.end(html) }) }) } }) server.listen(3001, () => { console.log('服务已开启') })