Skip to content

Commit

Permalink
Feature support for async prefetch in inferno-router (#1621)
Browse files Browse the repository at this point in the history
* initial code

* test beginning to work

* working implementation with server side prefetch of loaders for SSR + rehydration

* Fixed call to loader on first render

* Fixed tests and added SSR and browser render

* Moved SSR render test to inferno-server

* Shrink and move

* Use async await on server

* Fix tests

* renamed loaderData to initialData

* Pass match params to async loader

* Move first invokation of loader to lifecycle hook where it belongs

* The condition can be solved through recursion for readability and reduced bundle size

* Some tests working

* Fixed regression bugs

* Remove moved code

* Improve typing and structure components to be more similar. Added switch test

* Added demo application to test inferno-router with async loading

* Added note on possibly false test

* unused props

* Demo: SSR (WIP) with resolving of loaders and using Parcel to transpile app on startup

* Serve a proper SSR page to browser

* SSR and hydration

* Moved invocation of loader to Router

* Fixed redirect issue that broke Switch tests

* Notes for investigation of request

* Demo using params in loader

* Added test with params

* First, dumbed down, try at adding request param and AbortController to allow cancelling inflight fetch on consecutive navigation WIP

* Add test for abort signal on nav

* Fix demo

* Remove duplicate code in inferno-server

* Simplify inherited code

* Simplify again, Location objects return a href string

* Change deprecated call, cleanup and and typing

* Switch only renders first match so bailing out early

* Don't traverse deeper if Route doesn't match

* Removed loader code in Switch since it is handled by Router now

* Change name to loaderData for individual match

* Fixed SSR-rendering and added base argument to traverseLoaders to allow creating fully qualified URI from route path during SSR (if one wants to use it in the loader for hybrid endpoints)

* Remove unused imports

* Remove unreachable code

* Unused types

* Support subclassing Switch

* Send cancel signal in demo

* Fix issue where multiple nav clicks with loaders resolving out of order wouldn't end up on the last clicked route

* Support typing response from loader data/error helpers

* Use cross-env for Win-compat

* Fix loader signature in AboutPage.tsx to allow calls to be cancelled

* Fixed linting errors

* Use custom resolver to allow parcel to choose correct bundle when packaging

* Don't expose parentIsSwitch in exported function traverseLoaders since it is an implementation detail

* Trigger rerender after promises have been resolved to make sure any pending renders are flushed

* Update test to make sure we don't get false passing (I have broken the library code to verify that test fails)

* Missed a Switch check

* Remove parcel-resolver-inferno and use alias instead to pick transpiled dev version in demo

* Remove need for esModuleInterop and isolatedModules

* Update README for inferno-router

* Update docs

* Added link to demo and react-router loader docs

* Call .json() and .text() on fetch result automatically

* Rephrase part about missing features vs. react-router@6

* Clarified which headers aren't exposed

* Bump versions

* Cleaned up demo code and added clarification

* Convert tests to Typescript

* Updated package-lock.json

* Store spy in variable to avoid type error and harmonise naming of said variables

* Improve test of passing Location obj to Link

* Fix linting errors

* Final fix

* Change type of `to` property so it is compatible with tests and parsePath
  • Loading branch information
jhsware committed May 13, 2023
1 parent 43437ba commit b1befe9
Show file tree
Hide file tree
Showing 55 changed files with 2,338 additions and 301 deletions.
5 changes: 5 additions & 0 deletions demo/inferno-router-demo/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"plugins": [
"inferno"
]
}
22 changes: 22 additions & 0 deletions demo/inferno-router-demo/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.DS_Store
.git/

