Skip to content
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
15 changes: 10 additions & 5 deletions core/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,12 @@ const createApp = async (ssrContext, config, storeCode = null): Promise<{app: Vu
store.state.version = process.env.APPVERSION
store.state.config = config // @deprecated
store.state.__DEMO_MODE__ = (config.demomode === true)
if (ssrContext) Vue.prototype.$ssrRequestContext = ssrContext
if (ssrContext) {
// @deprecated - we shouldn't share server context between requests
Vue.prototype.$ssrRequestContext = {output: {cacheTags: ssrContext.output.cacheTags}}

Vue.prototype.$cacheTags = ssrContext.output.cacheTags
}
if (!store.state.config) store.state.config = globalConfig // @deprecated - we should avoid the `config`
const storeView = await prepareStoreView(storeCode) // prepare the default storeView
store.state.storeView = storeView
Expand All @@ -66,6 +71,10 @@ const createApp = async (ssrContext, config, storeCode = null): Promise<{app: Vu
Object.keys(coreMixins).forEach(key => {
Vue.mixin(coreMixins[key])
})

Object.keys(coreFilters).forEach(key => {
Vue.filter(key, coreFilters[key])
})
})

// @todo remove this part when we'll get rid of global multistore mixin
Expand All @@ -78,10 +87,6 @@ const createApp = async (ssrContext, config, storeCode = null): Promise<{app: Vu
})
}

Object.keys(coreFilters).forEach(key => {
Vue.filter(key, coreFilters[key])
})

