前沿

通过上一篇文章 通过vue-cli3构建一个SSR应用程序 我们知道了什么是SSR,以及如何通过vue-cli3构建一个SSR应用程序。但是最后遗留了一些问题没有处理,就是没有添加开发时的热更新功能,难道要每次更新代码都要重新编译打包吗?显然不是很合理。那接下来我们将为该SSR程序添加热更新的功能。

1、解决思路

我们知道SSR程序每次打包编译完成后,都会生成这两个文件 vue-ssr-client-manifest.jsonvue-ssr-server-bundle.json

  • vue-ssr-client-manifest.json

主要记录了静态资源文件的配置信息

  • vue-ssr-server-bundle.json

主要记录了js文件的内容

那现在就是要解决如何在保存代码后,获取到最新的vue-ssr-client-manifest.jsonvue-ssr-server-bundle.json这两个文件。

通过该图,我们知道,既然要热更新,那 webpack dev server 肯定跑不了。

所以解决的步骤如下:

  1. 起一个webpack dev server 服务,暴露8080端口
  2. 起一个webpack compiler 编译webpack配置文件,监听文件修改,实时编译获取最新的 vue-ssr-server-bundle.json
  3. 通过webpack dev server 获取最新的 vue-ssr-client-manifest.json
  4. 结合 vue-ssr-server-bundle.json 和 vue-ssr-client-manifest.json 渲染html页面返回给浏览器

2、编码实现

有了思路后,剩下的就是要思考如何通过代码实现了。

2.1、 起一个webpack dev server 服务

通过 npm run serve 我们能很快的起一个webpack dev server 服务

npm run serve 

2.2、获取webpack配置文件,并编译

通过阅读官方文档我们知道webpack的配置文件在 /node_modules/@vue/cli-service/webpack.config.js 中

// 1、webpack配置文件
const webpackConfig = require('@vue/cli-service/webpack.config')

2.3、编译webpack配置文件,并监听文件修改


// 2、编译webpack配置文件
const serverCompiler = webpack(webpackConfig)
const mfs = new MemoryFS()
// 指定输出到的内存流中
serverCompiler.outputFileSystem = mfs

// 3、监听文件修改,实时编译获取最新的 vue-ssr-server-bundle.json
let bundle
serverCompiler.watch({}, (err, stats) =>{
  if (err) {
    throw err
  }
  stats = stats.toJson()
  stats.errors.forEach(error => console.error(error) )
  stats.warnings.forEach( warn => console.warn(warn) )
  const bundlePath = path.join(
    webpackConfig.output.path,
    'vue-ssr-server-bundle.json'
  )
  bundle = JSON.parse(mfs.readFileSync(bundlePath,'utf-8'))
  console.log('new bundle generated')
})

2.4、获取最新的 vue-ssr-client-manifest.json

// 4、获取最新的 vue-ssr-client-manifest.json
// 这边的 8080 是 dev server 的端口号
const clientManifestResp = await axios.get('http://localhost:8080/vue-ssr-client-manifest.json')
const clientManifest = clientManifestResp.data

2.5、结合各个步骤的核心后的最后代码

安装所需要的库

npm install webpack memory-fs concurrently -D
npm install koa-router axios -S

在项目根目录下 新建一个 server/dev.ssr.js,代码如下

// server/dev.ssr.js
const webpack = require('webpack')
const axios = require('axios')
const MemoryFS = require('memory-fs')
const fs = require('fs')
const path = require('path')
const Router = require('koa-router')
// 1、webpack配置文件
const webpackConfig = require('@vue/cli-service/webpack.config')
const { createBundleRenderer } = require("vue-server-renderer");

// 2、编译webpack配置文件
const serverCompiler = webpack(webpackConfig)
const mfs = new MemoryFS()
// 指定输出文件到的内存流中
serverCompiler.outputFileSystem = mfs