# VS Code specific
.vscode
jsconfig.json
typings/*
typings.json

# Dependency directory for NPM
node_modules

# Built at startup
.env
pwd.txt
lerna-debug.log

dist
distServer
distBrowser
package-lock.json
.parcel-cache
5 changes: 5 additions & 0 deletions demo/inferno-router-demo/.proxyrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"/api": {
"target": "http://127.0.0.1:3000/"
}
}
11 changes: 11 additions & 0 deletions demo/inferno-router-demo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Demo of Inferno-Router

NOTE: Requires Nodejs >=18 (uses `fetch`)

```sh
cd demo/inferno-router-demo
npm i
npm run dev
```

Go to http://127.0.0.1:1234/
53 changes: 53 additions & 0 deletions demo/inferno-router-demo/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"name": "inferno-router-demo",
"version": "8.0.5",
"description": "Influence CMS Demo",
"author": "Sebastian Ware <sebastian@urbantalk.se> (https://github.com/jhsware)",
"license": "SEE LICENSE IN LICENSE",
"source": "src/index.html",
"browserslist": "> 0.5%, last 2 versions, not dead",
"scripts": {
"dev": "npm-run-all -l --parallel dev:*",
"dev:frontend": "cross-env FORCE_COLOR=1 parcel",
"dev:backend": "cross-env FORCE_COLOR=1 ts-node-dev --respawn src/server.ts"
},
"dependencies": {
"inferno": "file:../../packages/inferno",
"inferno-animation": "file:../../packages/inferno-animation",
"inferno-create-element": "file:../../packages/inferno-create-element",
"inferno-hydrate": "file:../../packages/inferno-hydrate",
"inferno-router": "file:../../packages/inferno-router",
"inferno-server": "file:../../packages/inferno-server",
"koa": "^2.14.1",
"koa-json-body": "^5.3.0",
"koa-logger": "^3.2.1",
"koa-mount": "^4.0.0",
"koa-router": "^12.0.0",
"koa-static": "^5.0.0"
},
"devDependencies": {
"@babel/plugin-transform-modules-commonjs": "^7.20.11",
"@parcel/config-default": "^2.8.2",
"@parcel/packager-ts": "^2.8.1",
"@parcel/transformer-babel": "^2.8.1",
"@parcel/transformer-sass": "^2.8.1",
"@parcel/transformer-typescript-types": "^2.8.1",
"@types/node": "^18.13.0",
"babel-plugin-inferno": "^6.6.0",
"cross-env": "^7.0.3",
"jest-environment-jsdom": "^29.4.2",
"npm-run-all": "^4.1.5",
"parcel": "^2.8.2",
"process": "^0.11.10",
"ts-node-dev": "^2.0.0",
"typescript": "^4.9.5"
},
"alias": {
"inferno": "inferno/dist/index.dev.esm.js",
"inferno-animation": "inferno-animation/dist/index.dev.esm.js",
"inferno-create-element": "inferno-create-element/dist/index.dev.esm.js",
"inferno-hydrate": "inferno-hydrate/dist/index.dev.esm.js",
"inferno-router": "inferno-router/dist/index.dev.esm.js",
"inferno-server": "inferno-server/dist/index.dev.esm.js"
}
}
30 changes: 30 additions & 0 deletions demo/inferno-router-demo/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Component } from 'inferno'
import { Route } from 'inferno-router'

/**
* Pages
*/
import StartPage from './pages/StartPage'
import AboutPage from './pages/AboutPage'
import ContentPage from './pages/ContentPage'

/**
* The Application
*/
export class App extends Component {
render(props) {
return props.children;
}
};

export function appFactory () {

return (
<App>
{/* Public Pages */}
<Route exact path={`/`} component={ StartPage } />
<Route exact path={`/about`} component={ AboutPage } loader={AboutPage.fetchData} />
<Route exact path={`/page/:slug`} component={ ContentPage } loader={ContentPage.fetchData} />
</App>
)
}
11 changes: 11 additions & 0 deletions demo/inferno-router-demo/src/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>Inferno Router Demo</title>
<script type="module" src="index.tsx"></script>
</head>
<body>
<div id="app"></div>
</body>
</html>
11 changes: 11 additions & 0 deletions demo/inferno-router-demo/src/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { render } from 'inferno';
import { BrowserRouter } from 'inferno-router'
import { appFactory } from './App';

declare global {
interface Window {
__initialData__: any
}
}

render(<BrowserRouter initialData={window.__initialData__}>{appFactory()}</BrowserRouter>, document.getElementById('app'))
11 changes: 11 additions & 0 deletions demo/inferno-router-demo/src/indexServer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { hydrate } from 'inferno-hydrate';
import { BrowserRouter } from 'inferno-router'
import { appFactory } from './App';

