Init
This commit is contained in:
3
node_modules/preact-iso/src/hydrate.d.ts
generated
vendored
Normal file
3
node_modules/preact-iso/src/hydrate.d.ts
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
import { ComponentChild, ContainerNode } from 'preact';
|
||||
|
||||
export default function hydrate(jsx: ComponentChild, parent?: ContainerNode): void;
|
17
node_modules/preact-iso/src/hydrate.js
generated
vendored
Normal file
17
node_modules/preact-iso/src/hydrate.js
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
import { render, hydrate as hydrativeRender } from 'preact';
|
||||
|
||||
let initialized;
|
||||
|
||||
/** @type {typeof hydrativeRender} */
|
||||
export default function hydrate(jsx, parent) {
|
||||
if (typeof window === 'undefined') return;
|
||||
let isodata = document.querySelector('script[type=isodata]');
|
||||
// @ts-ignore-next
|
||||
parent = parent || (isodata && isodata.parentNode) || document.body;
|
||||
if (!initialized && isodata) {
|
||||
hydrativeRender(jsx, parent);
|
||||
} else {
|
||||
render(jsx, parent);
|
||||
}
|
||||
initialized = true;
|
||||
}
|
4
node_modules/preact-iso/src/index.d.ts
generated
vendored
Normal file
4
node_modules/preact-iso/src/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
export { default as prerender } from './prerender.js';
|
||||
export * from './router.js';
|
||||
export { default as lazy, ErrorBoundary } from './lazy.js';
|
||||
export { default as hydrate } from './hydrate.js';
|
7
node_modules/preact-iso/src/index.js
generated
vendored
Normal file
7
node_modules/preact-iso/src/index.js
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
export { Router, LocationProvider, useLocation, Route, useRoute } from './router.js';
|
||||
export { default as lazy, ErrorBoundary } from './lazy.js';
|
||||
export { default as hydrate } from './hydrate.js';
|
||||
|
||||
export function prerender(vnode, options) {
|
||||
return import('./prerender.js').then(m => m.default(vnode, options));
|
||||
}
|
7
node_modules/preact-iso/src/lazy.d.ts
generated
vendored
Normal file
7
node_modules/preact-iso/src/lazy.d.ts
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
import { ComponentChildren, VNode } from 'preact';
|
||||
|
||||
export default function lazy<T>(load: () => Promise<{ default: T } | T>): T & {
|
||||
preload: () => Promise<T>;
|
||||
};
|
||||
|
||||
export function ErrorBoundary(props: { children?: ComponentChildren; onError?: (error: Error) => void }): VNode;
|
65
node_modules/preact-iso/src/lazy.js
generated
vendored
Normal file
65
node_modules/preact-iso/src/lazy.js
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
import { h, options } from 'preact';
|
||||
import { useState, useRef } from 'preact/hooks';
|
||||
|
||||
const oldDiff = options.__b;
|
||||
options.__b = (vnode) => {
|
||||
if (vnode.type && vnode.type._forwarded && vnode.ref) {
|
||||
vnode.props.ref = vnode.ref;
|
||||
vnode.ref = null;
|
||||
}
|
||||
if (oldDiff) oldDiff(vnode);
|
||||
};
|
||||
|
||||
export default function lazy(load) {
|
||||
let p, c;
|
||||
|
||||
const loadModule = () =>
|
||||
load().then(m => (c = (m && m.default) || m));
|
||||
|
||||
const LazyComponent = props => {
|
||||
const [, update] = useState(0);
|
||||
const r = useRef(c);
|
||||
if (!p) p = loadModule();
|
||||
if (c !== undefined) return h(c, props);
|
||||
if (!r.current) r.current = p.then(() => update(1));
|
||||
throw p;
|
||||
};
|
||||
|
||||
LazyComponent.preload = () => {
|
||||
if (!p) p = loadModule();
|
||||
return p;
|
||||
}
|
||||
|
||||
LazyComponent._forwarded = true;
|
||||
return LazyComponent;
|
||||
}
|
||||
|
||||
// See https://github.com/preactjs/preact/blob/88680e91ec0d5fc29d38554a3e122b10824636b6/compat/src/suspense.js#L5
|
||||
const oldCatchError = options.__e;
|
||||
options.__e = (err, newVNode, oldVNode) => {
|
||||
if (err && err.then) {
|
||||
let v = newVNode;
|
||||
while ((v = v.__)) {
|
||||
if (v.__c && v.__c.__c) {
|
||||
if (newVNode.__e == null) {
|
||||
newVNode.__c.__z = [oldVNode.__e];
|
||||
newVNode.__e = oldVNode.__e; // ._dom
|
||||
newVNode.__k = oldVNode.__k; // ._children
|
||||
}
|
||||
if (!newVNode.__k) newVNode.__k = [];
|
||||
return v.__c.__c(err, newVNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (oldCatchError) oldCatchError(err, newVNode, oldVNode);
|
||||
};
|
||||
|
||||
export function ErrorBoundary(props) {
|
||||
this.__c = childDidSuspend;
|
||||
this.componentDidCatch = props.onError;
|
||||
return props.children;
|
||||
}
|
||||
|
||||
function childDidSuspend(err) {
|
||||
err.then(() => this.forceUpdate());
|
||||
}
|
17
node_modules/preact-iso/src/prerender.d.ts
generated
vendored
Normal file
17
node_modules/preact-iso/src/prerender.d.ts
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
import { VNode } from 'preact';
|
||||
|
||||
export interface PrerenderOptions {
|
||||
props?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface PrerenderResult {
|
||||
html: string;
|
||||
links?: Set<string>
|
||||
}
|
||||
|
||||
export default function prerender(
|
||||
vnode: VNode,
|
||||
options?: PrerenderOptions
|
||||
): Promise<PrerenderResult>;
|
||||
|
||||
export function locationStub(path: string): void;
|
59
node_modules/preact-iso/src/prerender.js
generated
vendored
Normal file
59
node_modules/preact-iso/src/prerender.js
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
import { h, options, cloneElement } from 'preact';
|
||||
import { renderToStringAsync } from 'preact-render-to-string';
|
||||
|
||||
let vnodeHook;
|
||||
|
||||
const old = options.vnode;
|
||||
options.vnode = vnode => {
|
||||
if (old) old(vnode);
|
||||
if (vnodeHook) vnodeHook(vnode);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {ReturnType<h>} vnode The root JSX element to render (eg: `<App />`)
|
||||
* @param {object} [options]
|
||||
* @param {object} [options.props] Additional props to merge into the root JSX element
|
||||
*/
|
||||
export default async function prerender(vnode, options) {
|
||||
options = options || {};
|
||||
|
||||
const props = options.props;
|
||||
|
||||
if (typeof vnode === 'function') {
|
||||
vnode = h(vnode, props);
|
||||
} else if (props) {
|
||||
vnode = cloneElement(vnode, props);
|
||||
}
|
||||
|
||||
let links = new Set();
|
||||
vnodeHook = ({ type, props }) => {
|
||||
if (type === 'a' && props && props.href && (!props.target || props.target === '_self')) {
|
||||
links.add(props.href);
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
let html = await renderToStringAsync(vnode);
|
||||
html += `<script type="isodata"></script>`;
|
||||
return { html, links };
|
||||
} finally {
|
||||
vnodeHook = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update `location` to current URL so routers can use things like `location.pathname`
|
||||
*
|
||||
* @param {string} path - current URL path
|
||||
*/
|
||||
export function locationStub(path) {
|
||||
globalThis.location = {};
|
||||
const u = new URL(path, 'http://localhost');
|
||||
for (const i in u) {
|
||||
try {
|
||||
globalThis.location[i] = /to[A-Z]/.test(i)
|
||||
? u[i].bind(u)
|
||||
: String(u[i]);
|
||||
} catch {}
|
||||
}
|
||||
}
|
69
node_modules/preact-iso/src/router.d.ts
generated
vendored
Normal file
69
node_modules/preact-iso/src/router.d.ts
generated
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
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>;
|
||||
route: (url: string, replace?: boolean) => void;
|
||||
}
|
||||
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 function Route<Props>(props: RouteProps<Props> & Partial<Props>): VNode;
|
||||
|
||||
declare module 'preact' {
|
||||
namespace JSX {
|
||||
interface IntrinsicAttributes extends RoutableProps {}
|
||||
}
|
||||
interface Attributes extends RoutableProps {}
|
||||
}
|
278
node_modules/preact-iso/src/router.js
generated
vendored
Normal file
278
node_modules/preact-iso/src/router.js
generated
vendored
Normal file
@@ -0,0 +1,278 @@
|
||||
import { h, createContext, cloneElement, toChildArray } from 'preact';
|
||||
import { useContext, useMemo, useReducer, useLayoutEffect, useRef } from 'preact/hooks';
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {import('preact').RefObject<T>} RefObject
|
||||
* @typedef {import('./internal.d.ts').VNode} VNode
|
||||
*/
|
||||
|
||||
let push, scope;
|
||||
const UPDATE = (state, url) => {
|
||||
push = undefined;
|
||||
if (url && url.type === 'click') {
|
||||
// ignore events the browser takes care of already:
|
||||
if (url.ctrlKey || url.metaKey || url.altKey || url.shiftKey || url.button !== 0) {
|
||||
return state;
|
||||
}
|
||||
|
||||
const link = url.composedPath().find(el => el.nodeName == 'A' && el.href),
|
||||
href = link && link.getAttribute('href');
|
||||
if (
|
||||
!link ||
|
||||
link.origin != location.origin ||
|
||||
/^#/.test(href) ||
|
||||
!/^(_?self)?$/i.test(link.target) ||
|
||||
scope && (typeof scope == 'string'
|
||||
? !href.startsWith(scope)
|
||||
: !scope.test(href)
|
||||
)
|
||||
) {
|
||||
return state;
|
||||
}
|
||||
|
||||
push = true;
|
||||
url.preventDefault();
|
||||
url = link.href.replace(location.origin, '');
|
||||
} else if (typeof url === 'string') {
|
||||
push = true;
|
||||
} else if (url && url.url) {
|
||||
push = !url.replace;
|
||||
url = url.url;
|
||||
} else {
|
||||
url = location.pathname + location.search;
|
||||
}
|
||||
|
||||
if (push === true) history.pushState(null, '', url);
|
||||
else if (push === false) history.replaceState(null, '', url);
|
||||
return url;
|
||||
};
|
||||
|
||||
export const exec = (url, route, matches = {}) => {
|
||||
url = url.split('/').filter(Boolean);
|
||||
route = (route || '').split('/').filter(Boolean);
|
||||
if (!matches.params) matches.params = {};
|
||||
for (let i = 0, val, rest; i < Math.max(url.length, route.length); i++) {
|
||||
let [, m, param, flag] = (route[i] || '').match(/^(:?)(.*?)([+*?]?)$/);
|
||||
val = url[i];
|
||||
// segment match:
|
||||
if (!m && param == val) continue;
|
||||
// /foo/* match
|
||||
if (!m && val && flag == '*') {
|
||||
matches.rest = '/' + url.slice(i).map(decodeURIComponent).join('/');
|
||||
break;
|
||||
}
|
||||
// segment mismatch / missing required field:
|
||||
if (!m || (!val && flag != '?' && flag != '*')) return;
|
||||
rest = flag == '+' || flag == '*';
|
||||
// rest (+/*) match:
|
||||
if (rest) val = url.slice(i).map(decodeURIComponent).join('/') || undefined;
|
||||
// normal/optional field:
|
||||
else if (val) val = decodeURIComponent(val);
|
||||
matches.params[param] = val;
|
||||
if (!(param in matches)) matches[param] = val;
|
||||
if (rest) break;
|
||||
}
|
||||
return matches;
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {import('./router.d.ts').LocationProvider}
|
||||
*/
|
||||
export function LocationProvider(props) {
|
||||
// @ts-expect-error - props.url is not implemented correctly & will be removed in the future
|
||||
const [url, route] = useReducer(UPDATE, props.url || location.pathname + location.search);
|
||||
if (props.scope) scope = props.scope;
|
||||
const wasPush = push === true;
|
||||
|
||||
const value = useMemo(() => {
|
||||
const u = new URL(url, location.origin);
|
||||
const path = u.pathname.replace(/\/+$/g, '') || '/';
|
||||
// @ts-ignore-next
|
||||
return {
|
||||
url,
|
||||
path,
|
||||
query: Object.fromEntries(u.searchParams),
|
||||
route: (url, replace) => route({ url, replace }),
|
||||
wasPush
|
||||
};
|
||||
}, [url]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
addEventListener('click', route);
|
||||
addEventListener('popstate', route);
|
||||
|
||||
return () => {
|
||||
removeEventListener('click', route);
|
||||
removeEventListener('popstate', route);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// @ts-ignore
|
||||
return h(LocationProvider.ctx.Provider, { value }, props.children);
|
||||
}
|
||||
|
||||
const RESOLVED = Promise.resolve();
|
||||
/** @this {import('./internal.d.ts').AugmentedComponent} */
|
||||
export function Router(props) {
|
||||
const [c, update] = useReducer(c => c + 1, 0);
|
||||
|
||||
const { url, query, wasPush, path } = useLocation();
|
||||
if (!url) {
|
||||
throw new Error(`preact-iso's <Router> must be used within a <LocationProvider>, see: https://github.com/preactjs/preact-iso#locationprovider`);
|
||||
}
|
||||
const { rest = path, params = {} } = useContext(RouteContext);
|
||||
|
||||
const isLoading = useRef(false);
|
||||
const prevRoute = useRef(path);
|
||||
// Monotonic counter used to check if an un-suspending route is still the current route:
|
||||
const count = useRef(0);
|
||||
// The current route:
|
||||
const cur = /** @type {RefObject<VNode<any>>} */ (useRef());
|
||||
// Previous route (if current route is suspended):
|
||||
const prev = /** @type {RefObject<VNode<any>>} */ (useRef());
|
||||
// A not-yet-hydrated DOM root to remove once we commit:
|
||||
const pendingBase = /** @type {RefObject<Element | Text>} */ (useRef());
|
||||
// has this component ever successfully rendered without suspending:
|
||||
const hasEverCommitted = useRef(false);
|
||||
// was the most recent render successful (did not suspend):
|
||||
const didSuspend = /** @type {RefObject<boolean>} */ (useRef());
|
||||
didSuspend.current = false;
|
||||
|
||||
let pathRoute, defaultRoute, matchProps;
|
||||
toChildArray(props.children).some((/** @type {VNode<any>} */ vnode) => {
|
||||
const matches = exec(rest, vnode.props.path, (matchProps = { ...vnode.props, path: rest, query, params, rest: '' }));
|
||||
if (matches) return (pathRoute = cloneElement(vnode, matchProps));
|
||||
if (vnode.props.default) defaultRoute = cloneElement(vnode, matchProps);
|
||||
});
|
||||
|
||||
/** @type {VNode<any> | undefined} */
|
||||
let incoming = pathRoute || defaultRoute;
|
||||
|
||||
const isHydratingSuspense = cur.current && cur.current.__u & MODE_HYDRATE && cur.current.__u & MODE_SUSPENDED;
|
||||
const isHydratingBool = cur.current && cur.current.__h;
|
||||
const routeChanged = useMemo(() => {
|
||||
prev.current = cur.current;
|
||||
|
||||
cur.current = /** @type {VNode<any>} */ (h(RouteContext.Provider, { value: matchProps }, incoming));
|
||||
|
||||
// Only mark as an update if the route component changed.
|
||||
const outgoing = prev.current && prev.current.props.children;
|
||||
if (!outgoing || !incoming || incoming.type !== outgoing.type || incoming.props.component !== outgoing.props.component) {
|
||||
// This hack prevents Preact from diffing when we swap `cur` to `prev`:
|
||||
if (this.__v && this.__v.__k) this.__v.__k.reverse();
|
||||
count.current++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, [url, JSON.stringify(matchProps)]);
|
||||
|
||||
if (isHydratingSuspense) {
|
||||
cur.current.__u |= MODE_HYDRATE;
|
||||
cur.current.__u |= MODE_SUSPENDED;
|
||||
} else if (isHydratingBool) {
|
||||
cur.current.__h = true;
|
||||
}
|
||||
|
||||
// Reset previous children - if rendering succeeds synchronously, we shouldn't render the previous children.
|
||||
const p = prev.current;
|
||||
prev.current = null;
|
||||
|
||||
// This borrows the _childDidSuspend() solution from compat.
|
||||
this.__c = (e, suspendedVNode) => {
|
||||
// Mark the current render as having suspended:
|
||||
didSuspend.current = true;
|
||||
|
||||
// The new route suspended, so keep the previous route around while it loads:
|
||||
prev.current = p;
|
||||
|
||||
// Fire an event saying we're waiting for the route:
|
||||
if (props.onLoadStart) props.onLoadStart(url);
|
||||
isLoading.current = true;
|
||||
|
||||
// Re-render on unsuspend:
|
||||
let c = count.current;
|
||||
e.then(() => {
|
||||
// Ignore this update if it isn't the most recently suspended update:
|
||||
if (c !== count.current) return;
|
||||
|
||||
// Successful route transition: un-suspend after a tick and stop rendering the old route:
|
||||
prev.current = null;
|
||||
if (cur.current) {
|
||||
if (suspendedVNode.__h) {
|
||||
// _hydrating
|
||||
cur.current.__h = suspendedVNode.__h;
|
||||
}
|
||||
|
||||
if (suspendedVNode.__u & MODE_SUSPENDED) {
|
||||
// _flags
|
||||
cur.current.__u |= MODE_SUSPENDED;
|
||||
}
|
||||
|
||||
if (suspendedVNode.__u & MODE_HYDRATE) {
|
||||
cur.current.__u |= MODE_HYDRATE;
|
||||
}
|
||||
}
|
||||
|
||||
RESOLVED.then(update);
|
||||
});
|
||||
};
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const currentDom = this.__v && this.__v.__e;
|
||||
|
||||
// Ignore suspended renders (failed commits):
|
||||
if (didSuspend.current) {
|
||||
// If we've never committed, mark any hydration DOM for removal on the next commit:
|
||||
if (!hasEverCommitted.current && !pendingBase.current) {
|
||||
pendingBase.current = currentDom;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// If this is the first ever successful commit and we didn't use the hydration DOM, remove it:
|
||||
if (!hasEverCommitted.current && pendingBase.current) {
|
||||
if (pendingBase.current !== currentDom) pendingBase.current.remove();
|
||||
pendingBase.current = null;
|
||||
}
|
||||
|
||||
// Mark the component has having committed:
|
||||
hasEverCommitted.current = true;
|
||||
|
||||
// The route is loaded and rendered.
|
||||
if (prevRoute.current !== path) {
|
||||
if (wasPush) scrollTo(0, 0);
|
||||
if (props.onRouteChange) props.onRouteChange(url);
|
||||
|
||||
prevRoute.current = path;
|
||||
}
|
||||
|
||||
if (props.onLoadEnd && isLoading.current) props.onLoadEnd(url);
|
||||
isLoading.current = false;
|
||||
}, [path, wasPush, c]);
|
||||
|
||||
// Note: cur MUST render first in order to set didSuspend & prev.
|
||||
return routeChanged
|
||||
? [h(RenderRef, { r: cur }), h(RenderRef, { r: prev })]
|
||||
: h(RenderRef, { r: cur });
|
||||
}
|
||||
|
||||
const MODE_HYDRATE = 1 << 5;
|
||||
const MODE_SUSPENDED = 1 << 7;
|
||||
|
||||
// Lazily render a ref's current value:
|
||||
const RenderRef = ({ r }) => r.current;
|
||||
|
||||
Router.Provider = LocationProvider;
|
||||
|
||||
LocationProvider.ctx = createContext(
|
||||
/** @type {import('./router.d.ts').LocationHook & { wasPush: boolean }} */ ({})
|
||||
);
|
||||
const RouteContext = createContext(
|
||||
/** @type {import('./router.d.ts').RouteHook & { rest: string }} */ ({})
|
||||
);
|
||||
|
||||
export const Route = props => h(props.component, props);
|
||||
|
||||
export const useLocation = () => useContext(LocationProvider.ctx);
|
||||
export const useRoute = () => useContext(RouteContext);
|
Reference in New Issue
Block a user