Skip to content

[WIP] PoC - multipart, dynamic URL rewrite #2401

New issue

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

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

Already on GitHub? Sign in to your account

Closed
wants to merge 15 commits into from
Closed
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
8 changes: 4 additions & 4 deletions config/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"elasticCacheQuota": 4096
},
"seo": {
"useUrlDispatcher": false
"useUrlDispatcher": true
},
"console": {
"showErrorOnProduction" : true,
Expand Down Expand Up @@ -144,7 +144,7 @@
"twoStageCaching": true,
"optimizeShoppingCart": true,
"category": {
"includeFields": [ "id", "*.children_data.id", "*.id", "children_count", "sku", "name", "is_active", "parent_id", "level", "url_key", "product_count", "path"],
"includeFields": [ "id", "*.children_data.id", "*.id", "children_count", "sku", "name", "is_active", "parent_id", "level", "url_key", "url_path", "product_count", "path"],
"excludeFields": [ "sgn" ],
"categoriesRootCategorylId": 2,
"categoriesDynamicPrefetchLevel": 2,
Expand All @@ -155,11 +155,11 @@
},
"productList": {
"sort": "",
"includeFields": [ "type_id", "sku", "product_links", "tax_class_id", "special_price", "special_to_date", "special_from_date", "name", "price", "priceInclTax", "originalPriceInclTax", "originalPrice", "specialPriceInclTax", "id", "image", "sale", "new", "url_key", "status", "tier_prices", "configurable_children.sku", "configurable_children.price", "configurable_children.special_price", "configurable_children.priceInclTax", "configurable_children.specialPriceInclTax", "configurable_children.originalPrice", "configurable_children.originalPriceInclTax" ],
"includeFields": [ "type_id", "sku", "product_links", "tax_class_id", "special_price", "special_to_date", "special_from_date", "name", "price", "priceInclTax", "originalPriceInclTax", "originalPrice", "specialPriceInclTax", "id", "image", "sale", "new", "url_path", "url_key", "status", "tier_prices", "configurable_children.sku", "configurable_children.price", "configurable_children.special_price", "configurable_children.priceInclTax", "configurable_children.specialPriceInclTax", "configurable_children.originalPrice", "configurable_children.originalPriceInclTax" ],
"excludeFields": [ "description", "configurable_options", "sgn", "*.sgn", "msrp_display_actual_price_type", "*.msrp_display_actual_price_type", "required_options" ]
},
"productListWithChildren": {
"includeFields": [ "type_id", "sku", "name", "tax_class_id", "special_price", "special_to_date", "special_from_date", "price", "priceInclTax", "originalPriceInclTax", "originalPrice", "specialPriceInclTax", "id", "image", "sale", "new", "configurable_children.image", "configurable_children.sku", "configurable_children.price", "configurable_children.special_price", "configurable_children.priceInclTax", "configurable_children.specialPriceInclTax", "configurable_children.originalPrice", "configurable_children.originalPriceInclTax", "configurable_children.color", "configurable_children.size", "configurable_children.id", "configurable_children.tier_prices", "product_links", "url_key", "status", "tier_prices"],
"includeFields": [ "type_id", "sku", "name", "tax_class_id", "special_price", "special_to_date", "special_from_date", "price", "priceInclTax", "originalPriceInclTax", "originalPrice", "specialPriceInclTax", "id", "image", "sale", "new", "configurable_children.image", "configurable_children.sku", "configurable_children.price", "configurable_children.special_price", "configurable_children.priceInclTax", "configurable_children.specialPriceInclTax", "configurable_children.originalPrice", "configurable_children.originalPriceInclTax", "configurable_children.color", "configurable_children.size", "configurable_children.id", "configurable_children.tier_prices", "product_links", "url_path", "url_key", "status", "tier_prices"],
"excludeFields": [ "description", "sgn", "*.sgn", "msrp_display_actual_price_type", "*.msrp_display_actual_price_type", "required_options"]
},
"review": {
Expand Down
2 changes: 1 addition & 1 deletion core/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ const createApp = async (ssrContext, config): Promise<{app: Vue, router: VueRou
// sync router with vuex 'router' store
sync(store, router)
// TODO: Don't mutate the state directly, use mutation instead
store.state.version = '1.7.0'
store.state.version = '1.8.2'
store.state.config = config
store.state.__DEMO_MODE__ = (config.demomode === true) ? true : false
if(ssrContext) Vue.prototype.$ssrRequestContext = ssrContext
Expand Down
1 change: 1 addition & 0 deletions core/build/webpack.base.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export default {
inject: isProd == false
})
],
devtool: 'source-map',
Copy link
Collaborator

@filrak filrak Feb 16, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's longest sourcemap alghorithm that imcreases build time
We prev changed it to increase build time speed, it should be available only under special flag imho and completely disabled in prod

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change it please to other one. Currently it’s disabled at all which makes the debugging almost impossible. It was a big oversaw issue with the latest release :(

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It’s disabled in prod with my change. You haven’t checked the *.prod.ts config where it’s disabled.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, sorry i overseen this

entry: {
app: ['babel-polyfill', './core/client-entry.ts']
},
Expand Down
2 changes: 1 addition & 1 deletion core/build/webpack.client.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const config = merge(base, {
'process.env.VUE_ENV': '"client"'
}),
new VueSSRClientPlugin()
]
],
})