// 3、监听文件修改,实时编译获取最新的 vue-ssr-server-bundle.json
let bundle
serverCompiler.watch({}, (err, stats) =>{
  if (err) {
    throw err
  }
  stats = stats.toJson()
  stats.errors.forEach(error => console.error(error) )
  stats.warnings.forEach( warn => console.warn(warn) )
  const bundlePath = path.join(
    webpackConfig.output.path,
    'vue-ssr-server-bundle.json'
  )
  bundle = JSON.parse(mfs.readFileSync(bundlePath,'utf-8'))
  console.log('new bundle generated')
})
// 处理请求
const handleRequest = async ctx => {
  console.log('path', ctx.path)
  if (!bundle) {
    ctx.body = '等待webpack打包完成后在访问在访问'
    return
  }
  // 4、获取最新的 vue-ssr-client-manifest.json
  const clientManifestResp = await axios.get('http://localhost:8080/vue-ssr-client-manifest.json')
  const clientManifest = clientManifestResp.data

  const renderer = createBundleRenderer(bundle, {
    runInNewContext: false,
    template: fs.readFileSync(path.resolve(__dirname, "../src/index.temp.html"), "utf-8"),
    clientManifest: clientManifest
  });
  const html = await renderToString(ctx,renderer)
  ctx.body = html;
}
function renderToString(context,renderer) {
  return new Promise((resolve, reject) => {
    renderer.renderToString(context, (err, html) => {
      err ? reject(err) : resolve(html);
    });
  });
}

const router = new Router()

router.get("*", handleRequest);

module.exports = router

新建一个 server/ssr.js,代码如下

// server/ssr.js
const Koa = require('koa')
const koaStatic = require("koa-static");
const path = require('path')

const resolve = file => path.resolve(__dirname, file);
const app = new Koa()

const isDev = process.env.NODE_ENV !== 'production'
const router = isDev ? require('./dev.ssr') : require('./server')

app.use(router.routes()).use(router.allowedMethods())
// 开放目录
app.use(koaStatic(resolve("../dist")));

const port = process.env.PORT || 3000;

app.listen(port, () => {
  console.log(`server started at localhost:${port}`);
});

module.exports = app

修改package.json 添加几个执行脚本

// package.json scripts字段
"dev:serve": "cross-env WEBPACK_TARGET=node node ./server/ssr.js",
"dev": "concurrently \"npm run serve\" \"npm run dev:serve\" "

执行 npm run dev 命令

npm run dev

访问 localhost:3000 你会发现,还是有问题。

静态资源的文件引用的是 node.js server 的服务的,即引用到了3000端口上去了,但是3000端口的服务并没有这些静态资源文件,
这些静态资源文件在webpack dev server中。

那如何解决呢?

  1. node server将这些静态资源的请求代理到 webpack dev server中
  2. 改变webpack的baseUrl,直接引用到webpack dev server中

很显然,第二种方式实现起来比较简单,那我们就修改webpack的baseUrl配置

修改 vue.config.js

// vue.config.js
// 添加一个字段,如果是开发环境,就指定到webpack dev server中
baseUrl: isDev ? 'http://127.0.0.1:8080' : '',

重新 npm run dev ,然后访问 localhost:3000

已经能正常访问了,那我们试试 热更新的更新能不能实现,修改一段代码,

会发现 node server 会重新编译webpack配置文件,然后在看看浏览器有没有更新

你会发现浏览器还是没能热更新内容,打开 F12,你会发现这个错误,

这是我们常见的不允许跨域的错误提示。

解决方式:

  1. 配置 webpack dev server 允许跨域
// vue.config.js
// 添加一个 devServer的字段
devServer: {
    headers: {'Access-Control-Allow-Origin': '*'}
},

重新 npm run dev ,然后访问 localhost:3000

是已经能实现热更新的了。

3、优化

1、favicon的问题
打开f12还是能看到有问题,

具体实现可以参考我的github代码

2、修改 server 端代码自动重启代码

可以使用nodemon,或者pm2实现

4、总结

通过上一篇 通过vue-cli3构建一个SSR应用程序 和这篇文章,我们一步一步搭建起了基于vue-cli3的一个ssr应用程序,并添加了热更新的功能,在这期间也踩了很多坑。但是最终实现了之后,你会觉得这些付出都是值得的,因为这些都是自己的成长奠定基础。

如果有更好的实现方法,欢迎交流交流!

如果有不对的地方,欢迎指出!

5、源码

项目源码:vue-cli-ssr-example 欢迎 star

公众号

欢迎关注我的公众号“码上开发”,每天分享最新技术资讯。关注获取最新资源

