Приблизьте вас к рендерингу Vue на стороне сервера (VUE SSR)

сервер Командная строка Vue.js HTML

В прошлой статье ("Рендеринг на стороне сервера и Nuxt.js") был представлен рендеринг на стороне сервера и некоторые концепции Nuxt.js. Теперь мы начнем с основ Vue SSR и разделим его на три уровня: низкий, средний и высокий. , Легендарный серверный рендеринг.

предисловие

Перед официальной сборкой проекта нам еще предстоит рассмотреть некоторые особенности рендеринга Vue на стороне сервера.
Рендеринг приложений Vue.js на стороне сервера позволяет выполнять приложения vue как на стороне клиента (браузера), так и на стороне сервера, что мы называем «изоморфным» или «универсальным».

Vue.js is a framework for building client-side applications. By default, Vue components produce and manipulate DOM in the browser as output. However, it is also possible to render the same components into HTML strings on the server, send them directly to the browser, and finally "hydrate" the static markup into a fully interactive app on the client.

Причина, по которой может быть достигнут изоморфизм, заключается в том, что приложения vue создаются как на стороне клиента, так и на стороне сервера, и оба они упакованы с помощью веб-пакета для создания серверных и клиентских пакетов. Пакет сервера используется для рендеринга сервера. Пакет клиента представляет собой статический тег на стороне клиента. После того, как сервер отобразит фрагмент HTML-страницы, он будет отправлен клиенту, а затем смешан со статическим тегом на стороне клиента, чтобы приложение имеет характеристики приложения Vue.
Необходимо отметить, что:

  • Во время рендеринга на стороне сервера толькоbeforeCreateа такжеcreatedДве хуковые функции, остальные будут выполняться только на стороне клиента. Итак, в предыдущем спа-приложении вcreatedсоздатьsetInterval, затем вdestroyedПодобная операция по его уничтожению не может происходить на сервере, а функция уничтожения хука не будет вызываться при рендеринге сервера, поэтому этот таймер останется навсегда, и сервер легко рухнет.
  • Поскольку сервер и клиент являются двумя различными средами платформы выполнения, некоторые API-специфические платформы не могут быть использованы, например,windowа такжеdocument, в node.js (например,createdФункция ловушки) сообщит об ошибке. И в используемом нами стороннем API нам необходимо убедиться, что он может правильно работать как в узле, так и в браузере, например, axios, который предоставляет один и тот же API как серверу, так и клиенту (собственный XHR браузера не работает). ).

Уровень 1: Рендеринг сервера от 0 до 1

Давайте сначала реализуем базовую демонстрацию рендеринга на стороне сервера без учета изоморфизма и различных конфигураций.

Подготовить

npm install vue vue-server-renderer express --save

vue-server-rendererдаvueОсновной модуль для рендеринга на стороне сервера, он должен соответствовать вашей версии vue. УстановитьexpressПотому что мы будем использовать его позже, чтобы запустить службу, чтобы увидеть эффект от нашей страницы.

Рендеринг экземпляра Vue в три шага

// 第 1 步:创建一个 Vue 实例
const Vue = require('vue')
const app = new Vue({
  template: `<div>Hello Vue SSR</div>`
})

// 第 2 步:创建一个 renderer
const renderer = require('vue-server-renderer').createRenderer()

// 第 3 步:将 Vue 实例渲染为 HTML
renderer.renderToString(app, (err, html) => {
  if (err) throw err
  console.log(html)   // <div data-server-rendered="true">Hello Vue SSR</div>
})

Используйте шаблоны

В приведенном выше примере создается только фрагмент html-кода, в общем случае html-фрагмент необходимо вставить в файл шаблона. Хорошо, тогда давайте напишем файл шаблонаindex.html.

<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
  <head><title>Hello</title></head>
  <body>
    <!--vue-ssr-outlet-->
  </body>
</html>

Во время рендеринга html-фрагмент будет вставлен в<!--vue-ssr-outlet-->Эта записка помечена здесь.

const Vue = require('vue')
const app = new Vue({
  template: `<div>Hello Vue SSR</div>`
})
const renderer = require('vue-server-renderer').createRenderer({
  template: require('fs').readFileSync('./index.html', 'utf-8')
})
renderer.renderToString(app, (err, html) => {
  if (err) throw err
  console.log(html) // html 将是注入应用程序内容的完整页面
})

мы используемfsМодуль читает файл и кидает в рендерtemplate, и повторите описанные выше шаги, чтобы вставить html-фрагмент в нашу позицию разметки.

использовать на сервере node.js

Теперь мы будем использовать экспресс для запуска службы узла и проверки эффекта страницы.

