How to prefetch a lazy-loaded feature in Spartacus - spartacus-storefront

There exists a cool library https://github.com/mgechev/ngx-quicklink of Minko Gechev for automatically prefetching Angular lazy loaded routes for links that are visible in the page.
However, the lazy loading of features in Spartacus is not based on routes, but on CMS components. So how can I manually prefetch a lazy-loaded module in Spartacus?
PS. Here are few possible places for prefetch’es, from top of my head:
in added-to-cart modal -> prefetch the checkout
in homepage -> prefetch product? (and/or prefetch cart)
in PDP -> prefetch cart
in PLP -> prefetch product? (and/or prefetch cart)

In Spartacus, you can prefetch a lazy-loaded feature module manually, with a little custom code:
// manual prefetch of CART_BASE_FEATURE lazy loaded feature:
this.featureModulesService.resolveFeature(CART_BASE_FEATURE).subscribe();
Because the CMS-driven lazy loading in Spartaus is not strictly related to routes, you’d need to implement such prefetch’es deliberately. Preferably following the best practices mentioned also in the README of ngx-quicklink library:
wait until the browser is idle (using requestIdleCallback)
check if the user isn’t on a slow connection (using navigator.connection.effectiveType) or has data-saver enabled (using navigator.connection.saveData)

Related

NextJS multiple dynamic routes with different destionation