为我们的SSR程序添加热更新功能的更多相关文章

  1. 手把手教你实现热更新功能,带你了解 Arthas 热更新背后的原理

    文章来源:https://studyidea.cn/java-hotswap 一.前言 一天下午正在摸鱼的时候,测试小姐姐走了过来求助,说是需要改动测试环境 mock 应用.但是这个应用一时半会又找不 ...

  2. 搭建带热更新功能的本地开发node server

    引言 使用webpack有一段时间了,对其中的热更新的大概理解是:对某个模块做了修改,页面只做局部更新而不需要刷新整个页面来进行更新.这样就能节省因为整个页面刷新所产生开销的时间,模块热加载加快了开发 ...

  3. 【实用篇】Android之应用程序实现自动更新功能

    我个人用的是友盟提供的自动更新组件,因此在这里只描述如何实用友盟提供的组件来完成程序的自动更新,步骤如下: 1.登录友盟官网,点击注册一个友盟账号. 2.注册成功后将会自动进入到添加新应用界面,选择添 ...

  4. webpack添加热更新

    之前的wbepack一直没有加上热更新,这是一种遗憾,今天终于加上去了,看不懂我博客的可以看这篇文章:http://blog.csdn.net/hyy1115/article/details/5302 ...

  5. go-web程序的热更新

    前言: 一直编译累死人啊,该偷懒就得偷懒 当使用go开发web程序时,修改点代码就得编译,虽然编译速度很快,但是也累啊,想起java的spring-boot有热更新插件, php根本都不需要重启,go ...

  6. Elastic ik插件配置热更新功能

    ik github地址:https://github.com/medcl/elasticsearch-analysis-ik 官网说明: 热更新 IK 分词使用方法 目前该插件支持热更新 IK 分词, ...

  7. iOS 利用JSPatch 添加热补丁功能

    ios 由于苹果的审核政策,一旦上线后发现bug是件让人崩溃的事情 不过可以利用oc的runtime机制可以家用JSPatch动态的为工程打热补丁 下载地址:https://github.com/ag ...

  8. react-native-pushy 热更新

    教程来源于官网: 准备工作 添加热更新功能 发布应用 说明: 在往 pushy 发布了安装包之后,后续都是通过下面 2个命令来发布 热更新版本的,而不是再次发布安装包, 在使用热更新服务更新版本的时候 ...

  9. 微信小程序热更新,小程序提示版本更新,版本迭代,强制更新,微信小程序版本迭代

    相信很多人在做小程序的时候都会有迭代每当版本迭代的时候之前老版本的一些方法或者显示就不够用了这就需要用到小程序的热更新.或者说是提示升级小程序版本 editionUpdate:function(){ ...

随机推荐

  1. MongoDB基本语句

    1.创建数据库      use  库名 2.查看所有库      show dbs 3.定义一个对象变量,下面挂着数据 4.col 是集合名,如果该集合不在该数据库中, MongoDB 会自动创建该 ...

  2. Java执行操作系统命令

    从网上学来的方法,sample: try { String[] cmd = new String[] { System.getenv("HOMEPATH") + "/te ...

  3. 【总结】sqlmap-tamper编写小结

    目的:修改sqlmap中的tamper脚本来绕过代码对特定参数的过滤和转义 环境:win10.phpstudy2016.sqli-labs-master平台 工具:sqlmap.burpsuite 地 ...

  4. 常用维护SQL-数据清理

    truncate某个库的表数据 show full processlist; select concat('truncate table ',table_schema,'.',table_name,' ...

  5. Linux下远程连接工具SSHSecureShellClient的使用

    实际开发中,Linux 服务器都在其他的地方,我们要通过远程的方式去连接 Linux 并操作它,Linux 远程的操作工具有很多,企业中常用的有 Puttty.secureCRT.SSH Secure ...

  6. Bootstrap 斜体、文本对齐、缩略图、地址、列表等

    目录1.标题2.页面主体3.强调    a.小号文本    b.着重    c.斜体    d.对齐class    e.强调class4.缩略语5.地址6.列表    a.无序列表    b.有序列 ...

  7. pat1012. The Best Rank (25)

    1012. The Best Rank (25) 时间限制 400 ms 内存限制 65536 kB 代码长度限制 16000 B 判题程序 Standard 作者 CHEN, Yue To eval ...

  8. HAProxy advanced Redis health check---ref

    http://blog.exceliance.fr/2014/01/02/haproxy-advanced-redis-health-check/ HAProxy advanced Redis hea ...

  9. Python项目中如何优雅的import

    Python项目中如何优雅的import 前言 之前有一篇关于Python编码规范的随笔, 但是写的比较杂乱, 因为提到了import语句, 在篇文章中, 我专门来讲Python项目中如何更好的imp ...

  10. D3(v5) in TypeScript 坐标轴之 scaleBand用法

    在学习d3时候,发现在TS中实现D3的坐标轴中遇到一些错误,而这些错误却不会存在于js(因为ts的类型检查)写法中,因此做下笔记: import * as d3 from 'd3';import * ...