Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Apply lazy-hydrate on category page #3344

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Brazilian Portuguese (pt_BR) translation improved - @pxfm (#3288)
- Moved store/lib to /lib - @pxfm (#3253)
- Corrected usage of "configurableChildrenStockPrefetchStatic" setting, refactored logic to tested helper - @philippsander (#859)
- Improved some of the german translations in spelling and wording - @MariaKern (#3297)
- Improved some of the german translations in spelling and wording - @MariaKern (#3297)
- Added lazy-hydrate for category products - @andrzejewsky (#3327)

## [1.10.0] - 2019.08.10

Expand Down Expand Up @@ -148,14 +149,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- No placeholders / no photos for Get Inspire section in offline - @przspa (#3072)
- Back icon on product page causing inconsistent behavior - @patzick (#3056)
- Remove static definition of `cashondelivery` in payment module - @danielmaier42 (#2983)
- Fixed wrong meta description attribute by page overwrite - @przspa (#3091)
- Fixed wrong meta description attribute by page overwrite - @przspa (#3091)
- Fixed the `AddToCart` button behavior in case of synchronization errors - @pkarw (#3150)
- User token re-validation fixed to use proper HTTP codes - @pkarw (#3151, #3178)
- Fixed undefined id of color swatches issue for simple product - @vishal-7037 (#3239)
- Date filter ignoring format param and locales - @grimasod, @patzick (#3102)
- Problem with placing an order if shipping method is different than default one - @patzick (#3203)
- Fixed product video embed on PDP - @juho-jaakkola (#3263)
- Fixed memory leak with loading DayJS in SSR - @lukeromanowicz (#3310)
- Fixed memory leak with loading DayJS in SSR - @lukeromanowicz (#3310)
- Fixed invalid localized routes in SSR content of multistore configuration - @lukeromanowicz (#3262)
- Fixed startSession which loaded from the wrong place the user when multistore was active - @resubaka (#3322)
- Login after registration - @patzick (#3343)
Expand Down
1 change: 1 addition & 0 deletions config/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@
"products": {
"disablePersistentProductsCache": true,
"useMagentoUrlKeys": true,
"lazyLoadingCategoryProducts": true,
"setFirstVarianAsDefaultInURL": false,
"configurableChildrenStockPrefetchStatic": false,
"configurableChildrenStockPrefetchDynamic": false,
Expand Down
9 changes: 6 additions & 3 deletions core/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import Vuelidate from 'vuelidate'
import Meta from 'vue-meta'
import { sync } from 'vuex-router-sync'
import VueObserveVisibility from 'vue-observe-visibility'

import cloneDeep from 'lodash-es/cloneDeep'
import omit from 'lodash-es/omit'
// Apollo GraphQL client
import { getApolloProvider } from './scripts/resolvers/resolveGraphQL'

Expand Down Expand Up @@ -62,7 +63,9 @@ once('__VUE_EXTEND_RR__', () => {
Vue.use(VueRouter)
})

const createApp = async (ssrContext, config, storeCode = null): Promise<{app: Vue, router: VueRouter, store: Store<RootState>}> => {
const initialState = cloneDeep(store.state)

const createApp = async (ssrContext, config, storeCode = null): Promise<{app: Vue, router: VueRouter, store: Store<RootState>, initialState: RootState}> => {
router = createRouter()
// sync router with vuex 'router' store
sync(store, router)
Expand Down Expand Up @@ -132,7 +135,7 @@ const createApp = async (ssrContext, config, storeCode = null): Promise<{app: Vu
// @deprecated from 2.0
EventBus.$emit('application-after-init', app)

return { app, router, store }
return { app, router, store, initialState }
}

export { router, createApp }
6 changes: 4 additions & 2 deletions core/client-entry.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import Vue from 'vue'
import union from 'lodash-es/union'

import { createApp } from '@vue-storefront/core/app'
import rootStore from '@vue-storefront/core/store'
import { registerSyncTaskProcessor } from '@vue-storefront/core/lib/sync/task'
import i18n from '@vue-storefront/i18n'
import omit from 'lodash-es/omit'
import { prepareStoreView, storeCodeFromRoute, currentStoreView, localizedRoute } from '@vue-storefront/core/lib/multistore'
import { onNetworkStatusChange } from '@vue-storefront/core/modules/offline-order/helpers/onNetworkStatusChange'
import '@vue-storefront/core/service-worker/registration' // register the service worker
Expand All @@ -20,7 +20,9 @@ const invokeClientEntry = async () => {
const { app, router, store } = await createApp(null, dynamicRuntimeConfig, storeCode)

if (window.__INITIAL_STATE__) {
store.replaceState(Object.assign({}, store.state, window.__INITIAL_STATE__, { config: globalConfig }))
// skip fields that were set by createApp
const initialState = omit(window.__INITIAL_STATE__, ['storeView', 'config', 'version'])
store.replaceState(Object.assign({}, store.state, initialState, { config: globalConfig }))
}

await store.dispatch('url/registerDynamicRoutes')
Expand Down
2 changes: 1 addition & 1 deletion core/lib/multistore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ export function storeCodeFromRoute (matchedRouteOrUrl: LocalizedRoute | RawLocat
return storeCode
}
}
}
}
return ''
}

Expand Down
33 changes: 33 additions & 0 deletions core/scripts/utils/ssr-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ const path = require('path')
const compile = require('lodash.template')
const rootPath = require('app-root-path').path
const resolve = file => path.resolve(rootPath, file)
const omit = require('lodash/omit')
const set = require('lodash/set')
const get = require('lodash/get')
const config = require('config')

function createRenderer (bundle, clientManifest, template) {
// https://github.com/vuejs/vue/blob/dev/packages/vue-server-renderer/README.md#why-use-bundlerenderer
Expand All @@ -16,7 +20,35 @@ function createRenderer (bundle, clientManifest, template) {
})
}

function getFieldsToFilter () {
const fields = config.ssr.initialStateFilter

if (config.products.lazyLoadingCategoryProducts) {
fields.push('category-next.products')
}

return fields
}

function filterState (context) {
if (!config.ssr.useInitialStateFilter) {
return context
}

for (const field of getFieldsToFilter()) {
const newValue = get(context.initialState, field, null)
set(context.state, field, newValue)
}

if (!config.server.dynamicConfigReload) {
context.state = omit(context.state, ['config'])
}

return omit(context, ['initialState'])
}

function applyAdvancedOutputProcessing (context, output, templatesCache, isProd = true, relatvePaths = false, destDir = '', outputFilename = '') {
context = filterState(context)
const contentPrepend = (typeof context.output.prepend === 'function') ? context.output.prepend(context) : '';
const contentAppend = (typeof context.output.append === 'function') ? context.output.append(context) : '';
output = contentPrepend + output + contentAppend;
Expand All @@ -34,6 +66,7 @@ function applyAdvancedOutputProcessing (context, output, templatesCache, isProd
output = output.replace(new RegExp('/assets', 'g'), `${relativePath}/dist`)
output = output.replace(new RegExp('href="/', 'g'), `href="${relativePath}/`)
}

return output;
}

Expand Down
14 changes: 4 additions & 10 deletions core/server-entry.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import union from 'lodash-es/union'

import { createApp } from '@vue-storefront/core/app'
import { HttpError } from '@vue-storefront/core/helpers/internal'
import { prepareStoreView, storeCodeFromRoute } from '@vue-storefront/core/lib/multistore'
Expand Down Expand Up @@ -31,14 +30,8 @@ function _ssrHydrateSubcomponents (components, store, router, resolve, reject, a
}
})).then(() => {
AsyncDataLoader.flush({ store, route: router.currentRoute, context: null } /* AsyncDataLoaderActionContext */).then((r) => {
if (buildTimeConfig.ssr.useInitialStateFilter) {
context.state = omit(store.state, config.ssr.initialStateFilter)
} else {
context.state = store.state
}
if (!buildTimeConfig.server.dynamicConfigReload) { // if dynamic config reload then we're sending config along with the request
context.state = omit(store.state, buildTimeConfig.ssr.useInitialStateFilter ? [...config.ssr.initialStateFilter, 'config'] : ['config'])
} else {
context.state = store.state
if (buildTimeConfig.server.dynamicConfigReload) {
const excludeFromConfig = buildTimeConfig.server.dynamicConfigExclude
const includeFromConfig = buildTimeConfig.server.dynamicConfigInclude
console.log(excludeFromConfig, includeFromConfig)
Expand All @@ -63,7 +56,8 @@ function getHostFromHeader (headers: string[]): string {
}

export default async context => {
const { app, router, store } = await createApp(context, context.vs && context.vs.config ? context.vs.config : buildTimeConfig)
const { app, router, store, initialState } = await createApp(context, context.vs && context.vs.config ? context.vs.config : buildTimeConfig)
context.initialState = initialState
return new Promise((resolve, reject) => {
context.output.cacheTags = new Set<string>()
const meta = (app as any).$meta()
Expand Down
7 changes: 6 additions & 1 deletion core/store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ const state = {
ui: {},
newsletter: {},
wishlist: {},
attribute: '',
attribute: {
list_by_code: {},
list_by_id: {},
blacklist: [],
labels: {}
},
category: {
current_path: '',
current_product_query: {},
Expand Down
2 changes: 1 addition & 1 deletion core/types/RootState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default interface RootState {
shipping: any,
user: any,
wishlist: any,
attribute: string,
attribute: any,
ui: any,
newsletter: any,
category: {
Expand Down
6 changes: 6 additions & 0 deletions docs/guide/basics/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,12 @@ Product attributes representing the images. We'll see it in the Product page gal

The dimensions of the images in the gallery.

```json
"lazyLoadingCategoryProducts": true
```
It this option is enabled, the category products will not be applied in the `window.__INITIAL_STATE__`.
The client side will be responsible for loading them and store in vuex state.

## Orders

```json
Expand Down
18 changes: 16 additions & 2 deletions src/themes/default/pages/Category.vue
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,18 @@
</h4>
<p>{{ $t('Please change Your search criteria and try again. If still not finding anything relevant, please visit the Home page and try out some of our bestsellers!') }}</p>
</div>
<product-listing :columns="defaultColumn" :products="getCategoryProducts" />
<lazy-hydrate :trigger-hydration="!loading" v-if="isLazyHydrateEnabled">
<product-listing :columns="defaultColumn" :products="getCategoryProducts" />
</lazy-hydrate>
<product-listing v-else :columns="defaultColumn" :products="getCategoryProducts" />
</div>
</div>
</div>
</div>
</template>

<script>
import LazyHydrate from 'vue-lazy-hydration'
import Sidebar from '../components/core/blocks/Category/Sidebar.vue'
import ProductListing from '../components/core/ProductListing.vue'
import Breadcrumbs from '../components/core/Breadcrumbs.vue'
Expand All @@ -86,6 +90,7 @@ import ButtonFull from 'theme/components/theme/ButtonFull.vue'
import { mapGetters } from 'vuex'
import uniq from 'lodash-es/uniq'
import onBottomScroll from '@vue-storefront/core/mixins/onBottomScroll'
import rootStore from '@vue-storefront/core/store';
const composeInitialPageState = async (store, route) => {
try {
Expand All @@ -106,6 +111,7 @@ const composeInitialPageState = async (store, route) => {
export default {
components: {
LazyHydrate,
ButtonFull,
ProductListing,
Breadcrumbs,
Expand All @@ -118,7 +124,8 @@ export default {
return {
mobileFilters: false,
defaultColumn: 3,
loadingProducts: false
loadingProducts: false,
pkarw marked this conversation as resolved.
Show resolved Hide resolved
loading: true
}
},
computed: {
Expand All @@ -129,6 +136,9 @@ export default {
getCategoryProductsTotal: 'category-next/getCategoryProductsTotal',
getAvailableFilters: 'category-next/getAvailableFilters'
}),
isLazyHydrateEnabled () {
return config.products.lazyLoadingCategoryProducts
},
isCategoryEmpty () {
return this.getCategoryProductsTotal === 0
},
Expand All @@ -143,12 +153,16 @@ export default {
if (isServer) next() // SSR no need to invoke SW caching here
else if (from.name) { // SSR but client side invocation, we need to cache products
next(async vm => {
vm.loading = true
await vm.$store.dispatch('category-next/cacheProducts', { route: to })
vm.loading = false
})
} else { // Pure CSR, with no initial category state
next(async vm => {
vm.loading = true
await composeInitialPageState(vm.$store, to)
await vm.$store.dispatch('category-next/cacheProducts', { route: to })
vm.loading = false
})
}
},
Expand Down