Vue.js 服务器端渲染

文章内容输出来源:拉勾教育大前端高薪训练营

官方文档: Vue.js 服务器端渲染指南

使用服务器端渲染(SSR)优势:

  • 更好的SEO,由于搜索引擎爬虫抓取工具可以直接查看完全渲染的页面。
  • 更快的内容到达时间,特别是对于缓慢的网络情况或运行缓慢的设备。

1.依赖安装

通过express创建整个项目的运行服务,及安装部分开发依赖

# vue-server-renderer版本须与vue保持一致
npm install --save vue vue-server-renderer express cross-env

npm install --save-dev webpack webpack-cli webpack-merge webpack-node-externals rimraf friendly-errors-webpack-plugin

npm install --save-dev @babel/core @babel/preset-env @babel/plugin-transform-runtime babel-loader

npm install --save-dev vue-loader vue-template-compiler vue-style-loader

npm install --save-dev url-loader css-loader style-loader file-loader

npm install --save-dev chokidar

2.源码结构

.
├── app.server.js # 项目服务启动入口
├── src
│   ├── components
│	│   ├── Foo.vue
│	│   └── Bar.vue
│   ├── router
│	│   └── index.js
│   ├── store
│	│   └── index.js
│   ├── App.vue
│   ├── app.js # 通用 entry(universal entry)
│   ├── entry-client.js # 仅运行于浏览器
│   └── entry-server.js # 仅运行于服务器

3.项目配置

1. 配置结构

build
├── build.production.js # 将webpack.client.config.js、webpack.server.config.js组合到一起
├── setup-dev-server.js # 加载webpack配置读取资源
├── utils.js
├── webpack.base.config.js # 公共配置项
├── webpack.client.config.js # 客户端配置项
├── webpack.server.config.js # 服务端配置项

2. setup-dev-server.js

const fs = require('fs')
const chalk = require('chalk')
const webpack = require('webpack')
const chokidar = require('chokidar')
const ServerConfig = require('./webpack.server.config.js')
const ClientConfig = require('./webpack.client.config.js')
const utils = require('./utils.js')
const devMiddleware = require('webpack-dev-middleware')
const hotMiddleware = require('webpack-hot-middleware')


/**
 * 通过webpack-dev-middleware将资源写入内存中
 *
 * @param {Express} server
 * @param {Function} callback
 * @returns
 */
module.exports = (server, callback) => {
  let serverBundle, template, clientManifest
  const templatePath = utils.resolve('../index.template.html')
  template = fs.readFileSync(templatePath, 'utf-8')

  const notifyWrapper = (resolve, reject) => {
    return (args) => {
      try {
        const isCompleted = Object.values(args).every(val => val)
        if (isCompleted) {
          resolve(callback(args))
        }
      } catch (error) {
        reject(error)
      }
    }
  }

  return new Promise((resolve, reject) => {
    const notify = notifyWrapper(resolve, reject)
	// 检测模板文件变化
    chokidar.watch(templatePath).on('change', (path, stats) => {
      template = fs.readFileSync(templatePath, 'utf-8')
      notify({ serverBundle, clientManifest, template })
    })


    const ServerCompiler = webpack(ServerConfig)
    const ServerDevMiddleware = devMiddleware(ServerCompiler, {
      logLevel: 'silent' // 关闭默认日志输出
    })
    ServerCompiler.hooks.done.tap('server', () => {
      bundleString = ServerDevMiddleware
        .fileSystem
        .readFileSync(utils.resolve('../dist/vue-ssr-server-bundle.json'), 'utf-8')
      serverBundle = JSON.parse(bundleString)
      console.log(chalk.cyan('ServerCompiler Build complete.'))
      notify({ serverBundle, clientManifest, template })
    })


    ClientConfig.plugins.push(new webpack.HotModuleReplacementPlugin())
    ClientConfig.entry.app.push('webpack-hot-middleware/client?quiet=true&reload=true')
    const ClientCompiler = webpack(ClientConfig)
    const ClientDevMiddleware = devMiddleware(ClientCompiler, {
      logLevel: 'silent', // 关闭默认日志输出
      publicPath: ClientConfig.output.publicPath
    })
    ClientCompiler.hooks.done.tap('client', () => {
      bundleString = ClientDevMiddleware
        .fileSystem
        .readFileSync(utils.resolve('../dist/vue-ssr-client-manifest.json'), 'utf-8')
      clientManifest = JSON.parse(bundleString)
      console.log(chalk.cyan('ClientCompiler Build complete.'))
      notify({ serverBundle, clientManifest, template })
    })
    
    
    server.use(ClientDevMiddleware)
    server.use(hotMiddleware(ClientCompiler, {
      log: false // 关闭默认日志输出
    }))
  })
}