const Vue = require('vue')
// 第一步: 创建一个 express 应用
const server = require('express')()

// 第二步: 创建一个 Vue 实例
const app = new Vue({
  data: {
    msg: 'Hello Vue SSR'
  },
  template: `<div>{{msg}}</div>`
})

// 第三步: 创建一个 renderer
const renderer = require('vue-server-renderer').createRenderer({
  template: require('fs').readFileSync('./index.html', 'utf-8')
})

// 第四步: 设置路由,"*" 表示任意路由都可以访问它
server.get('*', (req, res) => {
  renderer.renderToString(app, (err, html) => {
    if (err) {
      res.status(500).end('Internal Server Error')
      return
    }
    res.end(html)
  })
})

// 第五步: 启动服务并监听从8080端口进入的所有连接请求
server.listen(8080)

Таким образом, наша простая отрисовка страницы завершена, давайте взглянем на эффект страницы и данные ответа.

Уровень 2: Трансформация - от Спа до ССР

Зная, как отображать страницу на стороне сервера, следующим шагом будет реализация изоморфизма. Чтобы пропустить различные конфигурации проекта, начнем со знакомого шаблона vue-cli.
Предоставляется официальный инструмент быстрого создания проектов vue-cli, который также можно использовать для быстрого создания проектов SPA.Теперь мы преобразуем этот шаблон в шаблон, который может интегрировать SSR.

Вторая часть ссылки на содержаниеПусть интеграция проекта после инициализации vue-cli поддерживает SSR, вторгаться и удалять.

Подготовить

После установки vue-cli (по крайней мере версии v2.x) используйте базовый шаблон для сборки проекта.

vue init webpack spa_ssr
cd spa_ssr

Запустите его, чтобы убедиться, что проект работает правильно, затем не забудьте установитьvue-server-rendererмодуль

npm install vue-server-renderer --save-dev

После завершения установки переходим к следующему шагу.

Модернизацияsrcфайл под

мы должныsrcСоздайте два js в каталоге.

src
├── router
│   └── index.js
├── components
│   └── HelloSsr.vue
├── App.vue
├── main.js
├── entry-client.js # 仅运行于浏览器
└── entry-server.js # 仅运行于服务器

эти двоеentryОн будет настроен позже, сначала для преобразованияmain.js.
реконструкцияmain.jsПредварительно нужно пояснить, что из-за однопоточного механизма при рендеринге на стороне сервера в процессе происходит операция аналогичная синглтону, то все запросы будут разделять работу этого синглтона, поэтому фабричная функция следует использовать для обеспечения независимости каждого запроса между ними. например, вmain.js, изначально мы создали экземпляр Vue напрямую и смонтировали его непосредственно в DOM. настоящее времяmain.jsКак общий входной файл, он должен быть преобразован в фабричную функцию, которую можно выполнять многократно, создавая новый экземпляр приложения для каждого запроса. Монтажные работы выполняются последующим входом клиента.

import Vue from 'vue'
import App from './App'
import { CreateRouter } from './router'

export function createApp () {
  const router = new CreateRouter()
  const app = new Vue({
    router,
    render: h => h(App)
  })
  return { app, router }
}

существует/router/index.js, нам также нужно использовать фабричную функцию для создания экземпляра маршрута. Затем измените конфигурацию маршрутизации наhistoryрежим (поскольку хэш не поддерживается)

import Vue from 'vue'
import Router from 'vue-router'
import HelloSsr from '@/components/HelloSsr'

Vue.use(Router)

export function CreateRouter () {
  return new Router({
    mode: 'history',
    routes: [{
      path: '/ssr',
      name: 'HelloSsr',
      component: HelloSsr
    }]
  })
}

Далее давайте напишем запись на стороне клиента и запись на стороне сервера. Запись клиента очень проста, то есть монтировать экземпляр vue в DOM, однако, учитывая, что могут быть асинхронные компоненты, перед этим нужно дождаться, пока маршрут загрузит асинхронные компоненты.

// entry-client.js
import { createApp } from './main'
const { app, router } = createApp()

router.onReady(() => {
  app.$mount('#app')
})

Есть два шага для входа на сервер: 1. Разобрать маршрут на стороне сервера 2. Вернуть экземпляр vue для рендеринга.

