今天是我入职第二天, leader 跟我说, 昨天配置好了服务端渲染的文件, 今天就先研究研究如何使用 koa 来搭建一个 node server 吧!
按照惯例, 我去 koa 官网查了一下什么是 koa, 结果官网很简单的一句话介绍: koa-- 基于 node.js 平台的下一代 web 开发框架.
个人感觉 koa 官方文档对于前端小白来说, 写的不是很友好, 建议上手之前先看看阮一峰的 koa 框架教程和廖雪峰写的关于 koa 入门文章.
然后引入项目第一步, 安装 koa:
npm i koa -S
安装完之后, 首先在项目根目录下新建一个 server 文件夹, 然后在此文件夹下新建一个 server.js 文件, 然后在里面引入 koa:
- const Koa = require('koa')
- const app = new Koa()
- const isDev = process.env.NODE_ENV === 'development'
这里为什么要声明 isDev 呢? 因为服务端渲染是分开发环境和生产环境两种不同的情况.
然后我们继续在 server.js 里面先写一个中间件来记录所有的请求和抓取的错误, 这样可以很好的了解到在服务端渲染的过程中是否出现了一些错误, 并及时排查掉错误.
先撸为敬:
- app.use(async (ctx, next) => {
- try {
- console.log(`request with path ${ctx.path}`)
- await next()
- } catch (err) {
- console.log(err)
- ctx.status = 500
- if (isDev) {
- ctx.body = err.message
- } else {
- ctx.body = 'please try again later'
- }
- }
- })
简单解释一下: 在函数前面加一个 async, 就代表异步处理函数, 而参数 next 表示执行下一个 异步处理的函数. 在 try 循环体内, console 打印出请求的路径. 如果是 isDev 为 true 的情况, 可以直接将错误信息写到 body 里面, 这样就可以在页面上直接看到错误信息. 如果不是开发环境, 可以写一个友善的提醒文字, 例如:"please try again later".
这就是最简单的一个 koa 中间件, 用来记录所有的请求及出现的错误, 并且返回一个错误信息.
接下来, 聊一聊如何处理服务端渲染.
在处理服务端渲染之前, 首先要在 terminal 里面安装一下 koa-router:
npm i koa-router -S
这是 koa 提供的一个路由的工具. 然后在 server 文件夹下面新建一个 routers 文件夹, 紧接着在里面新建两个文件, 一个是 dev-ssr.js, 另一个是 ssr.js. 前者是处理开发时服务端渲染的情况, 后者是处理正式环境下的情况.
在 dev-ssr.js 文件中, 首先要引入 koa-router:
const Router = require('koa-router')
在这里, 还需要使用到两个工具, 需要安装下:
- npm i axios -S
- npm i memory-fs -D
在 node 端发送请求的 axios, 当然也可以在浏览器端发送请求. 在安装的时候记住后面跟的是 - S, 因为在业务代码中可以用到.
而 memory-fs 只有在开发的时候才会用到, 所以后面跟的是 - D. 可能有童鞋要问了, 这个 memory-fs 是用来干嘛的? 别急, 闰土给大家截一张官网图片看看便一目了然了:
大意是: 一个简单的内存文件系统. 将数据保存在 JavaScript 对象中.
然后, 话不多说, 先把这两个工具引入进来:
- const axios = require('axios')
- const MemoryFS = require('memory-fs')
紧接着, 再来引入两个工具:
- const webpack = require('webpack')
- const vueServerRenderer = require('vue-server-renderer')
因为要在 node 开发环境中打包代码, 并且需要服务端渲染.
接下来, 要引入 serverConfig, 就是入职第一天写的那个配置文件 webpack.config.server.js:
const serverConfig = require('../../build/webpack.config.server')
然后, 如何能在 node 开发环境中让 webpack 跑起来呢?
答案是通过 serverCompiler:
const serverCompiler = webpack(serverConfig)
然后去 new 一个 mfs 实例:
- const mfs = new MemoryFS()
- serverCompiler.outputFileSystem = mfs
这样就指定了 webpack 的输出目录在 MemoryFS 里面.
有了这些配置之后, 再去声明一个 bundle:
let bundle
用来记录 webpack 每次打包生成的新的文件.
- serverCompiler.watch({}, (err, stats) => {
- if (err) throw err
- stats = stats.toJson()
- stats.erros.forEach(err => console.log(err))
- stats.hasWarnings.forEach(warn => console.warn(err))
- const bundlePath = path.join(
- serverConfig.output.path,
- 'vue-ssr-server-bundle.json'
- )
- bundle = JSON.parse(mfs.readFileSync(bundlePath, 'utf-8'))
- })
这里使用 watch()的好处是: 跟使用 webpack-dev-server 一样, 在 client 目录下每次修改一个文件, 它都会重新执行一次打包, 然后就可以拿到新的文件了.
serverCompiler.watch()的第一个参数是空对象, 第二个参数是一个回调. 如果有 err 直接抛出.
然后 stats 这块我感觉有点晦涩难懂, leader 告诉我说, 先照着做, 然后有空再去看 webpack 的文档.
接下来就可以读取生成的 bundle 文件了, 拼接读取文件的路径, 设置文件名字, 并且制定编码为 utf-8, 最后通过 JSON.parse()将字符串转成 JSON.
执行完以上步骤之后, 就可以将内容返回给 html 了.
在服务端渲染期间, 使用 ejs 模板引擎生成 HTML. 通过 VueServerRenderer 的 createBundleRenderer()方法帮助生成一个可以直接调用 renderer 的函数. 在这里面接收几个参数, 第一个是 inject, 设置为 false, 这样它就不会执行其他的注入的操作了. 第二个是 clientManifest, 它会自动生成一个带有 script 标签的 js 文件引用的字符串, 这样可以直接添加到 ejs 的内容里面.
最后, dev-ssr.js 的完整代码如下:
- const Router = require('koa-router')
- const axios = require('axios')
- const path = require('path')
- const fs = require('fs')
- const MemoryFS = require('memory-fs')
- const webpack = require('webpack')
- const VueServerRenderer = require('vue-server-renderer')
- const serverConfig = require('../../build/webpack.config.server')
- const serverCompiler = webpack(serverConfig)
- const mfs = new MemoryFS()
- serverCompiler.outputFileSystem = mfs
- let bundle
- serverCompiler.watch({}, (err, stats) => {
- if (err) throw err
- stats = stats.toJson()
- stats.erros.forEach(err => console.log(err))
- stats.hasWarnings.forEach(warn => console.warn(err))
- const bundlePath = path.join(
- serverConfig.output.path,
- 'vue-ssr-server-bundle.json'
- )
- bundle = JSON.parse(mfs.readFileSync(bundlePath, 'utf-8'))
- })
- const handleSSR = async (ctx) => {
- if (bundle) {
- ctx.body = 'wait a moment...'
- return
- }
- const clientManifestResp = await axios.get(
- 'http://127.0.0.1:8080/vue-ssr-client-manifest.json'
- )
- const clientManifest = clientManifestResp.data
- const template = fs.readFileSync(
- path.join(__dirname, '../server.template.ejs')
- )
- const renderer = VueServerRenderer
- .createBundleRenderer(bundle, {
- inject: false,
- clientManifest
- })
- }
来源: https://www.cnblogs.com/running-runtu/p/8996932.html