4. 开发构建

1.依赖安装

Express 中文网Vue SSR 指南-API 参考

# 其实例对象用法详见: memory-fs; 将输出的文件存在于内存中;
# webpack-dev-middleware 是一个容器(wrapper), 可以把 webpack 处理后的文件传递给一个服务器; 
npm install --save-dev webpack-dev-middleware 

# https://www.npmjs.com/package/webpack-hot-middleware
npm install --save-dev webpack-hot-middleware

2.开发构建

1. app.server.js
const renderer = require('vue-server-renderer').createBundleRenderer();
   
// 1.将Vue实例渲染为html字符串 - 渲染方式
renderer.renderToString(app, (err, html) => {});
// or
renderer.renderToString(app).then((html) => {}, (err) => {});
   
// 2.将Vue实例渲染为stream流 - 渲染方式
const renderStream = renderer.renderToStream(app);
// 通过订阅事件,在回调中进行操作
// event可取值'data'、'beforeStart'、'start'、'beforeEnd'、'end'、'error'等
renderStream.on(event, (res) => {});

// 接受所有请求
server.get('*', async (req, res) => {
  try {
    if (!isProd) await onReady
    const html = await renderer.renderToString({
      url: req.url,
      title: 'Vue.js 服务器端',
      cookies:req.cookies
    })
    res.set('Content-Type', 'text/html; charset=utf8;')
    res.end(html)

  } catch (error) {
    res.sendStatus(error.code || 500)
  }
})
2. entry-client.js
/**
 * 文件功能描述: 客户端启动入口
 */
import { createApp } from '@/app.js'

const cookie = { token: 123 }

const { app, router, store } = createApp(cookie)

router.onReady(() => {
  // 当使用 template 时,context.state 将作为 window.__INITIAL_STATE__ 状态,自动嵌入到最终的 HTML 中。
  // 而在客户端,在挂载到应用程序之前,store 就应该获取到状态
  if (window.__INITIAL_STATE__) {
    store.replaceState(window.__INITIAL_STATE__)
  }
  app.$mount('#app')
})
3. entry-server.js
/**
 * 文件功能描述: 服务端启动入口
 * 使用default export导出函数,
 * 并在每次渲染中重复调用此函数;
 */
import { createApp } from '@/app.js'

export default context => {
  return new Promise((resolve, reject) => {
    const { app, router, store } = createApp(context.cookies)

    // 设置服务器端 router 的位置
    router.push(context.url)

    // 等到 router 将可能的异步组件和钩子函数解析完
    router.onReady(() => {
      store.commit('initToken', context.cookies) // 将cookie信息注册到store里

      context.rendered = () => {
      	// 我们的 store 现在已经填充入渲染应用程序所需的状态。
        // 当我们将状态附加到上下文,
        // 并且 `template` 选项用于 renderer 时,
        // 状态将自动序列化为 `window.__INITIAL_STATE__`,并注入 HTML;   
        // 在entry-client.js中进行客户端渲染时使用;
        context.state = store.state
      }

      const matchedComponents = router.getMatchedComponents()
      // 匹配不到的路由,执行 reject 函数,并返回 404
      if (!matchedComponents.length) {
        return reject({ code: 404 })
      }

      // Promise 应该 resolve 应用程序实例,以便它可以渲染
      resolve(app)
    }, reject)
  })
}

5.项目开发

1.依赖安装

npm install --save vue-router # 采用vue-router的Vue的SSR渲染,必须使用history作为路由模式
npm install --save vuex
npm install --save vuex-router-sync # 通过动态注册模块将vuex与vue-router结合在一起,实现应用的路由状态管理
npm install --save cookie-parser

2.路由和代码分割

路由和代码分割

// entry-server.js entry-client.js app.server.js 依次修改

3.数据预取和状态

数据预取和状态

// vuex-router-sync Uasge -- 将vue-router的状态同步到vuex中
import { sync } from 'vuex-router-sync'
// 默认模块名: route
const unsync = sync(store, router, { moduleName: 'RouteModule' } )  

// app.js 部分代码
export function createApp(cookies) {
  const router = createRouter(cookies)
  const store = createStore()

  sync(store, router, { moduleName: 'route' } )

  const app = new Vue({
    router,
    store,
    render: h => h(App)
  })
  return { app, router, store }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值