single-spa-layout
single-spa-layout copied to clipboard
Add single-spa-link component
Inspiration:
Within the nuxtjs framework they have a feature where when the <nuxt-link>
component is on a page, it prefetches the associated js that is going to be loaded by that link.
Proposal: Instead of using vue-router, or react router lets create a top level single-spa-link/router set of components which would allow us to do this. When we see the top level navigation this could prefetch all of the bundles associated with these routes.
This would result in more initial network requests, but it could also be put to the back of the queue to ensure that all other network requests have happened within someones application to ensure that we do not block any data loading.
Benefits:
- Faster loading time when navigation through pages
- could be toggled on / off via some props if users do not want it to happen
Cons:
- could increase network overhead
What do you think @joeldenning
Interesting idea, I was not familiar with <nuxt-link>
. I think this could be good to add.
I don't think that this would need to be part of single-spa-layout, but could be part of single-spa-vue, single-spa-react, and single-spa-angular? Example React implementation:
// single-spa-link.component.js within single-spa-react
import { useEffect } from 'react';
import { checkActivityFunctions, preloadApplication, singleSpaNavigate } from 'single-spa';
export function SingleSpaLink({prefetch = true, to, children}) {
useEffect(() => {
if (prefetch) {
const activeApplications = checkActivityFunctions(new URL(window.location.origin + to))
activeApplications.forEach(preloadApplication)
}
}, [prefetch, to])
return <a href={to} onClick={handleClick}>{children}</a>
}
function handleClick(evt) {
evt.preventDefault()
singleSpaNavigate(evt.target.href)
}
The above code would work regardless of whether one is using single-spa-layout, but would require the addition of the preloadApplication
api to single-spa. Alternatively, we could tie it to single-spa-layout, but I don't see many advantages to that. What do you think?
Would we want to support someone passing in their router link and we render children? Eg someone using react router links and just wants to wrap <Link/>
with our wrapper?
Part of me thinks that doesn’t make sense ( if you know you’re not routing to an external app then why would you wrap it?) but at the same time it does make sense (you never know if in the future that link turns into a different app).
Adding preloadApplication shouldn’t be too hard, though I think it should be called preloadApplications because the app really doesn’t know if one or many apps are active at the new location
Would we want to support someone passing in their router link and we render children?
Interesting idea. I currently really like and prefer how single-spa applications use the framework's built-in link components instead of a single-spa-specific one. Because of that, I generally discourage using singleSpaNavigate
in favor of just using <router-link>
, <Link>
, etc. So the idea of wrapping the framework-native component is appealing to me. However, I don't know what an implementation would look like for that at a general level for all frameworks.
Note that one difference between using singleSpaNavigate
versus the framework's built-in Link component is that in the former the framework re-renders once due to popstate, whereas in the latter the framework re-renders 1-2 times, due to onClick and popstate.
Adding preloadApplication shouldn’t be too hard, though I think it should be called preloadApplications because the app really doesn’t know if one or many apps are active at the new location
I was thinking the preloadApplication
api would be called with a string application name, not a Location/URL object. The checkActivityFunctions
API already exists to map a Location/URL into an array of strings.
// inside of single-spa
export function preloadApplication(name) {
const app = apps[name]
if (!app) {
throw Error(`No such application ${name}`)
}
return toLoadPromise(app)
}
Perhaps single-spa-link is actually just single-spa-preload and only takes in a single “to” prop. And then just renders children.
Then in the preload component we can do a requestIdleCallback to preload the next app when the browser isn’t doing anything.
Ah requestIdleCallback doesn’t have the browser support we would probably need though :(
Using requestIdleCallback would be cool - I’m sure we could feature check it and use something else for old browsers
Agree, it could live in each individual frameworks single-spa package. Could test it with vue / react first and then depending on the results of that check out how other frameworks will work with it.
@EvanBurbidge would you be interested in adding the preloadApplication
API to single-spa?
I think something like this could be a decent start:
export function preloadApplication(name) {
return new Promise((resolve, reject) => {
const app = apps[name]
if (app) {
const method = window.requestIdleCallback || setTimeout
method(() => {
resolve(toLoadPromise(app))
})
} else {
reject(Error(`No such application ${name}`)
}
})
}
Should we also allow passing in a time for when the idleCallback/timeout is required to run?
Making it part of the API means we must commit to backwards compatibility - are we confident that we'd want to support passing in a millisecond delay even if the implementation changes?
Interestingly, using requestIdleCallback may not be good here? source
Is there any kind of work I shouldn’t do in a requestIdleCallback? Ideally the work you do should be in small chunks (microtasks) that have relatively predictable characteristics. For example, changing the DOM in particular will have unpredictable execution times, since it will trigger style calculations, layout, painting, and compositing. As such you should only make DOM changes in a requestAnimationFrame callback as suggested above. Another thing to be wary of is resolving (or rejecting) Promises, as the callbacks will execute immediately after the idle callback has finished, even if there is no more time remaining.
I began work on a <router-link>
component today for single-spa-layout. The preloading behavior won't be part of the initial implementation, but that part is very easy.
@joeldenning I had a similar use case around preloading the application, instead of doing it automatically can't we let users decide when they want to preload the application? I would be happy to raise the PR for the same. Thanks
In my use case, I want to preload the application when a user does some kind of interaction and I'm sure the browser won't be doing any heavy work. https://github.com/single-spa/single-spa/discussions/1044
Nevermind, Found a easy solution for my use case with SystemJS import, https://stackoverflow.com/questions/73659115/single-spa-background-loading-of-child-application/73669369#73669369