let vueOptions = {
router: routerProxy,
store,
Expand Down
2 changes: 1 addition & 1 deletion core/client-entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ const invokeClientEntry = async () => {
}
return next() // do not resolve asyncData on server render - already been done
}
if (Vue.prototype.$ssrRequestContext) Vue.prototype.$ssrRequestContext.output.cacheTags = new Set<string>()
if (!Vue.prototype.$cacheTags) Vue.prototype.$cacheTags = new Set<string>()
const matched = router.getMatchedComponents(to)
if (to) { // this is from url
if (globalConfig.storeViews.multistore === true) {
Expand Down
3 changes: 3 additions & 0 deletions core/filters/price.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ export function price (value) {
return value;
}
const storeView = currentStoreView();
if (!storeView.i18n) {
return value;
}
const { defaultLocale, currencySign, priceFormat } = storeView.i18n

const formattedValue = formatValue(value, defaultLocale);
Expand Down
55 changes: 40 additions & 15 deletions core/lib/store/storage.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as localForage from 'localforage'
import { Logger } from '@vue-storefront/core/lib/logger'
import { isServer } from '@vue-storefront/core/helpers'
import cloneDeep from 'lodash-es/cloneDeep'

const CACHE_TIMEOUT = 800
const CACHE_TIMEOUT_ITERATE = 2000
Expand Down Expand Up @@ -34,6 +35,13 @@ function roughSizeOfObject (object) {
return bytes
}

interface CacheTimeouts {
getItem: any,
iterate: any,
setItem: any,
base: any
}

class LocalForageCacheDriver {
private _collectionName: string;
private _dbName: string;
Expand All @@ -44,6 +52,12 @@ class LocalForageCacheDriver {
private _useLocalCacheByDefault: boolean;
private cacheErrorsCount: any;
private _storageQuota: number;
private _cacheTimeouts: CacheTimeouts = {
getItem: null,
iterate: null,
setItem: null,
base: null
}

public constructor (collection, useLocalCacheByDefault = true, storageQuota = 0) {
const collectionName = collection._config.storeName
Expand All @@ -54,7 +68,8 @@ class LocalForageCacheDriver {
const storageQuota = this._storageQuota
const iterateFnc = this.iterate.bind(this)
const removeItemFnc = this.removeItem.bind(this)
setInterval(() => {
clearInterval(this._cacheTimeouts.base)
this._cacheTimeouts.base = setInterval(() => {
let storageSize = 0
this.iterate((item, id, number) => {
storageSize += roughSizeOfObject(item)
Expand Down Expand Up @@ -133,6 +148,10 @@ class LocalForageCacheDriver {
return this._dbName
}

public getLocalCache (key) {
return typeof this._localCache[key] !== 'undefined' ? cloneDeep(this._localCache[key]) : null
}

// Retrieve an item from the store. Unlike the original async_storage
// library in Gaia, we don't modify return values at all. If a key's value
// is `undefined`, we pass that value to the callback function.
Expand All @@ -142,7 +161,7 @@ class LocalForageCacheDriver {
if (this._useLocalCacheByDefault && this._localCache[key]) {
// Logger.debug('Local cache fallback for GET', key)()
return new Promise((resolve, reject) => {
const value = typeof this._localCache[key] !== 'undefined' ? this._localCache[key] : null
const value = this.getLocalCache(key)
if (isCallbackCallable) callback(null, value)
resolve(value)
})
Expand All @@ -163,46 +182,49 @@ class LocalForageCacheDriver {
// Logger.debug('No local cache fallback for GET', key)()
const promise = this._localForageCollection.ready().then(() => this._localForageCollection.getItem(key).then(result => {
const endTime = new Date().getTime()
const clonedResult = cloneDeep(result)
if ((endTime - startTime) >= CACHE_TIMEOUT) {
Logger.error('Cache promise resolved after [ms]' + key + (endTime - startTime))()
}
if (!this._localCache[key] && result) {
this._localCache[key] = result // populate the local cache for the next call
if (!this._localCache[key] && clonedResult) {
this._localCache[key] = clonedResult // populate the local cache for the next call
}
if (!isResolved) {
if (isCallbackCallable) {
callback(null, result)
callback(null, clonedResult)
}
isResolved = true
} else {
Logger.debug('Skipping return value as it was previously resolved')()
}
return result
return clonedResult
}).catch(err => {
this._lastError = err
if (!isResolved) {
if (isCallbackCallable) callback(null, typeof this._localCache[key] !== 'undefined' ? this._localCache[key] : null)
const value = this.getLocalCache(key)
if (isCallbackCallable) callback(null, value)
}
Logger.error(err)()
isResolved = true
}))

setTimeout(() => {
clearTimeout(this._cacheTimeouts.getItem)
this._cacheTimeouts.getItem = setTimeout(() => {
if (!isResolved) { // this is cache time out check
if (!this._persistenceErrorNotified) {
Logger.error('Cache not responding for ' + key + '.', 'cache', { timeout: CACHE_TIMEOUT, errorsCount: this.cacheErrorsCount[this._collectionName] })()
this._persistenceErrorNotified = true
this.recreateDb()
}
this.cacheErrorsCount[this._collectionName] = this.cacheErrorsCount[this._collectionName] ? this.cacheErrorsCount[this._collectionName] + 1 : 1
if (isCallbackCallable) callback(null, typeof this._localCache[key] !== 'undefined' ? this._localCache[key] : null)
const value = this.getLocalCache(key)
if (isCallbackCallable) callback(null, value)
}
}, CACHE_TIMEOUT)
return promise
}
} else {
return new Promise((resolve, reject) => {
const value = typeof this._localCache[key] !== 'undefined' ? this._localCache[key] : null
const value = this.getLocalCache(key)
if (isCallbackCallable) callback(null, value)
resolve(value)
})
Expand Down Expand Up @@ -249,7 +271,8 @@ class LocalForageCacheDriver {
if (isCallbackCallable) callback(err, null)
}
})
setTimeout(() => {
clearTimeout(this._cacheTimeouts.iterate)
this._cacheTimeouts.iterate = setTimeout(() => {
if (!isResolved) { // this is cache time out check
if (!this._persistenceErrorNotified) {
Logger.error('Cache not responding. (iterate)', 'cache', { timeout: CACHE_TIMEOUT, errorsCount: this.cacheErrorsCount[this._collectionName] })()
Expand Down Expand Up @@ -291,7 +314,8 @@ class LocalForageCacheDriver {
// saved, or something like that.
public setItem (key, value, callback?, memoryOnly = false) {
const isCallbackCallable = (typeof callback !== 'undefined' && callback)
this._localCache[key] = value
const copiedValue = cloneDeep(value)
this._localCache[key] = copiedValue
if (memoryOnly) {
return new Promise((resolve, reject) => {
if (isCallbackCallable) callback(null, null)
Expand All @@ -310,7 +334,7 @@ class LocalForageCacheDriver {
})
} else {
let isResolved = false
const promise = this._localForageCollection.ready().then(() => this._localForageCollection.setItem(key, value).then(result => {
const promise = this._localForageCollection.ready().then(() => this._localForageCollection.setItem(key, copiedValue).then(result => {
if (isCallbackCallable) {
callback(null, result)
}
Expand All @@ -320,7 +344,8 @@ class LocalForageCacheDriver {
this._lastError = err
throw err
}))
setTimeout(() => {
clearTimeout(this._cacheTimeouts.iterate)
this._cacheTimeouts.setItem = setTimeout(() => {
if (!isResolved) { // this is cache time out check
if (!this._persistenceErrorNotified) {
Logger.error('Cache not responding for ' + key + '.', 'cache', { timeout: CACHE_TIMEOUT, errorsCount: this.cacheErrorsCount[this._collectionName] })()
Expand Down
11 changes: 10 additions & 1 deletion core/modules/catalog-next/store/category/getters.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { nonReactiveState } from './index';
import { GetterTree } from 'vuex'
import RootState from '@vue-storefront/core/types/RootState'
import CategoryState from './CategoryState'
Expand All @@ -13,12 +14,20 @@ import { Category } from '../../types/Category'
import { parseCategoryPath } from '@vue-storefront/core/modules/breadcrumbs/helpers'
import { _prepareCategoryPathIds, getSearchOptionsFromRouteParams } from '../../helpers/categoryHelpers';
import { removeStoreCodeFromRoute } from '@vue-storefront/core/lib/multistore'
import cloneDeep from 'lodash-es/cloneDeep'

function mapCategoryProducts (productsSkus, productsData) {
return productsSkus.map(prodSku => {
const product = productsData.find(prodData => prodData.sku === prodSku)
return cloneDeep(product)
})
}

const getters: GetterTree<CategoryState, RootState> = {
getCategories: (state): Category[] => Object.values(state.categoriesMap),
getCategoriesMap: (state): { [id: string]: Category} => state.categoriesMap,
getNotFoundCategoryIds: (state): string[] => state.notFoundCategoryIds,
getCategoryProducts: (state) => state.products,
getCategoryProducts: (state) => mapCategoryProducts(state.products, nonReactiveState.products),
getCategoryFrom: (state, getters) => (path: string = '') => {
return getters.getCategories.find(category => (removeStoreCodeFromRoute(path) as string).replace(/^(\/)/gm, '') === category.url_path)
},
Expand Down
4 changes: 4 additions & 0 deletions core/modules/catalog-next/store/category/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,7 @@ export const categoryModule: Module<CategoryState, RootState> = {
actions,
mutations
}

export const nonReactiveState = {
products: []
}
5 changes: 4 additions & 1 deletion core/modules/catalog-next/store/category/mutations.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { nonReactiveState } from './index';
import Vue from 'vue'
import { MutationTree } from 'vuex'
import * as types from './mutation-types'
import CategoryState from './CategoryState'
import { Category } from '../../types/Category'
import cloneDeep from 'lodash-es/cloneDeep'

const mutations: MutationTree<CategoryState> = {
[types.CATEGORY_SET_PRODUCTS] (state, products = []) {
state.products = products
nonReactiveState.products = cloneDeep(products)
state.products = cloneDeep(products).map(prod => prod.sku)
},
[types.CATEGORY_ADD_PRODUCTS] (state, products = []) {
state.products.push(...products)
Expand Down
4 changes: 2 additions & 2 deletions core/modules/catalog/helpers/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export const storeProductToCache = (product, cacheByKey) => {
};

export const preConfigureProduct = ({ product, populateRequestCacheTags }) => {
const shouldPopulateCacheTags = populateRequestCacheTags && Vue.prototype.$ssrRequestContext;
const shouldPopulateCacheTags = populateRequestCacheTags && Vue.prototype.$cacheTags;
const isFirstVariantAsDefaultInURL =
config.products.setFirstVarianAsDefaultInURL &&
product.hasOwnProperty('configurable_children') &&
Expand All @@ -66,7 +66,7 @@ export const preConfigureProduct = ({ product, populateRequestCacheTags }) => {
product.info = {};

if (shouldPopulateCacheTags) {
Vue.prototype.$ssrRequestContext.output.cacheTags.add(`P${product.id}`);
Vue.prototype.$cacheTags.add(`P${product.id}`);
}

if (!product.parentSku) {
Expand Down
4 changes: 2 additions & 2 deletions core/modules/catalog/store/category/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,8 @@ const actions: ActionTree<CategoryState, RootState> = {
if (setCurrentCategory) {
commit(types.CATEGORY_UPD_CURRENT_CATEGORY, mainCategory)
}
if (populateRequestCacheTags && mainCategory && Vue.prototype.$ssrRequestContext) {
Vue.prototype.$ssrRequestContext.output.cacheTags.add(`C${mainCategory.id}`)
if (populateRequestCacheTags && mainCategory && Vue.prototype.$cacheTags) {
Vue.prototype.$cacheTags.add(`C${mainCategory.id}`)
}
if (setCurrentCategoryPath) {
let currentPath = []
Expand Down
5 changes: 4 additions & 1 deletion core/scripts/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ app.get('*', (req, res, next) => {
res.setHeader('Content-Type', 'text/html')
}
let tagsArray = []
if (config.server.useOutputCacheTagging && context.output.cacheTags !== null) {
if (config.server.useOutputCacheTagging && context.output.cacheTags && context.output.cacheTags.size > 0) {
tagsArray = Array.from(context.output.cacheTags)
const cacheTags = tagsArray.join(' ')
res.setHeader('X-VS-Cache-Tags', cacheTags)
Expand Down Expand Up @@ -228,6 +228,9 @@ app.get('*', (req, res, next) => {
console.log(`whole request [${req.url}]: ${Date.now() - s}ms`)
next()
}).catch(errorHandler)
.finally(() => {
ssr.clearContext(context)
})
}

const dynamicCacheHandler = () => {
Expand Down
11 changes: 9 additions & 2 deletions core/scripts/utils/ssr-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ function initSSRRequestContext (app, req, res, config) {
filter: (output, context) => { return output },
appendHead: (context) => { return ''; },
template: 'default',
cacheTags: null
cacheTags: new Set()
},
server: {
app: app,
Expand All @@ -112,10 +112,17 @@ function initSSRRequestContext (app, req, res, config) {
};
}

function clearContext (context) {
Object.keys(context.server).forEach(key => delete context.server[key])
delete context.output['cacheTags']
delete context['meta']
}

module.exports = {
createRenderer,
initTemplatesCache,
initSSRRequestContext,
applyAdvancedOutputProcessing,
compileTemplate: compile
compileTemplate: compile,
clearContext
}
1 change: 0 additions & 1 deletion core/server-entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ export default async context => {
RouterManager.flushRouteQueue()
context.initialState = initialState
return new Promise((resolve, reject) => {
context.output.cacheTags = new Set<string>()
const meta = (app as any).$meta()
router.push(context.url)
context.meta = meta
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"static-server": "cross-env TS_NODE_PROJECT=\"tsconfig-build.json\" ts-node ./core/scripts/static-server.ts",
"generate": "cross-env TS_NODE_PROJECT=\"tsconfig-build.json\" ts-node ./core/scripts/generate.ts",
"start": "cross-env NODE_ENV=production TS_NODE_PROJECT=\"tsconfig-build.json\" pm2 start ecosystem.json $PM2_ARGS",
"start:inspect": "cross-env NODE_ENV=production TS_NODE_PROJECT=\"tsconfig-build.json\" node --inspect ./core/scripts/server.js",
"start:inspect": "cross-env NODE_ENV=production TS_NODE_PROJECT=\"tsconfig-build.json\" node --inspect -r ts-node/register ./core/scripts/server",
"installer": "node ./core/scripts/installer",
"installer:ci": "yarn installer --default-config",
"all": "cross-env NODE_ENV=development node ./core/scripts/all",
Expand Down