// entry-server.js
import { createApp } from './main'
export default context => {
  // 因为有可能会是异步路由钩子函数或组件,所以我们将返回一个 Promise,
  // 以便服务器能够等待所有的内容在渲染前,
  // 就已经准备就绪。
  return new Promise((resolve, reject) => {
    const { app, router } = createApp()
    // 设置服务器端 router 的位置
    router.push(context.url)
    // 等到 router 将可能的异步组件和钩子函数解析完
    router.onReady(() => {
      const matchedComponents = router.getMatchedComponents()
      // 匹配不到的路由,执行 reject 函数,并返回 404
      if (!matchedComponents.length) {
        // eslint-disable-next-line
        return reject({ code: 404 })
      }
      // Promise 应该 resolve 应用程序实例,以便它可以渲染
      resolve(app)
    }, reject)
  })
}

webpackнастроить

Код, связанный с vue, обработан, и следующим шагом будетwebpackКонфигурация пакета изменена. Официальный рекомендовал следующую конфигурацию:

 build
  ├── webpack.base.conf.js  # 基础通用配置
  ├── webpack.client.conf.js  # 客户端打包配置
  └── webpack.server.conf.js  # 服务器端打包配置

Файл конфигурации в нашем проектеbase,dev,prod, теперь мы все еще храним эти три файла конфигурации, просто добавимwebpack.server.conf.jsВот и все.

webpack.base.conf.jsИсправлять

Сначала мы модифицируемwebpack.base.conf.jsизentryКонфигурация входа:./src/entry-client.js, чтобы сгенерировать манифест сборки клиентаclient manifest. Конфигурация на стороне сервера относится к базовой конфигурации,entryпройдешьmergeпокрывать, указыватьserver-entry.js.

// webpack.base.conf.js
module.exports = {
  entry: {
    // app: './src/main.js'
    app: './src/entry-client.js'   // <-修改入口文件改为
  },
  // ...
}

webpack.prod.conf.jsИсправлять

В настройках клиентаprod, нам нужно ввести плагин рендеринга на стороне сервераclient-plugin, генерироватьvue-ssr-client-manifest.json(используется для инъекций статических ресурсов), в то же время нам нужно поставитьHtmlWebpackPluginчтобы удалить, в приложении SPA мы используем его для созданияindex.htmlфайл, но здесь у нас естьvue-ssr-client-manifest.jsonПосле этого за нас эту работу сделает серверная часть.

const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
// ...
  plugins: [
    new webpack.DefinePlugin({
      'process.env': env,
      'process.env.VUE_ENV': '"client"' // 增加process.env.VUE_ENV
    }),
    // ...
    // 以下内容注释(或去除)
    // new HtmlWebpackPlugin({
    //   filename: config.build.index,
    //   template: 'index.html',
    //   inject: true,
    //   minify: {
    //     removeComments: true,
    //     collapseWhitespace: true,
    //     removeAttributeQuotes: true
    //     // more options:
    //     // https://github.com/kangax/html-minifier#options-quick-reference
    //   },
    //   // necessary to consistently work with multiple chunks via CommonsChunkPlugin
    //   chunksSortMode: 'dependency'
    // }),
    // ...
    // 此插件在输出目录中生成 `vue-ssr-client-manifest.json`。
    new VueSSRClientPlugin()
  ]
// ...

webpack.server.conf.jsнастроить

serverКонфигурация в основном относится к официальной конфигурации, вот описание:

  1. Нам нужно удалить конфигурацию упакованного css в baseConfig;
  2. используется здесьwebpack-node-externalsЧтобы ускорить сборку и уменьшить размер пакета, мы должны сначала установить его:npm install webpack-node-externals --save-dev.
  3. а такжеprodКонфигурация та же, ее нужно импортировать и использовать здесьserver-pluginПлагин для генерацииvue-ssr-server-bundle.json. Эта вещь используется для рендеринга на стороне сервера позже.
const webpack = require('webpack')
const merge = require('webpack-merge')
const nodeExternals = require('webpack-node-externals')
const baseConfig = require('./webpack.base.conf.js')
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')
// 去除打包css的配置
baseConfig.module.rules[1].options = ''

module.exports = merge(baseConfig, {
  // 将 entry 指向应用程序的 server entry 文件
  entry: './src/entry-server.js',
  // 这允许 webpack 以 Node 适用方式(Node-appropriate fashion)处理动态导入(dynamic import),
  // 并且还会在编译 Vue 组件时,
  // 告知 `vue-loader` 输送面向服务器代码(server-oriented code)。
  target: 'node',
  // 对 bundle renderer 提供 source map 支持
  devtool: 'source-map',
  // 此处告知 server bundle 使用 Node 风格导出模块(Node-style exports)
  output: {
    libraryTarget: 'commonjs2'
  },
  // https://webpack.js.org/configuration/externals/#function
  // https://github.com/liady/webpack-node-externals
  // 外置化应用程序依赖模块。可以使服务器构建速度更快,
  // 并生成较小的 bundle 文件。
  externals: nodeExternals({
    // 不要外置化 webpack 需要处理的依赖模块。
    // 你可以在这里添加更多的文件类型。例如,未处理 *.vue 原始文件,
    // 你还应该将修改 `global`(例如 polyfill)的依赖模块列入白名单
    whitelist: /\.css$/
  }),
  plugins: [
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
      'process.env.VUE_ENV': '"server"'
    }),
    // 这是将服务器的整个输出
    // 构建为单个 JSON 文件的插件。
    // 默认文件名为 `vue-ssr-server-bundle.json`
    new VueSSRServerPlugin()
  ]
})

