@torpor/build 0.1.34 → 0.3.0
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/CHANGELOG.md +48 -0
- package/dist/RouteType-g8Ce5gOd.js +107 -0
- package/dist/RouteType-g8Ce5gOd.js.map +1 -0
- package/dist/Server-CSpY_-Ba.d.ts +78 -0
- package/dist/Server-CSpY_-Ba.d.ts.map +1 -0
- package/dist/ServerEvent-2-CnAy1W.js +74 -0
- package/dist/ServerEvent-2-CnAy1W.js.map +1 -0
- package/dist/Site-D4x1l0p5.d.ts +67 -0
- package/dist/Site-D4x1l0p5.d.ts.map +1 -0
- package/dist/_page-CsdPubw4.js +33 -0
- package/dist/_page-CsdPubw4.js.map +1 -0
- package/dist/bin/index.d.ts +1 -1
- package/dist/bin/index.js +14 -19
- package/dist/bin/index.js.map +1 -1
- package/dist/connectMiddleware-DbLj0St1.js +252 -0
- package/dist/connectMiddleware-DbLj0St1.js.map +1 -0
- package/dist/index.d.ts +191 -182
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +109 -145
- package/dist/index.js.map +1 -1
- package/dist/nav.d.ts +14 -0
- package/dist/nav.d.ts.map +1 -0
- package/dist/nav.js +184 -0
- package/dist/nav.js.map +1 -0
- package/dist/pathToRegex-CTgx7hQP.js +11 -0
- package/dist/pathToRegex-CTgx7hQP.js.map +1 -0
- package/dist/response-D-XpIuMH.js +307 -0
- package/dist/response-D-XpIuMH.js.map +1 -0
- package/dist/response.d.ts +336 -151
- package/dist/response.d.ts.map +1 -0
- package/dist/response.js +4 -37
- package/dist/run-CNdaKSPR.js +331 -0
- package/dist/run-CNdaKSPR.js.map +1 -0
- package/dist/run.d.ts +11 -8
- package/dist/run.d.ts.map +1 -0
- package/dist/run.js +8 -18
- package/dist/seeOther-BmehiI4C.js +110 -0
- package/dist/seeOther-BmehiI4C.js.map +1 -0
- package/dist/server.d.ts +12 -11
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +5 -18
- package/dist/state.d.ts +14 -10
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +3 -7
- package/dist/test.d.ts +12 -11
- package/dist/test.d.ts.map +1 -0
- package/dist/test.js +152 -225
- package/dist/test.js.map +1 -1
- package/package.json +25 -29
- package/src/bin/index.ts +9 -11
- package/src/index.ts +3 -0
- package/src/nav/load.ts +9 -0
- package/src/nav/loadData.ts +121 -0
- package/src/nav/navigate.ts +154 -0
- package/src/nav/reload.ts +9 -0
- package/src/nav.ts +4 -0
- package/src/response/badRequest.ts +9 -4
- package/src/response/created.ts +10 -3
- package/src/response/forbidden.ts +11 -4
- package/src/response/found.ts +30 -0
- package/src/response/methodNotAllowed.ts +8 -4
- package/src/response/movedPermanently.ts +25 -0
- package/src/response/notFound.ts +14 -7
- package/src/response/notModified.ts +24 -4
- package/src/response/ok.ts +19 -10
- package/src/response/permanentRedirect.ts +30 -0
- package/src/response/seeOther.ts +9 -3
- package/src/response/serverError.ts +14 -2
- package/src/response/temporaryRedirect.ts +31 -0
- package/src/response/unauthorized.ts +11 -4
- package/src/response/unprocessable.ts +8 -2
- package/src/response.ts +8 -4
- package/src/run/prepareTemplate.ts +32 -0
- package/src/{site → run}/run.ts +1 -1
- package/src/{site → run}/runBuild.ts +2 -2
- package/src/{site → run}/runDev.ts +2 -2
- package/src/{site → run}/runPreview.ts +12 -2
- package/src/run.ts +4 -4
- package/src/server/Routerx.ts +3 -3
- package/src/server/Server.ts +3 -3
- package/src/server/ServerEvent.ts +1 -1
- package/src/server/connect/bufferToArrayBuffer.ts +8 -0
- package/src/server/connect/connectMiddleware.ts +4 -2
- package/src/server/connect/nodeMessageToNodeResponse.ts +2 -2
- package/src/server/types/ServerFunction.ts +3 -1
- package/src/site/Router.ts +3 -3
- package/src/site/clientEntry.ts +40 -273
- package/src/site/defaultAdapter.ts +1 -1
- package/src/site/serverEntry.ts +86 -50
- package/src/state/$page.ts +3 -12
- package/src/state/client.ts +6 -0
- package/src/state/internal.ts +45 -0
- package/src/test/runTest.ts +4 -4
- package/src/types/Adapter.ts +2 -4
- package/src/types/ClientState.ts +8 -0
- package/src/types/InternalState.ts +7 -0
- package/src/types/LayoutHandler.ts +2 -4
- package/src/types/LayoutPath.ts +10 -0
- package/src/types/ManifestRoute.ts +2 -4
- package/src/types/PageEndPoint.ts +5 -5
- package/src/types/PageLoadEvent.ts +2 -4
- package/src/types/PageServerAction.ts +1 -1
- package/src/types/PageServerEndPoint.ts +2 -4
- package/src/types/PageServerLoad.ts +1 -1
- package/src/types/PageState.ts +8 -0
- package/src/types/RouteHandler.ts +2 -4
- package/src/types/RouteLayoutHandler.ts +2 -4
- package/src/types/RouteMatchResult.ts +2 -4
- package/src/types/ServerHook.ts +3 -7
- package/src/types/ServerLoadEvent.ts +2 -4
- package/src/types/ServerRequest.ts +1 -1
- package/src/{site → utils}/pathToRegex.ts +1 -1
- package/{tsup.config.ts → tsdown.config.ts} +3 -4
- package/dist/Server-BC_dixZQ.d.cts +0 -71
- package/dist/Server-BC_dixZQ.d.ts +0 -71
- package/dist/Site-4tGiw2-I.d.ts +0 -63
- package/dist/Site-BXX-S541.d.cts +0 -63
- package/dist/bin/index.cjs +0 -755
- package/dist/bin/index.cjs.map +0 -1
- package/dist/bin/index.d.cts +0 -1
- package/dist/chunk-565UOT3F.js +0 -18
- package/dist/chunk-565UOT3F.js.map +0 -1
- package/dist/chunk-B2UNTYN5.js +0 -68
- package/dist/chunk-B2UNTYN5.js.map +0 -1
- package/dist/chunk-KKFND4HK.js +0 -85
- package/dist/chunk-KKFND4HK.js.map +0 -1
- package/dist/chunk-LO7P2LJE.js +0 -278
- package/dist/chunk-LO7P2LJE.js.map +0 -1
- package/dist/chunk-PTVHEJZ5.js +0 -152
- package/dist/chunk-PTVHEJZ5.js.map +0 -1
- package/dist/chunk-RHDSM5GS.js +0 -357
- package/dist/chunk-RHDSM5GS.js.map +0 -1
- package/dist/chunk-SJVHHGVW.js +0 -13
- package/dist/chunk-SJVHHGVW.js.map +0 -1
- package/dist/chunk-WVXAN4IV.js +0 -50
- package/dist/chunk-WVXAN4IV.js.map +0 -1
- package/dist/index.cjs +0 -322
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -199
- package/dist/metafile-cjs.json +0 -1
- package/dist/metafile-esm.json +0 -1
- package/dist/response.cjs +0 -150
- package/dist/response.cjs.map +0 -1
- package/dist/response.d.cts +0 -166
- package/dist/response.js.map +0 -1
- package/dist/run.cjs +0 -764
- package/dist/run.cjs.map +0 -1
- package/dist/run.d.cts +0 -14
- package/dist/run.js.map +0 -1
- package/dist/server.cjs +0 -390
- package/dist/server.cjs.map +0 -1
- package/dist/server.d.cts +0 -17
- package/dist/server.js.map +0 -1
- package/dist/state.cjs +0 -44
- package/dist/state.cjs.map +0 -1
- package/dist/state.d.cts +0 -11
- package/dist/state.js.map +0 -1
- package/dist/test.cjs +0 -538
- package/dist/test.cjs.map +0 -1
- package/dist/test.d.cts +0 -15
- package/src/response/moved.ts +0 -16
- package/src/response/redirect.ts +0 -17
- package/src/site/prepareTemplate.ts +0 -20
- package/src/state/Page.ts +0 -8
package/src/site/clientEntry.ts
CHANGED
|
@@ -1,38 +1,33 @@
|
|
|
1
1
|
import manifest from "@torpor/build/manifest";
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import type
|
|
8
|
-
import type PageServerEndPoint from "../types/PageServerEndPoint.ts";
|
|
9
|
-
import type RouteHandler from "../types/RouteHandler.ts";
|
|
2
|
+
import loadData from "../nav/loadData.ts";
|
|
3
|
+
import navigate from "../nav/navigate.ts";
|
|
4
|
+
import client from "../state/client";
|
|
5
|
+
import type LayoutPath from "../types/LayoutPath";
|
|
6
|
+
import type PageEndPoint from "../types/PageEndPoint";
|
|
7
|
+
import type PageServerEndPoint from "../types/PageServerEndPoint";
|
|
10
8
|
import Router from "./Router.ts";
|
|
11
9
|
|
|
12
10
|
// Build the router from the Site object created by the user
|
|
13
11
|
const router = new Router();
|
|
14
12
|
router.addPages(manifest.routes);
|
|
15
13
|
|
|
16
|
-
//
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
reuse: boolean;
|
|
21
|
-
/** Data loaded from endpoints that should be re-used */
|
|
22
|
-
data: any;
|
|
23
|
-
/** The range of the UI in this layout's slot */
|
|
24
|
-
slotRange: any;
|
|
25
|
-
}
|
|
26
|
-
let layoutStack: LayoutPath[] = [];
|
|
27
|
-
|
|
28
|
-
// TODO: Probably expire after 30 seconds or something?
|
|
29
|
-
let prefetchedData: Record<string, any> = {};
|
|
14
|
+
// Put it in client state so that it can be accessed from navigate. We can't
|
|
15
|
+
// just import it to navigate because that gets built by tsdown, without Vite
|
|
16
|
+
// and the manifest
|
|
17
|
+
client.router = router;
|
|
30
18
|
|
|
31
19
|
// Intercept clicks on links
|
|
32
20
|
window.addEventListener("click", async (e) => {
|
|
33
21
|
if (e.target && (e.target as HTMLElement).tagName === "A") {
|
|
22
|
+
const link = e.target as HTMLAnchorElement;
|
|
23
|
+
|
|
24
|
+
if (ignoreAnchorMouseEvent(link, e)) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
34
28
|
e.preventDefault();
|
|
35
|
-
|
|
29
|
+
|
|
30
|
+
const href = link.href;
|
|
36
31
|
const url = new URL(href);
|
|
37
32
|
|
|
38
33
|
if (await navigate(url)) {
|
|
@@ -46,12 +41,17 @@ window.addEventListener("click", async (e) => {
|
|
|
46
41
|
// Intercept mouseover/focus/touchstart on links for prefetching
|
|
47
42
|
// TODO: settings e.g. <a href="" data-tb-prefetch="eager"/"visible"/"hover"/"touch">
|
|
48
43
|
window.addEventListener("mouseover", maybePrefetch);
|
|
49
|
-
window.addEventListener("focus", maybePrefetch);
|
|
50
44
|
window.addEventListener("touchstart", maybePrefetch);
|
|
51
45
|
|
|
52
|
-
async function maybePrefetch(e:
|
|
46
|
+
async function maybePrefetch(e: MouseEvent | TouchEvent) {
|
|
53
47
|
if (e.target && (e.target as HTMLElement).tagName === "A") {
|
|
54
|
-
const
|
|
48
|
+
const link = e.target as HTMLAnchorElement;
|
|
49
|
+
|
|
50
|
+
if (ignoreAnchorMouseEvent(link, e)) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const href = link.href;
|
|
55
55
|
const url = new URL(href);
|
|
56
56
|
|
|
57
57
|
const path = url.pathname;
|
|
@@ -76,6 +76,19 @@ async function maybePrefetch(e: Event) {
|
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
function ignoreAnchorMouseEvent(link: HTMLAnchorElement, e: MouseEvent | TouchEvent) {
|
|
80
|
+
// Ignore the event if a modifier key is pressed, or target is not
|
|
81
|
+
// "_self", or if rel="external"
|
|
82
|
+
return (
|
|
83
|
+
e.altKey ||
|
|
84
|
+
e.ctrlKey ||
|
|
85
|
+
e.metaKey ||
|
|
86
|
+
e.shiftKey ||
|
|
87
|
+
(link.target && link.target !== "_self") ||
|
|
88
|
+
link.rel === "external"
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
79
92
|
// Listen for changes to the URL that occur when the user navigates using the
|
|
80
93
|
// back or forward buttons
|
|
81
94
|
window.addEventListener("popstate", async () => {
|
|
@@ -83,254 +96,8 @@ window.addEventListener("popstate", async () => {
|
|
|
83
96
|
});
|
|
84
97
|
|
|
85
98
|
// Do the initial navigation and hydration
|
|
86
|
-
navigateToLocation(document.location, true);
|
|
99
|
+
await navigateToLocation(document.location, true);
|
|
87
100
|
|
|
88
101
|
async function navigateToLocation(location: Location, firstTime = false) {
|
|
89
102
|
return await navigate(new URL(location.toString()), firstTime);
|
|
90
103
|
}
|
|
91
|
-
|
|
92
|
-
async function navigate(url: URL, firstTime = false): Promise<boolean> {
|
|
93
|
-
let parent = document.getElementById("app");
|
|
94
|
-
if (!parent) {
|
|
95
|
-
// TODO: 500
|
|
96
|
-
console.log("500");
|
|
97
|
-
return false;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const path = url.pathname;
|
|
101
|
-
const query = url.searchParams;
|
|
102
|
-
|
|
103
|
-
console.log(`navigating to '${path}'${query.size ? ` with ${query}` : ""}`);
|
|
104
|
-
|
|
105
|
-
const route = router.match(path, query);
|
|
106
|
-
if (!route) {
|
|
107
|
-
// TODO: 404
|
|
108
|
-
console.log("404");
|
|
109
|
-
return false;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Update $page before building the components
|
|
113
|
-
$page.url = url;
|
|
114
|
-
if (path.endsWith("/_error")) {
|
|
115
|
-
$page.status = parseInt(query.get("status") ?? "404");
|
|
116
|
-
$page.error.message = query.get("message") ?? "";
|
|
117
|
-
// Make it look a bit classier by removing the query
|
|
118
|
-
window.history.replaceState({}, "", url.toString().split("?")[0]);
|
|
119
|
-
} else {
|
|
120
|
-
$page.status = 200;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const handler = route.handler;
|
|
124
|
-
const params = route.params || {};
|
|
125
|
-
|
|
126
|
-
// There must be a client endpoint with a component
|
|
127
|
-
const clientEndPoint: PageEndPoint | undefined = (await handler.endPoint()).default;
|
|
128
|
-
if (!clientEndPoint?.component) {
|
|
129
|
-
// TODO: 404
|
|
130
|
-
console.log("404");
|
|
131
|
-
return false;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// There may be a server endpoint
|
|
135
|
-
const serverEndPoint: PageServerEndPoint | undefined =
|
|
136
|
-
handler.serverEndPoint && (await handler.serverEndPoint())?.default;
|
|
137
|
-
|
|
138
|
-
let newLayoutStack: LayoutPath[] = [];
|
|
139
|
-
|
|
140
|
-
// Pass the data into $props
|
|
141
|
-
// TODO: Don't load if this is the first time -- it should have been passed to us, somehow...
|
|
142
|
-
const data = await loadData(
|
|
143
|
-
handler,
|
|
144
|
-
params,
|
|
145
|
-
path,
|
|
146
|
-
newLayoutStack,
|
|
147
|
-
clientEndPoint,
|
|
148
|
-
serverEndPoint,
|
|
149
|
-
);
|
|
150
|
-
const $props: Record<string, any> = { data };
|
|
151
|
-
|
|
152
|
-
layoutStack.push({ path: route.handler.path, data: {}, reuse: false, slotRange: null });
|
|
153
|
-
layoutStack = newLayoutStack;
|
|
154
|
-
|
|
155
|
-
// If there are layouts, work our way upwards, pushing each component into
|
|
156
|
-
// the default slot of its parent
|
|
157
|
-
// TODO: There's probably a nicer way to do this with reducers or something
|
|
158
|
-
let component = clientEndPoint.component as Component;
|
|
159
|
-
let slots: Record<string, SlotRender> | undefined = undefined;
|
|
160
|
-
if (handler.layouts) {
|
|
161
|
-
let slotFunctions: SlotRender[] = [];
|
|
162
|
-
// The last slot function will render the client component
|
|
163
|
-
slotFunctions[handler.layouts.length] = function clientComponent(parent, anchor, _, $context) {
|
|
164
|
-
let i = layoutStack.length - 1;
|
|
165
|
-
layoutStack[i].slotRange = fillLayoutSlot(
|
|
166
|
-
clientEndPoint.component!,
|
|
167
|
-
slotFunctions[i + 1],
|
|
168
|
-
parent,
|
|
169
|
-
anchor,
|
|
170
|
-
$props,
|
|
171
|
-
$context,
|
|
172
|
-
);
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
// Each earlier slot function will render a layout
|
|
176
|
-
for (let i = handler.layouts.length - 1; i >= 0; i--) {
|
|
177
|
-
const layoutEndPoint: PageEndPoint | undefined = (await handler.layouts[i].endPoint())
|
|
178
|
-
?.default;
|
|
179
|
-
if (layoutEndPoint?.component) {
|
|
180
|
-
if (layoutStack[i].reuse) {
|
|
181
|
-
// Set the parent to add the new content to (from the old
|
|
182
|
-
// content), clear the range under this point, and set the
|
|
183
|
-
// component to the slot function within this layout
|
|
184
|
-
parent = layoutStack[i].slotRange.startNode.parentNode as HTMLElement;
|
|
185
|
-
clearLayoutSlot(layoutStack[i].slotRange);
|
|
186
|
-
component = slotFunctions[i + 1];
|
|
187
|
-
break;
|
|
188
|
-
} else if (i === 0) {
|
|
189
|
-
component = layoutEndPoint.component as Component;
|
|
190
|
-
slots = { _: slotFunctions[i + 1] };
|
|
191
|
-
} else {
|
|
192
|
-
slotFunctions[i] = function layoutComponent(parent, anchor, _, $context) {
|
|
193
|
-
layoutStack[i - 1].slotRange = fillLayoutSlot(
|
|
194
|
-
layoutEndPoint.component!,
|
|
195
|
-
slotFunctions[i + 1],
|
|
196
|
-
parent,
|
|
197
|
-
anchor,
|
|
198
|
-
$props,
|
|
199
|
-
$context,
|
|
200
|
-
);
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
if (firstTime) {
|
|
208
|
-
hydrate(parent, component, $props, slots);
|
|
209
|
-
} else {
|
|
210
|
-
try {
|
|
211
|
-
mount(parent, component, $props, slots);
|
|
212
|
-
} catch (error) {
|
|
213
|
-
// TODO: Show a proper Error component
|
|
214
|
-
parent.innerHTML = '<span style="color: red">Script syntax error</span><p>' + error + "</p>";
|
|
215
|
-
console.log(error);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// Reset prefetched data on each navigation
|
|
220
|
-
prefetchedData = {};
|
|
221
|
-
|
|
222
|
-
return true;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
async function loadData(
|
|
226
|
-
handler: RouteHandler,
|
|
227
|
-
params: Record<string, string>,
|
|
228
|
-
path: string,
|
|
229
|
-
newLayoutStack: LayoutPath[],
|
|
230
|
-
clientEndPoint: PageEndPoint | undefined,
|
|
231
|
-
serverEndPoint: PageServerEndPoint | undefined,
|
|
232
|
-
prefetch = false,
|
|
233
|
-
) {
|
|
234
|
-
let data = {};
|
|
235
|
-
if (handler.layouts) {
|
|
236
|
-
for (let [i, layout] of handler.layouts.entries()) {
|
|
237
|
-
let layoutPath = layout.path;
|
|
238
|
-
for (let key in params) {
|
|
239
|
-
layoutPath = layoutPath.replace(`[${key}]`, params[key]);
|
|
240
|
-
}
|
|
241
|
-
if (layoutStack.at(i)?.path === layoutPath) {
|
|
242
|
-
// We've already loaded this layout, we can just re-use its data and UI
|
|
243
|
-
if (!prefetch) {
|
|
244
|
-
layoutStack[i].reuse = true;
|
|
245
|
-
}
|
|
246
|
-
newLayoutStack[i] = layoutStack[i];
|
|
247
|
-
Object.assign(data, layoutStack[i].data);
|
|
248
|
-
} else {
|
|
249
|
-
const stackLayout = { path: layoutPath, data: {}, reuse: false, slotRange: null };
|
|
250
|
-
const layoutEndPoint: PageEndPoint | undefined = (await layout.endPoint())?.default;
|
|
251
|
-
const layoutServerEndPoint: PageServerEndPoint | undefined =
|
|
252
|
-
layout.serverEndPoint && (await layout.serverEndPoint())?.default;
|
|
253
|
-
const layoutResponse = await loadClientAndServerData(
|
|
254
|
-
stackLayout.data,
|
|
255
|
-
document.location.origin + layoutPath,
|
|
256
|
-
params,
|
|
257
|
-
layoutEndPoint,
|
|
258
|
-
layoutServerEndPoint,
|
|
259
|
-
);
|
|
260
|
-
if (layoutResponse?.ok === false) {
|
|
261
|
-
return false;
|
|
262
|
-
}
|
|
263
|
-
Object.assign(data, stackLayout.data);
|
|
264
|
-
newLayoutStack.push(stackLayout);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
let endPointResponse = await loadClientAndServerData(
|
|
269
|
-
data,
|
|
270
|
-
document.location.origin + path,
|
|
271
|
-
params,
|
|
272
|
-
clientEndPoint,
|
|
273
|
-
serverEndPoint,
|
|
274
|
-
);
|
|
275
|
-
if (endPointResponse?.ok === false) {
|
|
276
|
-
return false;
|
|
277
|
-
}
|
|
278
|
-
return data;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
async function loadClientAndServerData(
|
|
282
|
-
data: Record<string, any>,
|
|
283
|
-
location: string,
|
|
284
|
-
params: Record<string, string>,
|
|
285
|
-
clientEndPoint?: PageEndPoint,
|
|
286
|
-
serverEndPoint?: PageServerEndPoint,
|
|
287
|
-
): Promise<Response | void> {
|
|
288
|
-
if (clientEndPoint?.load) {
|
|
289
|
-
if (prefetchedData[location]) {
|
|
290
|
-
Object.assign(data, prefetchedData[location]);
|
|
291
|
-
} else {
|
|
292
|
-
const clientUrl = new URL(document.location.href);
|
|
293
|
-
const clientParams = buildClientParams(clientUrl, params, data);
|
|
294
|
-
const clientResponse = await clientEndPoint.load(clientParams);
|
|
295
|
-
if (clientResponse) {
|
|
296
|
-
if (clientResponse.ok) {
|
|
297
|
-
if (clientResponse.headers.get("Content-Type")?.includes("application/json")) {
|
|
298
|
-
const clientData = await clientResponse.json();
|
|
299
|
-
Object.assign(data, clientData);
|
|
300
|
-
prefetchedData[location] = clientData;
|
|
301
|
-
}
|
|
302
|
-
} else {
|
|
303
|
-
return clientResponse;
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
if (serverEndPoint?.load) {
|
|
309
|
-
const serverUrl = location.replace(/\/$/, "") + "/~server";
|
|
310
|
-
|
|
311
|
-
if (prefetchedData[serverUrl]) {
|
|
312
|
-
Object.assign(data, prefetchedData[serverUrl]);
|
|
313
|
-
} else {
|
|
314
|
-
const serverResponse = await fetch(serverUrl);
|
|
315
|
-
if (serverResponse) {
|
|
316
|
-
if (serverResponse.ok) {
|
|
317
|
-
if (serverResponse.headers.get("Content-Type")?.includes("application/json")) {
|
|
318
|
-
const serverData = await serverResponse.json();
|
|
319
|
-
Object.assign(data, serverData);
|
|
320
|
-
prefetchedData[serverUrl] = serverData;
|
|
321
|
-
}
|
|
322
|
-
} else {
|
|
323
|
-
return serverResponse;
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
function buildClientParams(url: URL, params: Record<string, string>, data: Record<string, any>) {
|
|
331
|
-
return {
|
|
332
|
-
url,
|
|
333
|
-
params,
|
|
334
|
-
data,
|
|
335
|
-
};
|
|
336
|
-
}
|
package/src/site/serverEntry.ts
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import manifest from "@torpor/build/manifest";
|
|
2
|
-
// @ts-ignore This errors in the Cloudflare build?
|
|
3
|
-
import { $page } from "@torpor/build/state";
|
|
4
2
|
import { type ServerComponent, type ServerSlotRender } from "@torpor/view";
|
|
5
3
|
import notFound from "../response/notFound.ts";
|
|
6
4
|
import ok from "../response/ok.ts";
|
|
7
5
|
import seeOther from "../response/seeOther.ts";
|
|
8
6
|
import ServerEvent from "../server/ServerEvent.ts";
|
|
7
|
+
import $page from "../state/$page";
|
|
9
8
|
import type PageEndPoint from "../types/PageEndPoint.ts";
|
|
10
9
|
import type PageServerEndPoint from "../types/PageServerEndPoint.ts";
|
|
11
|
-
import RouteHandler from "../types/RouteHandler.ts";
|
|
10
|
+
import type RouteHandler from "../types/RouteHandler.ts";
|
|
12
11
|
import {
|
|
13
12
|
ERROR_ROUTE,
|
|
14
13
|
HOOK_ROUTE,
|
|
@@ -30,33 +29,6 @@ router.addPages(manifest.routes);
|
|
|
30
29
|
|
|
31
30
|
//console.log(`routes:\n ${router.routes.map((r) => r.path).join("\n ")}`);
|
|
32
31
|
|
|
33
|
-
async function handleResponse(response: Response): Promise<Response> {
|
|
34
|
-
$page.status = response.status;
|
|
35
|
-
|
|
36
|
-
// Success codes and redirect codes are acceptable
|
|
37
|
-
if (response.status >= 200 && response.status <= 399) {
|
|
38
|
-
return response;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// It's an error, so redirect to the error page
|
|
42
|
-
// We're just pushing the status and message in the URL, but maybe there's a
|
|
43
|
-
// more sophisticated way to do this?
|
|
44
|
-
// TODO: Should be returning loadView rather than redirecting
|
|
45
|
-
// TODO: Should be returning the NEAREST error page to this path, including layouts
|
|
46
|
-
let params = new URLSearchParams();
|
|
47
|
-
params.append("status", $page.status.toString());
|
|
48
|
-
let message = await response.text();
|
|
49
|
-
if (response.headers.get("Content-Type")?.includes("application/json")) {
|
|
50
|
-
const data = JSON.parse(message);
|
|
51
|
-
message = data.message ?? message;
|
|
52
|
-
}
|
|
53
|
-
if (message) {
|
|
54
|
-
$page.error.message = message;
|
|
55
|
-
params.append("message", message);
|
|
56
|
-
}
|
|
57
|
-
return seeOther(`/_error?${params.toString()}`);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
32
|
export async function load(ev: ServerEvent, template: string): Promise<Response> {
|
|
61
33
|
//const url = new URL(`http://${process.env.HOST ?? "localhost"}${ev.request.url}`);
|
|
62
34
|
const url = new URL(ev.request.url);
|
|
@@ -69,19 +41,18 @@ export async function load(ev: ServerEvent, template: string): Promise<Response>
|
|
|
69
41
|
if (!route) {
|
|
70
42
|
return handleResponse(notFound());
|
|
71
43
|
}
|
|
44
|
+
const handler = route.handler;
|
|
45
|
+
const params = route.params || {};
|
|
72
46
|
|
|
73
47
|
// Update $page before building the components
|
|
74
48
|
$page.url = url;
|
|
75
49
|
if (path.endsWith("/_error")) {
|
|
76
50
|
$page.status = parseInt(query.get("status") ?? "404");
|
|
77
|
-
$page.error
|
|
51
|
+
$page.error = { message: query.get("message") ?? "" };
|
|
78
52
|
} else {
|
|
79
53
|
$page.status = 200;
|
|
80
54
|
}
|
|
81
55
|
|
|
82
|
-
const handler = route.handler;
|
|
83
|
-
const params = route.params || {};
|
|
84
|
-
|
|
85
56
|
// TODO: Hit the server hook out here
|
|
86
57
|
// We could maybe set data loading up as middleware on a route??????
|
|
87
58
|
|
|
@@ -92,7 +63,7 @@ export async function load(ev: ServerEvent, template: string): Promise<Response>
|
|
|
92
63
|
if (ev.request.method === "GET") {
|
|
93
64
|
return handleResponse(await loadView(ev, url, handler, params, template));
|
|
94
65
|
} else if (ev.request.method === "POST") {
|
|
95
|
-
return handleResponse(await runAction(ev, url, handler, params, query));
|
|
66
|
+
return handleResponse(await runAction(ev, url, handler, params, query, template), true);
|
|
96
67
|
}
|
|
97
68
|
break;
|
|
98
69
|
}
|
|
@@ -121,6 +92,42 @@ export async function load(ev: ServerEvent, template: string): Promise<Response>
|
|
|
121
92
|
return notFound();
|
|
122
93
|
}
|
|
123
94
|
|
|
95
|
+
async function handleResponse(response: Response, fromForm = false): Promise<Response> {
|
|
96
|
+
$page.status = response.status;
|
|
97
|
+
|
|
98
|
+
// Success codes and redirect codes are acceptable
|
|
99
|
+
if (response.status >= 200 && response.status <= 399) {
|
|
100
|
+
return response;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 4xx error codes are acceptable from form actions
|
|
104
|
+
if (fromForm && response.status >= 400 && response.status <= 499) {
|
|
105
|
+
return response;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// It's an error, so redirect to the error page
|
|
109
|
+
// We're just pushing the status and message in the URL, but maybe there's a
|
|
110
|
+
// more sophisticated way to do this?
|
|
111
|
+
// TODO: Should be returning loadView rather than redirecting
|
|
112
|
+
// TODO: Should be returning the NEAREST error page to this path, including layouts
|
|
113
|
+
let params = new URLSearchParams();
|
|
114
|
+
params.append("status", $page.status.toString());
|
|
115
|
+
let message = await response.text();
|
|
116
|
+
if (response.headers.get("Content-Type")?.includes("application/json")) {
|
|
117
|
+
const data = JSON.parse(message);
|
|
118
|
+
message = data.message ?? message;
|
|
119
|
+
}
|
|
120
|
+
if (message) {
|
|
121
|
+
if ($page.error) {
|
|
122
|
+
$page.error.message = message;
|
|
123
|
+
} else {
|
|
124
|
+
$page.error = { message };
|
|
125
|
+
}
|
|
126
|
+
params.append("message", message);
|
|
127
|
+
}
|
|
128
|
+
return seeOther(`/_error?${params.toString()}`);
|
|
129
|
+
}
|
|
130
|
+
|
|
124
131
|
async function loadData(
|
|
125
132
|
ev: ServerEvent,
|
|
126
133
|
url: URL,
|
|
@@ -155,6 +162,8 @@ async function loadView(
|
|
|
155
162
|
handler: RouteHandler,
|
|
156
163
|
params: Record<string, string>,
|
|
157
164
|
template: string,
|
|
165
|
+
formStatus?: number,
|
|
166
|
+
form?: Record<string, string>,
|
|
158
167
|
) {
|
|
159
168
|
// There must be a client endpoint with a component
|
|
160
169
|
const clientEndPoint: PageEndPoint | undefined = (await handler.endPoint()).default;
|
|
@@ -209,19 +218,23 @@ async function loadView(
|
|
|
209
218
|
if (endPointResponse?.ok === false) {
|
|
210
219
|
return endPointResponse;
|
|
211
220
|
}
|
|
212
|
-
let $props: Record<string, any> = { data };
|
|
221
|
+
let $props: Record<string, any> = { data, form };
|
|
222
|
+
|
|
223
|
+
let styles = "";
|
|
213
224
|
|
|
214
225
|
// If there are layouts, work our way upwards, pushing each component into
|
|
215
226
|
// the default slot of its parent
|
|
216
227
|
// TODO: Also handle layout server data
|
|
217
228
|
// TODO: There's probably a nicer way to do this with reducers or something
|
|
218
|
-
// TODO: Re-use parent layouts
|
|
219
229
|
let component = clientEndPoint.component as ServerComponent;
|
|
220
230
|
let slots: Record<string, ServerSlotRender> | undefined = undefined;
|
|
221
231
|
if (handler.layouts) {
|
|
222
232
|
let slotFunctions: ServerSlotRender[] = [];
|
|
223
|
-
slotFunctions[handler.layouts.length] = (_, $context) =>
|
|
224
|
-
(clientEndPoint.component as ServerComponent)($props, $context);
|
|
233
|
+
slotFunctions[handler.layouts.length] = (_, $context) => {
|
|
234
|
+
let { body, head } = (clientEndPoint.component as ServerComponent)($props, $context);
|
|
235
|
+
styles += head;
|
|
236
|
+
return body;
|
|
237
|
+
};
|
|
225
238
|
for (let i = handler.layouts.length - 1; i >= 0; i--) {
|
|
226
239
|
const layoutEndPoint: PageEndPoint | undefined = (await handler.layouts[i].endPoint())
|
|
227
240
|
?.default;
|
|
@@ -230,10 +243,13 @@ async function loadView(
|
|
|
230
243
|
component = layoutEndPoint.component as ServerComponent;
|
|
231
244
|
slots = { _: slotFunctions[i + 1] };
|
|
232
245
|
} else {
|
|
233
|
-
slotFunctions[i] = (_, $context) =>
|
|
234
|
-
(layoutEndPoint.component as ServerComponent)($props, $context, {
|
|
246
|
+
slotFunctions[i] = (_, $context) => {
|
|
247
|
+
let { body, head } = (layoutEndPoint.component as ServerComponent)($props, $context, {
|
|
235
248
|
_: slotFunctions[i + 1],
|
|
236
249
|
});
|
|
250
|
+
styles += head;
|
|
251
|
+
return body;
|
|
252
|
+
};
|
|
237
253
|
}
|
|
238
254
|
}
|
|
239
255
|
}
|
|
@@ -241,8 +257,15 @@ async function loadView(
|
|
|
241
257
|
|
|
242
258
|
let html;
|
|
243
259
|
try {
|
|
244
|
-
|
|
245
|
-
|
|
260
|
+
let { body, head } = component($props, undefined, slots);
|
|
261
|
+
|
|
262
|
+
// Put the form info in a hidden input so that it can be accessed on the client
|
|
263
|
+
if (form) {
|
|
264
|
+
body += `\n<input type="hidden" id="t-form-data" value='${JSON.stringify(form).replaceAll("'", "\\'")}' />`;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
styles += head;
|
|
268
|
+
html = template.replace("%COMPONENT_BODY%", body).replace("%COMPONENT_HEAD%", styles);
|
|
246
269
|
} catch (error) {
|
|
247
270
|
// TODO: Show a proper Error component
|
|
248
271
|
html = '<span style="color: red">Script syntax error</span><p>' + error + "</p>";
|
|
@@ -250,7 +273,7 @@ async function loadView(
|
|
|
250
273
|
}
|
|
251
274
|
|
|
252
275
|
return new Response(html, {
|
|
253
|
-
status: 200,
|
|
276
|
+
status: formStatus ?? 200,
|
|
254
277
|
headers: {
|
|
255
278
|
"Content-Type": "text/html",
|
|
256
279
|
},
|
|
@@ -263,6 +286,7 @@ async function runAction(
|
|
|
263
286
|
handler: RouteHandler,
|
|
264
287
|
params: Record<string, string>,
|
|
265
288
|
query: URLSearchParams,
|
|
289
|
+
template: string,
|
|
266
290
|
) {
|
|
267
291
|
const actionName = (Array.from(query.keys())[0] || "default").replace(/^\//, "");
|
|
268
292
|
const serverEndPoint: PageServerEndPoint | undefined =
|
|
@@ -270,7 +294,6 @@ async function runAction(
|
|
|
270
294
|
if (serverEndPoint?.actions) {
|
|
271
295
|
const action = serverEndPoint.actions[actionName];
|
|
272
296
|
if (action) {
|
|
273
|
-
// TODO: form.errors etc
|
|
274
297
|
const serverParams = buildServerParams(ev, url, params);
|
|
275
298
|
|
|
276
299
|
if (handler.serverHook) {
|
|
@@ -280,11 +303,24 @@ async function runAction(
|
|
|
280
303
|
}
|
|
281
304
|
}
|
|
282
305
|
|
|
283
|
-
const result = await action(serverParams);
|
|
284
|
-
|
|
285
|
-
// If
|
|
286
|
-
// or
|
|
287
|
-
|
|
306
|
+
const result = await action(serverParams);
|
|
307
|
+
|
|
308
|
+
// If no result, ok or 4xx error, re-render the view with the form result
|
|
309
|
+
// If redirect or server error, just return the result to handle it as normal
|
|
310
|
+
// TODO: do not re-run hooks?
|
|
311
|
+
if (
|
|
312
|
+
!result ||
|
|
313
|
+
(result.status >= 200 && result.status <= 299) ||
|
|
314
|
+
(result.status >= 400 && result.status <= 499)
|
|
315
|
+
) {
|
|
316
|
+
const newUrl = new URL(url.pathname, url);
|
|
317
|
+
const formStatus = result?.status;
|
|
318
|
+
const form = await result?.json();
|
|
319
|
+
$page.form = form;
|
|
320
|
+
return await loadView(ev, newUrl, handler, params, template, formStatus, form);
|
|
321
|
+
} else {
|
|
322
|
+
return result;
|
|
323
|
+
}
|
|
288
324
|
}
|
|
289
325
|
}
|
|
290
326
|
|
package/src/state/$page.ts
CHANGED
|
@@ -1,15 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import type PageState from "../types/PageState";
|
|
2
|
+
import internal from "./internal";
|
|
3
3
|
|
|
4
|
-
const $page:
|
|
5
|
-
{
|
|
6
|
-
status: 404,
|
|
7
|
-
url: new URL("http://localhost"),
|
|
8
|
-
error: {
|
|
9
|
-
message: "",
|
|
10
|
-
},
|
|
11
|
-
},
|
|
12
|
-
{ shallow: true },
|
|
13
|
-
);
|
|
4
|
+
const $page: PageState = internal().$page;
|
|
14
5
|
|
|
15
6
|
export default $page;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { $watch } from "@torpor/view";
|
|
2
|
+
import type ClientState from "../types/ClientState";
|
|
3
|
+
import type InternalState from "../types/InternalState";
|
|
4
|
+
import type PageState from "../types/PageState";
|
|
5
|
+
|
|
6
|
+
// Do a weird little dance to ensure that everyone has the same $page and
|
|
7
|
+
// client, regardless of if they are entering via serverEntry/clientEntry or the
|
|
8
|
+
// built library. There may be an easier way to do this!
|
|
9
|
+
|
|
10
|
+
const INTERNAL_GLOBAL: unique symbol = Symbol.for("torpor-internal");
|
|
11
|
+
|
|
12
|
+
export default function internal(): InternalState {
|
|
13
|
+
// @ts-ignore
|
|
14
|
+
if (globalThis[INTERNAL_GLOBAL] === undefined) {
|
|
15
|
+
// TODO: Should use the SSR $watch if entering via serverEntry
|
|
16
|
+
const $page: PageState = $watch(
|
|
17
|
+
{
|
|
18
|
+
status: 404,
|
|
19
|
+
url: new URL("http://localhost"),
|
|
20
|
+
form: {},
|
|
21
|
+
error: {
|
|
22
|
+
message: "",
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
shallow: true,
|
|
27
|
+
},
|
|
28
|
+
);
|
|
29
|
+
const client: ClientState = {
|
|
30
|
+
// @ts-ignore This will definitely, 100% get set
|
|
31
|
+
router: null,
|
|
32
|
+
layoutStack: [],
|
|
33
|
+
// TODO: Probably expire after 30 seconds or something?
|
|
34
|
+
prefetchedData: {},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// @ts-ignore
|
|
38
|
+
globalThis[INTERNAL_GLOBAL] = {
|
|
39
|
+
$page,
|
|
40
|
+
client,
|
|
41
|
+
} satisfies InternalState;
|
|
42
|
+
}
|
|
43
|
+
// @ts-ignore
|
|
44
|
+
return globalThis[INTERNAL_GLOBAL] as InternalState;
|
|
45
|
+
}
|