Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ Isomorphic async tools for Preact.
- [hydrate()](#hydrate)
- [prerender()](#prerender)
- [locationStub()](#locationstub)
- [Navigation API Entry Docs](#navigation-api-entry-docs)
- [Differences in usage](#differences-in-usage)

---

Expand Down Expand Up @@ -438,6 +440,18 @@ locationStub('/foo/bar?baz=qux#quux');
console.log(location.pathname); // "/foo/bar"
```

## Navigation API Entry Docs

The Navigation API is a new web standard that provides an updated method of handling "navigation" in web applications, supporting SPA-style routing as a first-class citizen. The older History API can be wrangled to support this and has been the standard for many years, but the Navigation API provides a much more robust set of tools that are really, really attractive for routers like `preact-iso` to take advantage of.

Whilst the API [sees fairly wide support](https://caniuse.com/wf-navigation), it is still newly available and thus may not be viable for some targets. As such, we've provided a new entry point that will allow you to take advantage of this API if you wish, but the default remains targetting the History API. The Navigation API entry point is available at `preact-iso/router/navigation-api`.

### Differences in usage

The differences lie entirely within the [`useLocation()`](#uselocation) hook: instead of returning a `route()` function, you use the global `navigation` object to perform all navigations.

The [`navigation` object](https://developer.mozilla.org/en-US/docs/Web/API/Navigation) contains many of the useful utilities that go along with a router, like `.forward()`, `.back()`, `.canGoForward()`, `.canGoBack()`, `.entries()`, etc. It actually offers far more utilities than the base router did, and does so with less library code overall, so if you have access to it, it's a really nice upgrade.

## License

[MIT](./LICENSE)
14 changes: 9 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"exports": {
".": "./src/index.js",
"./router": "./src/router.js",
"./router/navigation-api": "./src/router-navigation-api.js",
"./lazy": "./src/lazy.js",
"./prerender": "./src/prerender.js",
"./hydrate": "./src/hydrate.js"
Expand Down Expand Up @@ -44,6 +45,7 @@
"chai": "^5.1.1",
"htm": "^3.1.1",
"kleur": "^4.1.5",
"navigation-api-types": "^0.6.1",
"preact": "^10.26.5",
"preact-render-to-string": "^6.6.1",
"sinon": "^18.0.0",
Expand Down
1 change: 1 addition & 0 deletions src/internal.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/// <reference types="navigation-api-types" />
import { Component } from 'preact';

export interface AugmentedComponent extends Component<any, any> {
Expand Down
101 changes: 101 additions & 0 deletions src/router-navigation-api.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { AnyComponent, ComponentChildren, Context, VNode } from 'preact';

export const LocationProvider: {
(props: { scope?: string | RegExp; children?: ComponentChildren; }): VNode;
ctx: Context<LocationHook>;
};

type NestedArray<T> = Array<T | NestedArray<T>>;

interface KnownProps {
path: string;
query: Record<string, string>;
params: Record<string, string>;
default?: boolean;
rest?: string;
component?: AnyComponent;
}

interface ArbitraryProps {
[prop: string]: any;
}

type MatchProps = KnownProps & ArbitraryProps;

/**
* Check if a URL path matches against a URL path pattern.
*
* Warning: This is largely an internal API, it may change in the future
* @param url - URL path (e.g. /user/12345)
* @param route - URL pattern (e.g. /user/:id)
*/
export function exec(url: string, route: string, matches?: MatchProps): MatchProps

export function Router(props: {
onRouteChange?: (url: string) => void;
onLoadEnd?: (url: string) => void;
onLoadStart?: (url: string) => void;
children?: NestedArray<VNode>;
}): VNode;

interface LocationHook {
url: string;
path: string;
query: Record<string, string>;
}
export const useLocation: () => LocationHook;

interface RouteHook {
path: string;
query: Record<string, string>;
params: Record<string, string>;
}
export const useRoute: () => RouteHook;

type RoutableProps =
| { path: string; default?: false; }
| { path?: never; default: true; }

export type RouteProps<Props> = RoutableProps & { component: AnyComponent<Props> };

export type RoutePropsForPath<Path extends string> = Path extends '*'
? { params: {}; rest: string }

: Path extends `:${infer placeholder}?/${infer rest}`
? { [k in placeholder]?: string } & { params: RoutePropsForPath<rest>['params'] & { [k in placeholder]?: string } } & Omit<RoutePropsForPath<rest>, 'params'>

: Path extends `:${infer placeholder}/${infer rest}`
? { [k in placeholder]: string } & { params: RoutePropsForPath<rest>['params'] & { [k in placeholder]: string } } & Omit<RoutePropsForPath<rest>, 'params'>

: Path extends `:${infer placeholder}?`
? { [k in placeholder]?: string } & { params: { [k in placeholder]?: string } }

: Path extends `:${infer placeholder}*`
? { [k in placeholder]?: string } & { params: { [k in placeholder]?: string } }

: Path extends `:${infer placeholder}+`
? { [k in placeholder]: string } & { params: { [k in placeholder]: string } }

: Path extends `:${infer placeholder}`
? { [k in placeholder]: string } & { params: { [k in placeholder]: string } }

: Path extends (`/${infer rest}` | `${infer _}/${infer rest}`)
? RoutePropsForPath<rest>

: { params: {} };

export function Route<Props>(props: RouteProps<Props> & Partial<Props>): VNode;

declare module 'preact' {
// The code below automatically adds `path` and `default` as optional props for every component
// (effectively reserving those names, so no component should use those names in its own props).
// These declarations extend from `RouteableProps`, which is not allowed in modern TypeScript and
// causes a TS2312 error. However, the compiler does seems to honor the intent of this code, so
// to avoid an API regression, let's ignore the error rather than loosening the type validation.
namespace JSX {
/** @ts-ignore */
interface IntrinsicAttributes extends RoutableProps {}
}
/** @ts-ignore */
interface Attributes extends RoutableProps {}
}
Loading
Loading