Рендеринг сервера Vue отбрасывает запись

задняя часть внешний интерфейс JavaScript Vue.js

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

Структура проекта

├─.babelrc // babel 配置文件
├─index.template.html // html 模板文件
├─server.js // 提供服务端渲染及 api 服务
├─src // 前端代码
|  ├─app.js // 主要用于创建 vue 实例
|  ├─App.vue // 根组件
|  ├─entry-client.js // 客户端渲染入口文件
|  ├─entry-server.js // 服务端渲染入口文件
|  ├─stores // vuex 相关
|  ├─routes // vue-router 相关
|  ├─components // 组件
├─dist // 代码编译目标路径
├─build // webpack 配置文件

Основная структура каталогов проекта показана выше, гдеpackage.jsonпожалуйста, проверьтепроект. О том, почему вам следует использовать библиотеку управления состояниемVuex, официальный сайт имеет четкуюобъяснять. Ниже приведены примеры, помогающие лучшему пониманию.

Далее мы временно проигнорируем рендеринг на стороне сервера и сначала построим простойvueсреда разработки.

Создайте среду разработки vue

использоватьwebpackВы можете построить простойvueсреда разработки, вы можете напрямую умножатьлифтИдти к.

Для эффективного развития,vueСреда разработки должна иметь функции горячей загрузки кода и переадресации запросов. Их можно использоватьwebpack-dev-serverчтобы легко реализовать, просто настройтеwebpackизdevServerпункт:

module.exports = merge(baseWebpackConfig, {
  devServer: {
    historyApiFallback: true,
    noInfo: true,
    overlay: true,
    proxy: config.proxy
  },
  devtool: '#eval-source-map',
  plugins: [
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: 'index.template.html',
      inject: true // 插入css和js
    }),
    new webpack.HotModuleReplacementPlugin(),
    new FriendlyErrors()
  ]
})

Затем добавить при запуске--hotПараметры могут быть:

cross-env NODE_ENV=development webpack-dev-server --config build/webpack.dev.conf.js --open --hot

уведомлениеrouterа такжеstoreтак же какvueОба используют фабричные функции для создания экземпляров, что должно облегчить повторное использование кода при рендеринге на стороне сервера позже, потому что «сервер Node.js — это длительный процесс. Для каждого запроса должен создаваться новый экземпляр Vue» (Официальный сайт).

Опять же, внешний запрос используетaxiosБиблиотеки, но и заботиться о сервере.

Запуск в корневом каталоге проектаnpm run serverЗапустите серверную службу API, а затем запуститеnpm run dev,webpackавтоматически откроется в браузере по умолчаниюhttp://localhost:8080адрес, вы можете увидеть эффект.

рендеринг на стороне сервера

Проще построить рендеринг на стороне сервера на основе проекта, созданного выше, так что давайте начнем. Или просто посмотреть последнийкод.

Для реализации рендеринга на стороне сервера просто добавьте следующееwebpackКонфигурация:

module.exports = merge(baseWebpackConfig, {
  entry: './src/entry-server.js',
  // 告知 `vue-loader` 输送面向服务器代码(server-oriented code)。
  target: 'node',
  output: {
    filename: 'server-bundle.js',
    libraryTarget: 'commonjs2',
  },
  plugins: [
     new VueSSRServerPlugin()
  ]
})

уведомлениеentryПуть к файлу не такой, как раньше, вот файл входа, специально подготовленный для рендеринга на стороне сервера:

import { createApp } from './app'
// 这里的 context 是服务端渲染模板时传入的
export default context => {
  // 因为有可能会是异步路由钩子函数或组件,所以我们将返回一个 Promise,
  // 以便服务器能够等待所有的内容在渲染前,
  // 就已经准备就绪。
  return new Promise((resolve, reject) => {
    const { app, router, store } = createApp()

    const { url } = context
    const { fullPath } = router.resolve(url).route

    if (fullPath !== url) {
      return reject({ url: fullPath })
    }

    router.push(url)

    // 等到 router 将可能的异步组件和钩子函数解析完
    router.onReady(() => {
      const matchedComponents = router.getMatchedComponents()
      // 匹配不到的路由,执行 reject 函数,并返回 404
      if (!matchedComponents.length) {
        return reject({ code: 404 })
      }

      // 执行所有组件中的异步数据请求
      Promise.all(matchedComponents.map(({ asyncData }) => asyncData && asyncData({
        store,
        route: router.currentRoute
      }))).then(() => {
        context.state = store.state
        resolve(app)
      }).catch(reject)
    }, reject)
  })
}

один из нихasyncDataЭто может сбивать с толку, но позже мы проиллюстрируем это на примере. Теперь скомпилируем, запустимnpm run build:server, будет вdistПолучите файл vue-ssr-server-bundle.json в каталоге. Как видите, файл содержитwebpackВсе упакованоchunkи указать вход. Позже сервер будет отображать на основе этого файла.

Теперь давайте перейдем к серверу и добавим немного кода:

...
 const { createBundleRenderer } = require('vue-server-renderer')
 const bundle = require('./dist/vue-ssr-server-bundle.json')

 const renderer = createBundleRenderer(bundle, {
   template: fs.readFileSync('./index.template.html', 'utf-8')
 })
...
// 服务端渲染
server.get('*', (req, res) => {
  const context = { url: req.originalUrl }
  renderer.renderToString(context, (err, html) => {
    if (err) {
      if (err.code === 404) {
        res.status(404).end('Page not found')
      } else {
        res.status(500).end('Internal Server Error')
      }
    } else {
      res.end(html)
    }
  })
})

Нового кода не так много, сначала создайте файл, используя файл, сгенерированный выше.rendererобъект, а затем вызвать егоrenderToStringметод и передать объект, содержащий путь запроса, в качестве параметра для рендеринга, и, наконец, визуализированные данныеhtmlвернуть.

бегатьnpm run serverЗапустите сервер, откройтеhttp://localhost:8081Вы можете увидеть эффект:

Об асинхронных данных

упомянутый ранееasyncData, а теперь возьмем этот пример, чтобы разобраться. Во-первых, взгляните на код в компоненте:

...
<script>
export default {
  asyncData ({ store, route }) {
    // 触发 action 后,会返回 Promise
    return store.dispatch('fetchItems')
  },
  data () {
    return {
      title: "",
      content: ""
    }
  },
  computed: {
    // 从 store 的 state 对象中的获取 item。
    itemList () {
      return this.$store.state.items
    }
  },
  methods: {
    submit () {
      const {title, content} = this
      this.$store.dispatch('addItem', {title, content})
    }
  }
}
</script>

Это очень простой компонент, включающий в себя список, содержимое которого извлекается из бэкенда по запросу, и форму для отправки новых записей в бэкенд для сохранения. вasyncDataэто наше согласованное имя функции, указывающее, что компонент рендеринга должен выполнить ее заранее, чтобы получить исходные данные, и возвращаетPromise, чтобы мы могли знать, когда операция завершена при рендеринге в бэкенде. Здесь срабатывает функцияfetchItemsобновитьstoreсостояние в . Помните наш файл entry-server.js, где вызывается компонентasyncDataметод предварительной выборки данных.

На этапе разработки нам также необходимо выполнить предварительную выборку данных, чтобы повторно использоватьasyncDataкод, мы находимся в компонентеbeforeMountвызвать этот метод, мы передаем эту логику обработки черезVue.mixinСмешать со всеми компонентами:

Vue.mixin({
  beforeMount() {
    const { asyncData } = this.$options
    if (asyncData) {
      // 将获取数据操作分配给 promise
      // 以便在组件中,我们可以在数据准备就绪后
      // 通过运行 `this.dataPromise.then(...)` 来执行其他任务
      this.dataPromise = asyncData({
        store: this.$store,
        route: this.$route
      })
    }
  }
})