declare global {
interface Window {
__initialData__: any
}
}

hydrate(<BrowserRouter initialData={window.__initialData__}>{appFactory()}</BrowserRouter>, document.getElementById('app'))
Empty file.
35 changes: 35 additions & 0 deletions demo/inferno-router-demo/src/pages/AboutPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Component } from 'inferno';
import PageTemplate from './PageTemplate';
import { useLoaderData } from 'inferno-router';

import './AboutPage.scss';
import { useLoaderError } from 'inferno-router';

const BACKEND_HOST = 'http://localhost:1234';

export default class AboutPage extends Component {
static async fetchData({ request }) {
const fetchOptions: RequestInit = {
headers: {
Accept: 'application/json',
},
signal: request?.signal,
};

return fetch(new URL('/api/about', BACKEND_HOST), fetchOptions);
}

render(props) {
const data = useLoaderData<{ title: string, body: string}>(props);
const err = useLoaderError<{ message: string }>(props);

return (
<PageTemplate>
<article>
<h1>{data?.title}</h1>
<p>{data?.body || err?.message}</p>
</article>
</PageTemplate>
);
}
}
Empty file.
37 changes: 37 additions & 0 deletions demo/inferno-router-demo/src/pages/ContentPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Component } from 'inferno';
import PageTemplate from './PageTemplate';
import { useLoaderData } from 'inferno-router';

import './AboutPage.scss';
import { useLoaderError } from 'inferno-router';

const BACKEND_HOST = 'http://localhost:1234';

export default class ContentPage extends Component {
static async fetchData({ params, request }) {
const pageSlug = params.id;

const fetchOptions: RequestInit = {
headers: {
Accept: 'application/json',
},
signal: request?.signal
};

return fetch(new URL(`/api/page/${params.slug}`, BACKEND_HOST), fetchOptions);
}

render(props) {
const data = useLoaderData<{ title: string, body: string}>(props);
const err = useLoaderError<{ message: string }>(props);

return (
<PageTemplate>
<article>
<h1>{data?.title}</h1>
<p>{data?.body || err?.message}</p>
</article>
</PageTemplate>
);
}
}
48 changes: 48 additions & 0 deletions demo/inferno-router-demo/src/pages/PageTemplate.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
:root {
--rowGap: 2rem;
}

.page {
display: flex;
flex-flow: column nowrap;
min-height: 100vh;

h1, h2 {
margin-bottom: 1.5em;
}

header {
flex-grow: 0;
& > nav > ul {
display: flex;
flex-flow: row wrap;
gap: var(--rowGap);
align-items: center;
justify-content: center;
padding: 0;

& > li {
list-style: none;
}
}
}
main {
flex-grow: 1;
display: flex;
flex-flow: column;
align-items: center;
justify-content: flex-start;
text-align: center;

& > * {
max-width: 50rem
}

& > .body {
width: 100%;
}
}
footer {
flex-grow: 0
}
}
25 changes: 25 additions & 0 deletions demo/inferno-router-demo/src/pages/PageTemplate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Link } from 'inferno-router'

import './PageTemplate.scss'

export default function PageTemplate({ id = undefined, children }) {
return (
<div id={id} className="page">
<header>
<nav>
<ul>
<li><Link to="/">Start</Link></li>
<li><Link to="/about">About</Link></li>
<li><Link to="/page/one">Page One</Link></li>
<li><Link to="/page/two">Page Two</Link></li>
</ul>
</nav>
</header>
<main>{children}</main>
<footer>
<p>Page Footer</p>
</footer>
</div>
)

}
Empty file.
25 changes: 25 additions & 0 deletions demo/inferno-router-demo/src/pages/StartPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Component } from 'inferno'
import PageTemplate from './PageTemplate'
import './StartPage.scss'

interface IProps {
fetchData: any;
}

export default class StartPage extends Component<IProps> {
static async fetchData({ match }) {
const pageSlug = match.params.id
return [];
}

render() {
return (
<PageTemplate>
<article>
<h1>Start Page</h1>
<p>Some content</p>
</article>
</PageTemplate>
)
}
}
Loading

0 comments on commit b1befe9

Please sign in to comment.