In my project I have the following requirement: having 2 routes that are dynamic but that are going to be drawn by different components.
More context:
I have a CMS that provides all the info and in most of the cases I'm going to read from a "page" model. This is going to be drawn by the Page component and its going to have a getServerSideProps that makes a call to a CMS endpoint to get that information
The endpoint and component are not the same for the other case with a dynamic route
Both types of routes are completely dynamic so I cannot prepare then in advance (or at least I'm trying to find another solution) since they come from the CMS
As an example, I can have this slugs
mypage.com/about-us (page endpoint & component)
mypage.com/resource (resources endpoint & component)
Both routes are configured using dynamic routes like this
{
source: '/:pages*',
destination: '/cms/pages'
}
The problem here is that it can only match one of the endpoints and the logic for both calling the endpoint and the component used to draw it are completely different
Is there a way of fallbacking to the next matched URL in case that there's more than one that matches similar to the return {notFound: true}?
Some solutions that I have are:
Somehow hardcode all the resources urls (since are less than pages) and pass them as defined routes instead of dynamic routes
Make some dirty code that will check if the slug is from a resource and if not fallback to a page and then return a Resource component or a Page component depending on that (this idea I like it less since it makes the logic of the Page component super dirty)
Thanks so much!

VueJS Process complete template before operations

Good afternoon.
I have an interesting problem at the moment. We have a third party server that offer translations for static html content. I need to fetch this content via Ajax and display it in my Vue components.
The current situation
These translations are fetched via a dictionary-like data structure, i.e. via a category and a key. We have incorporated a Vue plugin to load these into our components via a function t, like this:
<template>
<section>
<h1>{{ t('CommonHeaders', 'HomePage') }}</h1>
<p v-html="t('Articles', 'SiteDescription')"></p>
</section>
</template>
At the moment these translations are shipped to the browser by embedding them in the HTML, after which our client-side hydration mechanism reads them and adds them to the Vuex store. The t function then looks up the translations and displays them where needed. These translations are reactive and accept data parameters to format translations.
We use Vue SFC to render user flows in an SPA-like fashion, although the site is not yet an SPA.
The problem
In order for this to work the translations required for a page have to be listed in the back-end controller methods in a dictionary.
This has become un-maintainable and much more data is shipped to the front-end than what is necessary. Additionally, the back-end system has no definitive end-point when a page is built before being shipped to the browser that we can hook into in order to add the translations to the HTML and content often end up being duplicated.
The back-end system was built using DotNet MVC 4, so we have no SSR capabilities at this point.
The solution (hopefully)
In order to better maintain our code I would like to utilise the 't' function from the plugin to load translations via an AJAX call before the vue engine has rendered the template, i.e. via the beforeCreate or created hook. The problem is that the Vue instance will have to know about translations required in child component templates before the AJAX call can be fired, and I have no idea how to accomplish this.
On a side note, delaying rendering like this goes against all my instincts but it doesn't look like we have a choice at this point.
I am planning to cache the translations client side with a content hash in case they get updated, so the ajax calls will hopefully not be required very often, only on first load.
The site is gradually being converted into an SPA, at which point I will be able to split off the FE and utilise SSR via node. Up until that point though this is the best idea I could come up with.
Any help will be greatly appreciated.
I have been thinking about this my self as at the moment I just send an entire cached json to the client on App Init with a loading screen, is not bad at the moment since there is not a lot to translate but was considering the following approach otherwise:
Have an array in the translation vuex module store a list of keys that need to translate (array).
Have t() push the keys to translate if not already in translated store and return either empty string or a placeholder until the translation is re-actively available.
On mounted dispatch a fetch method on the store to perform the ajax call and set the translation state and clear out the translate list when complete.
Vue should by default it's behavior re-render upon the VUEX state being changed and cause t() in the template body to be recalled and return matching values on nextTick instead of placeholder value previously returned.

Assign/add mixin or sub-component at runtime?

Can I dynamically add a mixin? I think I read that runtime insertion of mixins is not supported and will never be. Is the below runtime insertion possible?
My usecase is; all our pages are stored in a database, each page standard properties like; title, content and template. We have components for each template. Each template component displays the title and content differently. So I need to build the routes and say this page uses this (template) component. Maybe I can use sub-components to achieve this? Can I dynamically add sub-components at runtime?
The easiest solution is to do the following:
Router:
// myPages retrieved by REST call
const routes = _.map(myPages, page => {
return {
path: `/${page.url}`,
name: page.name,
component: DefaultPage // make all pages use DefaultPage component
}
});
DefaultPage.vue
<template>
</template>
<script>
import mixins from './mixins';
export default {
mixins: [mixins.Base]
beforeMount() {
// I dont think this is possible?
let templateMixin = mixins[ this.page.template ]
this.mixins.push( templateMixin );
}
}
</script>
Maybe its possible to assign a sub-component at runtime?
<template>
// Somehow call the sub-component (template)?
<template></template>
</template>
<script>
import templates from './templates';
export default {
components: {},
beforeMount() {
// Is this possible?
let templateCmp = templates[ this.page.template ]
this.components = {
templateCmp
}
}
}
</script>
Unfortunately there is a lot of misinformation on the web stating that "because of userland perils, it's not safe or secure to load dynamic or "runtime" components", and that doing so is "a security risk".
This stems from the reasoning that Vue uses the eval statement when compiling components. However, so does React and Angular. There is in fact no way to compile components without this. while eval is a sharp knife, so is any JavaScript. Saying that a sharp knife is insecure is false as long as you keep that sharp knife itself secure.
The notion that runtime components is insecure is utterly and completely false. There are tons of officially supported ways to do this if you control the source. In fact, there is even an officially Vue supported "userland" method to load dynamic/async components even if you don't control the source(!!)
Async components
Probably the easiest way to load a page from a database (which itself may have it's own sub-components, mixins and dependencies), is with async components.
This is an official part of Vue and I've used it in dozens of production apps. It works exactly like expected; nothing is invoked by your application until all the conditions are met, and when the load conditions are met, you are free to obtain the component however you see fit.
Note: You must control the source of these components, or XSS injection and other hacking is possible.
If you're using webpack, it's easy to get all the components dependencies into a single file. In the above URL are specific how-to articles including a video tutorial of how to produce a single js file for your pages dependencies that aren't part of your main application.
One caveat for loading multiple sub components this way is you may overlap/repeat loading (i.e. page A may load 5 sub components that no other static page loads, and page B may load 5 sub components, 3 of which are shared with page A, and 2 of which are specific to page B). Caveats like these can mess up your optimization techniques if you don't think them through. This can be unavoidable and fine, and still much faster than loading the entire app at once though.
Async components in userland
If you only want to load custom templates from a database (i.e. if you don't want to allow users to load their own custom components, mixins, filters, directives, etc), with each template, then you are in luck; Vue even has official support for async templates that are locked-down to only allowing template changes. This is enough to offer your component builders scaffolding for creating an app, but prevents them from executing arbitrary JavaScript code (this method won't let them setup a data section, or hook into the component lifecycle for example).
v-runtime-template is officially recognized by the Vue.js team as the official method for creating userland-safe Vue templates.
I've used v-runtime-template in platforms used by some of the biggest names in the industry across tens of millions of users without a single security breach, because this method only exposes the components you say are OK.
Further template lockdown using JSON schemas
If you need only form generation, or you can simplify your components further, and you only need to reason about data in simple ways like queries (i.e. if you're building a survey generator, or an analytics or other widget dashboard), you can build components out of JSON Schemas using vue-form-json-schema. This method takes 2 schemas: one for your data, and one for your form. The parent component you load specifies all the components that are accessible to the schema, so you must whitelist components that userland has access to, further, the forms cannot run arbitrary JavaScript, they can only call functions that you make available in the parent component, which are your JavaScript whitelist.
Userland-Safe Queries on arbitrary data
You can let users query specific JSON objects for use in completely-safe userland queries that can be provided by the public using JSONata. Developed by IBM, JSONata is a way to safely query and, with the exposure of a few functions, allows your users to even manipulate data in verified-safe ways.
Although JSONata is logically proven to be safe in unserland, it is also Turing-complete without functions, meaning your users can manipulate the Schema provided data by outputting new data that can technically do anything; i.e. your users can create Doom 3 using JSONata queries. People have made games like Pong and other interesting things out of JSONata.
JSONata's power cannot be overstated. It powers the magic behind nearly all no-code/low-code platforms like the open source Node-RED, and is behind many other proprietary no-code/low-code platforms that are closed source, including Google, Microsoft, Amazon and IBM platforms.
They use JSONata to translate data schemas between platforms, and whenever business logic is needed on data manipulation, and the manipulation must be left in user-land (i.e. an app that needs to manipulate data, but you want to run that app on your platform and not have to worry about QA or people being able to hack or write a nefarious app that breaks into your platform).

AngularJS dynamic application with or without routing

My application has 2 purposes:
It needs to run stand-alone, where it needs routing for choosing a
study etc.
Or, it runs integrated in an other project, and only needs
one controller and one view.
Currently i have a routeProvider configured for the stand-alone application, injecting the pages in the ng-view tag in the HTML.
Now is my question: How can i inject an controller and view in the ng-view (For the integration). I cannot manipulate the HTML since it is static. I cant use a single routeProvider rule, because this can interfeir the application that integrates mine (Other plugins can use the #/.. for info or other things).
In your situation you can't use routeProvider when other stuff interferes.
Of Course you could prevent routeProvider to act on outside changes of the hashbang with workarounds but thats not nice.
routeProvider will listen to all changes of the url after the hashbang.
So what you should do is to manually bootstrap() your angular app with the controllers you need. If your app is small enough you could even use directives to achieve lazy loading of templates with the attribute templateUrl : "/myurl"
Usually to create a dynamic App use Routing. Simnple point.
The best way to use Angular if you want to unleash all its might don't integrate it.
I explain why:
+ Your state never gets lost due to page reloads
+ You have full control of the environment and don't have to worry about interfering scripts etc.
+ If your user should manually reload, you can redirect to home/login or even better use requireJS or HTML5 local storage to recover your scopes after a reload
Cheers, Heinrich

Backbone.sync – Collection using ajax as well as Socket.IO/WebSockets

I have a Backbone application, which has a collection called Links. Links maps to a REST API URI of /api/links.
The API will give the user the latest links. However, I have a system in place that will add a job to the message queue when the user hits this API, requesting that the links in the database are updated.
When this job is finished, I would to push the new links to the Backbone collection.
How should I do this? In my mind I have two options:
From the Backbone collection, long poll the API for new links
Setup WebSockets to send a "message" to the collection when the job is done, sending the new data with it
Scrap the REST API for my application and just use WebSockets for everything, as I am likely to have more realtime needs later down the line
WebSockets with the REST API
If I use WebSockets, I'm not sure of the best way to integrate this into my Backbone collection so that it works alongside the REST API.
At the moment my Backbone collection looks like this:
var Links = Backbone.Collection.extend({
url: '/api/links'
});
I'm not sure how to enable the Backbone collection to handle AJAX and WebSockets. Do I continue to use the default Backbone.sync for the CRUD Ajax operations, and then deal with the single WebSocket connection manually? In my mind:
var Links = Backbone.Collection.extend({
url: '/api/links',
initialize: function () {
var socket = io.connect('http://localhost');
socket.on('newLinks', addLinks)
},
addLinks: function (data) {
// Prepend `data` to the collection
};
})
Questions
How should I implement my realtime needs, from the options above or any other ideas you have? Please provide examples of code to give some context.
No worries! Backbone.WS got you covered.
You can init a WebSocket connection like:
var ws = new Bakcbone.WS('ws://exmaple.com/');
And bind a Model to it like:
var model = new Backbone.Model();
ws.bind(model);
Then this model will listen to messages events with the type ws:message and you can call model.send(data) to send data via that connection.
Of course the same goes for Collections.
Backbone.WS also gives some tools for mapping a custom REST-like API to your Models/Collections.
My company has a fully Socket.io based solution using backbone, primarily because we want our app to "update" the gui when changes are made on another users screen in real time.
In a nutshell, it's a can of worms. Socket.IO works well, but it also opens a lot of doors you may not be interested in seeing behind. Backbone events get quite out of whack because they are so tightly tied to the ajax transactions...you're effectively overriding that default behavior. One of our better hiccups has been deletes, because our socket response isn't the model that changed, but the entire collection, for example. Our solution does go a bit further than most, because transactions are via a DDL that is specifically setup to be universal across the many devices we need to be able to communicate with, now and in the future.
If you do go the ioBind path, beware that you'll be using different methods for change events compared to your non-socket traffic (if you mix and match) That's the big drawback of that method, standard things like "change" becomes "update" for example to avoid collisions. It can get really confusing in late-night debug or when you have a new developer join the team. For that reason, I prefer either going sockets, or not, not a combination. Sockets have been good so far, and scary fast.
We use a base function that does the heavy lifting, and have several others that extend this base to give us the transaction functionality we need.
This article gives a great starter for the method we used.