package.jsonИзменение команды упаковки

"scripts": {
    //...
    "build:client": "node build/build.js",
    "build:server": "cross-env NODE_ENV=production webpack --config build/webpack.server.conf.js --progress --hide-modules",
    "build": "rimraf dist && npm run build:client && npm run build:server"
} 

Его нужно установить в первую очередьcross-env. (cross-envИспользуется для предотвращения сообщения командной строки Windows об ошибке при использовании NODE_ENV =production для установки переменных среды)

npm install --save-dev cross-env

Исправлятьindex.html

Как сказано в первом слое, нам нужноindex.htmlВо внешний файл шаблона вставьте<!--vue-ssr-outlet-->Метка комментария, используемая для обозначения места вставки фрагмента HTML-кода, отображаемого сервером, и удаления исходного кода.<div id="app">.
Сервер автоматически сгенерирует<div id="app" data-server-rendered="true">, клиент пройдетapp.$mount('#app')Монтируется на сгенерированные сервером элементы и делается реактивным.

  • ps: Шаблон просто меняется на шаблон подходящий для рендеринга на стороне сервера, но в режиме dev будет выдано сообщение об ошибке, так как #app не может быть найден, поэтому обработка под dev здесь производиться не будет. создайте отдельный для режима dev.html шаблон.

сборка пакета

npm run build

В каталоге dist будут сгенерированы два файла json:vue-ssr-server-bundle.jsonа такжеvue-ssr-client-manifest.json, для рендеринга на стороне сервера и внедрения статических ресурсов.

построить серверную часть

все еще использую здесьexpressЧтобы служить сервером, сначала установите его:

npm install express --save

После этого создайте в корневом каталогеserver.js, код в основном делится на 3 шага:

  1. использоватьcreateBundleRendererсоздаватьrenderer, вводим ранее сгенерированный файл json и читаемindex.htmlкак внешний шаблон;
  2. Установите маршрут, при запросе указанного маршрута установите заголовок запроса, вызовите функцию рендеринга и верните клиенту отрендеренный html;
  3. Слушайте порт 3001.
const express = require('express')
const app = express()

const fs = require('fs')
const path = require('path')
const { createBundleRenderer } = require('vue-server-renderer')

const resolve = file => path.resolve(__dirname, file)

// 生成服务端渲染函数
const renderer = createBundleRenderer(require('./dist/vue-ssr-server-bundle.json'), {
  // 模板html文件
  template: fs.readFileSync(resolve('./index.html'), 'utf-8'),
  // client manifest
  clientManifest: require('./dist/vue-ssr-client-manifest.json')
})

function renderToString (context) {
  return new Promise((resolve, reject) => {
    renderer.renderToString(context, (err, html) => {
      err ? reject(err) : resolve(html)
    })
  })
}
app.use(express.static('./dist'))

app.use(async(req, res, next) => {
  try {
    const context = {
      title: '服务端渲染测试', // {{title}}
      url: req.url
    }
    // 设置请求头
    res.set('Content-Type', 'text/html')
    const render = await renderToString(context)
    // 将服务器端渲染好的html返回给客户端
    res.end(render)
  } catch (e) {
    console.log(e)
    // 如果没找到,放过请求,继续运行后面的中间件
    next()
  }
})

app.listen(3000)

Запустите сервисную команду после того, как закончите:

node server.js

Посетите localhost:3000/ssr, чтобы получить страницу, которую мы определили ранее.

третий этаж:Nuxt.jsПредварительное изучение исходного кода

Nuxt.jsчто

Nuxt.jsЭто официально рекомендованный Vue проект, универсальный фреймворк для приложений, основанный на Vue.js. Предустановлены различные конфигурации, необходимые для рендеринга на стороне сервера, такие как асинхронные данные, промежуточное ПО и маршрутизация.Если вы следуете правилам, вы можете легко внедрить SSR. Из коробки опыт дружелюбный. Nuxt.js фокусируется в первую очередь на рендеринге пользовательского интерфейса приложений, абстрагируя инфраструктуру клиент/сервер.

