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

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

内容简介:上一篇文章(地址)我们先是实现了官方实例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(二)

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

查看所有标签

猜你喜欢:

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

Paradigms of Artificial Intelligence Programming

Paradigms of Artificial Intelligence Programming

Peter Norvig / Morgan Kaufmann / 1991-10-01 / USD 77.95

Paradigms of AI Programming is the first text to teach advanced Common Lisp techniques in the context of building major AI systems. By reconstructing authentic, complex AI programs using state-of-the-......一起来看看 《Paradigms of Artificial Intelligence Programming》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

SHA 加密
SHA 加密

SHA 加密工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具