@teyik0/furin 0.1.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter/bun.d.ts +3 -0
- package/dist/build/client.d.ts +14 -0
- package/dist/build/compile-entry.d.ts +22 -0
- package/dist/build/entry-template.d.ts +13 -0
- package/dist/build/hydrate.d.ts +20 -0
- package/dist/build/index.d.ts +7 -0
- package/dist/build/index.js +2212 -0
- package/dist/build/route-types.d.ts +20 -0
- package/dist/build/scan-server.d.ts +8 -0
- package/dist/build/server-routes-entry.d.ts +22 -0
- package/dist/build/shared.d.ts +12 -0
- package/dist/build/types.d.ts +53 -0
- package/dist/cli/config.d.ts +9 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +2240 -0
- package/dist/client.d.ts +158 -0
- package/dist/client.js +20 -0
- package/dist/config.d.ts +16 -0
- package/dist/config.js +23 -0
- package/dist/furin.d.ts +45 -0
- package/dist/furin.js +937 -0
- package/dist/internal.d.ts +18 -0
- package/dist/link.d.ts +119 -0
- package/dist/link.js +281 -0
- package/dist/plugin/index.d.ts +20 -0
- package/dist/plugin/index.js +1408 -0
- package/dist/plugin/transform-client.d.ts +9 -0
- package/dist/render/assemble.d.ts +13 -0
- package/dist/render/cache.d.ts +7 -0
- package/dist/render/element.d.ts +4 -0
- package/dist/render/index.d.ts +26 -0
- package/dist/render/loaders.d.ts +12 -0
- package/dist/render/shell.d.ts +17 -0
- package/dist/render/template.d.ts +4 -0
- package/dist/router.d.ts +32 -0
- package/dist/router.js +575 -0
- package/dist/runtime-env.d.ts +3 -0
- package/dist/tsconfig.dts.tsbuildinfo +1 -0
- package/dist/utils.d.ts +6 -0
- package/package.json +74 -0
- package/src/adapter/README.md +13 -0
- package/src/adapter/bun.ts +119 -0
- package/src/build/client.ts +110 -0
- package/src/build/compile-entry.ts +99 -0
- package/src/build/entry-template.ts +62 -0
- package/src/build/hydrate.ts +106 -0
- package/src/build/index.ts +120 -0
- package/src/build/route-types.ts +88 -0
- package/src/build/scan-server.ts +88 -0
- package/src/build/server-routes-entry.ts +38 -0
- package/src/build/shared.ts +80 -0
- package/src/build/types.ts +60 -0
- package/src/cli/config.ts +68 -0
- package/src/cli/index.ts +106 -0
- package/src/client.ts +237 -0
- package/src/config.ts +31 -0
- package/src/furin.ts +251 -0
- package/src/internal.ts +36 -0
- package/src/link.tsx +480 -0
- package/src/plugin/index.ts +80 -0
- package/src/plugin/transform-client.ts +372 -0
- package/src/render/assemble.ts +57 -0
- package/src/render/cache.ts +9 -0
- package/src/render/element.tsx +28 -0
- package/src/render/index.ts +312 -0
- package/src/render/loaders.ts +67 -0
- package/src/render/shell.ts +128 -0
- package/src/render/template.ts +54 -0
- package/src/router.ts +234 -0
- package/src/runtime-env.ts +6 -0
- package/src/utils.ts +68 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface EmbeddedAppData {
|
|
2
|
+
assets: Record<string, string>;
|
|
3
|
+
template: string;
|
|
4
|
+
}
|
|
5
|
+
export interface CompileContextRoute {
|
|
6
|
+
mode: "ssr" | "ssg" | "isr";
|
|
7
|
+
path: string;
|
|
8
|
+
pattern: string;
|
|
9
|
+
}
|
|
10
|
+
export interface CompileContext {
|
|
11
|
+
embedded?: EmbeddedAppData;
|
|
12
|
+
modules: Record<string, unknown>;
|
|
13
|
+
rootPath: string;
|
|
14
|
+
routes: CompileContextRoute[];
|
|
15
|
+
}
|
|
16
|
+
export declare function __setCompileContext(ctx: CompileContext): void;
|
|
17
|
+
export declare function getCompileContext(): CompileContext | null;
|
|
18
|
+
export declare function __resetCompileContext(): void;
|
package/dist/link.d.ts
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type React from "react";
|
|
2
|
+
import type { RuntimeRoute } from "./client";
|
|
3
|
+
/**
|
|
4
|
+
* Augment this interface via declaration merging in .furin/routes.d.ts
|
|
5
|
+
* to get type-safe `to` autocompletion and per-route `search` types on <Link>.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* // .furin/routes.d.ts (auto-generated by Furin)
|
|
9
|
+
* import "@teyik0/furin/link";
|
|
10
|
+
* declare module "@teyik0/furin/link" {
|
|
11
|
+
* interface RouteManifest {
|
|
12
|
+
* "/blog": { search?: { page?: number; tag?: string } };
|
|
13
|
+
* "/": { search?: never };
|
|
14
|
+
* [key: `/blog/${string}`]: { search?: never };
|
|
15
|
+
* }
|
|
16
|
+
* }
|
|
17
|
+
*/
|
|
18
|
+
export interface RouteManifest {
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The valid `to` pathname union derived from the augmented RouteManifest.
|
|
22
|
+
* Falls back to `string` when no routes.d.ts has been included in tsconfig.
|
|
23
|
+
*/
|
|
24
|
+
export type RouteTo = keyof RouteManifest extends never ? string : keyof RouteManifest;
|
|
25
|
+
/**
|
|
26
|
+
* The typed search params for a given `to` pathname.
|
|
27
|
+
* Falls back to a permissive record when RouteManifest is not augmented.
|
|
28
|
+
*/
|
|
29
|
+
export type RouteSearch<To extends RouteTo> = keyof RouteManifest extends never ? Record<string, string | number | boolean | null | undefined> : To extends keyof RouteManifest ? RouteManifest[To] extends {
|
|
30
|
+
search?: infer S;
|
|
31
|
+
} ? S : undefined : undefined;
|
|
32
|
+
export type PreloadStrategy = false | "intent" | "viewport" | "render";
|
|
33
|
+
export interface LinkProps<To extends RouteTo = RouteTo> extends Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, "href"> {
|
|
34
|
+
/** Optional URL fragment (without the #). */
|
|
35
|
+
hash?: string;
|
|
36
|
+
/** Preload strategy. Default: "intent" (preload on hover/focus). */
|
|
37
|
+
preload?: PreloadStrategy;
|
|
38
|
+
/** Delay in ms before intent preload triggers. Default: 50. */
|
|
39
|
+
preloadDelay?: number;
|
|
40
|
+
/** How long a preloaded entry stays fresh (ms). Default: 30_000. */
|
|
41
|
+
preloadStaleTime?: number;
|
|
42
|
+
/** Typed search params for this route. Auto-completed from the route's query schema. */
|
|
43
|
+
search?: RouteSearch<To>;
|
|
44
|
+
to: To;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Builds a full href string from a pathname, optional search params, and optional hash.
|
|
48
|
+
* Null/undefined search values are omitted.
|
|
49
|
+
*
|
|
50
|
+
* @internal Exported for unit testing only.
|
|
51
|
+
*/
|
|
52
|
+
export declare function buildHref(to: string, search?: Record<string, unknown> | null, hash?: string): string;
|
|
53
|
+
export interface RouterContextValue {
|
|
54
|
+
defaultPreload: PreloadStrategy;
|
|
55
|
+
defaultPreloadDelay: number;
|
|
56
|
+
defaultPreloadStaleTime: number;
|
|
57
|
+
isNavigating: boolean;
|
|
58
|
+
navigate: (href: string, opts?: {
|
|
59
|
+
replace?: boolean;
|
|
60
|
+
}) => Promise<void>;
|
|
61
|
+
prefetch: (href: string, opts?: {
|
|
62
|
+
staleTime?: number;
|
|
63
|
+
}) => void;
|
|
64
|
+
}
|
|
65
|
+
export declare const RouterContext: React.Context<RouterContextValue | null>;
|
|
66
|
+
/**
|
|
67
|
+
* Returns the current router context.
|
|
68
|
+
* Provides a graceful fallback (full-page navigation) when used outside RouterProvider.
|
|
69
|
+
*/
|
|
70
|
+
export declare function useRouter(): RouterContextValue;
|
|
71
|
+
export interface ClientRoute {
|
|
72
|
+
component?: React.ComponentType<Record<string, unknown>>;
|
|
73
|
+
load: () => Promise<{
|
|
74
|
+
default: {
|
|
75
|
+
component: React.ComponentType<Record<string, unknown>>;
|
|
76
|
+
_route: RuntimeRoute;
|
|
77
|
+
};
|
|
78
|
+
}>;
|
|
79
|
+
pageRoute?: RuntimeRoute;
|
|
80
|
+
pattern: string;
|
|
81
|
+
regex: RegExp;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* A ClientRoute that has been loaded — component and pageRoute are guaranteed defined.
|
|
85
|
+
* The initial route is eagerly loaded in the hydrate entry; subsequent routes are loaded
|
|
86
|
+
* on navigation via fetchPageState.
|
|
87
|
+
*/
|
|
88
|
+
export type LoadedClientRoute = ClientRoute & {
|
|
89
|
+
component: React.ComponentType<Record<string, unknown>>;
|
|
90
|
+
pageRoute: RuntimeRoute;
|
|
91
|
+
};
|
|
92
|
+
export interface RouterProviderProps {
|
|
93
|
+
defaultPreload?: PreloadStrategy;
|
|
94
|
+
defaultPreloadDelay?: number;
|
|
95
|
+
defaultPreloadStaleTime?: number;
|
|
96
|
+
initialData: Record<string, unknown>;
|
|
97
|
+
initialMatch: LoadedClientRoute;
|
|
98
|
+
/** Maximum number of prefetch cache entries. Oldest entry is evicted when exceeded. Default: 50. */
|
|
99
|
+
prefetchCacheSize?: number;
|
|
100
|
+
root: RuntimeRoute | null;
|
|
101
|
+
routes: ClientRoute[];
|
|
102
|
+
}
|
|
103
|
+
interface RouterState {
|
|
104
|
+
data: Record<string, unknown>;
|
|
105
|
+
match: LoadedClientRoute;
|
|
106
|
+
title?: string;
|
|
107
|
+
}
|
|
108
|
+
/** @internal */
|
|
109
|
+
export interface CacheEntry {
|
|
110
|
+
createdAt: number;
|
|
111
|
+
promise: Promise<RouterState | null>;
|
|
112
|
+
}
|
|
113
|
+
/** @internal Exported for unit testing only. */
|
|
114
|
+
export declare function shouldRefetch(entry: CacheEntry, staleTime: number): boolean;
|
|
115
|
+
/** @internal Exported for unit testing only. */
|
|
116
|
+
export declare function buildPageElement(match: LoadedClientRoute, root: RuntimeRoute | null, data: Record<string, unknown>): React.ReactNode;
|
|
117
|
+
export declare function RouterProvider({ routes, root, initialMatch, initialData, defaultPreload, defaultPreloadDelay, defaultPreloadStaleTime, prefetchCacheSize, }: RouterProviderProps): React.ReactElement;
|
|
118
|
+
export declare function Link<To extends RouteTo>({ to, search, hash, preload, preloadDelay, preloadStaleTime, onClick, onMouseEnter, onMouseLeave, onFocus, children, ...anchorProps }: LinkProps<To>): React.ReactElement;
|
|
119
|
+
export {};
|
package/dist/link.js
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/link.tsx
|
|
3
|
+
import {
|
|
4
|
+
createContext,
|
|
5
|
+
createElement,
|
|
6
|
+
useCallback,
|
|
7
|
+
useContext,
|
|
8
|
+
useEffect,
|
|
9
|
+
useRef,
|
|
10
|
+
useState
|
|
11
|
+
} from "react";
|
|
12
|
+
function buildHref(to, search, hash) {
|
|
13
|
+
let url = to;
|
|
14
|
+
if (search && Object.keys(search).length > 0) {
|
|
15
|
+
const params = new URLSearchParams;
|
|
16
|
+
for (const [k, v] of Object.entries(search)) {
|
|
17
|
+
if (v != null) {
|
|
18
|
+
params.set(k, String(v));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const qs = params.toString();
|
|
22
|
+
if (qs) {
|
|
23
|
+
url += `?${qs}`;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (hash) {
|
|
27
|
+
url += `#${hash}`;
|
|
28
|
+
}
|
|
29
|
+
return url;
|
|
30
|
+
}
|
|
31
|
+
var RouterContext = createContext(null);
|
|
32
|
+
function useRouter() {
|
|
33
|
+
return useContext(RouterContext) ?? {
|
|
34
|
+
navigate: (href) => {
|
|
35
|
+
window.location.href = href;
|
|
36
|
+
return Promise.resolve();
|
|
37
|
+
},
|
|
38
|
+
prefetch: () => {},
|
|
39
|
+
isNavigating: false,
|
|
40
|
+
defaultPreload: "intent",
|
|
41
|
+
defaultPreloadDelay: 50,
|
|
42
|
+
defaultPreloadStaleTime: 30000
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function shouldRefetch(entry, staleTime) {
|
|
46
|
+
return Date.now() - entry.createdAt > staleTime;
|
|
47
|
+
}
|
|
48
|
+
function buildPageElement(match, root, data) {
|
|
49
|
+
let element = createElement(match.component, data);
|
|
50
|
+
const allLayouts = [];
|
|
51
|
+
let current = match.pageRoute;
|
|
52
|
+
while (current) {
|
|
53
|
+
if (current.layout) {
|
|
54
|
+
allLayouts.unshift(current.layout);
|
|
55
|
+
}
|
|
56
|
+
current = current.parent;
|
|
57
|
+
}
|
|
58
|
+
const layouts = root ? allLayouts.slice(1) : allLayouts;
|
|
59
|
+
for (let i = layouts.length - 1;i >= 0; i--) {
|
|
60
|
+
const Layout = layouts[i];
|
|
61
|
+
if (Layout) {
|
|
62
|
+
element = createElement(Layout, { ...data }, element);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (root?.layout) {
|
|
66
|
+
element = createElement(root.layout, { ...data }, element);
|
|
67
|
+
}
|
|
68
|
+
return element;
|
|
69
|
+
}
|
|
70
|
+
function RouterProvider({
|
|
71
|
+
routes,
|
|
72
|
+
root,
|
|
73
|
+
initialMatch,
|
|
74
|
+
initialData,
|
|
75
|
+
defaultPreload = "intent",
|
|
76
|
+
defaultPreloadDelay = 50,
|
|
77
|
+
defaultPreloadStaleTime = 30000,
|
|
78
|
+
prefetchCacheSize = 50
|
|
79
|
+
}) {
|
|
80
|
+
const [state, setState] = useState({
|
|
81
|
+
match: initialMatch,
|
|
82
|
+
data: initialData
|
|
83
|
+
});
|
|
84
|
+
const [isNavigating, setIsNavigating] = useState(false);
|
|
85
|
+
const prefetchCache = useRef(new Map);
|
|
86
|
+
const fetchPageState = useCallback(async (href) => {
|
|
87
|
+
try {
|
|
88
|
+
const url = new URL(href, window.location.origin);
|
|
89
|
+
const match = routes.find((r) => r.regex.test(url.pathname));
|
|
90
|
+
if (!match) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
const [res, loadedMod] = await Promise.all([fetch(href), match.load()]);
|
|
94
|
+
const html = await res.text();
|
|
95
|
+
const doc = new DOMParser().parseFromString(html, "text/html");
|
|
96
|
+
const dataEl = doc.getElementById("__FURIN_DATA__");
|
|
97
|
+
const data = dataEl ? JSON.parse(dataEl.textContent || "{}") : {};
|
|
98
|
+
const loadedMatch = {
|
|
99
|
+
...match,
|
|
100
|
+
component: loadedMod.default.component,
|
|
101
|
+
pageRoute: loadedMod.default._route
|
|
102
|
+
};
|
|
103
|
+
return { match: loadedMatch, data, title: doc.title };
|
|
104
|
+
} catch {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}, [routes]);
|
|
108
|
+
const prefetch = useCallback((href, opts) => {
|
|
109
|
+
const staleTime = opts?.staleTime ?? defaultPreloadStaleTime;
|
|
110
|
+
const existing = prefetchCache.current.get(href);
|
|
111
|
+
if (existing && !shouldRefetch(existing, staleTime)) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
prefetchCache.current.set(href, {
|
|
115
|
+
promise: fetchPageState(href),
|
|
116
|
+
createdAt: Date.now()
|
|
117
|
+
});
|
|
118
|
+
if (prefetchCache.current.size > prefetchCacheSize) {
|
|
119
|
+
const oldest = prefetchCache.current.keys().next().value;
|
|
120
|
+
prefetchCache.current.delete(oldest);
|
|
121
|
+
}
|
|
122
|
+
}, [fetchPageState, defaultPreloadStaleTime, prefetchCacheSize]);
|
|
123
|
+
const navigate = useCallback(async (href, opts) => {
|
|
124
|
+
prefetch(href);
|
|
125
|
+
setIsNavigating(true);
|
|
126
|
+
try {
|
|
127
|
+
const newState = await prefetchCache.current.get(href)?.promise;
|
|
128
|
+
if (!newState) {
|
|
129
|
+
window.location.href = href;
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
setState(newState);
|
|
133
|
+
if (newState.title) {
|
|
134
|
+
document.title = newState.title;
|
|
135
|
+
}
|
|
136
|
+
if (opts?.replace) {
|
|
137
|
+
window.history.replaceState(null, "", href);
|
|
138
|
+
} else {
|
|
139
|
+
window.history.pushState(null, "", href);
|
|
140
|
+
}
|
|
141
|
+
window.scrollTo(0, 0);
|
|
142
|
+
} finally {
|
|
143
|
+
setIsNavigating(false);
|
|
144
|
+
}
|
|
145
|
+
}, [prefetch]);
|
|
146
|
+
const handlePopState = useCallback(async () => {
|
|
147
|
+
const href = window.location.pathname + window.location.search;
|
|
148
|
+
setIsNavigating(true);
|
|
149
|
+
try {
|
|
150
|
+
const newState = await fetchPageState(href);
|
|
151
|
+
if (!newState) {
|
|
152
|
+
window.location.reload();
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
setState(newState);
|
|
156
|
+
if (newState.title) {
|
|
157
|
+
document.title = newState.title;
|
|
158
|
+
}
|
|
159
|
+
} finally {
|
|
160
|
+
setIsNavigating(false);
|
|
161
|
+
}
|
|
162
|
+
}, [fetchPageState]);
|
|
163
|
+
useEffect(() => {
|
|
164
|
+
window.addEventListener("popstate", handlePopState);
|
|
165
|
+
return () => window.removeEventListener("popstate", handlePopState);
|
|
166
|
+
}, [handlePopState]);
|
|
167
|
+
return createElement(RouterContext.Provider, {
|
|
168
|
+
value: {
|
|
169
|
+
navigate,
|
|
170
|
+
prefetch,
|
|
171
|
+
isNavigating,
|
|
172
|
+
defaultPreload,
|
|
173
|
+
defaultPreloadDelay,
|
|
174
|
+
defaultPreloadStaleTime
|
|
175
|
+
}
|
|
176
|
+
}, buildPageElement(state.match, root, state.data));
|
|
177
|
+
}
|
|
178
|
+
function Link({
|
|
179
|
+
to,
|
|
180
|
+
search,
|
|
181
|
+
hash,
|
|
182
|
+
preload,
|
|
183
|
+
preloadDelay,
|
|
184
|
+
preloadStaleTime,
|
|
185
|
+
onClick,
|
|
186
|
+
onMouseEnter,
|
|
187
|
+
onMouseLeave,
|
|
188
|
+
onFocus,
|
|
189
|
+
children,
|
|
190
|
+
...anchorProps
|
|
191
|
+
}) {
|
|
192
|
+
const router = useRouter();
|
|
193
|
+
const anchorRef = useRef(null);
|
|
194
|
+
const intentTimerRef = useRef(null);
|
|
195
|
+
const href = buildHref(to, search, hash);
|
|
196
|
+
const effectivePreload = preload ?? router.defaultPreload;
|
|
197
|
+
const effectiveDelay = preloadDelay ?? router.defaultPreloadDelay;
|
|
198
|
+
const effectiveStaleTime = preloadStaleTime ?? router.defaultPreloadStaleTime;
|
|
199
|
+
const triggerPrefetch = useCallback(() => {
|
|
200
|
+
router.prefetch(href, { staleTime: effectiveStaleTime });
|
|
201
|
+
}, [router, href, effectiveStaleTime]);
|
|
202
|
+
useEffect(() => {
|
|
203
|
+
if (effectivePreload === "render") {
|
|
204
|
+
triggerPrefetch();
|
|
205
|
+
}
|
|
206
|
+
}, [effectivePreload, triggerPrefetch]);
|
|
207
|
+
useEffect(() => {
|
|
208
|
+
if (effectivePreload !== "viewport" || !anchorRef.current) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const observer = new IntersectionObserver(([entry]) => {
|
|
212
|
+
if (entry?.isIntersecting) {
|
|
213
|
+
triggerPrefetch();
|
|
214
|
+
observer.disconnect();
|
|
215
|
+
}
|
|
216
|
+
}, { rootMargin: "200px" });
|
|
217
|
+
observer.observe(anchorRef.current);
|
|
218
|
+
return () => observer.disconnect();
|
|
219
|
+
}, [effectivePreload, triggerPrefetch]);
|
|
220
|
+
const isInternal = (url) => {
|
|
221
|
+
try {
|
|
222
|
+
return new URL(url, window.location.origin).origin === window.location.origin;
|
|
223
|
+
} catch {
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
const handleClick = (e) => {
|
|
228
|
+
onClick?.(e);
|
|
229
|
+
if (e.defaultPrevented) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
if (anchorProps.target && anchorProps.target !== "_self") {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (!isInternal(href)) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
e.preventDefault();
|
|
242
|
+
router.navigate(href);
|
|
243
|
+
};
|
|
244
|
+
const handleMouseEnter = (e) => {
|
|
245
|
+
onMouseEnter?.(e);
|
|
246
|
+
if (effectivePreload === "intent" && isInternal(href)) {
|
|
247
|
+
intentTimerRef.current = setTimeout(triggerPrefetch, effectiveDelay);
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
const handleMouseLeave = (e) => {
|
|
251
|
+
onMouseLeave?.(e);
|
|
252
|
+
if (intentTimerRef.current !== null) {
|
|
253
|
+
clearTimeout(intentTimerRef.current);
|
|
254
|
+
intentTimerRef.current = null;
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
const handleFocus = (e) => {
|
|
258
|
+
onFocus?.(e);
|
|
259
|
+
if (effectivePreload === "intent" && isInternal(href)) {
|
|
260
|
+
triggerPrefetch();
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
return createElement("a", {
|
|
264
|
+
ref: anchorRef,
|
|
265
|
+
href,
|
|
266
|
+
onClick: handleClick,
|
|
267
|
+
onMouseEnter: handleMouseEnter,
|
|
268
|
+
onMouseLeave: handleMouseLeave,
|
|
269
|
+
onFocus: handleFocus,
|
|
270
|
+
...anchorProps
|
|
271
|
+
}, children);
|
|
272
|
+
}
|
|
273
|
+
export {
|
|
274
|
+
useRouter,
|
|
275
|
+
shouldRefetch,
|
|
276
|
+
buildPageElement,
|
|
277
|
+
buildHref,
|
|
278
|
+
RouterProvider,
|
|
279
|
+
RouterContext,
|
|
280
|
+
Link
|
|
281
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standalone Bun bundler plugin for Furin.
|
|
3
|
+
*
|
|
4
|
+
* Register it in your project's bunfig.toml so that Bun's HTML bundler
|
|
5
|
+
* applies it when building the client bundle:
|
|
6
|
+
*
|
|
7
|
+
* ```toml
|
|
8
|
+
* [serve.static]
|
|
9
|
+
* plugins = ["@teyik0/furin/strip-plugin"]
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* The plugin:
|
|
13
|
+
* 1. Stubs `elysia` for the browser with a minimal proxy.
|
|
14
|
+
* 2. Stubs `bun:*` builtins with an empty module (safety net — DCE removes
|
|
15
|
+
* loader imports before they reach the browser bundle in practice).
|
|
16
|
+
* 3. Strips server-only code (loader, query, params) from page files before
|
|
17
|
+
* they are bundled into the client entry.
|
|
18
|
+
*/
|
|
19
|
+
declare const plugin: Bun.BunPlugin;
|
|
20
|
+
export default plugin;
|