配置Vue服务器端渲染SSR(二)

栏目: 编程语言 · 发布时间: 5年前

内容简介:上一篇文章(地址)我们先是实现了官方实例demo,理解了服务器端渲染基本的原理。然后按照官方提供的demo代码,配合我们自己编写的webpack配置,实现了先构建、再启动服务。跑通了基本的服务端渲染流程。但还没有涉及到异步数据、缓存等问题。在此之前,我们需要先实现开发环境的搭建。因为我们不可能敲的每一行代码都需要重新打包并起服务。这是不利于调试,并且很愚蠢的行为。想一想vue-cli构建出来的项目,我们可以通过我们把build、start命令都设置了

上一篇文章(地址)我们先是实现了官方实例demo,理解了服务器端渲染基本的原理。然后按照官方提供的demo代码,配合我们自己编写的webpack配置,实现了先构建、再启动服务。跑通了基本的服务端渲染流程。但还没有涉及到异步数据、缓存等问题。在此之前,我们需要先实现开发环境的搭建。因为我们不可能敲的每一行代码都需要重新打包并起服务。这是不利于调试,并且很愚蠢的行为。

正文

修改server.js

想一想vue-cli构建出来的项目,我们可以通过 npm run dev (vue-cli3使用了 npm run serve )起一个服务,然后更改文件的时候,页面也会自动的热加载,不需要手动刷新。我们也要实现一个类似的开发环境,所以我们需要利用node来构建webpack配置,并且实时监控文件的改变,当改变时应该重新进行打包,重新生成俩个JSON文件,并重新进行 BundleRenderer.renderToString() 方法。我们除了重新生成JSON文件意外,其他逻辑和之前实现的逻辑大体相同。所以我们可以在server.js基础上进行修改,在原基础上进行环境的判断,做不同的 render 。我们需要一个环境变量来决定执行哪个逻辑。这里我们使用 cross-env 来设置 process.env.NODE_ENV 变量:

配置Vue服务器端渲染SSR(二)

我们把build、start命令都设置了 process.env.NODE_ENV 为production生产环境,这样我们在文件中可以获取到该值,如果没有我们就默认是development开发环境。那我们的server.js都需要修改哪里呢?

1. 首先是生成 BundleRenderer 实例,之前我们是通过固定路径(打包后的dist文件夹下)获取JSON文件

// 之前代码逻辑
const serverBundle = require('./dist/vue-ssr-server-bundle.json')
const clientManifest = require('./dist/vue-ssr-client-manifest.json')
const template = require('fs').readFileSync('./index.template.html', 'utf-8')

//... 

const renderer = createBundleRenderer(serverBundle, {
  runInNewContext: false,
  template, // 页面模板
  clientManifest // 客户端构建 manifest
})
复制代码

我们需要按照环境变量更改逻辑,如果是生产环境上述代码不变,如果是开发环境,我们需要有一个函数来动态的获取打包的JSON文件并且重新生成 BundleRenderer 实例,我们先定义好这个函数为 setupDevServer ,顾名思义这个函数是构建开发环境的,它的作用是nodeAPI构建webpack配置,并且做到监听文件。我们server.js中可以通过传递个回调函数来做重新生成 BundleRenderer 实例的操作。而接受的参数就是俩个新生成的JSON文件。

const setupDevServer = require('./build/setup-dev-server')
// 第 2步:根据环境变量生成不同BundleRenderer实例
if (process.env.NODE_ENV === 'production') {
  // 获取客户端、服务器端打包生成的json文件
  const serverBundle = require('./dist/vue-ssr-server-bundle.json')
  const clientManifest = require('./dist/vue-ssr-client-manifest.json')
  // 赋值
  renderer = createBundleRenderer(serverBundle, {
    runInNewContext: false,
    template,
    clientManifest
  })
  // 静态资源,开发环境不需要指定
  router.get('/static/*', async (ctx, next) => {
    console.log('进来')
    await send(ctx, ctx.path, { root: __dirname + '/dist' });
  })
} else {
  // 假设setupDevServer已经实现,并传入的回调函数会接受生成的json文件
  setupDevServer(app, (bundle, clientManifest) => {
    // 赋值
    renderer = createBundleRenderer(bundle, {
      runInNewContext: false,
      template,
      clientManifest
    })
  })
}
复制代码

这里我们先假设已实现setupDevServer的功能,后面我们再来仔细讲其中的代码逻辑。

2. 其次我们可以把中间件函数也提取出来,命名成render函数

const render = async (ctx) => {
  // 渲染上下文
  const context = {
    url: ctx.url,
    title: 'Vue-SSR Demo',
    meta: `
      <meta charset="UTF-8">
      <meta name="descript" content="基于webpack、koa搭建的SSR">
    `
  }

  // 设置网页内容类型
  ctx.set('Content-Type', 'text/html')

  try {
    const html = await renderer.renderToString(context)
    ctx.status = 200
    ctx.body = html
  } catch (err) {
    // 当执行到reject可以自定义 例:reject({ code: 404, msg: 'Page Not Found' })
    if (err.code) {
      ctx.status = err.code
      ctx.body = err.msg
    } else {
      // 发生未知错误
      ctx.status = 500
      ctx.body = 'Internal Server Error'
    }
  }
}
// ...
router.get('*', render)
复制代码

我们可以在判断生产环境的地方加上log,打印一下是否如我们所愿,针对不同的NODE_ENV环境执行不同的逻辑。

