предисловие
Поскольку мой блог полностью отделен от передней и задней частей,seo
Я не рассматривал это, поэтому я начал в этот раз.SSR
путешествие
стек технологий
vue2 + koa2 + webpack4 + mongodb
Потому что вебпак тоже прибыл4.1
версия, кстатиwebpack3
мигрировал вwebpack4
.
Рендеринг на стороне сервера (SSR)
Вероятно, это означает, что он генерируется на стороне сервера.html
фрагмент, который затем возвращается клиенту
такvue-ssr
Это также можно понимать как написание того, что мы привыкли писать на стороне клиента..vue
файл преобразован вhtml
Фрагмент, возвращенный клиенту.
На самом деле, конечно, это будет сложно, например, сервер, возвращающийhtml
Фрагменты, клиент напрямую принимает отображение, и мы не можем инициировать события (события кликов и т. д.), ничего не делая.
Для решения вышеуказанной проблемы.
так что вы проходитеvue-server-rendererПри рендеринге он будет прикреплен к корневому узлу сdata-server-rendered="true"
специальные свойства.
пусть клиентVue
знаю эту частьHTML
ОтVue
Отрисовывается сервером и должен монтироваться в активном режиме.
**Режим активации:** относится к процессу, в котором Vue принимает статический HTML-код, отправленный сервером на стороне браузера, и превращает его в динамический DOM, управляемый Vue.
Вероятно, это означает, что сервер был отрендерен.html
, но рендеринг на стороне сервера — это статическая страница, которой нельзя управлятьDOM
.
Но потому чтоdom
Элемент уже сгенерирован, нет необходимости отбрасывать его и создавать заново.
Таким образом, клиенту нужно только активировать эти статические страницы и сделать их динамическими (которые могут реагировать на последующие изменения данных).
SSR
Преимущество
- Лучшее SEO, так как сканеры поисковых систем могут напрямую просматривать полностью обработанные страницы.
- Более быстрое получение контента, особенно для медленных сетевых условий или медленных устройств. Нет необходимости ждать завершения загрузки и выполнения всего JavaScript перед отображением разметки, отображаемой на сервере, поэтому ваши пользователи быстрее увидят полностью обработанную страницу. Часто приводит к улучшению взаимодействия с пользователем, а рендеринг на стороне сервера (SSR) имеет решающее значение для приложений, где «время до контента напрямую связано с коэффициентами конверсии».
SSR
Развитие требует внимания
- Рендеринг на стороне сервера выполняется только
vue
Две хуковые функцииbeforeCreate
иcreated
- Рендеринг на стороне сервера недоступен
window
иdocument
И т. д. глобальные объекты, которые есть только у браузеров. (Если в вашем проекте или вbeforeCreate
иcreated
Если эти объекты используются, будет сообщено об ошибке, поскольку эти объекты не существуют на сервере. Если вы действительно хотите его использовать, вы можете попробовать этот плагинjsdom
В основном, пока вы правыnode
Если разберетесь, будем настраиватьwebpack
,vue
Его можно использовать нормально, в принципе эта штука относительно проста в реализации, тем более на официальном сайте есть полный примерHackerNews Demo, конечно, это основано наexpress
кадрирование, использованиеkoa
Если это так, использование промежуточного программного обеспечения необходимо изменить. Остальным в принципе просто нужно брать пример с официального сайта и это в принципе нормально
Пример на официальном сайте выше требует, чтобы терминал перевернул стену для доступа к данным.Если вы не хотите, вы можете посмотреть этот пример, который в основном такой же, как пример на официальном сайте.Сайт самородков
Здесь также о реализации официального сайта
каталог проекта
src
├── components
│ ├── Foo.vue
│ ├── Bar.vue
│ └── Baz.vue
├── router
│ └── index.js
├── store
│ └── index.js
├── App.vue
├── app.js # universal entry
├── entry-client.js # 运行于客户端的项目入口
└── entry-server.js # 运行于服务端的项目入口
Требуется несколько очков знаний
-
vuex
используется, потому что приложение использует некоторые асинхронные данные, которые необходимо предварительно загрузить и проанализировать перед запуском процесса рендеринга. так будет использоватьvuex
как контейнер для хранения предварительной выборки данных -
asyncData
Пользовательская функция (получить данные интерфейса):<template> <div>{{ item.title }}</div> </template> <script> export default { // 自定义获取数据的函数。 asyncData ({ store, route }) { // 触发 action 后,会返回 Promise return store.dispatch('fetchItem', route.params.id) }, computed: { // 从 store 的 state 对象中的获取 item。 item () { return this.$store.state.items[this.$route.params.id] } } } </script>
-
Избегайте синглетонов с состоянием: При написании клиентского кода мы привыкли каждый раз оценивать код в новом контексте. Однако сервер Node.js — это длительный процесс. Когда наш код входит в процесс, он принимает значение и сохраняет его в памяти. Это означает, что если вы создадите одноэлементный объект, он будет совместно использоваться каждым входящим запросом. Поэтому мы создаем новый корневой экземпляр Vue для каждого запроса. Следовательно, вместо того, чтобы создавать экземпляр приложения напрямую, мы должны предоставить фабричную функцию, которая может выполняться многократно, создавая новый экземпляр приложения для каждого запроса:
// router.js import Vue from 'vue' import Router from 'vue-router' Vue.use(Router) export function createRouter () { return new Router({ mode: 'history', routes: [ // ... ] }) }
// store.js import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) // 假定我们有一个可以返回 Promise 的 // 通用 API(请忽略此 API 具体实现细节) import { fetchItem } from './api' export function createStore () { return new Vuex.Store({ state: { items: {} }, actions: { fetchItem ({ commit }, id) { // `store.dispatch()` 会返回 Promise, // 以便我们能够知道数据在何时更新 return fetchItem(id).then(item => { commit('setItem', { id, item }) }) } }, mutations: { setItem (state, { id, item }) { Vue.set(state.items, id, item) } } }) }
// app.js import Vue from 'vue' import App from './App.vue' import { createRouter } from './router' import { createStore } from './store' export function createApp () { // 创建 router 和 store 实例 const router = createRouter() const store = createStore() // 创建应用程序实例,将 router 和 store 注入 const app = new Vue({ router, store, render: h => h(App) }) // 暴露 app, router 和 store。 return { app, router, store } }
import {createApp} from './app' const {app, router, store} = createApp()
В соответствии с приведенными выше шагами и методами создайте новый экземпляр приложения для каждого запроса, и не будет загрязнения состояния перекрестного запроса, вызванного несколькими запросами.
Этапы реализации
- Во-первых, получить текущий посещенный путь, потому что
renderToString
Поддерживает передачу объекта рендеринга контекста, поэтому мы передаем объект контекста, содержащий текущийurl
```
// server.js
const context = {
url: ctx.url
}
renderer.renderToString(context, (err, html) => {
if (err) {
return reject(err)
}
console.log(html)
})
```
- Затем с помощью конфигурации, такой как веб-пакет посередине, можно сделать запись проекта на стороне сервера.
entry-server.js
полученный контекст
```
// entry-server.js
import {createApp} from './app'
export default context => {
// 因为有可能会是异步路由钩子函数或组件,所以我们将返回一个 Promise.
return new Promise((resolve, reject) => {
const { app, router, store } = createApp()
const { url } = context
// 设置服务器端 router 的位置
router.push(url)
// 等到 router 将可能的异步组件和钩子函数解析完
router.onReady(() => {
// 获取当前路径的组件
const matchedComponents = router.getMatchedComponents()
// 没有返回404
if (!matchedComponents.length) {
return reject({ code: 404 })
}
// 如果该路径存在,而且该路径存在需要调用接口来预取数据的情况,便等所有`asyncData`函数执行完毕.
// `asyncData`函数是组件自定义静态函数, 用来提前获取数据。
Promise.all(matchedComponents.map( ({asyncData}) => asyncData && asyncData({
store,
route: router.currentRoute
}))).then( () => {
// 执行完毕后,因为获取到的数据都统一存入 vuex 中, 上方 `asyncData` 里面执行的方法就是调用 vuex 的 action, 然后把数据存入的 vuex 的 state 中
// 所以我们便 store 里面的 state 赋值给 `context.state`
// 然后 `renderToString` 解析 html 的时候会把 `context.state` 里面的数据 嵌入到 html 的 `window.__INITIAL_STATE__` 变量中
// 这样我们到时候处理 客户端 的时候,便可以把客户端中 vuex 中的state 替换成 `window.__INITIAL_STATE__` 中的数据,来完成客户端与服务端的数据统一
context.state = store.state
resolve(app)
}).catch(reject)
})
})
}
```
- Приведенный выше компонентный анализ нашего текущего пути доступа завершен и возвращен клиенту, и клиент активирует эти статические html, потому что наш сервер генерирует html для получения данных через
asyncData
функция, но нам нужно отрендерить только первый запрос к серверу, а при переключении страниц потом отрисовывать не нужно, а снова ставится вызов интерфейсаasyncData
функция, поэтому, когда страница переключается, наши клиенты должны иметь дело сasyncData
функция, мы обычно помещаем данные вcreated
В функции хука, когда вы поместите его сейчасasyncData
внутри, поэтому, когда мы переключаем клиентов, нам нужно выполнить его. получить данные
```
import {createApp} from './app'
const {app, router, store} = createApp()
// 把store中的state 替换成 window.__INITIAL_STATE__ 中的数据
if (window.__INITIAL_STATE__) {
store.replaceState(window.__INITIAL_STATE__)
}
router.onReady(() => {
// 添加路由钩子函数,用于处理 asyncData.
// 在初始路由 resolve 后执行,
// 以便我们不会二次预取(double-fetch)已有的数据。
// 使用 `router.beforeResolve()`,以便确保所有异步组件都 resolve。
router.beforeResolve((to, from, next) => {
const matched = router.getMatchedComponents(to)
const prevMatched = router.getMatchedComponents(from)
// 我们只关心之前没有渲染的组件
// 所以我们对比它们,找出两个匹配列表的差异组件
let diffed = false
const activated = matched.filter((c, i) => {
return diffed || (diffed = (prevMatched[i] !== c))
})
const asyncDataHooks = activated.map(c => c.asyncData).filter(_ => _)
if (!asyncDataHooks.length) {
return next()
}
// 这里如果有加载指示器(loading indicator),就触发
Promise.all(asyncDataHooks.map(hook => hook({ store, route: to })))
.then(() => {
// 停止加载指示器(loading indicator)
next()
})
.catch(next)
})
// 挂载到根节点上
app.$mount('#app')
})
```
В основном это работаетvue-ssr
Процесс, конкретный исходный код и конфигурацию можно найти в моемgithubПроверять.
webpack4
Самый очевидный момент этоwebpack4
После того, как у вас есть значение по умолчанию, вы можете использовать его для простой настройки.
Ниже приведено значение по умолчанию:
- Значение записи по умолчанию — ./src
- Значение по умолчанию output.path равно ./dist.
- Значение режима по умолчанию — производство.
- Плагин UglifyJs включает кеши и парализует по умолчанию
Когда режим разработки:
- открыть output.pathinfo
- отключить оптимизацию.минимизировать
Когда режим производства:
- Отключить кэширование в памяти
- Включить плагин NoEmitOnErrors
- Включить модуль объединения модулей
- Включить оптимизацию.минимизировать
Потому что я делаю это для своего блогаssr
Уведомление также обновил веб-пакет, а затем посмотрите на миграцию наwebpack4
Части, которые необходимо изменитьwebpack
настроить
-
Переместите CLI в
webpack-cli
, нужно установитьwebpack-cli
-
установив
mode
переменная для определения текущего режима, будет предупреждение, если она не настроена
- Конфигурация в командной строке
webpack --mode development
- конфигурация в файле
```
module.exports = {
mode: 'development',
entry: {
app: resolve('src')
},
...
```
-
webpack.optimize.CommonsChunkPlugin has been removed, please use config.optimization.splitChunks instead
webpack4
больше недоступноwebpack.optimize.CommonsChunkPlugin
Чтобы разделить код, вам нужно использовать новый атрибутoptimization.splitChunks
```
output: {
filename: assetsPath('js/[name].[chunkhash].min.js'),
},
optimization: {
runtimeChunk: {
name: "manifest"
},
splitChunks: {
chunks: "initial", // 必须三选一: "initial" | "all"(默认就是all) | "async"
minSize: 0, // 最小尺寸,默认0
minChunks: 1, // 最小 chunk ,默认1
maxAsyncRequests: 1, // 最大异步请求数, 默认1
maxInitialRequests: 1, // 最大初始化请求书,默认1
name: () => {}, // 名称,此选项课接收 function
cacheGroups: { // 这里开始设置缓存的 chunks
priority: "0", // 缓存组优先级 false | object |
vendor: { // key 为entry中定义的 入口名称
chunks: "initial", // 必须三选一: "initial" | "all" | "async"(默认就是异步)
test: /react|lodash/, // 正则规则验证,如果符合就提取 chunk
name: "vendor", // 要缓存的 分隔出来的 chunk 名称
minSize: 0,
minChunks: 1,
enforce: true,
maxAsyncRequests: 1, // 最大异步请求数, 默认1
maxInitialRequests: 1, // 最大初始化请求书,默认1
reuseExistingChunk: true // 可设置是否重用该chunk(查看源码没有发现默认值)
}
}
}
},
...
```
compilation.mainTemplate.applyPluginsWaterfall is not a function
解决方案: `yarn add webpack-contrib/html-webpack-plugin -D`
Use Chunks.groupsIterable and filter by instanceof Entrypoint instead:
解决方案: `yarn add extract-text-webpack-plugin@next -D`
Обновитьwebpack4
тоже столкнулся с рядом проблем
-
настраивать
optimization.splitChunks
Бэйл. будет упакован отдельноjs
,css
По одному из каждого, я не знаю, что случилось. -
После обновления 4 я использую
DllPlugin
Упакованный, но вердон в упакованном виде имеет тот же размер и не извлекает указанные мной модули. -
Кажется, это не действует, когда импорт выполняет загрузку по требованию. Например:
const _import_ = file => () => import(file + '.vue')
, затем через_import_('components/Foo')
могут быть загружены непосредственно по запросу, ноwebpack4
Это не вступает в силу, все загружается одновременно.
Выше приведены несколько проблем, с которыми мы столкнулись при обновлении 4, возможно, моя конфигурация неверна, ноwebpack4
Раньше было нормально.
В частности, я положил конфигурацию здесьgithubначальство.
Суммировать
это я на этот различный блогизSSR
путешествие.