Open rglover opened 1 year ago
Mock that just came to mind...
import SomeComponent from '/ui/blah';
joystick.spa.router({
'/path/to/:thing': (req = {}, res = {}) => {
// req?.params req?.query req?.url
// res.render() is only res method defined.
// Idea is to make it so that the above req and res mimic the relevant parts of those objects on the browser.
// Not a 1:1 copy, just a way to keep routes consistent.
return res.render(SomeComponent, {
layout: SomeLayoutComponent,
props: {
someProp: 123,
},
});
},
});
Usage of a .spa
object in the chain there is intentional. Want to be certain that developers don't confused the above as the main router (and suggest that it has a specific purpose).
This could run in index.client.js
identical to how node.app()
and its routes run in index.server.js
.
Thinking about this again and routing via links. Could do something like joystick.spa.location('/path/to/go/to')
which only maps to routes defined via the routes table above.
Another option (though could confuse) is to have some sort of spa: true
on the node.app()
function server side which tells the client to automatically nab all <a></a>
tag clicks and route them via joystick.spa.location()
.
I keep coming back to this and the solution seems to be doing a client-side router with dynamic fetches of pages. I just realized this isn't too far from how client-side hydration works now, so short of URL parsing wouldn't be too wild to implement. Instead of the above importing of components (messy), we can just rely on the existing public routes for components:
import { router } from '@joystick.js/ui';
router({
'/tester': (req = {}, res = {}) => {
// req?.params // req?.query
res.render('ui/pages/index/index.js', {
layout: 'ui/pages/layouts/app/index.js',
});
},
'/tester/another': (req = {}, res = {}) => {
// req?.params // req?.query
res.render('ui/pages/index/index.js', {
layout: 'ui/pages/layouts/app/index.js',
});
},
});
Another riff on this. You could also do an additional folder at /ui/spas
and just have Joystick components that represent an SPA (designated by a routes
option on the component). These could work identical to how layout components work with a few modifications:
import ui from "@joystick.js/ui";
const App = ui.component({
routes: {
'/tester': (req = {}, res = {}) => {
// req?.params // req?.query
res.render('ui/pages/index/index.js', {
layout: 'ui/pages/layouts/app/index.js',
});
},
'/tester/another': (req = {}, res = {}) => {
// req?.params // req?.query
res.render('ui/pages/index/index.js', {
layout: 'ui/pages/layouts/app/index.js',
});
},
},
render: ({ props, component }) => {
return `
<div>
${component(props.page)}
</div>
`;
},
});
export default App;
Was thinking about this the other night and there's potential to automate it. Basically, the same way that we handle a mount for layouts (dynamic import) could hypothetically be used for an SPA.
The trick would be to have a flag on the node.app()
object like spa: true
which would tell Joystick to bundle a client-side route listener. That listener would intercept all <a>
clicks and instead of doing the browser default, call to the server endpoint, get the response, and dynamically render it in the browser.
The goal/advantage being to remove any need for separate client-side routes config like the above. Define your routes once on the server, flag the app as spa: true
and you're done.
Thinking about an API for a programming location/push API for this and landed on this:
ui.location('/path/to/place', {
queryParam: 'thing',
});
This should also have some sort of hook logic for things like analytics. Something like...
// index.client.js
import joystick from '@joystick.js/ui';
joystick.spa.on('navigate', (event = {}) => {
// event.from (from path)
// event.to (to path)
});
In terms of implementing the automated version of this, I think I can just steal from the new HMR stuff and how I do remounting there.
Note I wrote in passing:
For client render:
You will need to check if you're rendering a standalone page or a layout. If a standalone page, just hijack the <a>
tags and handle their redirects with push state.
If layout, you will need to add a method to the component class instance like handleSetProps() which sets the
props for the component and then triggers a re-render. This would allow you to just get the current layout
component instance, set a new props.page (dynamically fetched/imported version of the page being routed to),
and trigger a re-render.
I'd want this to more or less mimic what we have on the server. Ideally, have a way to automate this via
@joystick.js/node
by passing anspa: true
flag tonode.app()
on the server that would bundle a routes table on the client. The idea being that when that is true, all hrefs on the client are automatically treated as internal and route via the History object.Need to think and play to see if this is the best way to handle SPAs. I really want to avoid the mess that pattern has created but there are some legitimate applications for it and it's ignorant to make it second-tier functionality.