Другая проблема заключается в том, что мы не вводим никаких js в сгенерированный html, и пользователи не могут выполнять какие-либо действия.Например, на приведенной выше странице со списком пользователи не могут отправлять новый контент. Конечно, если эта страница только для того, чтобы краулеры «видели», этого достаточно, но если мы рассматриваем реальных пользователей, нам также необходимо внедрить js-файлы рендеринга фронтенда в html.

внешний рендеринг

Код в этой части можно просмотреть напрямуюздесь.

Сначала необходимо добавить интерфейсную часть рендеринга.webpackФайл конфигурации используется для генерации необходимых js, css и других статических файлов:

module.exports = merge(baseWebpackConfig, {
  plugins: [
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false,
        drop_console: true
      }
    }),
    // 重要信息:这将 webpack 运行时分离到一个引导 chunk 中,
    // 以便可以在之后正确注入异步 chunk。
    // 这也为你的 应用程序/vendor 代码提供了更好的缓存。
    new webpack.optimize.CommonsChunkPlugin({
      name: "manifest",
      minChunks: Infinity
    }),
    // 此插件在输出目录中
    // 生成 `vue-ssr-client-manifest.json`。
    new VueSSRClientPlugin()
  ]
})

В то же время для внешнего рендеринга также нужен собственный входной файл.entry-client, говорится в документеasyncDataупоминается, когда:

import Vue from 'vue'
import {
  createApp
} from './app.js'
// 客户端特定引导逻辑……
const {
  app,
  router,
  store
} = createApp()
if (window.__INITIAL_STATE__) {
  store.replaceState(window.__INITIAL_STATE__)
}

Vue.mixin({
  beforeMount() {
    const { asyncData } = this.$options
    if (asyncData) {
      // 将获取数据操作分配给 promise
      // 以便在组件中,我们可以在数据准备就绪后
      // 通过运行 `this.dataPromise.then(...)` 来执行其他任务
      this.dataPromise = asyncData({
        store: this.$store,
        route: this.$route
      })
    }
  }
})
// 这里假定 App.vue 模板中根元素具有 `id="app"`
router.onReady(() => {
  app.$mount('#app')
})

Теперь мыnpm run build:clientСкомпилировав его, вы можете получить несколько файлов в каталоге dist:

0.js
1.js
2.js
app.js
manifest.js
vue-ssr-client-manifest.json

Среди них файлы js - это все файлы, которые нужно импортировать, а файл json - это как документ с описанием. Принцип здесь не обсуждается. Если вам интересно, вы можете проверить это.здесь.

наконец,server.js, с небольшой модификацией:

 const clientManifest = require('./dist/vue-ssr-client-manifest.json')

 const renderer = createBundleRenderer(bundle, {
   template: fs.readFileSync('./index.template.html', 'utf-8'),
   clientManifest
 })

потомnpm run serverЗапустите службу, затем включите ее сноваhttp://localhost:8081, вы можете видеть, что ресурсы js были добавлены в отрендеренный html-файл.

Новые записи также могут быть представлены на странице списка:

Суммировать

Эта статья начинается с создания простой среды разработки vue, а затем реализует на ее основе рендеринг на стороне сервера и знакомит с ресурсами, необходимыми для рендеринга на стороне клиента. В ходе этого процесса был выполнен общий процесс рендеринга vue на стороне сервера, но во многих местах необходимо пойти дальше:

  • обработка стиля

    В этой статье не рассматриваются стили, и необходимы дальнейшие исследования.

  • Пояснение к скомпилированным файлам

    Как используются json и другие файлы, скомпилированные и сгенерированные в статье?

  • Различные стратегии для поисковых роботов и реальных пользователей

    На самом деле рендеринг на стороне сервера в основном используется для решения проблемы seo, поэтому вы можете судить об источнике по заголовку запроса на стороне сервера и выполнять другую обработку.Пользователи по-прежнему используют оригинальный метод рендеринга на стороне клиента.