Nuxt.jsИнтерпретация небольшой части исходного кода

Nuxt.jsИсходный код включает в себя много контента, поэтому мы не будем вдаваться в подробности (ну, я недостаточно квалифицирован, чтобы понять его ==||). Давайте взглянем,Nuxt.jsКак промежуточное ПО, что делает весь процесс.
Nuxt.jsВ официально предоставленных примерах есть одинcustom-server,NuxtОн будет передан в экспресс как middleware, давайте посмотрим на код:

import express from 'express'
import { Nuxt, Builder } from 'nuxt'

const app = express()

const host = process.env.HOST || '127.0.0.1'
const port = process.env.PORT || 3000

// Import and set Nuxt.js options
let config = require('./nuxt.config.js')
config.dev = !(process.env.NODE_ENV === 'production')

const nuxt = new Nuxt(config)

// Start build process in dev mode
if (config.dev) {
  const builder = new Builder(nuxt)
  builder.build()
}

// Give nuxt middleware to express
app.use(nuxt.render)

// Start express server
app.listen(port, host)

Некоторые части этого кода аналогичны тому, что мы писали ранее, и обе используют экспресс для создания службы, которая включает в себяNuxt.jsКод имеет 2 места:

  1. Введите файл конфигурации nuxt.config.js и используйте его в качестве параметра для создания экземпляра Nuxt;
  2. Создайте экземпляр Builder для построения маршрута.

Nuxt генерирует соответствующие маршруты в соответствии со структурой каталогов в папке pages, что является одной из характеристик Nuxt, Мы не будем подробно останавливаться на этой части здесь и подробно обсудим ее позже. В основном мы говорим оnew Nuxt()вещи в.
Как мы видели выше, мы будемNuxtвизуализировать значение свойства объекта, созданное как проход промежуточного программного обеспечения, переданный экспресс, в нашем поиске, чтобы найти глобальный источникNuxtКонструктор:


Мы видим, что здесь создается еще один экземпляр Renderer, затем продолжаем находить этотRendererКонструктор. Код слишком длинный, и я не буду публиковать его весь. С таким количеством контента все еще кажется невозможным начать, не паникуйте, поскольку я уже говорил это раньше,vue-server-rendererявляется основным модулем рендеринга на стороне сервера SSR, тогда мы пытаемся выполнить поиск здесьvue-server-renderer, действительно, было найдено следующее содержимое:

В функции createRenderer мы можем видеть через аннотацию, что создается рендеринг на стороне сервера.bundle renderer, разве это не функция рендеринга сервера, которую мы реализовали ранее, ее первый параметрthis.resources.serverBundle, мы можем найти в файле:

Наш проект nuxt будет автоматически создан во время выполнения, создав папку .nuxt, содержащую:

Комбинируя эту строку кодов, мы видим, чтоNuxt.jsТакже с использованием предварительно скомпилированного пакета приложенияcreateBundleRendererсоздать рендерер там, где требуетсяserver-bundle.jsonа такжеclient-mainfest.jsonОн будет автоматически создан и сгенерирован nuxt во время выполнения.
下面是调用里面的renderToString方法,和我们上面写的demo一样,来生成html片段。 мы вrenderRouteНашел в методе:

Этот код показывает нам относительно завершенный процесс рендеринга, вызывающий функцию рендеринга для создания фрагментов HTML, сращивания фрагментов HTML, сращивания фрагментов HEAD и добавления их в предустановленный шаблон для рендеринга всего HTML. Отсюда видно, чтоNuxt.jsОсновной принцип рендеринга на стороне сервера в основном такой же, как в демо, которое мы написали ранее.

Суммировать

Преимущества и недостатки рендеринга на стороне сервера очень очевидны.Если это просто оптимизация SEO веб-страницы, мы также можем попробовать официальную рекомендацию Vue.Пререндеринг, я не буду вдаваться в подробности здесь. Приведенная выше демонстрация является лишь демонстрацией реализации рендеринга на стороне сервера.Если ее необходимо официально использовать в проекте, требуется более сложная конфигурация. Если мы просто используем рендеринг на стороне сервера для разработки простых проектов, мы можем напрямую использоватьNuxt.jsВот и все. Некоторые из приведенных выше точек знаний концептуально предвзяты, и исправления приветствуются.

использованная литература

  1. Официальное руководство Vue SSR:ssr.vuejs.org/zh/
  2. Пусть интеграция проекта после того, как vue-cli инициализирует поддержку SSR:blog.CSDN.net/Li Gang25851…
  3. vue-server-renderer: у-у-у. Краткое описание.com/afraid/8 oh 7099AE's…