предисловие
Прежде всего, приглашаю всех подписаться на меняБлог на гитхабе, что можно рассматривать как небольшое поощрение для меня, ведь у меня нет денег, чтобы писать вещи, и я могу продолжать на своем собственном энтузиазме и всеобщем поощрении.
Изоморфный (рендеринг на сервере)
Изоморфизм Vue — это то, что мы часто называем рендерингом на стороне сервера.Рендеринг на стороне сервера сегодня не является чем-то новым.React и Vue имеют свои собственные решения для рендеринга на сервере.Многие мелкие партнеры могут нужный? У таких фреймворков, как Vue и React, есть особенность, заключающаяся в том, что все они относятся к рендерингу в браузере, например, самый простой пример:
<div id="app">
{{ message }}
</div>
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
Мы видим, что шаблон, который мы получили с сервера, на самом деле не соответствует тому интерфейсу, который мы ожидаемhtml
структура, есть только один корневой элемент для монтирования приложения, а клиентский браузер выполняет загрузкуJavaScript
Соответствующая структура DOM создается только при выполнении кода. Однако рендеринг в браузере на самом деле имеет два очевидных недостатка:
- Не подходит для поисковой оптимизации (SEO: Search Engine Optimization), каждая поисковая система на самом деле является веб-страницей.
html
Структура и синхронизацияJavascript
код для индексации, поэтому рендеринг на стороне клиента может помешать правильной индексации ваших страниц поисковыми системами. - TTC (время поступления контента: Time-To-Conten) слишком велико.Представьте, что если сеть устройства плохая или скорость выполнения кода устройства низкая, пользователю нужно долго ждать, чтобы увидеть контент. Это все белый экран или другое состояние загрузки веб-страницы, что, безусловно, плохо для пользователя.
К счастью, появление Node прояснило все это, JavaScript можно выполнять не только в браузере, но и в бэкэнд-среде. Таким образом, мы можем визуализировать пользовательский интерфейс в виде строки HTML на сервере, затем передать его в браузер, чтобы пользователь получил интерфейс с возможностью предварительного просмотра, и, наконец, «смешать» статическую разметку в полностью интерактивное приложение на стороне клиента. весь процесс рендеринга завершен.
Простейший пример
Визуализация сервера Vue использует официальную библиотекуvue-server-renderer
,из-заExpress
Это более интуитивно понятно, мы используемExpress
В качестве бэкенд-сервера сначала приведем простейший пример:
// server.js
const Vue = require('vue')
const server = require('express')()
// 创建一个 renderer
const renderer = require('vue-server-renderer').createRenderer()
server.get('*', (req, res) => {
// 创建一个 Vue 实例
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
}
// html就是Vue实例app渲染的html
res.end(`
<!DOCTYPE html>
<html lang="en">
<head><title>Hello</title></head>
<body>${html}</body>
</html>
`)
})
})
server.listen(8080)
затем начнитеnode server.js
, и в доступе к браузеру, например.http://localhost:8080/app
, интерфейс браузера отобразит:
Доступ к URL-адресу: /app
В это время обратите внимание, что возвращаемое значение запроса:
Мы нашли возвращенноеhtml
Элементы DOM были отображены в . Таким образом, нам не нужно ждать, чтобы сразу увидеть содержимое страницы. Логика приведенного выше кода также очень проста: когда http-сервер получает запрос на получение, он создает экземпляр Vue, который находится в vue-server-renderer.createRenderer
создатьRenderer
пример,Renderer
серединаrenderToString
Он используется для преобразования экземпляра Vue в соответствующую строку HTML.Следует отметить, что нам нужно обернуть созданную строку вhtml
Вернуться вместе. Конечно, вы можете разделить их в виде шаблонов страниц:
<!DOCTYPE html>
<html lang="en">
<head><title>Hello</title></head>
<body>
<!--vue-ssr-outlet-->
<!--这里将是应用程序 HTML 标记注入的地方>
</body>
</html>
// renderer中包含了模板
const renderer = createRenderer({
template: require('fs').readFileSync('./index.template.html', 'utf-8')
})
renderer.renderToString(app, (err, html) => {
res.end(html)
})
Конечно, это только самый простой пример, браузер только получает html-код, соответствующий экземпляру Vue, и не активирует его, поэтому он не интерактивен.
Процесс рендеринга в браузере
Для рендеринга в браузере мы предпочитаем упаковывать код в Webpack.Общий процесс можно пояснить на следующем рисунке:
Для приложения Vue уровень исходного кода фактически включает три аспекта: компоненты, маршрутизацию и управление состоянием. Мы считаем эту часть кода общим кодом, который может выполняться как на стороне сервера, так и на стороне браузера.В Webpack есть две записи:server entry
а такжеclient entry
, которые используются для упаковки кода, выполняемого на стороне сервера, и кода, выполняемого на стороне браузера, соответственно.Server Bundle
Поскольку код упаковывается и выполняется на стороне сервера, он отвечает за создание соответствующего HTML иClinet Bundle
Поскольку код выполняется на стороне браузера, основная ответственность заключается в активации приложения.
Ниже мы приводим соответствующую конфигурацию вебпака.Для удобства начала работы мы приводим только самую простую конфигурацию, чтобы можно было запустить код.Конфигурация состоит из трех частей:base
,client
,server
,вbase
является общей частью между ними,client
Это конфигурация упаковки соответствующего браузера,server
это конфигурация упаковки на стороне сервера, черезwebpack-merge
(Это можно легко понять какObject.assign
), чтобы подключить его:
// webpack.base.config.js
const path = require('path')
const { VueLoaderPlugin } = require('vue-loader')
module.exports = {
output: {
path: path.resolve(__dirname, '../dist'),
publicPath: '/dist/',
filename: '[name].[chunkhash].js'
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
}
]
},
plugins: [
new VueLoaderPlugin()
]
}
Выше приведена самая простая общая конфигурация в webpack, которая определяет три части:
-
output
: Как файл пакета сохраняет выходные данные и где их хранить -
module
: Выполняем соответствующий загрузчик для файла js и файла vue -
plugins
:VueLoaderPlugin
Плагины необходимы, и их роль заключается в копировании и применении других правил, определенных вами, к соответствующему языковому блоку в файле .vue. Например, код JavaScript, соответствующий тегу script в файле vue, и код CSS, соответствующий тегу stype.
// webpack.server.config.js
const merge = require('webpack-merge')
const base = require('./webpack.base.config')
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')
module.exports = merge(base, {
target: 'node',
entry: './src/entry-server.js',
output: {
libraryTarget: 'commonjs2'
},
plugins: [
new VueSSRServerPlugin()
]
})
Приведенная выше конфигурация используется для упаковки комплекта серверной стойки:
-
target
: используется для указания цели сборки, узел указывает, что веб-пакет будет скомпилирован для использования в среде, подобной Node.js. -
entry
: Файл записи упаковки сервера -
libraryTarget
: Поскольку он используется в среде Node, мы выбираемcommonjs2
-
VueSSRServerPlugin
: используется для упаковки сгенерированного пакета на стороне сервера, и, наконец, все файлы могут быть упакованы в одинjson
файл и, наконец, отправлен на серверrenderer
использовать.
// webpack.client.config.js
const webpack = require('webpack')
const merge = require('webpack-merge')
const base = require('./webpack.base.config')
const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
module.exports = merge(base, {
entry: {
app: './src/entry-client.js'
},
plugins: [
// extract vendor chunks for better caching
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function (module) {
// a module is extracted into the vendor chunk if...
return (
// it's inside node_modules
/node_modules/.test(module.context)
)
}
}),
// extract webpack runtime & manifest to avoid vendor chunk hash changing
// on every build.
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest'
}),
new VueSSRClientPlugin()
]
})
-
entry
: Файл записи упаковки браузера. -
VueSSRClientPlugin
: похожий наVueSSRServerPlugin
Основная функция плагина — упаковать внешний код вbundle.json
, а затем передать значение вrenderer
, который может автоматически выводить и вставлять директивы предварительной загрузки/предварительной выборки и теги сценария в отображаемый HTML.
оCommonsChunkPlugin
Плагины, по сути, не нужны для самого простого приложения, но они добавляются, потому что помогают повысить производительность. Когда я впервые изучил Webpack, весь код каждый раз упаковывался в один и тот же файл, напримерapp.[hash].js
, по фактуapp.[hash].js
Он содержит две части кода, одна часть — это код бизнес-логики, который меняется каждый раз, а другая часть — это код библиотеки классов (например, исходный код Vue), который почти не меняется. Сейчас эта ситуация на самом деле очень неблагоприятна для кеширования браузера, потому что каждый раз, когда бизнес-код меняется,app.[hash].js
должны измениться, поэтому браузер должен повторно запросить, иapp.[hash].js
Объем кода может исчисляться терабайтами. Таким образом, мы можем отделить бизнес-код от кода библиотеки классов в приведенном выше примере:
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function (module) {
// a module is extracted into the vendor chunk if...
return (
// it's inside node_modules
/node_modules/.test(module.context)
)
}
}),
Мы процитируемnode_modules
Код упакован вvendor.[hash].js
, который содержит указанную библиотеку классов, которая является относительно неизменной частью кода. Но если у вас есть только вышеуказанная часть, вы обнаружите, что каждый раз, когда изменяется логический код,vendor.[hash].js
изhash
Значение тоже меняется, почему так? Поскольку каждый раз, когда Webpack упаковывается и запускается, он по-прежнему будет генерировать некоторый код, связанный с текущей операцией Webpack, что повлияет на текущее значение пакета, поэтомуvendor.[hash].js
Каждый раз пакет все равно меняется, по сути, браузер не может его корректно кэшировать. Итак, мы используем:
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest'
})
Нам нужно извлечь среду выполнения в отдельныйmanifest
файл, вот такvendor
изhash
не изменится, браузер можетvendor
правильно кэшируется,mainfest
изhash
Хотя он меняется каждый раз, он очень мал, по сравнению сvendor
Влияние изменения незначительно.
Как мы уже говорили, приложение Vue на самом деле можно разделить на три части: компоненты, маршрутизация и управление состоянием.В качестве первой статьи в серии SSR мы только представляем, как отобразить простой компонент на сервере и активировать его на компонент client., делая его интерактивным. Другие части, такие как маршрутизация и управление состоянием, рассматриваются в последующих разделах.
компоненты
Прежде всего, мы используем Vue для написания простого счетного компонента, нажмите «+», чтобы увеличить количество, нажмите «-», чтобы уменьшить количество.
// App.vue
<template>
<div id="app">
<span>times: {{times}}</span>
<button @click="add">+</button>
<button @click="sub">-</button>
</div>
</template>
<script>
export default {
name: "app",
data: function () {
return {
times: 0
}
},
methods: {
add: function () {
this.times = this.times + 1;
},
sub: function () {
this.times = this.times - 1;
}
}
}
</script>
<style scoped>
</style>
Приведенная выше часть представляет собой очень простой компонент Vue, а также общий код для рендеринга на стороне сервера и на стороне клиента. В чисто клиентской программе рендеринга будетapp.js
Используется для создания экземпляра Vue и его монтирования на соответствующий дом, например:
// 客户端渲染 app.js
import App from './App.vue'
new Vue({
el: '#app',
components: { App },
template: '<App/>',
})
В серверном рендерингеapp.js
Только одна фабричная функция открыта для внешнего мира, и каждый раз, когда она вызывается, для рендеринга возвращается новый экземпляр компонента. Другая специфическая логика переносится в входные файлы клиента и браузера соответственно.
import Vue from 'vue'
import App from './components/App.vue'
export function createApp() {
return new Vue({
render: h => h(App)
})
}
В отличие от рендеринга на стороне клиента, стоит отметить, что нам нужно создавать новый экземпляр Vue для каждого запроса, и мы не можем совместно использовать один и тот же экземпляр, потому что, если мы используем общий экземпляр между несколькими запросами, это может вызвать загрязнение состояния, поэтому мы создаем отдельный экземпляр компонента для каждого запроса.
Затем посмотрите на файл записи упаковки на стороне браузера:
// entry-server.js
import { createApp } from './app'
export default context => {
const app = createApp()
return app
}
entry-server.js
Предоставляет функцию для создания текущего экземпляра компонента. Затем посмотрите на файл записи клиентского пакета:
// client-server.js
import { createApp } from './app'
var app = createApp();
app.$mount('#app')
Логика тоже очень проста, мы создаем экземпляр Vue и монтируем его вid
дляapp
в структуре DOM.
В это время мы запускаем команду для упаковки клиентского и серверного кода соответственно и обнаруживаем, чтоdist
, в каталоге появятся следующие файлы:
Мы видим, чтоapp.[hash].js
представляет собой упакованный бизнес-код,vendor.[hash].js
Это код соответствующей библиотеки (например, исходный код Vue),manifest.[hash].js
являетсяCommonsChunkPlugin
Сгенерировать файл манифеста. а такжеvue-ssr-client-manifest.json
являетсяVueSSRClientPlugin
генерируется в соответствии с клиентомbundle
,а такжеvue-ssr-server-bundle.json
являетсяVueSSRServerPlugin
Плагин сгенерирован на стороне сервераbundle
. Имея указанный выше файл пакета, мы можем обработать запрос:
//server.js
const fs = require("fs")
const express = require("express")
const { createBundleRenderer } = require('vue-server-renderer')
const template = fs.readFileSync("./src/index.template.html", "utf-8")
const bundle = require('./dist/vue-ssr-server-bundle.json')
const clientManifest = require('./dist/vue-ssr-client-manifest.json')
const app = express();
app.use("/dist", express.static("dist"))
const renderer = createBundleRenderer(bundle, {
template,
clientManifest
})
app.get('*', (req, res) => {
renderer.renderToString({}, function (err, html) {
res.end(html);
});
})
app.listen(8080, function () {
console.log("server start and listen port 8080")
})
В этот раз мы не использовалиvue-server-renderer
серединаcreateRenderer
функционировать, но использоватьcreateBundleRenderer
функция, мыserver.js
были введены вserver-bundle.json
а такжеclient-manifest.json
с шаблономtemplate.html
, а затем передать егоcreateBundleRenderer
генерация функцийrenderer
, а затем в каждом запросе вызыватьrenderer
изrenderToString
метод, сгенерируйте соответствующий HTML-код, а затем верните клиенту.renderToString
Первый параметр - это, по сути, контекстcontext
объект, с одной стороныcontext
Используется для обработки файлов шаблонов, например существующих в файлах шаблонов.
<title>{{title}}</title>
а такжеcontext
существуют вtitle: 'SSR'
, файлы в шаблоне будут интерполированы. Другая часть, входной файл клиентаserver-entry.js
Средняя функция также получитcontext
, который можно использовать для передачи связанных параметров.
Почему мы используемexpress.static
правильноdist
Причина, по которой файлы в папке предоставляют службы статических ресурсов, заключается в том, что соответствующий код будет внедрен в код клиента.JavaScript
файл (например,app.[hash].js
), чтобы обеспечить возможность запроса соответствующих ресурсов.
Затем запускаем команду:
node server.js
и зайдите на http://localhost:8080 в браузере. Вы обнаружите, что простая программа счетчика уже запущена, и она работает, и нажатие кнопки вызовет соответствующее событие.
Это соответствует принятой структуре html:
Мы обнаружили, что возвращенный html-код имеет структуру DOM, соответствующую нашему экземпляру Vue, которая отличается от обычной структуры на стороне клиента и существует в корневом элементе.data-server-rendered
Атрибут, указывающий, что структура является узлом, соответствующим рендерингу сервером. В режиме разработки Vue сравнивает визуализированный виртуальный DOM с текущей структурой DOM. Если они равны, текущая структура будет повторно использоваться. В противном случае визуализированный виртуальный DOM будет отброшен.Хорошая структура, вместо повторного рендеринга на стороне клиента. В производственном режиме шаг обнаружения будет пропущен и повторно использован напрямую, чтобы избежать потери производительности.
При серверном рендеринге компонент проходит толькоbeforeCreate
а такжеcreated
два срока службы, а остальные, например.beforeMount
Жизненный цикл ожидания не выполняется на стороне сервера, поэтому следуетbeforeCreate
а такжеcreated
Код жизненного цикла, создающий глобальные побочные эффекты, напримерbeforeCreate
а такжеcreated
используется вsetInterval
настраиватьtimer
, пока вbeforeDestroy
илиdestroyed
Уничтожить его при жизни, что вызываетtimer
никогда не будет отменен.
До сих пор мы представили простейший пример рендеринга сервера Vue и активировали его на стороне клиента. Другие части рендеринга сервера, такие как маршрутизация и управление состоянием, будут представлены в следующих статьях.Блог на гитхабеНажмите звездочку посередине.Если в статье есть неточность, укажите на нее, и мы готовы вместе добиться прогресса.