Для решения 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, поэтому вы можете судить об источнике по заголовку запроса на стороне сервера и выполнять другую обработку.Пользователи по-прежнему используют оригинальный метод рендеринга на стороне клиента.