export default config;
1 change: 1 addition & 0 deletions core/build/webpack.prod.client.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const extendedConfig = require(path.join(themeRoot, '/webpack.config.js'))

const prodClientConfig = merge(baseClientConfig, {
mode: 'production',
devtool: 'nosources-source-map',
plugins: [
]
})
Expand Down
1 change: 1 addition & 0 deletions core/build/webpack.prod.server.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const extendedConfig = require(path.join(themeRoot, '/webpack.config.js'))

export default extendedConfig(baseServerConfig, {
mode: 'production',
devtool: 'nosources-source-map',
isClient: false,
isDev: false
})
18 changes: 13 additions & 5 deletions core/client-entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,14 @@ const invokeClientEntry = async () => {
_commonErrorHandler(err, next)
})
}

let _appMounted = false
router.onReady(() => {
router.beforeResolve((to, from, next) => {
router.afterEach((to, from) => {
if (config.seo.useUrlDispatcher && !_appMounted) { // hydrate after each other request
app.$mount('#app')
}
})
router.beforeResolve((to, from, next) => { // this is NOT CALLED after SSR request as no component is being resolved client side
if (!from.name) return next() // do not resolve asyncData on server render - already been done
if (Vue.prototype.$ssrRequestContext) Vue.prototype.$ssrRequestContext.output.cacheTags = new Set<string>()
const matched = router.getMatchedComponents(to)
Expand All @@ -89,9 +94,9 @@ const invokeClientEntry = async () => {
}
}
let diffed = false
const activated = matched.filter((c, i) => {
const activated = config.seo.useUrlDispatcher ? matched : (matched.filter((c, i) => {
return diffed || (diffed = (prevMatched[i] !== c))
})
}))
if (!activated.length) {
return next()
}
Expand All @@ -112,7 +117,10 @@ const invokeClientEntry = async () => {
}
}))
})
app.$mount('#app')
if (!config.seo.useUrlDispatcher) { // if UrlDispatcher is enabled, we're mounting the app in `beforeResolve` - shortly after the component is loaded
app.$mount('#app')
_appMounted = true
}
})
/*
* serial executes Promises sequentially.
Expand Down
33 changes: 32 additions & 1 deletion core/helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import rootStore from '@vue-storefront/store'
import SearchQuery from '@vue-storefront/core/lib/search/searchQuery'
import { remove as removeAccents } from 'remove-accents'
import { Logger } from '@vue-storefront/core/lib/logger'
import chain from 'lodash-es/chain'
import partial from 'lodash-es/partial'
import split from 'lodash-es/split'
import { type } from 'os';

/**
* Create slugify -> "create-slugify" permalink of text
Expand All @@ -19,7 +23,34 @@ export function slugify (text) {
.replace(/[^\w-]+/g, '') // Remove all non-word chars
.replace(/--+/g, '-') // Replace multiple - with single -
}
/**
*
* @param obj Build URL query string
*/
export function buildURLQuery (obj, removeNullValues = true) {
let pairs = Object.entries(obj)
if (removeNullValues) pairs = pairs.filter(pair => {
if (pair.length == 2 && !pair[1]) return false
return true
})
return (pairs.map(pair => {
return pair.map(encodeURIComponent).join('=')
}).join('&'))
}

