diff --git a/config/default.json b/config/default.json index 7fa3a7245c..e5323cdc81 100644 --- a/config/default.json +++ b/config/default.json @@ -7,8 +7,11 @@ "useOutputCacheTagging": false, "useOutputCache": false, "outputCacheDefaultTtl": 86400, - "availableCacheTags": ["product", "category", "home", "checkout", "page-not-found", "compare", "my-account", "P", "C"], - "invalidateCacheKey": "aeSu7aip" + "availableCacheTags": ["product", "category", "home", "checkout", "page-not-found", "compare", "my-account", "P", "C", "error"], + "invalidateCacheKey": "aeSu7aip", + "dynamicConfigReload": false, + "dynamicConfigExclude": ["ssr", "storeViews", "entities", "localForage", "shipping", "boost", "query"], + "dynamicConfigInclude": [] }, "console": { "verbosityLevel": "only-errors" @@ -37,7 +40,7 @@ "basic": "dist/index.basic.html" }, "executeMixedinAsyncData": true, - "initialStateFilter": ["config", "__DEMO_MODE__", "version", "storeView"], + "initialStateFilter": ["__DEMO_MODE__", "version", "storeView"], "useInitialStateFilter": true }, "defaultStoreCode": "", diff --git a/core/app.ts b/core/app.ts index abe406ec33..30935094fa 100755 --- a/core/app.ts +++ b/core/app.ts @@ -2,7 +2,7 @@ import Vue from 'vue' import { sync } from 'vuex-router-sync' import VueObserveVisibility from 'vue-observe-visibility' import { union } from 'lodash-es' -import config from 'config' +import buildTimeConfig from 'config' import VueLazyload from 'vue-lazyload' import Vuelidate from 'vuelidate' import Meta from 'vue-meta' @@ -28,23 +28,63 @@ import { InMemoryCache } from 'apollo-cache-inmemory' import VueApollo from 'vue-apollo' import { takeOverConsole } from '@vue-storefront/core/helpers/log' -if (config.console.verbosityLevel !== 'display-everything') { - takeOverConsole(config.console.verbosityLevel) +if (buildTimeConfig.console.verbosityLevel !== 'display-everything') { + takeOverConsole(buildTimeConfig.console.verbosityLevel) } -const httpLink = new HttpLink({ - uri: config.server.protocol + '://' + config.graphql.host + ':' + config.graphql.port + '/graphql' -}) - -const apolloClient = new ApolloClient({ +export function createApp (ssrContext, config): { app: Vue, router: any, store: any } { + sync(store, router) + store.state.version = '1.4.0' + store.state.config = config + store.state.__DEMO_MODE__ = (config.demomode === true) ? true : false + + if(ssrContext) Vue.prototype.$ssrRequestContext = ssrContext + + if (!store.state.config) store.state.config = buildTimeConfig // if provided from SSR, don't replace it + const storeModules = Object.assign(coreModules, themeModules || {}) + + for (const moduleName of Object.keys(storeModules)) { + console.debug('Registering Vuex module', moduleName) + store.registerModule(moduleName, storeModules[moduleName]) + } + + const storeView = prepareStoreView(null) // prepare the default storeView + store.state.storeView = storeView + // store.state.shipping.methods = shippingMethods + + Vue.use(Vuelidate) + Vue.use(VueLazyload, {attempt: 2}) + Vue.use(Meta) + Vue.use(VueObserveVisibility) + + require('theme/plugins') + const pluginsObject = plugins() + Object.keys(pluginsObject).forEach(key => { + Vue.use(pluginsObject[key]) + }) + + const mixinsObject = mixins() + Object.keys(mixinsObject).forEach(key => { + Vue.mixin(mixinsObject[key]) + }) + + const filtersObject = filters() + Object.keys(filtersObject).forEach(key => { + Vue.filter(key, filtersObject[key]) + }) + const httpLink = new HttpLink({ + uri: store.state.config.server.protocol + '://' + store.state.config.graphql.host + ':' + store.state.config.graphql.port + '/graphql' + }) + + const apolloClient = new ApolloClient({ link: httpLink, cache: new InMemoryCache(), connectToDevTools: true -}) - -let loading = 0 - -const apolloProvider = new VueApollo({ + }) + + let loading = 0 + + const apolloProvider = new VueApollo({ clients: { a: apolloClient }, @@ -60,49 +100,10 @@ const apolloProvider = new VueApollo({ console.log('Global error handler') console.error(error) } -}) - -Vue.use(VueApollo) -// End declare Apollo graphql client - -store.state.version = '1.3.0' -store.state.__DEMO_MODE__ = (config.demomode === true) ? true : false -store.state.config = config - -const storeModules = Object.assign(coreModules, themeModules || {}) - -for (const moduleName of Object.keys(storeModules)) { - console.debug('Registering Vuex module', moduleName) - store.registerModule(moduleName, storeModules[moduleName]) -} - -const storeView = prepareStoreView(null) // prepare the default storeView -store.state.storeView = storeView -// store.state.shipping.methods = shippingMethods - -Vue.use(Vuelidate) -Vue.use(VueLazyload, {attempt: 2}) -Vue.use(Meta) -Vue.use(VueObserveVisibility) - -require('theme/plugins') -const pluginsObject = plugins() -Object.keys(pluginsObject).forEach(key => { - Vue.use(pluginsObject[key]) -}) - -const mixinsObject = mixins() -Object.keys(mixinsObject).forEach(key => { - Vue.mixin(mixinsObject[key]) -}) - -const filtersObject = filters() -Object.keys(filtersObject).forEach(key => { - Vue.filter(key, filtersObject[key]) -}) - -export function createApp (serverContext = null): { app: Vue, router: any, store: any } { - sync(store, router) + }) + + Vue.use(VueApollo) + // End declare Apollo graphql client const app = new Vue({ router, store, @@ -115,11 +116,10 @@ export function createApp (serverContext = null): { app: Vue, router: any, store app, router, store, - config, - serverContext + store.state.config, + ssrContext ) - - registerTheme(config.theme, app, router, store) + registerTheme(buildTimeConfig.theme, app, router, store, store.state.config, ssrContext) app.$emit('application-after-init', app) diff --git a/core/client-entry.ts b/core/client-entry.ts index ad421e4b56..241fbf9fcb 100755 --- a/core/client-entry.ts +++ b/core/client-entry.ts @@ -5,6 +5,7 @@ import { union } from 'lodash-es' import { createApp } from '@vue-storefront/core/app' import EventBus from '@vue-storefront/core/plugins/event-bus' +import buildTimeConfig from 'config' import { execute } from '@vue-storefront/store/lib/task' import UniversalStorage from '@vue-storefront/store/lib/storage' import i18n from '@vue-storefront/i18n' @@ -15,14 +16,12 @@ require('@vue-storefront/core/service-worker-registration') // register the serv declare var window: any -const { app, router, store } = createApp() - -const config = store.state.config +const config = Object.assign(buildTimeConfig, window.__INITIAL_STATE__.config ? window.__INITIAL_STATE__.config : buildTimeConfig) +const { app, router, store } = createApp(null, config) let storeCode = null // select the storeView by prefetched vuex store state (prefetched serverside) if (window.__INITIAL_STATE__) { - store.replaceState(Object.assign({}, store.state, window.__INITIAL_STATE__)) - store.state.requestContext.outputCacheTags = new Set() + store.replaceState(Object.assign({}, store.state, window.__INITIAL_STATE__, { config: buildTimeConfig })) } if (config.storeViews.multistore === true) { if ((storeCode = store.state.user.current_storecode)) { @@ -64,7 +63,7 @@ function _ssrHydrateSubcomponents (components, next, to) { } router.onReady(() => { router.beforeResolve((to, from, next) => { - store.state.requestContext.outputCacheTags = new Set() + if (Vue.prototype.$ssrRequestContext) Vue.prototype.$ssrRequestContext.output.cacheTags = new Set() const matched = router.getMatchedComponents(to) const prevMatched = router.getMatchedComponents(from) if (to) { // this is from url diff --git a/core/components/ProductBundleOptions.js b/core/components/ProductBundleOptions.js index bbb28d3fe7..7642fcb9c1 100644 --- a/core/components/ProductBundleOptions.js +++ b/core/components/ProductBundleOptions.js @@ -2,7 +2,6 @@ import { mapMutations } from 'vuex' import * as types from '@vue-storefront/store/mutation-types' import rootStore from '@vue-storefront/store' import i18n from '@vue-storefront/i18n' -import config from 'config' function _defaultOptionValue (co, field = 'id') { if (co.product_links && co.product_links.length) { @@ -51,12 +50,12 @@ export default { this.setupInputFields() - if (config.usePriceTiers) { + if (rootStore.state.config.usePriceTiers) { this.$bus.$on('product-after-setup-associated', this.setupInputFields) } }, beforeDestroy () { - if (config.usePriceTiers) { + if (rootStore.state.usePriceTiers) { this.$bus.$off('product-after-setup-associated', this.setupInputFields) } }, diff --git a/core/components/ProductGallery.js b/core/components/ProductGallery.js index 5662bb6c1e..d268956558 100644 --- a/core/components/ProductGallery.js +++ b/core/components/ProductGallery.js @@ -1,6 +1,6 @@ import { Carousel, Slide } from 'vue-carousel' import VueOffline from 'vue-offline' -import config from 'config' +import store from '@vue-storefront/store' export default { name: 'ProductGallery', @@ -60,8 +60,8 @@ export default { } }, selectVariant () { - if (config.products.gallery.mergeConfigurableChildren) { - let option = this.configuration[config.products.gallery.variantsGroupAttribute] + if (store.state.config.products.gallery.mergeConfigurableChildren) { + let option = this.configuration[store.state.config.products.gallery.variantsGroupAttribute] if (typeof option !== 'undefined' && option !== null) { let index = this.gallery.findIndex(obj => obj.id && Number(obj.id) === Number(option.id)) this.navigate(index) diff --git a/core/components/SortBy.js b/core/components/SortBy.js index 51cdde83dc..05cdd60218 100644 --- a/core/components/SortBy.js +++ b/core/components/SortBy.js @@ -1,4 +1,4 @@ -import config from 'config' +import store from '@vue-storefront/store' export default { name: 'SortBy', data () { @@ -18,7 +18,7 @@ export default { }, computed: { sortByAttribute () { - return config.products.sortByAttributes + return store.state.config.products.sortByAttributes } } } diff --git a/core/components/blocks/Footer/Footer.js b/core/components/blocks/Footer/Footer.js index 7c24fcab22..9df932a3fc 100644 --- a/core/components/blocks/Footer/Footer.js +++ b/core/components/blocks/Footer/Footer.js @@ -1,10 +1,10 @@ -import config from 'config' +import rootStore from '@vue-storefront/store' export default { name: 'MainFooter', computed: { multistoreEnabled () { - return config.storeViews.multistore + return rootStore.state.config.storeViews.multistore } } } diff --git a/core/lib/extensions.js b/core/lib/extensions.js index 2c0cfdd2b1..967643fc53 100644 --- a/core/lib/extensions.js +++ b/core/lib/extensions.js @@ -1,8 +1,8 @@ -export default function registerExtensions (extensions, app, router, store, config, serverContext = null) { +export default function registerExtensions (extensions, app, router, store, config, ssrContext = null) { for (let extEntryPoint of extensions) { if (extEntryPoint !== null) { if (extEntryPoint.default) extEntryPoint = extEntryPoint.default - let extDescriptor = extEntryPoint(app, router, store, config, serverContext) // register module + let extDescriptor = extEntryPoint(app, router, store, config, ssrContext) // register module if (extDescriptor != null) { console.debug('Loaded', extDescriptor.EXTENSION_KEY) app.$emit('application-after-registerExtensions', extDescriptor) diff --git a/core/lib/themes.js b/core/lib/themes.js index 99c3800e47..5a206f342b 100644 --- a/core/lib/themes.js +++ b/core/lib/themes.js @@ -30,10 +30,10 @@ export function extendStore (coreStore, extendStore) { return merge(coreStore, extendStore) } -export function registerTheme (themeName, app, routes, store) { +export function registerTheme (themeName, app, routes, store, config, ssrContext) { let themeEntryPoint = require('theme/index.js') if (themeEntryPoint != null && themeEntryPoint.hasOwnProperty('default')) { - themeEntryPoint.default(app, routes, store) // register theme + themeEntryPoint.default(app, routes, store, config, ssrContext) // register theme } else { throw new Error('Wrong theme name: ' + themeName) } diff --git a/core/mixins/thumbnail/index.js b/core/mixins/thumbnail/index.js index 991bc7aa61..6cf8446370 100644 --- a/core/mixins/thumbnail/index.js +++ b/core/mixins/thumbnail/index.js @@ -1,4 +1,4 @@ -import config from 'config' +import store from '@vue-storefront/store' export const thumbnail = { methods: { @@ -9,7 +9,7 @@ export const thumbnail = { * @param {Int} height */ getThumbnail (relativeUrl, width, height) { - return relativeUrl && relativeUrl.indexOf('no_selection') < 0 ? `${config.images.baseUrl}${parseInt(width)}/${parseInt(height)}/resize${relativeUrl}` : config.images.productPlaceholder || '' + return relativeUrl && relativeUrl.indexOf('no_selection') < 0 ? `${store.state.config.images.baseUrl}${parseInt(width)}/${parseInt(height)}/resize${relativeUrl}` : store.state.config.images.productPlaceholder || '' } } } diff --git a/core/modules/offline-order/features/cancelOrders.ts b/core/modules/offline-order/features/cancelOrders.ts index 14a7d46e34..5867d98957 100644 --- a/core/modules/offline-order/features/cancelOrders.ts +++ b/core/modules/offline-order/features/cancelOrders.ts @@ -7,7 +7,7 @@ * Part of [Offline order API Module](https://github.com/DivanteLtd/vue-storefront/tree/master/doc/api-modules) */ import * as localForage from 'localforage' -import config from 'config' +import store from '@vue-storefront/store' import EventBus from '@vue-storefront/core/plugins/event-bus' import UniversalStorage from '@vue-storefront/store/lib/storage' @@ -18,7 +18,7 @@ export const cancelOrders = { const ordersCollection = new UniversalStorage(localForage.createInstance({ name: 'shop', storeName: 'orders', - driver: localForage[config.localForage.defaultDrivers['orders']] + driver: localForage[store.state.config.localForage.defaultDrivers['orders']] })) ordersCollection.iterate((order, id, iterationNumber) => { diff --git a/core/modules/offline-order/features/confirmOrders.ts b/core/modules/offline-order/features/confirmOrders.ts index 408558dc89..72f67acee1 100644 --- a/core/modules/offline-order/features/confirmOrders.ts +++ b/core/modules/offline-order/features/confirmOrders.ts @@ -7,13 +7,13 @@ * Part of [Offline order API Module](https://github.com/DivanteLtd/vue-storefront/tree/master/doc/api-modules) */ import EventBus from '@vue-storefront/core/plugins/event-bus' -import config from 'config' +import store from '@vue-storefront/store' export const confirmOrders = { methods: { confirmOrders () { - EventBus.$emit('order/PROCESS_QUEUE', { config: config }) - EventBus.$emit('sync/PROCESS_QUEUE', { config: config }) + EventBus.$emit('order/PROCESS_QUEUE', { config: store.state.config }) + EventBus.$emit('sync/PROCESS_QUEUE', { config: store.state.config }) this.$store.dispatch('cart/load') EventBus.$emit('modal-hide', 'modal-order-confirmation') } diff --git a/core/modules/offline-order/helpers/onNetworkStatusChange.ts b/core/modules/offline-order/helpers/onNetworkStatusChange.ts index 343ef83a6f..1609b5677c 100644 --- a/core/modules/offline-order/helpers/onNetworkStatusChange.ts +++ b/core/modules/offline-order/helpers/onNetworkStatusChange.ts @@ -1,5 +1,5 @@ import * as localForage from 'localforage' -import config from 'config' +import store from '@vue-storefront/store' import EventBus from '@vue-storefront/core/plugins/event-bus' @@ -10,11 +10,11 @@ export function onNetworkStatusChange (store) { console.log('Are we online: ' + navigator.onLine) if (typeof navigator !== 'undefined' && navigator.onLine) { - EventBus.$emit('sync/PROCESS_QUEUE', { config: config }) // process checkout queue + EventBus.$emit('sync/PROCESS_QUEUE', { config: store.state.config }) // process checkout queue store.dispatch('cart/load') - if (config.orders.offline_orders.automatic_transmission_enabled || store.getters['checkout/isThankYouPage']) { - EventBus.$emit('order/PROCESS_QUEUE', { config: config }) // process checkout queue + if (store.state.config.orders.offline_orders.automatic_transmission_enabled || store.getters['checkout/isThankYouPage']) { + EventBus.$emit('order/PROCESS_QUEUE', { config: store.state.config }) // process checkout queue // store.dispatch('cart/serverPull', { forceClientState: false }) } else { const ordersToConfirm = [] @@ -22,7 +22,7 @@ export function onNetworkStatusChange (store) { const ordersCollection = new UniversalStorage(localForage.createInstance({ name: 'shop', storeName: 'orders', - driver: localForage[config.localForage.defaultDrivers['orders']] + driver: localForage[store.state.config.localForage.defaultDrivers['orders']] })) ordersCollection.iterate((order, id, iterationNumber) => { diff --git a/core/modules/product/queries/common.js b/core/modules/product/queries/common.js index 5b69c0dee7..dc487bbec1 100644 --- a/core/modules/product/queries/common.js +++ b/core/modules/product/queries/common.js @@ -1,20 +1,20 @@ import SearchQuery from 'core/store/lib/search/searchQuery' -import config from 'config' +import store from '@vue-storefront/store' export function prepareQuery ({queryText = '', filters = [], queryConfig = ''}) { let query = new SearchQuery() // prepare filters and searchText if (filters.length === 0 && queryConfig !== '') { // try get filters from config - if (config.hasOwnProperty('query') && config.query.hasOwnProperty(queryConfig) && config.query[queryConfig].hasOwnProperty('filter')) { - filters = config.query[queryConfig].filter + if (store.state.config.hasOwnProperty('query') && store.state.config.query.hasOwnProperty(queryConfig) && store.state.config.query[queryConfig].hasOwnProperty('filter')) { + filters = store.state.config.query[queryConfig].filter } } if (queryText === '') { // try to get searchText from config - if (config.hasOwnProperty('query') && config.query.hasOwnProperty(queryConfig) && config.query[queryConfig].hasOwnProperty('searchText')) { - queryText = config.query[queryConfig].searchText + if (store.state.config.hasOwnProperty('query') && store.state.config.query.hasOwnProperty(queryConfig) && store.state.config.query[queryConfig].hasOwnProperty('searchText')) { + queryText = store.state.config.query[queryConfig].searchText } } diff --git a/core/modules/product/queries/related.js b/core/modules/product/queries/related.js index d12ef2c0a2..d3246a9fe1 100644 --- a/core/modules/product/queries/related.js +++ b/core/modules/product/queries/related.js @@ -1,5 +1,5 @@ import SearchQuery from 'core/store/lib/search/searchQuery' -import config from 'config' +import store from '@vue-storefront/store' export function prepareRelatedQuery (key, sku) { let relatedProductsQuery = new SearchQuery() @@ -10,7 +10,7 @@ export function prepareRelatedQuery (key, sku) { .applyFilter({key: 'visibility', value: {'in': [2, 3, 4]}}) .applyFilter({key: 'status', value: {'in': [0, 1, 2]}}) // @TODO Check if status 2 (disabled) was set not by occasion here - if (config.products.listOutOfStockProducts === false) { + if (store.state.config.products.listOutOfStockProducts === false) { relatedProductsQuery = relatedProductsQuery.applyFilter({key: 'stock.is_in_stock', value: {'eq': true}}) } diff --git a/core/modules/product/queries/searchPanel.js b/core/modules/product/queries/searchPanel.js index 3dd7363545..8d03f46fb5 100644 --- a/core/modules/product/queries/searchPanel.js +++ b/core/modules/product/queries/searchPanel.js @@ -1,5 +1,5 @@ import SearchQuery from 'core/store/lib/search/searchQuery' -import config from 'config' +import store from '@vue-storefront/store' export function prepareQuickSearchQuery (queryText) { let searchQuery = new SearchQuery() @@ -9,7 +9,7 @@ export function prepareQuickSearchQuery (queryText) { .applyFilter({key: 'visibility', value: {'in': [3, 4]}}) .applyFilter({key: 'status', value: {'in': [0, 1]}})/* 2 = disabled, 3 = out of stock */ - if (config.products.listOutOfStockProducts === false) { + if (store.state.config.products.listOutOfStockProducts === false) { searchQuery = searchQuery.applyFilter({key: 'stock.is_in_stock', value: {'eq': true}}) } diff --git a/core/pages/Category.js b/core/pages/Category.js index 15d0d5d794..c6d1bfe63d 100644 --- a/core/pages/Category.js +++ b/core/pages/Category.js @@ -1,7 +1,7 @@ import Vue from 'vue' import toString from 'lodash-es/toString' -import config from 'config' +import store from '@vue-storefront/store' import EventBus from '@vue-storefront/core/plugins/event-bus' import { baseFilterProductsQuery, buildFilterProductsQuery } from '@vue-storefront/store/helpers' import { htmlDecode } from '@vue-storefront/core/filters/html-decode' @@ -71,22 +71,22 @@ export default { route: route, current: 0, perPage: 50, - sort: config.entities.productList.sort, - filters: config.products.defaultFilters, - includeFields: config.entities.optimize && Vue.prototype.$isServer ? config.entities.productList.includeFields : null, - excludeFields: config.entities.optimize && Vue.prototype.$isServer ? config.entities.productList.excludeFields : null, + sort: store.state.config.entities.productList.sort, + filters: store.state.config.products.defaultFilters, + includeFields: store.state.config.entities.optimize && Vue.prototype.$isServer ? store.state.config.entities.productList.includeFields : null, + excludeFields: store.state.config.entities.optimize && Vue.prototype.$isServer ? store.state.config.entities.productList.excludeFields : null, append: false } }, - asyncData ({ store, route }) { // this is for SSR purposes to prefetch data + asyncData ({ store, route, context }) { // this is for SSR purposes to prefetch data return new Promise((resolve, reject) => { console.log('Entering asyncData for Category root ' + new Date()) - store.state.requestContext.outputCacheTags.add(`category`) - const defaultFilters = config.products.defaultFilters - store.dispatch('category/list', { includeFields: config.entities.optimize && Vue.prototype.$isServer ? config.entities.category.includeFields : null }).then((categories) => { + if (context) context.output.cacheTags.add(`category`) + const defaultFilters = store.state.config.products.defaultFilters + store.dispatch('category/list', { includeFields: store.state.config.entities.optimize && Vue.prototype.$isServer ? store.state.config.entities.category.includeFields : null }).then((categories) => { store.dispatch('attribute/list', { // load filter attributes for this specific category filterValues: defaultFilters, // TODO: assign specific filters/ attribute codes dynamicaly to specific categories - includeFields: config.entities.optimize && Vue.prototype.$isServer ? config.entities.attribute.includeFields : null + includeFields: store.state.config.entities.optimize && Vue.prototype.$isServer ? store.state.config.entities.attribute.includeFields : null }).catch(err => { console.error(err) reject(err) @@ -126,7 +126,7 @@ export default { beforeMount () { this.$bus.$on('filter-changed-category', this.onFilterChanged) this.$bus.$on('list-change-sort', this.onSortOrderChanged) - if (config.usePriceTiers) { + if (store.state.config.usePriceTiers) { this.$bus.$on('user-after-loggedin', this.onUserPricesRefreshed) this.$bus.$on('user-after-logout', this.onUserPricesRefreshed) } @@ -139,7 +139,7 @@ export default { beforeDestroy () { this.$bus.$off('list-change-sort', this.onSortOrderChanged) this.$bus.$off('filter-changed-category', this.onFilterChanged) - if (config.usePriceTiers) { + if (store.state.config.usePriceTiers) { this.$bus.$off('user-after-loggedin', this.onUserPricesRefreshed) this.$bus.$off('user-after-logout', this.onUserPricesRefreshed) } @@ -216,7 +216,7 @@ export default { this.$router.push('/') } else { this.pagination.current = 0 - let searchProductQuery = baseFilterProductsQuery(this.$store.state.category.current, config.products.defaultFilters) + let searchProductQuery = baseFilterProductsQuery(this.$store.state.category.current, store.state.config.products.defaultFilters) this.$bus.$emit('current-category-changed', this.$store.state.category.current_path) let query = this.$store.state.category.current_product_query query = Object.assign(query, { // base prototype from the asyncData is being used here @@ -236,7 +236,7 @@ export default { }) }, onUserPricesRefreshed () { - const defaultFilters = config.products.defaultFilters + const defaultFilters = store.state.config.products.defaultFilters this.$store.dispatch('category/single', { key: 'slug', value: this.$route.params.slug diff --git a/core/pages/Checkout.js b/core/pages/Checkout.js index 48cf66c5bf..e9f57e82d4 100644 --- a/core/pages/Checkout.js +++ b/core/pages/Checkout.js @@ -1,6 +1,6 @@ import Vue from 'vue' import i18n from '@vue-storefront/i18n' -import config from 'config' +import store from '@vue-storefront/store' import VueOfflineMixin from 'vue-offline/mixin' import Composite from '@vue-storefront/core/mixins/composite' @@ -268,8 +268,8 @@ export default { // This method checks if there exists a mapping of chosen payment method to one of Magento's payment methods. getPaymentMethod () { let paymentMethod = this.payment.paymentMethod - if (config.orders.payment_methods_mapping.hasOwnProperty(paymentMethod)) { - paymentMethod = config.orders.payment_methods_mapping[paymentMethod] + if (store.state.config.orders.payment_methods_mapping.hasOwnProperty(paymentMethod)) { + paymentMethod = store.state.config.orders.payment_methods_mapping[paymentMethod] } return paymentMethod }, @@ -357,9 +357,9 @@ export default { meta: this.$route.meta.description ? [{ vmid: 'description', description: this.$route.meta.description }] : [] } }, - asyncData ({ store, route }) { // this is for SSR purposes to prefetch data + asyncData ({ store, route, context }) { // this is for SSR purposes to prefetch data return new Promise((resolve, reject) => { - store.state.requestContext.outputCacheTags.add(`checkout`) + if (context) context.output.cacheTags.add(`checkout`) resolve() }) } diff --git a/core/pages/Compare.js b/core/pages/Compare.js index 114612990e..aa61987eb9 100644 --- a/core/pages/Compare.js +++ b/core/pages/Compare.js @@ -42,9 +42,9 @@ export default { meta: this.$route.meta.description ? [{ vmid: 'description', description: this.$route.meta.description }] : [] } }, - asyncData ({ store, route }) { // this is for SSR purposes to prefetch data + asyncData ({ store, route, context }) { // this is for SSR purposes to prefetch data return new Promise((resolve, reject) => { - store.state.requestContext.outputCacheTags.add(`compare`) + if (context) context.output.cacheTags.add(`compare`) resolve() }) } diff --git a/core/pages/Error.js b/core/pages/Error.js new file mode 100644 index 0000000000..9fd66c0fac --- /dev/null +++ b/core/pages/Error.js @@ -0,0 +1,21 @@ +import i18n from '@vue-storefront/i18n' +import Composite from '@vue-storefront/core/mixins/composite' + +export default { + name: 'Error', + mixins: [Composite], + asyncData ({ store, route, context }) { // this is for SSR purposes to prefetch data + return new Promise((resolve, reject) => { + console.log('Entering asyncData for Error page ' + new Date()) + if (context) { + context.output.cacheTags.add(`error`) + } + }) + }, + metaInfo () { + return { + title: this.$route.meta.title || i18n.t('Internal Server Error 500'), + meta: this.$route.meta.description ? [{ vmid: 'description', description: this.$route.meta.description }] : [] + } + } +} diff --git a/core/pages/Home.js b/core/pages/Home.js index 8c3cc3f115..6fbc6670ec 100644 --- a/core/pages/Home.js +++ b/core/pages/Home.js @@ -13,9 +13,9 @@ export default { rootCategories: 'category/list' }) }, - asyncData ({ store, route }) { // this is for SSR purposes to prefetch data + asyncData ({ store, route, context }) { // this is for SSR purposes to prefetch data return new Promise((resolve, reject) => { - store.state.requestContext.outputCacheTags.add(`home`) + if (context) context.output.cacheTags.add(`home`) console.log('Entering asyncData for Home root ' + new Date()) EventBus.$emitFilter('home-after-load', { store: store, route: route }).then((results) => { return resolve() diff --git a/core/pages/MyAccount.js b/core/pages/MyAccount.js index b5eecd50f0..6f48e227aa 100644 --- a/core/pages/MyAccount.js +++ b/core/pages/MyAccount.js @@ -67,9 +67,9 @@ export default { meta: this.$route.meta.description ? [{ vmid: 'description', description: this.$route.meta.description }] : [] } }, - asyncData ({ store, route }) { // this is for SSR purposes to prefetch data + asyncData ({ store, route, context }) { // this is for SSR purposes to prefetch data return new Promise((resolve, reject) => { - store.state.requestContext.outputCacheTags.add(`my-account`) + if (context) context.output.cacheTags.add(`my-account`) resolve() }) } diff --git a/core/pages/PageNotFound.js b/core/pages/PageNotFound.js index e841e6a469..230f46e443 100644 --- a/core/pages/PageNotFound.js +++ b/core/pages/PageNotFound.js @@ -8,10 +8,10 @@ import Composite from '@vue-storefront/core/mixins/composite' export default { name: 'PageNotFound', mixins: [Composite], - asyncData ({ store, route }) { // this is for SSR purposes to prefetch data + asyncData ({ store, route, context }) { // this is for SSR purposes to prefetch data return new Promise((resolve, reject) => { console.log('Entering asyncData for PageNotFound ' + new Date()) - store.state.requestContext.outputCacheTags.add(`page-not-found`) + if (context) context.output.cacheTags.add(`page-not-found`) let ourBestsellersQuery = prepareQuery({ queryConfig: 'bestSellers' }) store.dispatch('category/list', {}).then(categories => { store.dispatch('product/list', { diff --git a/core/pages/Product.js b/core/pages/Product.js index dd0f164f62..3155ec5936 100644 --- a/core/pages/Product.js +++ b/core/pages/Product.js @@ -1,7 +1,7 @@ import { mapGetters } from 'vuex' import i18n from '@vue-storefront/i18n' -import config from 'config' +import store from '@vue-storefront/store' import EventBus from '@vue-storefront/core/plugins/event-bus' import { htmlDecode, stripHTML } from '@vue-storefront/core/filters' import { currentStoreView } from '@vue-storefront/store/lib/multistore' @@ -61,9 +61,9 @@ export default { return currentStoreView() } }, - asyncData ({ store, route }) { // this is for SSR purposes to prefetch data + asyncData ({ store, route, context }) { // this is for SSR purposes to prefetch data EventBus.$emit('product-before-load', { store: store, route: route }) - store.state.requestContext.outputCacheTags.add(`product`) + if (context) context.output.cacheTags.add(`product`) return store.dispatch('product/fetchAsync', { parentSku: route.params.parentSku, childSku: route && route.params && route.params.childSku ? route.params.childSku : null }) }, watch: { @@ -75,7 +75,7 @@ export default { this.$bus.$off('product-after-priceupdate', this.onAfterPriceUpdate) this.$bus.$off('product-after-customoptions') this.$bus.$off('product-after-bundleoptions') - if (config.usePriceTiers) { + if (store.state.usePriceTiers) { this.$bus.$off('user-after-loggedin', this.onUserPricesRefreshed) this.$bus.$off('user-after-logout', this.onUserPricesRefreshed) } @@ -86,7 +86,7 @@ export default { this.$bus.$on('filter-changed-product', this.onAfterFilterChanged) this.$bus.$on('product-after-customoptions', this.onAfterCustomOptionsChanged) this.$bus.$on('product-after-bundleoptions', this.onAfterBundleOptionsChanged) - if (config.usePriceTiers) { + if (store.state.config.usePriceTiers) { this.$bus.$on('user-after-loggedin', this.onUserPricesRefreshed) this.$bus.$on('user-after-logout', this.onUserPricesRefreshed) } @@ -181,7 +181,7 @@ export default { selectDefaultVariant: true, fallbackToDefaultWhenNoAvailable: false }).then((selectedVariant) => { - if (config.products.setFirstVarianAsDefaultInURL) { + if (store.state.config.products.setFirstVarianAsDefaultInURL) { this.$router.push({params: { childSku: selectedVariant.sku }}) } if (!selectedVariant) { diff --git a/core/plugins/config/index.js b/core/plugins/config/index.js index f1f8989881..b1c8a45502 100644 --- a/core/plugins/config/index.js +++ b/core/plugins/config/index.js @@ -1,4 +1,5 @@ -import config from 'config' +import store from '@vue-storefront/store' +const config = store.state.config const ConfigPlugin = { install (Vue) { diff --git a/core/scripts/server.js b/core/scripts/server.js index 4f82eb18f3..c31422d306 100755 --- a/core/scripts/server.js +++ b/core/scripts/server.js @@ -3,7 +3,7 @@ const path = require('path') const express = require('express') const rootPath = require('app-root-path').path const resolve = file => path.resolve(rootPath, file) -const config = require('config') +let config = require('config') const TagCache = require('redis-tag-cache').default const utils = require('./server/utils') const compile = require('lodash.template') @@ -126,9 +126,7 @@ app.get('*', (req, res, next) => { if (err && err.code === 404) { res.redirect('/page-not-found') } else { - // Render Error Page or Redirect - // TODO: Add error page handler - res.status(500).end('500 | Internal Server Error. Check Server console for details.') + res.redirect('/error') console.error(`Error during render : ${req.url}`) console.error(err) next() @@ -150,38 +148,49 @@ app.get('*', (req, res, next) => { ' ') return next() } + if (config.server.dynamicConfigReload) { + delete require.cache[require.resolve('config')] + config = require('config') // reload config + } const context = { url: req.url, - renderPrepend: (context) => { return '' }, // these functions can be replaced in the Vue components to append or prepend some content AFTER all other things are rendered. So in this function You may call: renderPrepend() { return context.renderStyles() } to attach styles - renderAppend: (context) => { return '' }, - serverOutputTemplate: 'default', + output: { + prepend: (context) => { return '' }, // these functions can be replaced in the Vue components to append or prepend some content AFTER all other things are rendered. So in this function You may call: output.prepend() { return context.renderStyles() } to attach styles + append: (context) => { return '' }, + template: 'default', + cacheTags: null + }, + server: { + app: app, + response: res, + request: req + }, meta: null, - currentRoute: null/** will be set by Vue */, - storeCode: req.header('x-vs-store-code') ? req.header('x-vs-store-code') : process.env.STORE_CODE, - app: app, - response: res, - request: req + vs: { + config: config, + storeCode: req.header('x-vs-store-code') ? req.header('x-vs-store-code') : process.env.STORE_CODE + } } renderer.renderToString(context).then(output => { if (!res.get('content-type')) { res.setHeader('Content-Type', 'text/html') } let tagsArray = [] - if (config.server.useOutputCacheTagging) { - tagsArray = Array.from(context.state.requestContext.outputCacheTags) + if (config.server.useOutputCacheTagging && context.output.cacheTags !== null) { + tagsArray = Array.from(context.output.cacheTags) const cacheTags = tagsArray.join(' ') res.setHeader('X-VS-Cache-Tags', cacheTags) console.log(`cache tags for the request: ${cacheTags}`) } - const contentPrepend = (typeof context.renderPrepend === 'function') ? context.renderPrepend(context) : '' - const contentAppend = (typeof context.renderAppend === 'function') ? context.renderAppend(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 - if (context.serverOutputTemplate) { // case when we've got the template name back from vue app - if (templatesCache[context.serverOutputTemplate]) { // please look at: https://github.com/vuejs/vue/blob/79cabadeace0e01fb63aa9f220f41193c0ca93af/src/server/template-renderer/index.js#L87 for reference - output = templatesCache[context.serverOutputTemplate](context).replace('', output) + if (context.output.template) { // case when we've got the template name back from vue app + if (templatesCache[context.output.template]) { // please look at: https://github.com/vuejs/vue/blob/79cabadeace0e01fb63aa9f220f41193c0ca93af/src/server/template-renderer/index.js#L87 for reference + output = templatesCache[context.output.template](context).replace('', output) } else { - throw new Error(`The given template name ${context.serverOutputTemplate} does not exist`) + throw new Error(`The given template name ${context.output.template} does not exist`) } } if (config.server.useOutputCache && cache) { diff --git a/core/scripts/server/utils.js b/core/scripts/server/utils.js index ba9c66ebbb..e1c952e976 100644 --- a/core/scripts/server/utils.js +++ b/core/scripts/server/utils.js @@ -1,5 +1,5 @@ /** Creates a api status call and sends it thru to Express Response object. - * @param {express.Response} res Express HTTP Response + * @param {expressserver.response} res Express HTTP Response * @param {number} [code=200] Status code to send on success * @param {json} [result='OK'] Text message or result information object */ diff --git a/core/server-entry.ts b/core/server-entry.ts index a62371c8ab..fb0770e256 100755 --- a/core/server-entry.ts +++ b/core/server-entry.ts @@ -3,8 +3,9 @@ import { union } from 'lodash-es' import { createApp } from '@vue-storefront/core/app' import { HttpError } from '@vue-storefront/core/lib/exceptions' import { prepareStoreView, storeCodeFromRoute } from '@vue-storefront/store/lib/multistore' -import config from 'config' // can not be obtained from rootStore as this entry is loaded erlier than app.ts import omit from 'lodash-es/omit' +import pick from 'lodash-es/pick' +import buildTimeConfig from 'config' function _commonErrorHandler (err, reject) { if (err.message.indexOf('query returned empty result') > 0) { @@ -24,11 +25,24 @@ function _ssrHydrateSubcomponents (components, store, router, resolve, reject, a }) } })).then(() => { - if (config.ssr.useInitialStateFilter) { - context.state = omit(store.state, config.ssr.initialStateFilter) + if (buildTimeConfig.ssr.useInitialStateFilter) { + context.state = omit(store.state, 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, ['config']) + } else { + const excludeFromConfig = buildTimeConfig.server.dynamicConfigExclude + const includeFromConfig = buildTimeConfig.server.dynamicConfigInclude + console.log(excludeFromConfig, includeFromConfig) + if (includeFromConfig && includeFromConfig.length > 0) { + context.state.config = pick(context.state.config, includeFromConfig) + } + if (excludeFromConfig && excludeFromConfig.length > 0) { + context.state.config = omit(context.state.config, excludeFromConfig) + } + } resolve(app) }).catch(err => { _commonErrorHandler(err, reject) @@ -37,17 +51,16 @@ function _ssrHydrateSubcomponents (components, store, router, resolve, reject, a export default context => { return new Promise((resolve, reject) => { - const { app, router, store } = createApp(context) + const { app, router, store } = createApp(context, context.vs && context.vs.config ? context.vs.config : buildTimeConfig) const meta = (app as any).$meta() router.push(context.url) context.meta = meta router.onReady(() => { - store.state.requestContext.outputCacheTags = new Set() + context.output.cacheTags = new Set() if (store.state.config.storeViews.multistore === true) { - let storeCode = context.storeCode // this is from http header or env variable + let storeCode = context.vs.storeCode // this is from http header or env variable if (router.currentRoute) { // this is from url - context.currentRoute = router.currentRoute storeCode = storeCodeFromRoute(router.currentRoute) } if (storeCode !== '' && storeCode !== null) { diff --git a/core/store/index.ts b/core/store/index.ts index 41cb338bf0..f4c45747b5 100644 --- a/core/store/index.ts +++ b/core/store/index.ts @@ -40,10 +40,7 @@ const state = { twoStageCachingDisabled: false, userTokenInvalidated: null, userTokenInvalidateAttemptsCount: 0, - userTokenInvalidateLock: 0, - requestContext: { - outputCacheTags: new Set() - } + userTokenInvalidateLock: 0 } const mutations = { diff --git a/core/store/lib/search.ts b/core/store/lib/search.ts index 775cf8890f..bc33699603 100644 --- a/core/store/lib/search.ts +++ b/core/store/lib/search.ts @@ -2,14 +2,15 @@ import Vue from 'vue' import { currentStoreView } from './multistore' import { sha1 } from 'object-hash' import rootStore from '../' -import config from 'config' import SearchAdapterFactory from './search/adapter/factory' import Request from '../types/search/Request' import Response from '../types/search/Response' -const factory = new SearchAdapterFactory() -let adapterName = config.server.api -let searchAdapter = factory.getSearchAdapter(adapterName) +function getSearchAdapter(config) { + const factory = new SearchAdapterFactory() + let adapterName = config.server.api + return factory.getSearchAdapter(adapterName) +} export function isOnline () { if (typeof navigator !== 'undefined') { @@ -28,6 +29,7 @@ export function isOnline () { * @return {Promise} */ export function quickSearchByQuery ({ query, start = 0, size = 50, entityType = 'product', sort = '', storeCode = null, excludeFields = null, includeFields = null }): Promise { + const searchAdapter = getSearchAdapter(rootStore.state.config) if (size <= 0) size = 50 if (start < 0) start = 0 diff --git a/core/store/lib/search/adapter/api/searchAdapter.ts b/core/store/lib/search/adapter/api/searchAdapter.ts index efae8c713e..5a0f942152 100644 --- a/core/store/lib/search/adapter/api/searchAdapter.ts +++ b/core/store/lib/search/adapter/api/searchAdapter.ts @@ -7,7 +7,6 @@ import { currentStoreView, prepareStoreView } from '../../../multistore' import SearchQuery from 'core/store/lib/search/searchQuery' import HttpQuery from 'core/store/types/search/HttpQuery' import Response from 'core/store/types/search/Response' -import config from 'config' export class SearchAdapter { public entities: any @@ -28,7 +27,7 @@ export class SearchAdapter { if (Request.searchQuery instanceof SearchQuery) { ElasticsearchQueryBody = prepareElasticsearchQueryBody(Request.searchQuery) if (Request.searchQuery.getSearchText() !== '') { - ElasticsearchQueryBody['min_score'] = config.elasticsearch.min_score + ElasticsearchQueryBody['min_score'] = rootStore.state.config.elasticsearch.min_score } } else { // backward compatibility for old themes uses bodybuilder diff --git a/core/store/lib/search/adapter/factory.js b/core/store/lib/search/adapter/factory.js index 7cfe85f0ae..c9d703a445 100644 --- a/core/store/lib/search/adapter/factory.js +++ b/core/store/lib/search/adapter/factory.js @@ -1,10 +1,10 @@ 'use strict' -import config from 'config' +import store from '@vue-storefront/store' export default class SearchAdapterFactory { getSearchAdapter (adapterName = '') { if (adapterName === '') { - adapterName = config.server.api + adapterName = store.state.config.server.api } const {SearchAdapter} = require(`./${adapterName}/searchAdapter`) diff --git a/core/store/lib/search/boost.js b/core/store/lib/search/boost.js index ff95e2c99b..cadf06e88d 100644 --- a/core/store/lib/search/boost.js +++ b/core/store/lib/search/boost.js @@ -1,11 +1,11 @@ -import config from 'config' +import store from '@vue-storefront/store' export default function getBoosts (attribute = '') { let boosts = [ ] - if (config.boost) { - boosts = config.boost + if (store.state.boost) { + boosts = store.state.config.boost } if (boosts.hasOwnProperty(attribute)) { diff --git a/core/store/lib/search/mapping.js b/core/store/lib/search/mapping.js index f012fcf4fe..d104872173 100644 --- a/core/store/lib/search/mapping.js +++ b/core/store/lib/search/mapping.js @@ -1,11 +1,11 @@ -import config from 'config' +import store from '@vue-storefront/store' export default function getMapping (attribute, entityType = 'products') { let mapping = [ ] - if (config.hasOwnProperty(entityType) && config[entityType].hasOwnProperty('filterFieldMapping')) { - mapping = config[entityType].filterFieldMapping + if (store.state.config.hasOwnProperty(entityType) && store.state.config[entityType].hasOwnProperty('filterFieldMapping')) { + mapping = store.state.config[entityType].filterFieldMapping } if (mapping.hasOwnProperty(attribute)) { diff --git a/core/store/lib/search/searchQuery.js b/core/store/lib/search/searchQuery.js index 15890814ff..27ea8dfd5c 100644 --- a/core/store/lib/search/searchQuery.js +++ b/core/store/lib/search/searchQuery.js @@ -5,7 +5,6 @@ class SearchQuery { this._availableFilters = [] this._appliedFilters = [] this._searchText = '' - console.debug('create SearchQuery object') } /** diff --git a/core/store/modules/category/actions.ts b/core/store/modules/category/actions.ts index a2f3ab7f18..b6a93dc2f5 100644 --- a/core/store/modules/category/actions.ts +++ b/core/store/modules/category/actions.ts @@ -84,8 +84,8 @@ const actions: ActionTree = { if (setCurrentCategory) { commit(types.CATEGORY_UPD_CURRENT_CATEGORY, mainCategory) } - if (populateRequestCacheTags && mainCategory) { - rootStore.state.requestContext.outputCacheTags.add(`C${mainCategory.id}`) + if (populateRequestCacheTags && mainCategory && Vue.prototype.$ssrRequestContext) { + Vue.prototype.$ssrRequestContext.output.cacheTags.add(`C${mainCategory.id}`) } if (setCurrentCategoryPath) { let currentPath = [] diff --git a/core/store/modules/product/actions.ts b/core/store/modules/product/actions.ts index 27f3641adb..ec67022307 100644 --- a/core/store/modules/product/actions.ts +++ b/core/store/modules/product/actions.ts @@ -260,8 +260,8 @@ const actions: ActionTree = { return quickSearchByQuery({ query, start, size, entityType, sort, excludeFields, includeFields }).then((resp) => { if (resp.items && resp.items.length) { // preconfigure products; eg: after filters for (let product of resp.items) { - if (populateRequestCacheTags) { - rootStore.state.requestContext.outputCacheTags.add(`P${product.id}`) + if (populateRequestCacheTags && Vue.prototype.$ssrRequestContext) { + Vue.prototype.$ssrRequestContext.output.cacheTags.add(`P${product.id}`) } product.errors = {} // this is an object to store validation result for custom options and others product.info = {} diff --git a/core/store/modules/shipping/index.ts b/core/store/modules/shipping/index.ts index e34dc5673f..79b98294e4 100644 --- a/core/store/modules/shipping/index.ts +++ b/core/store/modules/shipping/index.ts @@ -1,12 +1,12 @@ import { Module } from 'vuex' import RootState from '../../types/RootState' import ShippingState from './types/ShippingState' -import config from 'config' +import buildTimeConfig from 'config' const shipping: Module = { namespaced: true, state: { - methods: config.shipping.methods + methods: buildTimeConfig.shipping.methods }, mutations: { addMethod (state, shippingMethods) { diff --git a/core/store/types/RootState.ts b/core/store/types/RootState.ts index f73660fdab..827253a5b8 100644 --- a/core/store/types/RootState.ts +++ b/core/store/types/RootState.ts @@ -29,8 +29,5 @@ export default interface RootState { twoStageCachingDisabled: boolean, userTokenInvalidated: string | null, userTokenInvalidateAttemptsCount: number, - userTokenInvalidateLock: number, - requestContext: { - outputCacheTags: Set - } + userTokenInvalidateLock: number } diff --git a/doc/Config docs.md b/doc/Config docs.md index f05270cb3a..afe9c7cee8 100644 --- a/doc/Config docs.md +++ b/doc/Config docs.md @@ -8,7 +8,7 @@ The structure of these files is exactly the same! Vue Storefront does kind of `O **Note:** Please take a look at the `node-config` docs as the library is open for some other ways to modify the configuration (using for example the `ENV` variables). -**Note:** Currently, the configuration files are being processed by the webpack during the build process. This means that whenever You apply some configuration changes You shall re-build the app - even when using the `yarn dev` mode. +**Note:** By default the configuration is being bundled by webpack and can not be modified without re-compiling Vue Storefront. This limitation can by however solved with VS 1.4 - special config variable. Now the config can be reloaded on-fly with each server request if `config.server.dynamicConfigReload` is set to true. However in that case the config is added to `window.__INITIAL_STATE__` with the responses. ## Vue Storefront configuration file - explained diff --git a/doc/Layouts and advanced output operations.md b/doc/Layouts and advanced output operations.md index 274f9bb85d..4c6d8e9fae 100644 --- a/doc/Layouts and advanced output operations.md +++ b/doc/Layouts and advanced output operations.md @@ -21,9 +21,9 @@ What we've changed is **You can now select which html template + layout Your app ## Changelog The changes we've introduced: -- now distinct routes can set `context.serverOutputTemplate` in `asyncData` method. By doing so You can skip using `dist/index.html` (which contains typical HTML5 elements - like `` tags) - either xml files - You name it +- now distinct routes can set `context.output.template` in `asyncData` method. By doing so You can skip using `dist/index.html` (which contains typical HTML5 elements - like `` tags) - either xml files - You name it - distinct routes can set `meta.layout` and by doing so switch the previously constant App.vue layout file - this is what @mercs600 proposed some time ago in slightly changed form -- You've got access to server `context` object in `asyncData` and two new features - `renderPrepend` and `renderAppend` have been created to allow You control the rendering flow of the template +- You've got access to server `context` object in `asyncData` and two new features - `output.prepend` and `output.append` have been created to allow You control the rendering flow of the template ## Templates @@ -75,8 +75,8 @@ Vue component to render the XML: export default { name: 'RawOutputExample', asyncData ({ store, route, context }) { - context.response.setHeader('Content-Type', 'text/xml') - context.serverOutputTemplate = '' + contextserver.response.setHeader('Content-Type', 'text/xml') + context.output.template = '' return new Promise((resolve, reject) => { resolve() }) @@ -95,11 +95,11 @@ export default { The key part is: ```js - context.response.setHeader('Content-Type', 'text/xml') - context.serverOutputTemplate = '' + contextserver.response.setHeader('Content-Type', 'text/xml') + context.output.template = '' ``` These two statements: -- set the HTTP header (by accessing ExpressJS response object - `context.response`. There is also `context.request` and `context.app` - the ExpressJS application)- set `serverOutputTemplate` to none which will cause to skip the HTML template rendering at all. +- set the HTTP header (by accessing ExpressJS response object - `contextserver.response`. There is also `contextserver.request` and `context.app` - the ExpressJS application)- set `output.template` to none which will cause to skip the HTML template rendering at all. ### Switching off layout + injecting dynamic content Example URL: `http://localhost:3000/append-prepend.html` @@ -120,11 +120,11 @@ Vue component to render the XML: export default { name: 'NoJSExample', asyncData ({ store, route, context }) { - context.serverOutputTemplate = '' - context.renderAppend = (context) => { + context.output.template = '' + context.output.append = (context) => { return '
This content has been dynamically appended
' } - context.renderPrepend = (context) => { + context.output.prepend = (context) => { return '
this content has been dynamically prepended
' } return new Promise((resolve, reject) => { @@ -145,22 +145,22 @@ export default { The key part is: ```js - context.serverOutputTemplate = '' - context.renderAppend = (context) => { + context.output.template = '' + context.output.append = (context) => { return '
This content has been dynamically appended
' } - context.renderPrepend = (context) => { + context.output.prepend = (context) => { return '
this content has been dynamically prepended
' } ``` These two statements: -- set `serverOutputTemplate` to none which will cause to skip the HTML template rendering at all. -- adds the `renderAppend` and `renderPrepend` methods to the server context. +- set `output.template` to none which will cause to skip the HTML template rendering at all. +- adds the `output.append` and `output.prepend` methods to the server context. The output will be generated with this logic: ```js - const contentPrepend = (typeof context.renderPrepend === 'function') ? context.renderPrepend(context) : '' - const contentAppend = (typeof context.renderAppend === 'function') ? context.renderAppend(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 ``` diff --git a/doc/SSR Cache.md b/doc/SSR Cache.md index 4b371b38e2..b029063b12 100644 --- a/doc/SSR Cache.md +++ b/doc/SSR Cache.md @@ -77,9 +77,9 @@ If You're adding new type of page (`core/pages`) and `config.server.useOutputCac After doing so, please add the `asyncData` method to Your page code assigning the right tag (please take a look at `core/pages/Home.js` for instance): ```js - asyncData ({ store, route }) { // this is for SSR purposes to prefetch data + asyncData ({ store, route, context }) { // this is for SSR purposes to prefetch data return new Promise((resolve, reject) => { - store.state.requestContext.outputCacheTags.add(`home`) + if (context) context.output.cacheTags.add(`home`) console.log('Entering asyncData for Home root ' + new Date()) EventBus.$emitFilter('home-after-load', { store: store, route: route }).then((results) => { return resolve() @@ -94,7 +94,7 @@ After doing so, please add the `asyncData` method to Your page code assigning th This line: ```js - store.state.requestContext.outputCacheTags.add(`home`) + if (context) context.output.cacheTags.add(`home`) ``` ... is in charge of assigning the specific tag with current HTTP request output. diff --git a/src/extensions/magento2-cms/components/CmsData.vue b/src/extensions/magento2-cms/components/CmsData.vue index 960376a698..f0d537abab 100644 --- a/src/extensions/magento2-cms/components/CmsData.vue +++ b/src/extensions/magento2-cms/components/CmsData.vue @@ -7,7 +7,7 @@ + + diff --git a/src/themes/default-amp/pages/Product.vue b/src/themes/default-amp/pages/Product.vue new file mode 100644 index 0000000000..0cabbc8ad5 --- /dev/null +++ b/src/themes/default-amp/pages/Product.vue @@ -0,0 +1,353 @@ + + + + + diff --git a/src/themes/default-amp/router/index.js b/src/themes/default-amp/router/index.js new file mode 100644 index 0000000000..3ec5198c7f --- /dev/null +++ b/src/themes/default-amp/router/index.js @@ -0,0 +1,29 @@ +// import router from '@vue-storefront/core/router' +// uncomment if you want to modify the router e.g. add before/after hooks +import Category from '../pages/Category.vue' +import Product from '../pages/Product.vue' + +import store from '@vue-storefront/store' + +let routes = [ +] +if (!store.state.config.products.useShortCatalogUrls) { + routes = routes.concat([{ name: 'virtual-product-amp', path: '/amp/p/:parentSku/:slug', component: Product, meta: { layout: 'minimal' } }, // :sku param can be marked as optional with ":sku?" (https://github.com/vuejs/vue-router/blob/dev/examples/route-matching/app.js#L16), but it requires a lot of work to adjust the rest of the site + { name: 'bundle-product-amp', path: '/amp/p/:parentSku/:slug', component: Product, meta: { layout: 'minimal' } }, // :sku param can be marked as optional with ":sku?" (https://github.com/vuejs/vue-router/blob/dev/examples/route-matching/app.js#L16), but it requires a lot of work to adjust the rest of the site + { name: 'simple-product-amp', path: '/amp/p/:parentSku/:slug', component: Product, meta: { layout: 'minimal' } }, // :sku param can be marked as optional with ":sku?" (https://github.com/vuejs/vue-router/blob/dev/examples/route-matching/app.js#L16), but it requires a lot of work to adjust the rest of the site + { name: 'downloadable-product-amp', path: '/amp/p/:parentSku/:slug', component: Product, meta: { layout: 'minimal' } }, // :sku param can be marked as optional with ":sku?" (https://github.com/vuejs/vue-router/blob/dev/examples/route-matching/app.js#L16), but it requires a lot of work to adjust the rest of the site + { name: 'grouped-product-amp', path: '/amp/p/:parentSku/:slug', component: Product, meta: { layout: 'minimal' } }, // :sku param can be marked as optional with ":sku?" (https://github.com/vuejs/vue-router/blob/dev/examples/route-matching/app.js#L16), but it requires a lot of work to adjust the rest of the site + { name: 'configurable-product-amp', path: '/amp/p/:parentSku/:slug/:childSku', component: Product, meta: { layout: 'minimal' } }, // :sku param can be marked as optional with ":sku?" (https://github.com/vuejs/vue-router/blob/dev/examples/route-matching/app.js#L16), but it requires a lot of work to adjust the rest of the site + { name: 'product-amp', path: '/amp/p/:parentSku/:slug/:childSku', component: Product, meta: { layout: 'minimal' } }])// :sku param can be marked as optional with ":sku?" (https://github.com/vuejs/vue-router/blob/dev/examples/route-matching/app.js#L16), but it requires a lot of work to adjust the rest of the site + // { name: 'category', path: '/amp/c/:slug', component: Category }]) +} else { + routes = routes.concat([{ name: 'virtual-product-amp', path: '/amp/:parentSku/:slug', component: Product, meta: { layout: 'minimal' } }, // :sku param can be marked as optional with ":sku?" (https://github.com/vuejs/vue-router/blob/dev/examples/route-matching/app.js#L16), but it requires a lot of work to adjust the rest of the site + { name: 'bundle-product-amp', path: '/amp/:parentSku/:slug', component: Product, meta: { layout: 'minimal' } }, // :sku param can be marked as optional with ":sku?" (https://github.com/vuejs/vue-router/blob/dev/examples/route-matching/app.js#L16), but it requires a lot of work to adjust the rest of the site + { name: 'simple-product-amp', path: '/amp/:parentSku/:slug', component: Product, meta: { layout: 'minimal' } }, // :sku param can be marked as optional with ":sku?" (https://github.com/vuejs/vue-router/blob/dev/examples/route-matching/app.js#L16), but it requires a lot of work to adjust the rest of the site + { name: 'downloadable-product-amp', path: '/amp/:parentSku/:slug', component: Product, meta: { layout: 'minimal' } }, // :sku param can be marked as optional with ":sku?" (https://github.com/vuejs/vue-router/blob/dev/examples/route-matching/app.js#L16), but it requires a lot of work to adjust the rest of the site + { name: 'grouped-product-amp', path: '/amp/:parentSku/:slug', component: Product, meta: { layout: 'minimal' } }, // :sku param can be marked as optional with ":sku?" (https://github.com/vuejs/vue-router/blob/dev/examples/route-matching/app.js#L16), but it requires a lot of work to adjust the rest of the site + { name: 'configurable-product-amp', path: '/amp/:parentSku/:slug/:childSku', component: Product, meta: { layout: 'minimal' } }, // :sku param can be marked as optional with ":sku?" (https://github.com/vuejs/vue-router/blob/dev/examples/route-matching/app.js#L16), but it requires a lot of work to adjust the rest of the site + { name: 'product-amp', path: '/amp/:parentSku/:slug/:childSku', component: Product, meta: { layout: 'minimal' } }, // :sku param can be marked as optional with ":sku?" (https://github.com/vuejs/vue-router/blob/dev/examples/route-matching/app.js#L16), but it requires a lot of work to adjust the rest of the site + { name: 'category-amp', path: '/amp/:slug', component: Category, meta: { layout: 'minimal' } }]) +} +export default routes diff --git a/src/themes/default/components/core/ProductTile.vue b/src/themes/default/components/core/ProductTile.vue index 29a3aaa4fb..3f793da9b3 100644 --- a/src/themes/default/components/core/ProductTile.vue +++ b/src/themes/default/components/core/ProductTile.vue @@ -57,7 +57,6 @@ + + diff --git a/src/themes/default/pages/Home.vue b/src/themes/default/pages/Home.vue index bd9b87a162..defeb69921 100755 --- a/src/themes/default/pages/Home.vue +++ b/src/themes/default/pages/Home.vue @@ -34,9 +34,6 @@ // 3rd party dependecies import { prepareQuery } from '@vue-storefront/core/modules/product/queries/common' -// Core dependecies -import config from 'config' - // Core pages import Home from '@vue-storefront/core/pages/Home' @@ -86,6 +83,7 @@ export default { } }, asyncData ({ store, route }) { // this is for SSR purposes to prefetch data + const config = store.state.config return new Promise((resolve, reject) => { console.log('Entering asyncData for Home ' + new Date()) diff --git a/src/themes/default/router/index.js b/src/themes/default/router/index.js index 5960e9dfe9..ed38f23e81 100644 --- a/src/themes/default/router/index.js +++ b/src/themes/default/router/index.js @@ -10,8 +10,7 @@ import PageNotFound from 'theme/pages/PageNotFound.vue' import MyAccount from 'theme/pages/MyAccount.vue' import CustomCmsPage from 'theme/pages/CustomCmsPage.vue' import CmsData from '@vue-storefront/extension-magento2-cms/components/CmsData' - -import config from 'config' +import store from '@vue-storefront/store' let routes = [ { name: 'home', path: '/', component: Home, alias: '/pwa.html' }, @@ -37,10 +36,11 @@ let routes = [ { name: 'contact', path: '/contact', component: Static, props: {page: 'contact', title: 'Contact'} }, { name: 'compare', path: '/compare', component: Compare, props: {title: 'Compare Products'} }, { name: 'page-not-found', path: '/page-not-found', component: PageNotFound }, + { name: 'error', path: '/error', component: Error, meta: { layout: 'empty' } }, { name: 'custom-cms-page', path: '/custom-cms-page', component: CustomCmsPage }, { name: 'cms-page-sync', path: '/cms-page-sync', component: CmsData, props: {identifier: 'about-us', type: 'Page', sync: true} } ] -if (!config.products.useShortCatalogUrls) { +if (!store.state.config.products.useShortCatalogUrls) { routes = routes.concat([{ name: 'virtual-product', path: '/p/:parentSku/:slug', component: Product }, // :sku param can be marked as optional with ":sku?" (https://github.com/vuejs/vue-router/blob/dev/examples/route-matching/app.js#L16), but it requires a lot of work to adjust the rest of the site { name: 'bundle-product', path: '/p/:parentSku/:slug', component: Product }, // :sku param can be marked as optional with ":sku?" (https://github.com/vuejs/vue-router/blob/dev/examples/route-matching/app.js#L16), but it requires a lot of work to adjust the rest of the site { name: 'simple-product', path: '/p/:parentSku/:slug', component: Product }, // :sku param can be marked as optional with ":sku?" (https://github.com/vuejs/vue-router/blob/dev/examples/route-matching/app.js#L16), but it requires a lot of work to adjust the rest of the site diff --git a/src/themes/theme-starter/index.js b/src/themes/theme-starter/index.js index b6a88a0450..07ea6f31ba 100644 --- a/src/themes/theme-starter/index.js +++ b/src/themes/theme-starter/index.js @@ -1,8 +1,7 @@ import { setupMultistoreRoutes } from '@vue-storefront/store/lib/multistore' -import config from 'config' import routes from './router' -export default function (app, router, store) { +export default function (app, router, store, config, ssrContext) { // if youre' runing multistore setup this is copying the routed above adding the 'storeCode' prefix to the urls and the names of the routes // You can do it on your own and then be able to customize the components used for example for German storeView checkout // To do so please execlude the desired storeView from the config.storeViews.mapStoreUrlsFor and map the urls by Your own like: diff --git a/src/themes/theme-starter/router/index.js b/src/themes/theme-starter/router/index.js index b64a098f56..95aeb20f48 100644 --- a/src/themes/theme-starter/router/index.js +++ b/src/themes/theme-starter/router/index.js @@ -3,13 +3,13 @@ import Home from 'theme/pages/Home.vue' import Product from 'theme/pages/Product.vue' import Category from 'theme/pages/Category.vue' -import config from 'config' +import store from '@vue-storefront/store' let routes = [ { path: '/', component: Home, name: 'home' } ] -if (!config.products.useShortCatalogUrls) { +if (!store.state.config.products.useShortCatalogUrls) { routes = routes.concat([{ name: 'virtual-product', path: '/p/:parentSku/:slug', component: Product }, // :sku param can be marked as optional with ":sku?" (https://github.com/vuejs/vue-router/blob/dev/examples/route-matching/app.js#L16), but it requires a lot of work to adjust the rest of the site { name: 'bundle-product', path: '/p/:parentSku/:slug', component: Product }, // :sku param can be marked as optional with ":sku?" (https://github.com/vuejs/vue-router/blob/dev/examples/route-matching/app.js#L16), but it requires a lot of work to adjust the rest of the site { name: 'simple-product', path: '/p/:parentSku/:slug', component: Product }, // :sku param can be marked as optional with ":sku?" (https://github.com/vuejs/vue-router/blob/dev/examples/route-matching/app.js#L16), but it requires a lot of work to adjust the rest of the site