содержание
- Что такое технология рендеринга на стороне сервера и сценарии ее применения
- Что такое технология предварительного рендеринга, рендеринг на стороне сервера VS предварительный рендеринг
- Принцип реализации серверного рендеринга
- Создание проекта рендеринга на стороне сервера
- Оптимизация производительности рендеринга на стороне сервера
Что такое рендеринг на стороне сервера Vue (SSR)
Так называемый рендеринг Vue на стороне сервера заключается в преобразовании экземпляров Vue на стороне сервера в строки HTML, отправке их непосредственно в браузер и, наконец, «смешивании» статической разметки в полностью интерактивное приложение на клиенте.
Зачем использовать рендеринг на стороне сервера (SSR)
- Лучшее SEO, так как сканеры поисковых систем могут напрямую просматривать полностью обработанные страницы.
- Ускоренный рендеринг в верхней части страницы. Специально для устройств с низкой скоростью сети или медленной работой нет необходимости ждать, пока все js будут загружены и проанализированы перед рендерингом страницы. Вместо этого страница рендерится на сервере и отправляется непосредственно клиенту для рендеринга страницы.
Рендеринг на стороне сервера (SSR) и предварительный рендеринг
- Тот же самый момент: все решают проблему одностраничного SEO, более быстрого времени доставки контента.
- разница: 1. Принципы и схемы реализации разные: реализация SSR полагается на сервер node.js как сервер для сборки статических ресурсов, а реализация prerender полагается на интегрированный webpack как prerender-spa-plugin, который извлекает статические ресурсы ресурсов и отображает их в интерфейсе. 2. Рендеринг на стороне сервера может обеспечить компиляцию в реальном времени на стороне сервера. Prerender просто генерирует статические файлы HTML для определенных маршрутов во время построения, чтобы добиться эффекта SEO. Преимущество предварительного рендеринга заключается в том, что конфигурация проще, а фронтальная end можно использовать как полноценный статический сайт.
Конфигурация веб-пакета Prerender
const path = require('path')
const PrerenderSPAPlugin = require('prerender-spa-plugin')
module.exports = {
plugins: [
...
new PrerenderSPAPlugin({
// Required - The path to the webpack-outputted app to prerender.
staticDir: path.join(__dirname, 'dist'),
// Required - Routes to render.
routes: [ '/', '/about', '/some/deep/nested/route' ],
})
]
}
Принципиальная схема рендеринга на стороне сервера
Стек технологий реализации SSR
Сервер: Нодейс Внешний фреймворк Vue2.0+ Инструмент для сборки внешнего интерфейса: веб-пакет Проверка кода: eslint Исходный код: es6 Внешняя маршрутизация: vue-router Управление состоянием: vuex Связь с сервером: axios Управление журналом: log4js Инструмент развертывания автоматизации проекта: jenkins
Серверный рендеринг экземпляра Vue
// 第 1 步:创建一个 Vue 实例
const Vue = require('vue')
const app = new Vue({
template: `<div>Hello World</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 World</div>
})
Интеграция службы узла Express
const Vue = require('vue')
const server = require('express')()
const renderer = require('vue-server-renderer').createRenderer()
server.get('*', (req, res) => {
const app = new Vue({
data: {
url: req.url
},
template: `<div>访问的 URL 是: {{ url }}</div>`
})
renderer.renderToString(app, (err, html) => {
if (err) {
res.status(500).end('Internal Server Error')
return
}
res.end(`
<!DOCTYPE html>
<html lang="en">
<head><title>Hello</title></head>
<body>${html}</body>
</html>
`)
})
})
server.listen(8080)
Структура каталогов проекта рендеринга на стороне сервера
структура кода app.js
import Vue from 'vue'
import App from './App.vue'
import store from './store'
import router from './router'
import { sync } from 'vuex-router-sync'
import Element from 'element-ui'
Vue.use(Element)
// sync the router with the vuex store.
// this registers `store.state.route`
sync(store, router)
/**
* 创建vue实例
* 在这里注入 router store 到所有的子组件
* 这样就可以在任何地方使用 `this.$router` and `this.$store`
* @type {Vue$2}
*/
const app = new Vue({
router,
store,
render: h => h(App)
})
/**
* 导出 router and store.
* 在这里不需要挂载到app上。这里和浏览器渲染不一样
*/
export { app, router, store }
структура кода entry-client.js
import 'es6-promise/auto'
import { app, store, router } from './app'
// prime the store with server-initialized state.
// the state is determined during SSR and inlined in the page markup.
if (window.__INITIAL_STATE__) {
store.replaceState(window.__INITIAL_STATE__)
}
/**
* 异步组件
*/
router.onReady(() => {
// 开始挂载到dom上
app.$mount('#app')
})
// service worker
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
}
структура кода server-entry.js
import { app, router, store } from './app'
const isDev = process.env.NODE_ENV !== 'production'
// This exported function will be called by `bundleRenderer`.
// This is where we perform data-prefetching to determine the
// state of our application before actually rendering it.
// Since data fetching is async, this function is expected to
// return a Promise that resolves to the app instance.
export default context => {
const s = isDev && Date.now()
return new Promise((resolve, reject) => {
// set router's location
router.push(context.url)
// wait until router has resolved possible async hooks
router.onReady(() => {
const matchedComponents = router.getMatchedComponents()
// no matched routes
if (!matchedComponents.length) {
reject({ code: 404 })
}
// Call preFetch hooks on components matched by the route.
// A preFetch hook dispatches a store action and returns a Promise,
// which is resolved when the action is complete and store state has been
// updated.
Promise.all(matchedComponents.map(component => {
return component.preFetch && component.preFetch(store)
})).then(() => {
isDev && console.log(`data pre-fetch: ${Date.now() - s}ms`)
// After all preFetch hooks are resolved, our store is now
// filled with the state needed to render the app.
// Expose the state on the render context, and let the request handler
// inline the state in the HTML response. This allows the client-side
// store to pick-up the server-side state without having to duplicate
// the initial data fetching on the client.
context.state = store.state
resolve(app)
}).catch(reject)
})
})
}
Написание общих рекомендаций по коду
Наш общий код - это набор кода, который может быть запущен в среде браузера и среды Node.js соответственно, поэтому есть некоторые вещи, которые обращают внимание на запись кода: 1. Во время процесса рендеринга на стороне сервера будет вызван только функции BeForecreate и созданные функции жизненного цикла. Другие жизненные циклы могут быть названы только лениво в среде браузера, а Node.js будет игнорировать эту часть функций жизненного цикла. 2. Общие коды не могут принимать специфичные платформы API (такие как документ, окно) и используют кроссплатформенную AXIOS (разоблачение того же API для браузеров и Node.js) для отправки запросов в среду браузера и Node.js. 3. Большинство пользовательских директив работают непосредственно на DOM, что вызывает ошибки во время рендеринга на стороне сервера (SSR)
Маршрутизация и сегментация кода
1. Введите vue-router, чтобы сделать одностраничное приложение страницы. 2. Разделение кода. Разделение кода или ленивая загрузка приложений помогает уменьшить объем ресурсов, загружаемых браузером при первоначальном рендеринге, что может значительно улучшить время до интерактивности (TTI — time-to-interactive) для больших объемов. связки. Ключевым моментом здесь является «загрузить только то, что вам нужно» для инициала над сгибом.
// 这里进行修改……
import Foo from './Foo.vue'
// 改为这样:
const Foo = () => import('./Foo.vue')
Предварительная выборка данных и статус
Во время рендеринга на стороне сервера (SSR) мы, по сути, рендерим «моментальный снимок» нашего приложения, поэтому, если приложение зависит от некоторых асинхронных данных, его необходимо предварительно загрузить и проанализировать перед запуском процесса рендеринга.
Еще одна проблема заключается в том, что на стороне клиента ему необходимо получить точно такие же данные, как и в приложении на стороне сервера, прежде чем монтировать их в приложение на стороне клиента, иначе клиентское приложение будет иметь разные состояния программы, что затем приведет к смесь провалится.
Чтобы решить эту проблему, извлеченные данные должны быть расположены вне компонента представления, то есть помещены в специальное хранилище данных или «контейнер состояния». Во-первых, на стороне сервера мы можем выполнять предварительную выборку данных перед рендерингом и заполнять хранилище данными. Кроме того, мы сериализуем и встроим предустановленное состояние в HTML. Таким образом, встроенное предустановленное состояние можно получить непосредственно из хранилища перед подключением к клиентскому приложению.
Для этого мы будем использовать официальную библиотеку управления состоянием Vuex.
клиентская смесь
Так называемая активация на стороне клиента относится к процессу, когда Vue принимает статический HTML, отправленный сервером на стороне браузера, и превращает его в динамический DOM, управляемый Vue. Если вы изучите вывод, отображаемый сервером, вы заметите, что корневой элемент приложения имеет специальный атрибут:
<div id="app" data-server-rendered="true">
Специальный атрибут data-server-rendered сообщает Vue на стороне клиента, что эта часть HTML обрабатывается Vue на сервере и должна быть смонтирована в активном режиме.
В режиме разработки Vue сделает вывод, соответствует ли созданное клиентом виртуальное дерево DOM структуре DOM, отображаемой с сервера. Если он не может совпасть, он выйдет из режима наложения, отбросит существующий DOM и отрендерит с нуля. В производственном режиме это обнаружение пропускается, чтобы избежать снижения производительности.
Конфигурация сборки клиента
Конфигурация клиента и базовая конфигурация в основном одинаковы. Очевидно, вам нужно указать запись в файл записи вашего клиента. Кроме того, если вы используете CommonsChunkPlugin, убедитесь, что вы используете его только в конфигурации клиента, потому что серверному пакету нужен отдельный фрагмент записи.
const webpack = require('webpack')
const merge = require('webpack-merge')
const baseConfig = require('./webpack.base.config.js')
const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
module.exports = merge(baseConfig, {
entry: '/path/to/entry-client.js',
plugins: [
// 重要信息:这将 webpack 运行时分离到一个引导 chunk 中,
// 以便可以在之后正确注入异步 chunk。
// 这也为你的 应用程序/vendor 代码提供了更好的缓存。
new webpack.optimize.CommonsChunkPlugin({
name: "manifest",
minChunks: Infinity
}),
// 此插件在输出目录中
// 生成 `vue-ssr-client-manifest.json`。
new VueSSRClientPlugin()
]
})
Конфигурация сборки сервера
Конфигурация сервера, которая используется для создания пакета сервера, передаваемого в createBundleRenderer. Это должно выглядеть так:
const merge = require('webpack-merge')
const nodeExternals = require('webpack-node-externals')
const baseConfig = require('./webpack.base.config.js')
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')
module.exports = merge(baseConfig, {
// 将 entry 指向应用程序的 server entry 文件
entry: '/path/to/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$/
}),
// 这是将服务器的整个输出
// 构建为单个 JSON 文件的插件。
// 默认文件名为 `vue-ssr-server-bundle.json`
plugins: [
new VueSSRServerPlugin()
]
})
тайник
В большинстве случаев приложения, отображаемые на сервере, полагаются на внешние данные, поэтому содержимое страницы по своей природе является динамическим и не может кэшироваться в течение длительного периода времени. Однако, если содержимое не зависит от пользователя (т. е. для одного и того же URL-адреса всегда отображается одно и то же содержимое для всех пользователей), мы можем воспользоваться стратегией кэширования, называемой микрокэшированием, чтобы значительно повысить скорость обработки приложения.
const microCache = LRU({
max: 100,
maxAge: 1000 // 重要提示:条目在 1 秒后过期。
})
const isCacheable = req => {
// 实现逻辑为,检查请求是否是用户特定(user-specific)。
// 只有非用户特定(non-user-specific)页面才会缓存
}
server.get('*', (req, res) => {
const cacheable = isCacheable(req)
if (cacheable) {
const hit = microCache.get(req.url)
if (hit) {
return res.end(hit)
}
}
renderer.renderToString((err, html) => {
res.end(html)
if (cacheable) {
microCache.set(req.url, html)
}
})
})
Потоковая визуализация
Как базовый рендерер, так и пакетный рендерер для vue-server-renderer предоставляют функции потокового рендеринга из коробки. Все, что вам нужно сделать, это заменить renderToString на renderToStream:
const stream = renderer.renderToStream(context)
Возвращаемое значение представляет собой поток Node.js:
let html = ''
stream.on('data', data => {
html += data.toString()
})
stream.on('end', () => {
console.log(html) // 渲染完成
})
stream.on('error', err => {
// handle error...
})
Nuxt.js
Создание серверного приложения с нуля довольно сложно. Nuxt — это фреймворк более высокого уровня, основанный на экосистеме Vue, обеспечивающий чрезвычайно удобный опыт разработки серверных приложений Vue.