export function parseURLQuery (queryString) {
if (queryString === null || typeof queryString !== 'string') return {}
const params = {}
let queries, temp, i, l
// Split into key/value pairs
queries = queryString.split("&")
// Convert the array of strings into an object
for ( i = 0, l = queries.length; i < l; i++ ) {
temp = queries[i].split('=')
params[temp[0]] = temp[1]
}
return params
}
/**
* @param relativeUrl
* @param width
Expand Down Expand Up @@ -54,7 +85,7 @@ export function breadCrumbRoutes (categoryPath) {
for (let sc of categoryPath) {
tmpRts.push({
name: sc.name,
route_link: (rootStore.state.config.products.useShortCatalogUrls ? '/' : '/c/') + sc.slug
route_link: rootStore.state.config.seo.useUrlDispatcher ? ('/' + sc.url_path) : ((rootStore.state.config.products.useShortCatalogUrls ? '/' : '/c/') + sc.slug)
})
}

Expand Down
4 changes: 2 additions & 2 deletions core/lib/async-data-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@ const AsyncDataLoader = {
flush : function (actionContext: AsyncDataLoaderActionContext) {
if (!actionContext.category) actionContext.category = DEFAULT_ACTION_CATEGORY
const actionsToExecute = this.queue.filter(ac => (!ac.category || !actionContext.category) || ac.category === actionContext.category && (!ac.executedAt)).map(ac => {
ac.executedAt = new Date()
return ac.execute(actionContext) // function must return Promise
})
if (actionsToExecute.length > 0) {
Logger.info('Executing data loader actions(' + actionsToExecute.length + ')', 'dataloader')()
}
return Promise.all(actionsToExecute).then(results => {
actionsToExecute.map(ac => ac.executedAt = new Date())
return Promise.all(actionsToExecute).then(results => {
return results
})
}
Expand Down
59 changes: 41 additions & 18 deletions core/lib/logger.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { isServer } from '@vue-storefront/core/helpers'
import buildTimeConfig from 'config'

const bgColorStyle = (color) => `color: white; background: ${color}; padding: 4px; font-weight: bold; font-size: 0.8em'`

/** VS message logger. By default works only on dev mode */
Expand Down Expand Up @@ -34,6 +33,22 @@ class Logger
this.isProduction = process.env.NODE_ENV === 'production';
}

/**
* Convert message to string - as it may be object, array either primitive
* @param payload
*/
convertToString (payload: any) {
if (typeof payload === 'string' || typeof payload === 'boolean' || typeof payload === 'number') return payload
if (Array.isArray(payload)) return JSON.stringify(payload)
if (typeof payload === 'object') {
if (payload.hasOwnProperty('message')) {
return payload.message
} else {
return JSON.stringify(payload)
}
}
}

