vue ssr 服务端渲染 - 初学者的开发环境搭建
前言
默认已经了解 ssr 的基本内容和实现,不熟悉的可以先看下 vue ssr 服务端渲染小白解惑
网上有关 ssr 开发环境搭建的文章不算多,就算找到也是比较高级的,不太适合新手入坑;这篇内容只抽取了其中最重要的部分,实现最基础的开发环境搭建;所谓开发环境无非两件事:自动打包·自动刷新页面, 叫法比较土,也可以叫热更新,热加载。
自动更新 renderer
先看目录结构
没啥东西,新增了一个 hot.config.js 文件,用来放置热加载的配置;
先看下 server.js
//server.js
const express = require('express');
const chalk = require('chalk');
const server = express();
let renderer;
const hotServer = require('./webpack.hot.config.js')
// 我们希望通过不停的执行下面这样一个函数的回调,从新实例化 renderer,已达到自动更新的目的;hotServer(server,({serverBundle,clientManifest})=>{console.log('hot***************************************************')
renderer = require('vue-server-renderer').createBundleRenderer(serverBundle,{
runInNewContext: false, // 推荐
template: require('fs').readFileSync('./index.html', 'utf-8'),
clientManifest //(可选)客户端构建 manifest
})
})
server.use('/',express.static('./dist')) // 设置访问静态文件路径
server.get('*', (req, res) => {res.set('content-type', "text/html");
const context = {url:req.url}
renderer.renderToString(context, (err, html) => {if (err) {res.status(500).end('Internal Server Error')
return
} else {res.end(html)
}
})
})
server.listen(8080,function(){let ip = getIPAdress();
console.log(` 服务器开在:http://${chalk.green(ip)}:${chalk.yellow(8080)}`)
})
function getIPAdress(){//node 下的 os 模块可以拿到启动该文件的服务端的部分信息,细节自己去 node 上面查
var interfaces = require('os').networkInterfaces();
for (var devName in interfaces) {var iface = interfaces[devName];
for (var i = 0; i < iface.length; i++) {var alias = iface[i];
if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {return alias.address;}
}
}
}
跟自动化有关的代码只有和 hotServer 有关的几行代码,其实也是经过分析,对于这个 server 服务,有用的文件只有服务端配置 vue-ssr-server-bundle.json 和客户端配置 vue-ssr-client-manifest.json,我们只要在这两个文件打包之后从新实例化 renderer 就可以了,应为 renderer 实例化是通过加载静态文件而生产的一个不再变化的实例;
webpack.hot.config.js 也可以写在 server.js 里面,不过看上去就不太好看,也不符合模块开发的原则;
自动打包
webpack 自动打包有 watch, 或者使用 webpack-dev-serve,dev-server 会自己启一个服务,然而我们有自己的 server, 他们之间通信不是不可以,只是比较困难,不适用;watch 也是一样,比较独立;
webpack 可以独立使用,然而它也只是 node 中的一个模块,可以作为插件使用,而且可以通过其他插件和 express 结合使用;
webpack-dev-middleware
webpack-dev-middleware 是实现热加载的核心组件,看一下 webpack.hot.config.js 的内容
//webpack.hot.config.js
const webpack = require('webpack');
// 新增 webpack-dev-middleware 插件
const webpackDevMiddleware = require('webpack-dev-middleware');
const path=require('path')
const clientConfig=require('./webpack.client.config.js')
const serverConfig=require('./webpack.server.config.js')
// 输出一个函数给 server 使用
module.exports = function(server,callBack){
let b,c;
// 这里先定义一个 run 方法,保证 vue-ssr-server-bundle.json 和 vue-ssr-client-manifest.json 都有的情况才去执行回调;function run(){console.log('run');
if(b && c){console.log('runend')
callBack({serverBundle:JSON.parse(b),clientManifest:JSON.parse(c)})
}
}
// 生成 vue-ssr-server-bundle.json
// 实例化 webpack
const serverComplier = webpack(serverConfig);
middleware = webpackDevMiddleware(serverComplier)
server.use(middleware);
//serverComplier 是 webpack 返回的实例,plugin 方法可以捕获事件,done 表示打包完成
serverComplier.plugin('done',complation => {console.log('serverdown')
// 核心内容,middleware.fileSystem.readFileSync 是 webpack-dev-middleware 提供的读取内存中文件的方法;// 不过拿到的是二进制,可以用 JSON.parse 格式化;let serverBundle=middleware.fileSystem.readFileSync(path.join(serverConfig.output.path, 'vue-ssr-server-bundle.json'))
// 把拿到的文件复制给 b
b=serverBundle;
run();})
// 生成 vue-ssr-client-manifest.json,方法和上面一模一样
const clientComplier = webpack(clientConfig)
clientMiddleware = webpackDevMiddleware(clientComplier),{
noInfo: true,
stats: {colors: true}
}
server.use(clientMiddleware)
clientComplier.plugin('done',complation=>{console.log('clientdown')
let clientBundle=clientMiddleware.fileSystem.readFileSync(path.join(clientConfig.output.path, 'vue-ssr-client-manifest.json'))
c=clientBundle;
run()})
}
webpack-dev-middleware 最大的特点就是把打包好的文件放到内存中自己玩,不生成文件,对于大部分项目没有问题,然而 ssr 需要读取静态文件才能继续玩,webpack-dev-middleware 也确实提供了读取文件的方法 middleware.fileSystem.readFileSync,类似 fs 插件;
简单说明下 webpack 流程,webpack 是单线程,在打包的过程中会广播不同的事件告诉大家 webpack 现在干到哪一步了,webpack()会返回一个实例 compalier,通过 compalier.plugin(‘done’,()=>{})方式监听 webpack 的情况,这也是自己编写 webpack 插件的核心内容;done 就表示打包完了,我们就这这个时候去拿我们想要的文件就对了;
页面自动刷新
默认上面的代码没有 bug, 下面这个插件没什么讲的,知道怎么用就行了;
webpack-hot-middleware
直接上最终版的代码,server,js 没有动,修改下 webpack.hot.config.js:
//webpack.hot.config.js
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
// 新增 webpack-hot-middleware 插件
const webpackHotMiddleware = require('webpack-hot-middleware');
const path=require('path')
const clientConfig=require('./webpack.client.config.js')
const serverConfig=require('./webpack.server.config.js')
module.exports = function(server,callBack){
let b,c;
function run(){console.log('run');
if(b && c){console.log('runend')
callBack({serverBundle:JSON.parse(b),clientManifest:JSON.parse(c)})
}
}
const serverComplier = webpack(serverConfig) ;
middleware = webpackDevMiddleware(serverComplier)
server.use(middleware);
serverComplier.plugin('done',complation => {console.log('serverdown')
let serverBundle=middleware.fileSystem.readFileSync(path.join(serverConfig.output.path, 'vue-ssr-server-bundle.json'))
b=serverBundle;
run();})
// 修改入口文件,固定写法
clientConfig.entry=['./entry-client.js','webpack-hot-middleware/client?reload=true'];
// 新增自动更新插件,固定写法
clientConfig.plugins.push(new webpack.HotModuleReplacementPlugin());
// 上面两条要写在下面这个实例化之前,很好理解
const clientComplier = webpack(clientConfig)
clientMiddleware = webpackDevMiddleware(clientComplier),{
noInfo: true,
stats: {colors: true}
}
server.use(clientMiddleware)
clientComplier.plugin('done',complation=>{console.log('clientdown')
let clientBundle=clientMiddleware.fileSystem.readFileSync(path.join(clientConfig.output.path, 'vue-ssr-client-manifest.json'))
c=clientBundle;
run()})
// 只需要加载这一个就行了
server.use(webpackHotMiddleware(clientComplier));
}
node server.js 试一下。讲道理没有问题 0.0
上面代码优化的点其实非常多,除了随意的变量名,就是生成两个文件的方式,一样的代码很多,而且 vue-ssr-server-bundle.json 是不是可以不用 webpack-dev-middleware 拿。这只是一个简单的例子,能用,但还不够。
总结
实现 express+webpack 的开发模式就是利用早期的 dev-middleware 插件,dev-serve 只适合纯前端,这里的难点就是怎么从内存中拿到真实的文件,拿到后怎么处理;虽然只是几行代码,理解起来却不太容易;还有就是 webpack 的打包原理,还是要学一下;
有关 node 前后端一起开发的开发环境搭建我这里也有一篇入门的文章:
手动搭建 vue+node 单页面(一)