-
Notifications
You must be signed in to change notification settings - Fork 2.1k
[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
[WIP] PoC - multipart, dynamic URL rewrite #2401
Conversation
The second phase can - adding the |
Notes:
|
In my opinion we should just change the way we are fetching data inside stores. As far as i understand the problem is that we need to pass additional params to determine what route we are fetching so why we can't just add proper keys in the By having this we can just use plain redirects but based on WIth this param the redirection flow will look like this:
Since routes are resolved top to bottom it should just be the first route after all static ones: const router = new VueRouter({
routes: [
// static routes like /about, ./whatever
{ path: '/:key', redirect: to => {
const type = checkKeyType(key) // check if to.params.key is product/category/what ever and set everything needed in vuex
next({ name: type, path: '/' + key })
}},
// above function will redirect to one of these two:
{ name: 'category', path: '/:categoryId' component: Category },
{ name: 'product', path: '/:productId', component: Product }
]
}) This mechanism can be used even without having this unique keys. We just need a helper that can determine what type of entity it is and voila - we have one helper instead of full module and everything is kept inside router config so full transparency and separation of concerns. We also avoid hacks with |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It can be extremely simplified
Filip I was considering these options:
|
So my approach is mixed of what you’ve proposed (url/mappingFallback and url/mapUrl is this fallback to determine which route is it) adapted to make no redirects. Users can override these actions with their modules to have different mapping logic I plan to make it simpler and more elegant and even more flexible with async components: https://vuejs.org/v2/guide/components-dynamic-async.html So it probably won’t by a single file component (I mean url dispatcher) but just JS function + Product, Category, ... will be loaded using import(). With this urldispatcher approach - which Ian pretty standard for any CMs on the market (including Magento, Drupal and others) - having a Front Controller / Dispatcher design pattern implemented - we will have the flexibility of adding the redirects support when products are updated etc We could then also include a support for the Magento url resolver: #2010 (comment) Basically - my approach isn’t any fresh idea, it’s just kind of implementation for Magentos resolver in Vue.js :) |
Vue.js should have not only “next()” but also “router.forward()” forwarding the request flow to different route without switching the url. This is reason we can’t use this elegant solution that I’ve spent 2.5h on yesterday (I’ve implemented it as a route guard and it was almost perfect :)) |
@pkarw i was thinking more about having this mapping property in product object (so it can be searched through elasticsearch) not as one downlaodable api endpoint. Regarding pattern matching - This is not a redirect if we keep it in the same route. Your problem is with browser history so it's worth investigating if the redirection record can be ommited since there is no actual redirect happening at all - only a javascript code adding route to browser history (for example resolving this I'm not tied to my solution if you tested it out and it didn't work. Apart from this I think implementing logic from magento backend and forcing frontend devs to use different patterns they used to is opposite of what we are trying to achieve with Vue Storefront and it's backend decoupling approach. What benefits we have over magento if we will start complicating things like them? In other words - if we can avoid complexity and unknown patterns - we should try ;). Specifically there shouldn't be any Vuex store here since it's nothing related to managing data. So assuming I understood you well mix of this two solutions would be just one component (instead of whole module) similar to what <template>
<component :is="pageType" />
</template>
<script>
export default {
data () {
return {
pageType: null
}
},
props: {
uniqueId: { required: true } // unique identifier used to specify which page we should render
},
methods: {
setPage () {
const pageData = getPageData(identifierFromRoute) // gets Vuex data (which previously was route params) for a given page required to fetch it (like product SKU etc) and type of page to render
setPageData(pageData) // sets Vuex for given product/category/whatever was matched by getPageData()
this.pageType = pageData.type
}
}
</script> // Edited many times, read again if you already did from notification in email |
Ok! Let me work on this for a little bit longer so I would be able to show more clearly how I see it working. Yesterday I was more focused on checking the options than on final solution :) thanks for the feedback it’s very helpful 👍 |
This static section very depends on the current database structure - so I removed it
TODO:
|
}) | ||
}, | ||
mapUrl ({ state, dispatch }, { url, query }) { | ||
const parsedQuery = typeof query === 'string' ? parseURLQuery(query) : query |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll change this one to https://www.npmjs.com/package/query-string
@pkarw can you describe more or less where this URL comes from and how on abstractive level this mechanism works? I have troubles understanding it. Also i think instead of going into a simplified architecture that can be adopted with nuxt and vue-cli we are going the opposite with custom hydration process |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR is extremely overcomplicated, violating separation of concerns, repeating logic, breaking native behavior that may lead into tons of potential future problems when we want to modularize VS. Every if
check is a code smell and at this point logic is unreadable.
I suggest meeting and discussing the approach to handle this problem first and code it when we will have proper answers since right now it's impossible to review even the idea since it's not clear at all from the code.
Imho in this form it'll break few milestones of simplifying VS.
Also it contains many of the things @lukeromanowicz with me and @patzick identified as ones we need to got rid from VS to make it more udnerstandable and easier to maintain. Right now bc of things like this there is only one person in the core team that understands most of VS logic and why every new core team member tells su that we shoudl refactor it.
People are using VS bc there is no better option on the market yet and direct competition is magento so basically everything is simplier than this but it's gonna change as soon as competitors will become mature and it will turn out that it's impossible to scale and customize VS in easy way and it'll become second Magento.
Sorry if this sounds mean - it's not meant to. It's just a honest opinion on overcomplicating our logic and 'happy coding' process instead of debating about problems and figuring out proper way to solve them.
// edited multiple times, please read again when used email notifications ;)
@@ -0,0 +1,5 @@ | |||
import { Logger } from '@vue-storefront/core/lib/logger' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
file is empty
@@ -0,0 +1,5 @@ | |||
import { AsyncDataLoader } from '@vue-storefront/core/lib/async-data-loader' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
file is empty
@@ -0,0 +1,79 @@ | |||
import { module } from './store' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
helpers should be placed in proper folder, not in a root.
|
||
if (from.name) { // don't execute the action on the Client's side of the SSR actions | ||
if (_matchingComponentInstance.asyncData) { | ||
AsyncDataLoader.push({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AsyncDataLoader will be removed in next version, instead update to vue 2.6 and use native nehavior
export default { | ||
computed: { | ||
page: () => { | ||
return _matchingComponentInstance |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this shouldn't be a computer property + you shouldn't use arrow functions in computed properties
// This object should represent structure of your modules Vuex state | ||
// It's a good practice is to name this interface accordingly to the KET (for example mailchimpState) | ||
export interface UrlState { | ||
dispatcherMap: {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
missing typings
Let’s discuss this on Monday - core planing at 10:00. |
Ok, but please just describe how it works. We can't understand it with @patzick // btw i edited prev post to better ilustrate my point |
UrlDispatcher is checking the custom url with mappingFallback - method that can be customized/overridden. It’s returning the route data where this irks is pointing to. So this mechanism was created as a separate layer over existing Product and Categor /CMS pages. More over it’s supporting all content types not only lroducts and categories as url dispatchers operates on the touring tables defined in the theme. I had huge problems with dynamically loading and hydrating the components and with the changes of url within the same routes - for example clickingvrelated products on product page don’t execute full router flow and not resolving lazy loaded components (kind of cache). |
Overall I’ve spent 24 hours in total just on experiments.:; |
where this custom url comes from? |
Alternative solution is to totally refactor / rewrite url dispatching - for example not rely on sky but only on url_path in product.js - marching “*” with both product and category pages and then executing kind of “next” if this url doesn’t match category but product etc. but this will be really hard to understand ... Now it’s pretty simple: this is another layer - separate module - which is optional. Can be plug in if you like to have these routable urls. |
@@ -78,6 +78,7 @@ export default { | |||
inject: isProd == false | |||
}) | |||
], | |||
devtool: 'source-map', |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 :(
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
* Router mapping fallback - get the proper URL from API | ||
* This method could be overriden in custom module to provide custom URL mapping logic | ||
*/ | ||
mappingFallback ({ commit, dispatch }, { url, params }) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here is the fallback if route hasn’t been cached we’re mapping url to route using server call - by default request for category/product are separated but it can be optimized with just one call to /api/mapUrl - or something like this. Here is the source for where the urls come from @filrak
Please analyze this solution carefully and propose other approach as really: there are not many different options for having this feature. At last from my r&d. I’m totally frustrated with this feature to be honest. Would love if somebody else will do this better way be cause I’m also not very happy with the overall complication and of the duplicates logic of client-entry.ts vs UrlClientDispatcher.ts.
I’ve had some partial successes with other approaches but always there was some deal breaker. The biggest problem was with not executing the full Vue-router flow in case when route has not changed. Imagine the situation when user request is handled by the by “urldisparcher” and user then clicks another time product link. Vue is just executing the watch on $route inside current component. In case if you are on product component clicking on category link you have no way to lazy load category component and replace the current one ....
I’m open to other solutions / PoC @filrak and @patzick. Acceptance criteria were like this:
|
Thanks we will try to figure it out. Basically we will just try to take your solution as a base and see how we can simplify it in terms of code. If it can't be then we just need a different solution. I believe that if achieving something requires too much conplication then the approach is wrong and it should be rethinked from a different perspective |
To not make the core more complicated we can probably publish this module as a separate / non-core one. Maybe someone will bring better solution later :) |
@filrak sure, that makes sense. I haven’t found any other dynamic routing solution for Vue. It could be cool hook for contribution if done properly. The simplest way it should work would be if Vue-router supported plugins / or dynamic routes so we could inject them dynamically. In that case we could have just insert static mapped url { path: “/something/product”, params: { sku: mappedSku..... This approach would be the cleanest one. It must be dynamic and we can’t geberate static routing for all urls as there could be lol 100k+ entries on mid size fashion store. Maybe there is way to insert routings dynamically so we don’t have to deal with all the other hacks like UrlClientDispatcher things. Haven’t tested dynamic “addRoutes” executed in the guard. Maybe we should inject addditiknal static route (from url/mapUrl) in the guard and call just next???? |
} | ||
|
||
export const UrlDispatcherGuard = (to, from, next) => { | ||
if (store.state.config.seo.useUrlDispatcher) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can take care of this
Dynamic routes generation seems fine. I'll ask Eduardo who is a current maintainer of Vue router about this approach |
If this dynamic routes will work then we’re saved 😎it will be ultimate solution: simple, elegant and compatible with all the acceptance criteria. Thanks for this feedback anyway 👍 |
vuejs/vue-router#1083 The first link looks like our case; the second one looks like our new proposed solution :) |
Awesome. So i think we have some common ground now! ❤️ |
Btw we should also think how to get rid of this config 'ifs' all around the project. Its producing module-specific side effects and makes our codebase bigger even when someone is not using particular features + it violates separwtion of concerns and encapsulation . I guess we need some kind of composition here instead of conditions |
Closing in favour of #2446 |
Related issues
#2010
Short description and why it's useful
This is proof of the concept of dynamic URL mapper. The challenge is that we need to support the fully customizable URLs - that don't have any specific elements pointing out if that's the Product, Category either CMS page.
Unfortunately, we CAN NOT use the vue-router guards as there is no way to
rewrite
URL (display different route/different component under the same URL). The only option was to create a page which is including the proper subcomponents.We should decide if that's the way we would like to go. If so, we should then decide if this module should be placed in
core
or rathersrc
- to let the users modify it when needed. After all - I think it should besrc
. The Dispatcher can vary from project to project - mostly because some custom page types added.If we decided that this is the way to go -> I'll add the rest of the business logic.
Other options for URL dispatching mechanism:
To test this out - please enter the http://localhost:3000/fake/product/url.html in the browser - and You'll get the
Gwyn Endurance
product page.I've got an idea that the
url/registerMapping
will be called inside thecategory/list
andproduct/list
matching the products and categories with their custom URLs (product.url_path
+product.url_key
andcategory.url_path
+category.url_key
). If some random URL will be called (that's been not yet mapped) then theurl/mappingFallback
action will be called.This action will be a subject to override by user - but be default it will be calling first
product/single
and thencategory/single
to check if specified URL is a product or category URL.Once mapped URL will be stored in localStorage +
url.state
(because we'll be getting the prefetched mappings from SSR renderer within this initial state)Screenshots of visual changes before/after (if there are any)
Screenshot of passed e2e tests (if you are using our standard setup as a backend)
(run
yarn test:e2e
and paste the results. If you are not using our standard backend setup or demo.vuestorefront.io you can ommit this step)Upgrade Notes and Changelog
Contribution and currently important rules acceptance