/**
* Check if method can print into console
*
Expand Down Expand Up @@ -65,12 +80,12 @@ class Logger
* @param tag short tag specifying area where message was spawned (eg. cart, sync, module)
* @param context meaningful data related to this message
*/
debug (message: string, tag: string = null, context: any = null) : () => void {
debug (message: any, tag: string = null, context: any = null) : () => void {
if (!isServer && this.canPrint('debug')) {
if (tag) {
return console.debug.bind(window.console, '%cVSF%c %c' + tag +'%c ' + message, bgColorStyle('grey'), 'color: inherit', bgColorStyle('gray'), 'font-weight: normal', context);
return console.debug.bind(window.console, '%cVSF%c %c' + tag +'%c ' + this.convertToString(message), bgColorStyle('grey'), 'color: inherit', bgColorStyle('gray'), 'font-weight: normal', context);
} else {
return console.debug.bind(window.console, '%cVSF%c ' + message, bgColorStyle('white'), 'font-weight: normal', context);
return console.debug.bind(window.console, '%cVSF%c ' + this.convertToString(message), bgColorStyle('grey'), 'font-weight: normal', context);
}
} else {
return function () {}
Expand All @@ -85,7 +100,7 @@ class Logger
* @param tag short tag specifying area where message was spawned (eg. cart, sync, module)
* @param context meaningful data related to this message
*/
log (message: string, tag: string = null, context: any = null) : () => void {
log (message: any, tag: string = null, context: any = null) : () => void {
return this.info(message, tag, context);
}

Expand All @@ -97,12 +112,12 @@ class Logger
* @param tag short tag specifying area where message was spawned (eg. cart, sync, module)
* @param context meaningful data related to this message
*/
info (message: string, tag: string = null, context: any = null) : () => void {
info (message: any, tag: string = null, context: any = null) : () => void {
if (!isServer && this.canPrint('info')) {
if (tag) {
return console.log.bind(window.console, '%cVSF%c %c' + tag +'%c ' + message, bgColorStyle('green'), 'color: inherit', bgColorStyle('gray'), 'font-weight: bold', context);
return console.log.bind(window.console, '%cVSF%c %c' + tag +'%c ' + this.convertToString(message), bgColorStyle('green'), 'color: inherit', bgColorStyle('gray'), 'font-weight: bold', context);
} else {
return console.log.bind(window.console, '%cVSF%c ' + message, bgColorStyle('green'), 'font-weight: bold', context);
return console.log.bind(window.console, '%cVSF%c ' + this.convertToString(message), bgColorStyle('green'), 'font-weight: bold', context);
}
} else {
return function () {}
Expand All @@ -117,12 +132,16 @@ class Logger
* @param tag short tag specifying area where message was spawned (eg. cart, sync, module)
* @param context meaningful data related to this message
*/
warn (message: string, tag: string = null, context: any = null) : () => void {
if (!isServer && this.canPrint('warn')) {
if (tag) {
return console.warn.bind(window.console, '%cVSF%c %c' + tag +'%c ' + message, bgColorStyle('orange'), 'color: inherit', bgColorStyle('gray'), 'font-weight: bold', context);
warn (message: any, tag: string = null, context: any = null) : () => void {
if (this.canPrint('warn')) {
if (!isServer) {
if (tag) {
return console.warn.bind(window.console, '%cVSF%c %c' + tag +'%c ' + this.convertToString(message), bgColorStyle('orange'), 'color: inherit', bgColorStyle('gray'), 'font-weight: bold', context);
} else {
return console.warn.bind(window.console, '%cVSF%c ' + this.convertToString(message), bgColorStyle('orange'), 'font-weight: bold', context);
}
} else {
return console.warn.bind(window.console, '%cVSF%c ' + message, bgColorStyle('orange'), 'font-weight: bold', context);
return console.warn.bind(console, message, context);
}
} else {
return function () {}
Expand All @@ -137,12 +156,16 @@ class Logger
* @param tag short tag specifying area where message was spawned (eg. cart, sync, module)
* @param context meaningful data related to this message
*/
error (message: string, tag: string = null, context: any = null) : () => void {
if (!isServer && this.canPrint('error')) {
if (tag) {
return console.error.bind(window.console, '%cVSF%c %c' + tag +'%c ' + message, bgColorStyle('red'), 'color: inherit', bgColorStyle('gray'), 'font-weight: bold', context);
error (message: any, tag: string = null, context: any = null) : () => void {
if (this.canPrint('error')) { // we should display SSR errors for better monitoring + error handling
if (!isServer) {
if (tag) {
return console.error.bind(window.console, '%cVSF%c %c' + tag +'%c ' + this.convertToString(message), bgColorStyle('red'), 'color: inherit', bgColorStyle('gray'), 'font-weight: bold', context);
} else {
return console.error.bind(window.console, '%cVSF%c ' + this.convertToString(message), bgColorStyle('red'), 'font-weight: bold', context);
}
} else {
return console.error.bind(window.console, '%cVSF%c ' + message, bgColorStyle('red'), 'font-weight: bold', context);
return console.error.bind(console, message, context);
}
} else {
return function () {}
Expand Down
17 changes: 16 additions & 1 deletion core/lib/multistore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { loadLanguageAsync } from '@vue-storefront/i18n'
import { initializeSyncTaskStorage } from './sync/task'
import Vue from 'vue'
import { Route } from 'vue-router'
import { buildURLQuery } from '@vue-storefront/core/helpers'

export interface StoreView {
storeCode: string,
Expand Down Expand Up @@ -92,13 +93,16 @@ export function adjustMultistoreApiUrl (url: string) : string {
}

export function localizedRoute (routeObj: Route | string, storeCode: string) {
if (rootStore.state.config.seo.useUrlDispatcher) {
if (routeObj && typeof routeObj === 'object' && routeObj.fullPath) return localizedDispatcherRoute(Object.assign({}, routeObj, { params: null }), storeCode)
}
if (storeCode && routeObj && rootStore.state.config.defaultStoreCode !== storeCode) {
if (typeof routeObj === 'object') {
if (routeObj.name) {
routeObj.name = storeCode + '-' + routeObj.name
}
if (routeObj.path) {
routeObj.path = '/' + storeCode + '/' + routeObj.path.slice(1)
routeObj.path = '/' + storeCode + '/' + (routeObj.path.startsWith('/') ? routeObj.path.slice(1) : routeObj.path)
}
} else {
return '/' + storeCode + routeObj
Expand All @@ -107,6 +111,17 @@ export function localizedRoute (routeObj: Route | string, storeCode: string) {
return routeObj
}

export function localizedDispatcherRoute (routeObj: Route | string, storeCode: string) {
if (typeof routeObj === 'string') {
return '/' + storeCode + routeObj
}
if (routeObj && typeof routeObj === 'object' && routeObj.fullPath) { // case of using dispatcher
return '/' + ((rootStore.state.config.defaultStoreCode !== storeCode) ? (storeCode + '/') : '') + routeObj.fullPath + (routeObj.params ? ('?' + buildURLQuery(routeObj.params)) : '')
} else {
return routeObj
}
}

export function setupMultistoreRoutes (config, router, routes) {
if (config.storeViews.mapStoreUrlsFor.length > 0 && config.storeViews.multistore === true) {
for (let storeCode of config.storeViews.mapStoreUrlsFor) {
Expand Down
7 changes: 2 additions & 5 deletions core/lib/search/adapter/api/searchAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import map from 'lodash-es/map'
import rootStore from '@vue-storefront/store'
import { prepareElasticsearchQueryBody } from './elasticsearchQuery'
import fetch from 'isomorphic-fetch'
import { slugify } from '@vue-storefront/core/helpers'
import { slugify, buildURLQuery } from '@vue-storefront/core/helpers'
import { currentStoreView, prepareStoreView } from '../../../multistore'
import SearchQuery from '@vue-storefront/core/lib/search/searchQuery'
import HttpQuery from '@vue-storefront/core/types/search/HttpQuery'
Expand All @@ -20,9 +20,6 @@ export class SearchAdapter {
if (!this.entities[Request.type]) {
throw new Error('No entity type registered for ' + Request.type )
}

const buildURLQuery = obj => Object.entries(obj).map(pair => pair.map(encodeURIComponent).join('=')).join('&')

let ElasticsearchQueryBody = {}
if (Request.searchQuery instanceof SearchQuery) {
ElasticsearchQueryBody = await prepareElasticsearchQueryBody(Request.searchQuery)
Expand Down Expand Up @@ -94,7 +91,7 @@ export class SearchAdapter {
if (resp.hasOwnProperty('hits')) {
return {
items: map(resp.hits.hits, hit => {
return Object.assign(hit._source, { _score: hit._score, slug: (hit._source.hasOwnProperty('url_key') && rootStore.state.config.products.useMagentoUrlKeys) ? hit._source.url_key : (hit._source.hasOwnProperty('name') ? slugify(hit._source.name) + '-' + hit._source.id : '') }) // TODO: assign slugs server side
return Object.assign(hit._source, { _score: hit._score, slug: hit._source.hasOwnProperty('slug') ? hit._source.slug : ((hit._source.hasOwnProperty('url_key') && rootStore.state.config.products.useMagentoUrlKeys) ? hit._source.url_key : (hit._source.hasOwnProperty('name') ? slugify(hit._source.name) + '-' + hit._source.id : '')) }) // TODO: assign slugs server side
}), // TODO: add scoring information
total: resp.hits.total,
start: start,
Expand Down
Loading