arcway 0.1.1 → 0.1.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/client/index.js +1 -1
- package/client/router.js +274 -0
- package/client/ws.js +2 -3
- package/package.json +1 -1
- package/server/pages/build-plugins.js +3 -3
package/client/index.js
CHANGED
|
@@ -18,7 +18,7 @@ import { useGraphQL, useGraphQLMutation } from './hooks/use-graphql.js';
|
|
|
18
18
|
import { WsManager } from './ws.js';
|
|
19
19
|
import useLocalStorage from './hooks/web/use-local-storage.js';
|
|
20
20
|
import useClickOutside from './hooks/web/use-click-outside.js';
|
|
21
|
-
import { Link, Router, SoloRouter, useRouter, usePathname, useParams, useSearchParams } from './router.
|
|
21
|
+
import { Link, Router, SoloRouter, useRouter, usePathname, useParams, useSearchParams } from './router.js';
|
|
22
22
|
import { Head, setSSRHeadData, clearSSRHeadData, renderHeadToString } from './head.js';
|
|
23
23
|
import { useEnv, env, collectPublicEnv, buildEnvScriptTag } from './env.js';
|
|
24
24
|
|
package/client/router.js
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import {
|
|
4
|
+
createContext,
|
|
5
|
+
useContext,
|
|
6
|
+
useState,
|
|
7
|
+
useEffect,
|
|
8
|
+
useCallback,
|
|
9
|
+
useMemo,
|
|
10
|
+
useRef,
|
|
11
|
+
useTransition,
|
|
12
|
+
createElement
|
|
13
|
+
} from "react";
|
|
14
|
+
import {
|
|
15
|
+
readClientManifest,
|
|
16
|
+
loadPage,
|
|
17
|
+
matchClientRoute,
|
|
18
|
+
loadLoadingComponents,
|
|
19
|
+
prefetchRoute
|
|
20
|
+
} from "./page-loader.js";
|
|
21
|
+
import { parseQuery } from "./query.js";
|
|
22
|
+
const ROUTER_CTX_KEY = "__router_context__";
|
|
23
|
+
const RouterContext = globalThis[ROUTER_CTX_KEY] ??= createContext(null);
|
|
24
|
+
function wrapInLayouts(element, layouts) {
|
|
25
|
+
for (let i = layouts.length - 1; i >= 0; i--) {
|
|
26
|
+
element = createElement(layouts[i], null, element);
|
|
27
|
+
}
|
|
28
|
+
return element;
|
|
29
|
+
}
|
|
30
|
+
function useRouter() {
|
|
31
|
+
const ctx = useContext(RouterContext);
|
|
32
|
+
if (!ctx) {
|
|
33
|
+
throw new Error("useRouter must be used within a <Router> provider");
|
|
34
|
+
}
|
|
35
|
+
return useMemo(
|
|
36
|
+
() => ({
|
|
37
|
+
pathname: ctx.pathname,
|
|
38
|
+
params: ctx.params,
|
|
39
|
+
query: typeof window !== "undefined" ? parseQuery(window.location.search) : {},
|
|
40
|
+
push: (to, options) => ctx.navigate(to, { ...options, replace: false }),
|
|
41
|
+
replace: (to, options) => ctx.navigate(to, { ...options, replace: true }),
|
|
42
|
+
back: () => {
|
|
43
|
+
if (typeof window !== "undefined") window.history.back();
|
|
44
|
+
},
|
|
45
|
+
forward: () => {
|
|
46
|
+
if (typeof window !== "undefined") window.history.forward();
|
|
47
|
+
},
|
|
48
|
+
refresh: () => {
|
|
49
|
+
if (typeof window !== "undefined") window.location.reload();
|
|
50
|
+
}
|
|
51
|
+
}),
|
|
52
|
+
[ctx.pathname, ctx.params, ctx.navigate]
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
function usePathname() {
|
|
56
|
+
return useRouter().pathname;
|
|
57
|
+
}
|
|
58
|
+
function useParams() {
|
|
59
|
+
return useRouter().params;
|
|
60
|
+
}
|
|
61
|
+
function useSearchParams() {
|
|
62
|
+
return useRouter().query;
|
|
63
|
+
}
|
|
64
|
+
function Router({
|
|
65
|
+
initialPath,
|
|
66
|
+
initialParams,
|
|
67
|
+
initialComponent,
|
|
68
|
+
initialLayouts,
|
|
69
|
+
initialLoadings,
|
|
70
|
+
children
|
|
71
|
+
}) {
|
|
72
|
+
const [pathname, setPathname] = useState(
|
|
73
|
+
() => initialPath ?? (typeof window !== "undefined" ? window.location.pathname : "/")
|
|
74
|
+
);
|
|
75
|
+
const [pageState, setPageState] = useState({
|
|
76
|
+
component: initialComponent ?? null,
|
|
77
|
+
layouts: initialLayouts ?? [],
|
|
78
|
+
loadings: initialLoadings ?? [],
|
|
79
|
+
params: initialParams ?? {}
|
|
80
|
+
});
|
|
81
|
+
const [isNavigating, setIsNavigating] = useState(false);
|
|
82
|
+
const [isPending, startTransition] = useTransition();
|
|
83
|
+
const manifestRef = useRef(null);
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
manifestRef.current = readClientManifest();
|
|
86
|
+
const onManifestUpdate = () => {
|
|
87
|
+
manifestRef.current = readClientManifest();
|
|
88
|
+
};
|
|
89
|
+
window.addEventListener("manifest-update", onManifestUpdate);
|
|
90
|
+
return () => window.removeEventListener("manifest-update", onManifestUpdate);
|
|
91
|
+
}, []);
|
|
92
|
+
const applyLoaded = useCallback((loaded, newPath) => {
|
|
93
|
+
startTransition(() => {
|
|
94
|
+
setPathname(newPath);
|
|
95
|
+
setPageState({
|
|
96
|
+
component: loaded.component,
|
|
97
|
+
layouts: loaded.layouts,
|
|
98
|
+
loadings: loaded.loadings,
|
|
99
|
+
params: loaded.params
|
|
100
|
+
});
|
|
101
|
+
setIsNavigating(false);
|
|
102
|
+
});
|
|
103
|
+
}, []);
|
|
104
|
+
const navigateToPage = useCallback(
|
|
105
|
+
async (to, options) => {
|
|
106
|
+
const scroll = options?.scroll !== false;
|
|
107
|
+
const replace = options?.replace === true;
|
|
108
|
+
if (to === pathname) return;
|
|
109
|
+
const manifest = manifestRef.current;
|
|
110
|
+
if (!manifest) {
|
|
111
|
+
window.location.href = to;
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const matched = matchClientRoute(manifest, to);
|
|
115
|
+
if (!matched) {
|
|
116
|
+
window.location.href = to;
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
setIsNavigating(true);
|
|
120
|
+
if (replace) {
|
|
121
|
+
window.history.replaceState(null, "", to);
|
|
122
|
+
} else {
|
|
123
|
+
window.history.pushState(null, "", to);
|
|
124
|
+
}
|
|
125
|
+
const targetLoadings = await loadLoadingComponents(matched.route);
|
|
126
|
+
if (targetLoadings.length > 0) {
|
|
127
|
+
setPathname(to);
|
|
128
|
+
setPageState((prev) => ({
|
|
129
|
+
...prev,
|
|
130
|
+
loadings: targetLoadings,
|
|
131
|
+
params: matched.params
|
|
132
|
+
}));
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
const loaded = await loadPage(manifest, to);
|
|
136
|
+
if (!loaded) {
|
|
137
|
+
window.location.href = to;
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
applyLoaded(loaded, to);
|
|
141
|
+
if (scroll) {
|
|
142
|
+
window.scrollTo(0, 0);
|
|
143
|
+
}
|
|
144
|
+
} catch (err) {
|
|
145
|
+
console.error("Client navigation failed, falling back to full page load:", err);
|
|
146
|
+
window.location.href = to;
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
[pathname, applyLoaded]
|
|
150
|
+
);
|
|
151
|
+
useEffect(() => {
|
|
152
|
+
async function onPopState() {
|
|
153
|
+
const newPath = window.location.pathname;
|
|
154
|
+
const manifest = manifestRef.current;
|
|
155
|
+
if (!manifest) {
|
|
156
|
+
setPathname(newPath);
|
|
157
|
+
setPageState((prev) => ({ ...prev, params: {} }));
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
try {
|
|
161
|
+
const loaded = await loadPage(manifest, newPath);
|
|
162
|
+
if (loaded) {
|
|
163
|
+
applyLoaded(loaded, newPath);
|
|
164
|
+
} else {
|
|
165
|
+
window.location.reload();
|
|
166
|
+
}
|
|
167
|
+
} catch {
|
|
168
|
+
window.location.reload();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
window.addEventListener("popstate", onPopState);
|
|
172
|
+
return () => window.removeEventListener("popstate", onPopState);
|
|
173
|
+
}, [applyLoaded]);
|
|
174
|
+
const { component: PageComponent, layouts, loadings, params } = pageState;
|
|
175
|
+
let content;
|
|
176
|
+
if (PageComponent) {
|
|
177
|
+
const inner = isNavigating && loadings.length > 0 ? createElement(loadings.at(-1)) : createElement(PageComponent, params);
|
|
178
|
+
content = wrapInLayouts(inner, layouts);
|
|
179
|
+
} else {
|
|
180
|
+
content = children;
|
|
181
|
+
}
|
|
182
|
+
return /* @__PURE__ */ jsxs(
|
|
183
|
+
RouterContext.Provider,
|
|
184
|
+
{
|
|
185
|
+
value: {
|
|
186
|
+
pathname,
|
|
187
|
+
params,
|
|
188
|
+
navigate: navigateToPage
|
|
189
|
+
},
|
|
190
|
+
children: [
|
|
191
|
+
content,
|
|
192
|
+
isNavigating && loadings.length === 0 && !isPending && /* @__PURE__ */ jsx(
|
|
193
|
+
"div",
|
|
194
|
+
{
|
|
195
|
+
style: {
|
|
196
|
+
position: "fixed",
|
|
197
|
+
top: 0,
|
|
198
|
+
left: 0,
|
|
199
|
+
width: "100%",
|
|
200
|
+
height: "2px",
|
|
201
|
+
backgroundColor: "#0070f3",
|
|
202
|
+
zIndex: 99999,
|
|
203
|
+
animation: "nav-progress 1s ease-in-out infinite"
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
)
|
|
207
|
+
]
|
|
208
|
+
}
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
function Link({ href, children, onClick, scroll, replace, prefetch = "hover", ...rest }) {
|
|
212
|
+
const router = useContext(RouterContext);
|
|
213
|
+
const linkRef = useRef(null);
|
|
214
|
+
const handleMouseEnter = useCallback(() => {
|
|
215
|
+
if (prefetch !== "hover") return;
|
|
216
|
+
const manifest = readClientManifest();
|
|
217
|
+
if (manifest) {
|
|
218
|
+
prefetchRoute(manifest, href);
|
|
219
|
+
}
|
|
220
|
+
}, [href, prefetch]);
|
|
221
|
+
useEffect(() => {
|
|
222
|
+
if (prefetch !== "viewport") return;
|
|
223
|
+
const el = linkRef.current;
|
|
224
|
+
if (!el || typeof IntersectionObserver === "undefined") return;
|
|
225
|
+
const observer = new IntersectionObserver(
|
|
226
|
+
(entries) => {
|
|
227
|
+
for (const entry of entries) {
|
|
228
|
+
if (entry.isIntersecting) {
|
|
229
|
+
const manifest = readClientManifest();
|
|
230
|
+
if (manifest) {
|
|
231
|
+
prefetchRoute(manifest, href);
|
|
232
|
+
}
|
|
233
|
+
observer.unobserve(el);
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
{ rootMargin: "200px" }
|
|
239
|
+
);
|
|
240
|
+
observer.observe(el);
|
|
241
|
+
return () => observer.disconnect();
|
|
242
|
+
}, [href, prefetch]);
|
|
243
|
+
function handleClick(e) {
|
|
244
|
+
if (onClick) onClick(e);
|
|
245
|
+
if (e.defaultPrevented) return;
|
|
246
|
+
if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
|
|
247
|
+
if (e.button !== 0) return;
|
|
248
|
+
if (rest.target === "_blank" || rest.download !== void 0) return;
|
|
249
|
+
try {
|
|
250
|
+
const url = new URL(href, window.location.origin);
|
|
251
|
+
if (url.origin !== window.location.origin) return;
|
|
252
|
+
} catch {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
e.preventDefault();
|
|
256
|
+
if (router) {
|
|
257
|
+
router.navigate(href, { scroll, replace });
|
|
258
|
+
} else {
|
|
259
|
+
window.history.pushState(null, "", href);
|
|
260
|
+
window.dispatchEvent(new PopStateEvent("popstate"));
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return /* @__PURE__ */ jsx("a", { ref: linkRef, href, onClick: handleClick, onMouseEnter: handleMouseEnter, ...rest, children });
|
|
264
|
+
}
|
|
265
|
+
const SoloRouter = Router;
|
|
266
|
+
export {
|
|
267
|
+
Link,
|
|
268
|
+
Router,
|
|
269
|
+
SoloRouter,
|
|
270
|
+
useParams,
|
|
271
|
+
usePathname,
|
|
272
|
+
useRouter,
|
|
273
|
+
useSearchParams
|
|
274
|
+
};
|
package/client/ws.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { io } from 'socket.io-client';
|
|
2
|
-
|
|
3
1
|
class WsManager {
|
|
4
2
|
socket = null;
|
|
5
3
|
socketId = null;
|
|
@@ -25,10 +23,11 @@ class WsManager {
|
|
|
25
23
|
return !!this.socket?.connected && this.socketId !== null;
|
|
26
24
|
}
|
|
27
25
|
|
|
28
|
-
connect() {
|
|
26
|
+
async connect() {
|
|
29
27
|
if (this.socket) return;
|
|
30
28
|
|
|
31
29
|
try {
|
|
30
|
+
const { io } = await import('socket.io-client');
|
|
32
31
|
const parsed = new URL(this.url);
|
|
33
32
|
this.socket = io(parsed.origin, {
|
|
34
33
|
path: this.wsPath,
|
package/package.json
CHANGED
|
@@ -12,7 +12,7 @@ function serverExternalsPlugin() {
|
|
|
12
12
|
const pkgName =
|
|
13
13
|
args.path.startsWith('@') && parts.length >= 2 ? `${parts[0]}/${parts[1]}` : parts[0];
|
|
14
14
|
if (!resolvedCache.has(pkgName)) {
|
|
15
|
-
resolvedCache.set(pkgName,
|
|
15
|
+
resolvedCache.set(pkgName, needsBundling(pkgName, args.resolveDir));
|
|
16
16
|
}
|
|
17
17
|
if (resolvedCache.get(pkgName)) return void 0;
|
|
18
18
|
return { path: args.path, external: true };
|
|
@@ -20,11 +20,11 @@ function serverExternalsPlugin() {
|
|
|
20
20
|
},
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
|
-
function
|
|
23
|
+
function needsBundling(pkgName, resolveDir) {
|
|
24
24
|
try {
|
|
25
25
|
const require2 = createRequire(resolveDir + '/');
|
|
26
26
|
const resolved = require2.resolve(pkgName);
|
|
27
|
-
return /\.
|
|
27
|
+
return /\.([jt]sx|ts)$/.test(resolved);
|
|
28
28
|
} catch {
|
|
29
29
|
return false;
|
|
30
30
|
}
|