配置Vue服务器端渲染SSR(二)

修改webpack配置

在之前,我们实现的webpack配置并没有对生产环境与开发环境做区别,但其实,我们应该像vue-cli一样针对环境来做不同的优化,比如开发环境devtool我们可以使用 cheap-module-eval-source-map 编译会更快,css样式没有必要打包单独文件,使用 vue-style-loader 做处理就好,并且因为开发环境需要模块热重载,所以不提取文件是必要的。开发环境可以做更友好的错误提示。还有就是生产环境需要做更多的打包优化,比如压缩,缓存之类。在这个系列文章中,我们就不对生产环境做更好的优化,因为我自己对这方面知识也是很懵懂:expressionless:。我们先修改webpack.base.conf.js:

// ...
// 定义是否是生产环境的标志位,用于配置中
const isProd = process.env.NODE_ENV === 'production'

module.exports = {
  // 这里使用对象的格式,因为在setDevServer.js中需要添加一个热重载的入口
  entry: {
    app: resolve('src/entry-client.js')
  },
  // 开发环境启动sourcemap可以更好地定位错误位置
  devtool: isProd
    ? false
    : 'cheap-module-eval-source-map',
  // ...... 省略
}
复制代码

我们在对webpack.client.conf.js进行修改:

// 定义是否是生产环境的标志位,用于配置中
const isProd = process.env.NODE_ENV === 'production'

const pordWebpackConfig = merge(baseWebpackConfig, {
  mode: process.env.NODE_ENV || 'development',
  output: {
    // chunkhash是根据内容生成的hash, 易于缓存。
    // 开发环境不需要生hash、这个我们在setDevServer函数里面改
    filename: 'static/js/[name].[chunkhash].js',
    chunkFilename: 'static/js/[id].[chunkhash].js'
  },
  module: {
    rules: [
      {
        test: /\.styl(us)?$/,
        // 开发环境不需要提取css单独文件
        use: isProd 
          ? [MiniCssExtractPlugin.loader, 'css-loader', 'stylus-loader']
          : ['vue-style-loader', 'css-loader', 'stylus-loader']
      },
    ]
  },
  // ... 省略
}
复制代码

关于服务器端webpack的配置可以不进行修改,因为它的功能最后只打包出一个JSON文件,并不需要针对环境做一些改变。

set-dev-server.js的编写

setDevServer函数主要是利用webpack手动构建应用,并实现热加载。首先我们需要俩个中间件 koa-webpack-dev-middlewarekoa-webpack-hot-middleware ,前者是通过传入webpack编译好的compiler实现热加载,而后者是实现模块热更替,热加载是监听文件变化,从而进行刷新网页,模块热更替则在它的基础上做到不需要刷新页面。我们客户端webpack配置可以通过前面说的实现自动更新,而服务端compiler,我们通过 watch API,进行监听。当俩者其中有一个变化时,我们就需要调用传入的回调,将新生成的JSON文件传入。整个流程大致就是这样,具体代码如下:

const fs = require('fs')
const path = require('path')
// memory-fs可以使webpack将文件写入到内存中,而不是写入到磁盘。
const MFS = require('memory-fs')
const webpack = require('webpack')
const clientConfig = require('./webpack.client.conf')
const serverConfig = require('./webpack.server.conf')
// webpack热加载需要
const webpackDevMiddleware = require('koa-webpack-dev-middleware')
// 配合热加载实现模块热替换
const webpackHotMiddleware = require('koa-webpack-hot-middleware')

// 读取vue-ssr-webpack-plugin生成的文件
const readFile = (fs, file) => {
  try {
    return fs.readFileSync(path.join(clientConfig.output.path, file), 'utf-8')
  } catch (e) {
    console.log('读取文件错误:', e)
  }
}

module.exports = function setupDevServer(app, cb) {
  let bundle
  let clientManifest

  // 监听改变后更新函数
  const update = () => {
    if (bundle && clientManifest) {
      cb(bundle, clientManifest)
    }
  }

  // 修改webpack配合模块热替换使用
  clientConfig.entry.app = ['webpack-hot-middleware/client', clientConfig.entry.app]
  clientConfig.output.filename = '[name].js'
  clientConfig.plugins.push(
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoEmitOnErrorsPlugin()
  )
  

  // 编译clinetWebpack 插入Koa中间件
  const clientshh = webpack(clientConfig)
  const devMiddleware = webpackDevMiddleware(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()
  })

  // 插入Koa中间件(模块热替换)
  app.use(webpackHotMiddleware(clientCompiler))

  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

    //  vue-ssr-webpack-plugin 生成的bundle
    bundle = JSON.parse(readFile(mfs, 'vue-ssr-server-bundle.json'))
    update()
  })
}
复制代码

我们用到了 memory-fs 将生成的JSON文件写入内存中,而不是磁盘中,是为了更快的读写。客户端不需要是因为 webpack-dev-middleware 已经帮我们完成了。这就是为什么我们在开发环境并有dist文件夹生成。我们现在可以通过 npm run dev 访问localhost:3000,更改代码,可以实现热加载。

配置Vue服务器端渲染SSR(二)

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Science of Programming

The Science of Programming

David Gries / Springer / 1989-4-21 / USD 99.00

Describes basic programming principles and their step-by- step applications.Numerous examples are included.一起来看看 《The Science of Programming》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

html转js在线工具
html转js在线工具

html转js在线工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换