diff --git a/CHANGELOG.md b/CHANGELOG.md index 4cbf809ebd..c1fa608755 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 @@ -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) diff --git a/config/default.json b/config/default.json index d446de9114..e9f9f1bbde 100644 --- a/config/default.json +++ b/config/default.json @@ -280,6 +280,7 @@ "products": { "disablePersistentProductsCache": true, "useMagentoUrlKeys": true, + "lazyLoadingCategoryProducts": true, "setFirstVarianAsDefaultInURL": false, "configurableChildrenStockPrefetchStatic": false, "configurableChildrenStockPrefetchDynamic": false, diff --git a/core/app.ts b/core/app.ts index 4199459bf7..1df20aa648 100755 --- a/core/app.ts +++ b/core/app.ts @@ -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' @@ -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}> => { +const initialState = cloneDeep(store.state) + +const createApp = async (ssrContext, config, storeCode = null): Promise<{app: Vue, router: VueRouter, store: Store, initialState: RootState}> => { router = createRouter() // sync router with vuex 'router' store sync(store, router) @@ -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 } diff --git a/core/client-entry.ts b/core/client-entry.ts index b5cda344ba..8834e01780 100755 --- a/core/client-entry.ts +++ b/core/client-entry.ts @@ -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 @@ -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') diff --git a/core/lib/multistore.ts b/core/lib/multistore.ts index e1ffff731a..09b3f877b7 100644 --- a/core/lib/multistore.ts +++ b/core/lib/multistore.ts @@ -147,7 +147,7 @@ export function storeCodeFromRoute (matchedRouteOrUrl: LocalizedRoute | RawLocat return storeCode } } - } + } return '' } diff --git a/core/scripts/utils/ssr-renderer.js b/core/scripts/utils/ssr-renderer.js index 9667601a29..64c01918ef 100644 --- a/core/scripts/utils/ssr-renderer.js +++ b/core/scripts/utils/ssr-renderer.js @@ -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 @@ -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; @@ -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; } diff --git a/core/server-entry.ts b/core/server-entry.ts index e045efa319..c9c9e0ca50 100755 --- a/core/server-entry.ts +++ b/core/server-entry.ts @@ -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' @@ -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) @@ -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() const meta = (app as any).$meta() diff --git a/core/store/index.ts b/core/store/index.ts index cfef7056cf..ee38dbdd08 100644 --- a/core/store/index.ts +++ b/core/store/index.ts @@ -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: {}, diff --git a/core/types/RootState.ts b/core/types/RootState.ts index be459a8b0a..bec8d6bbc3 100644 --- a/core/types/RootState.ts +++ b/core/types/RootState.ts @@ -10,7 +10,7 @@ export default interface RootState { shipping: any, user: any, wishlist: any, - attribute: string, + attribute: any, ui: any, newsletter: any, category: { diff --git a/docs/guide/basics/configuration.md b/docs/guide/basics/configuration.md index f8df75a3ff..a9cec95865 100644 --- a/docs/guide/basics/configuration.md +++ b/docs/guide/basics/configuration.md @@ -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 diff --git a/src/themes/default/pages/Category.vue b/src/themes/default/pages/Category.vue index 88d2e471e7..02d8ea10e2 100644 --- a/src/themes/default/pages/Category.vue +++ b/src/themes/default/pages/Category.vue @@ -67,7 +67,10 @@

{{ $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!') }}

- + + + + @@ -75,6 +78,7 @@