@torpor/build 0.0.1
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/LICENSE.md +5 -0
- package/README.md +55 -0
- package/dist/bin/index.cjs +137 -0
- package/dist/bin/index.cjs.map +1 -0
- package/dist/bin/index.d.cts +1 -0
- package/dist/bin/index.d.ts +1 -0
- package/dist/bin/index.js +114 -0
- package/dist/bin/index.js.map +1 -0
- package/dist/index.cjs +55 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +150 -0
- package/dist/index.d.ts +150 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/metafile-cjs.json +1 -0
- package/dist/metafile-esm.json +1 -0
- package/dist/response.cjs +150 -0
- package/dist/response.cjs.map +1 -0
- package/dist/response.d.cts +162 -0
- package/dist/response.d.ts +162 -0
- package/dist/response.js +109 -0
- package/dist/response.js.map +1 -0
- package/dist/state.cjs +41 -0
- package/dist/state.cjs.map +1 -0
- package/dist/state.d.cts +6 -0
- package/dist/state.d.ts +6 -0
- package/dist/state.js +14 -0
- package/dist/state.js.map +1 -0
- package/package.json +49 -0
- package/src/bin/index.ts +41 -0
- package/src/index.ts +24 -0
- package/src/response/badRequest.ts +14 -0
- package/src/response/created.ts +13 -0
- package/src/response/forbidden.ts +14 -0
- package/src/response/methodNotAllowed.ts +14 -0
- package/src/response/moved.ts +16 -0
- package/src/response/notFound.ts +17 -0
- package/src/response/notModified.ts +14 -0
- package/src/response/ok.ts +20 -0
- package/src/response/redirect.ts +17 -0
- package/src/response/response.ts +22 -0
- package/src/response/seeOther.ts +13 -0
- package/src/response/serverError.ts +10 -0
- package/src/response/transfer.ts +14 -0
- package/src/response/unauthorized.ts +12 -0
- package/src/response/unprocessable.ts +12 -0
- package/src/response.ts +33 -0
- package/src/site/FileSystemRouter.ts +24 -0
- package/src/site/buildRoutePath.ts +25 -0
- package/src/site/clientEntry.ts +191 -0
- package/src/site/defineConfig.ts +5 -0
- package/src/site/defineSite.ts +74 -0
- package/src/site/lazyRoute.ts +72 -0
- package/src/site/routeHandlers.ts +113 -0
- package/src/site/serverEntry.ts +384 -0
- package/src/state/$page.ts +11 -0
- package/src/state.ts +3 -0
- package/src/types/CookieHelper.ts +7 -0
- package/src/types/PageEndPoint.ts +37 -0
- package/src/types/PageEvent.ts +14 -0
- package/src/types/PageServerEndPoint.ts +18 -0
- package/src/types/RouteHandler.ts +12 -0
- package/src/types/RouteHandlerCollection.ts +7 -0
- package/src/types/RouteLayoutHandler.ts +5 -0
- package/src/types/RouteMatchResult.ts +7 -0
- package/src/types/ServerEndPoint.ts +37 -0
- package/src/types/ServerEvent.ts +27 -0
- package/src/types/ServerHook.ts +13 -0
- package/src/types/UserConfig.ts +3 -0
- package/tsconfig.json +4 -0
- package/tsup.config.ts +10 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import transfer from "./transfer";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 303 See Other
|
|
5
|
+
*
|
|
6
|
+
* The server sent this response to direct the client to get the requested
|
|
7
|
+
* resource at another URI with a GET request.
|
|
8
|
+
*
|
|
9
|
+
* See https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/303
|
|
10
|
+
*/
|
|
11
|
+
export default function seeOther(location: string): Response {
|
|
12
|
+
return transfer(303, location);
|
|
13
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import response from "./response";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The server has encountered a situation it does not know how to handle.
|
|
5
|
+
*
|
|
6
|
+
* See https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500
|
|
7
|
+
*/
|
|
8
|
+
export default function serverError(body?: object | string): Response {
|
|
9
|
+
return response(500, body);
|
|
10
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a redirect response with the supplied status code.
|
|
3
|
+
*/
|
|
4
|
+
export default function transfer(
|
|
5
|
+
status: 300 | 301 | 302 | 303 | 304 | 307 | 308,
|
|
6
|
+
location: string,
|
|
7
|
+
): Response {
|
|
8
|
+
return new Response(null, {
|
|
9
|
+
status,
|
|
10
|
+
headers: {
|
|
11
|
+
location,
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import response from "./response";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Although the HTTP standard specifies "unauthorized", semantically this
|
|
5
|
+
* response means "unauthenticated". That is, the client must authenticate
|
|
6
|
+
* itself to get the requested response.
|
|
7
|
+
*
|
|
8
|
+
* See https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401
|
|
9
|
+
*/
|
|
10
|
+
export default function unauthorized(body?: object | string): Response {
|
|
11
|
+
return response(401, body);
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import response from "./response";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 422 Unprocessable Content
|
|
5
|
+
*
|
|
6
|
+
* The request was well-formed but was unable to be followed due to semantic errors.
|
|
7
|
+
*
|
|
8
|
+
* See https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422
|
|
9
|
+
*/
|
|
10
|
+
export default function unprocessable(body?: object | string): Response {
|
|
11
|
+
return response(422, body);
|
|
12
|
+
}
|
package/src/response.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import badRequest from "./response/badRequest";
|
|
2
|
+
import created from "./response/created";
|
|
3
|
+
import forbidden from "./response/forbidden";
|
|
4
|
+
import methodNotAllowed from "./response/methodNotAllowed";
|
|
5
|
+
import moved from "./response/moved";
|
|
6
|
+
import notFound from "./response/notFound";
|
|
7
|
+
import notModified from "./response/notModified";
|
|
8
|
+
import ok from "./response/ok";
|
|
9
|
+
import redirect from "./response/redirect";
|
|
10
|
+
import response from "./response/response";
|
|
11
|
+
import seeOther from "./response/seeOther";
|
|
12
|
+
import serverError from "./response/serverError";
|
|
13
|
+
import transfer from "./response/transfer";
|
|
14
|
+
import unauthorized from "./response/unauthorized";
|
|
15
|
+
import unprocessable from "./response/unprocessable";
|
|
16
|
+
|
|
17
|
+
export {
|
|
18
|
+
response,
|
|
19
|
+
ok,
|
|
20
|
+
created,
|
|
21
|
+
transfer,
|
|
22
|
+
seeOther,
|
|
23
|
+
notModified,
|
|
24
|
+
redirect,
|
|
25
|
+
moved,
|
|
26
|
+
badRequest,
|
|
27
|
+
unauthorized,
|
|
28
|
+
forbidden,
|
|
29
|
+
notFound,
|
|
30
|
+
methodNotAllowed,
|
|
31
|
+
unprocessable,
|
|
32
|
+
serverError,
|
|
33
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { BaseFileSystemRouter, cleanPath } from "vinxi/fs-router";
|
|
2
|
+
|
|
3
|
+
export default class FileSystemRouter extends BaseFileSystemRouter {
|
|
4
|
+
toPath(src: string) {
|
|
5
|
+
let routePath = cleanPath(src, this.config)
|
|
6
|
+
.replace(/^\//, "")
|
|
7
|
+
.replace(/_layout.server$/, "_layout/~server")
|
|
8
|
+
.replace(/\+page.server$/, "~server")
|
|
9
|
+
.replace(/\+page$/, "")
|
|
10
|
+
.replace(/\+server$/, "")
|
|
11
|
+
.replace(/\/$/, "");
|
|
12
|
+
return routePath?.length > 0 ? `/${routePath}` : "/";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
toRoute(filePath: string) {
|
|
16
|
+
return {
|
|
17
|
+
path: this.toPath(filePath),
|
|
18
|
+
$handler: {
|
|
19
|
+
src: filePath,
|
|
20
|
+
pick: ["default"],
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
type ParamsType = Record<PropertyKey, any>;
|
|
2
|
+
|
|
3
|
+
export default function buildRoutePath<RP extends ParamsType, UP extends ParamsType>(
|
|
4
|
+
path: string,
|
|
5
|
+
routeParams: RP,
|
|
6
|
+
urlParams?: UP,
|
|
7
|
+
) {
|
|
8
|
+
for (let [key, value] of Object.entries(routeParams)) {
|
|
9
|
+
path = path.replace(`[${key}]`, value);
|
|
10
|
+
}
|
|
11
|
+
if (urlParams) {
|
|
12
|
+
// TODO: More sophisticated url params serialization
|
|
13
|
+
let params = Object.entries(urlParams)
|
|
14
|
+
.map(([key, value]) => {
|
|
15
|
+
if (value != null) {
|
|
16
|
+
return `${key}=${value}`;
|
|
17
|
+
}
|
|
18
|
+
})
|
|
19
|
+
.filter(Boolean);
|
|
20
|
+
if (params.length) {
|
|
21
|
+
path += `?${params.join("&")}`;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return path;
|
|
25
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { hydrate, mount } from "@torpor/view";
|
|
2
|
+
import type { Component, SlotRender } from "@torpor/view";
|
|
3
|
+
import "vinxi/client";
|
|
4
|
+
import $page from "../state/$page";
|
|
5
|
+
import type PageEndPoint from "../types/PageEndPoint";
|
|
6
|
+
import type PageServerEndPoint from "../types/PageServerEndPoint";
|
|
7
|
+
import routeHandlers from "./routeHandlers";
|
|
8
|
+
|
|
9
|
+
// Intercept clicks on links
|
|
10
|
+
window.addEventListener("click", async (e) => {
|
|
11
|
+
if (e.target && (e.target as HTMLElement).tagName === "A") {
|
|
12
|
+
e.preventDefault();
|
|
13
|
+
const href = (e.target as HTMLLinkElement).href;
|
|
14
|
+
const url = new URL(href);
|
|
15
|
+
|
|
16
|
+
// Update $page before building the components
|
|
17
|
+
// TODO: Find somewhere better to put this
|
|
18
|
+
$page.url = url;
|
|
19
|
+
|
|
20
|
+
if (await navigate(url.pathname, url.searchParams)) {
|
|
21
|
+
window.history.pushState(null, "", url);
|
|
22
|
+
} else {
|
|
23
|
+
window.location.href = href;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Listen for changes to the URL that occur when the user navigates using the
|
|
29
|
+
// back or forward buttons
|
|
30
|
+
window.addEventListener("popstate", async () => {
|
|
31
|
+
await navigateToLocation(document.location);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Do the initial navigation and hydration
|
|
35
|
+
navigateToLocation(document.location, true);
|
|
36
|
+
|
|
37
|
+
async function navigateToLocation(location: Location, firstTime = false) {
|
|
38
|
+
return await navigate(location.pathname, new URLSearchParams(location.search), firstTime);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function navigate(
|
|
42
|
+
path: string,
|
|
43
|
+
urlParams: URLSearchParams,
|
|
44
|
+
firstTime = false,
|
|
45
|
+
): Promise<boolean> {
|
|
46
|
+
console.log("navigating client to", path, "with", urlParams.toString());
|
|
47
|
+
|
|
48
|
+
const parent = document.getElementById("app");
|
|
49
|
+
if (!parent) {
|
|
50
|
+
// TODO: 500
|
|
51
|
+
console.log("500");
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const route = routeHandlers.match(path, urlParams);
|
|
56
|
+
if (!route) {
|
|
57
|
+
// TODO: 404
|
|
58
|
+
console.log("404");
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Update $page before building the components
|
|
63
|
+
// TODO: Find somewhere better to put this
|
|
64
|
+
//$page.url = new URL(document.location.href);
|
|
65
|
+
|
|
66
|
+
const handler = route.handler;
|
|
67
|
+
const params = route.routeParams || {};
|
|
68
|
+
|
|
69
|
+
// There must be a client endpoint with a component
|
|
70
|
+
const clientEndPoint: PageEndPoint | undefined = (await handler.endPoint).default;
|
|
71
|
+
if (!clientEndPoint?.component) {
|
|
72
|
+
// TODO: 404
|
|
73
|
+
console.log("404");
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// There may be a server endpoint
|
|
78
|
+
const serverEndPoint: PageServerEndPoint | undefined = (await handler.serverEndPoint)?.default;
|
|
79
|
+
|
|
80
|
+
// Pass the data into $props
|
|
81
|
+
// TODO: Don't load if this is the first time -- it should have been passed to us, somehow...
|
|
82
|
+
let data = {};
|
|
83
|
+
if (handler.layouts) {
|
|
84
|
+
for (let layout of handler.layouts) {
|
|
85
|
+
let layoutPath = layout.path;
|
|
86
|
+
for (let key in params) {
|
|
87
|
+
layoutPath = layoutPath.replace(`[${key}]`, params[key]);
|
|
88
|
+
}
|
|
89
|
+
const layoutEndPoint: PageEndPoint | undefined = (await layout.endPoint)?.default;
|
|
90
|
+
const layoutServerEndPoint: PageServerEndPoint | undefined = (await layout.serverEndPoint)
|
|
91
|
+
?.default;
|
|
92
|
+
const layoutData = await loadClientAndServerData(
|
|
93
|
+
data,
|
|
94
|
+
document.location.origin + layoutPath,
|
|
95
|
+
params,
|
|
96
|
+
layoutEndPoint,
|
|
97
|
+
layoutServerEndPoint,
|
|
98
|
+
);
|
|
99
|
+
Object.assign(data, layoutData);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
let endPointData = await loadClientAndServerData(
|
|
103
|
+
data,
|
|
104
|
+
document.location.origin + path,
|
|
105
|
+
params,
|
|
106
|
+
clientEndPoint,
|
|
107
|
+
serverEndPoint,
|
|
108
|
+
);
|
|
109
|
+
Object.assign(data, endPointData);
|
|
110
|
+
let $props: Record<string, any> = { data };
|
|
111
|
+
|
|
112
|
+
// If there are layouts, work our way upwards, pushing each component into
|
|
113
|
+
// the default slot of its parent
|
|
114
|
+
// TODO: Also handle layout server data
|
|
115
|
+
// TODO: There's probably a nicer way to do this with reducers or something
|
|
116
|
+
let component = clientEndPoint.component as Component;
|
|
117
|
+
let slots: Record<string, SlotRender> | undefined = undefined;
|
|
118
|
+
if (handler.layouts) {
|
|
119
|
+
let slotFunctions: SlotRender[] = [];
|
|
120
|
+
slotFunctions[handler.layouts.length] = (parent, anchor, _, context) =>
|
|
121
|
+
clientEndPoint.component!(parent, anchor, $props, context);
|
|
122
|
+
for (let i = handler.layouts.length - 1; i >= 0; i--) {
|
|
123
|
+
const layoutEndPoint: PageEndPoint | undefined = (await handler.layouts[i].endPoint)?.default;
|
|
124
|
+
if (layoutEndPoint?.component) {
|
|
125
|
+
if (i === 0) {
|
|
126
|
+
component = layoutEndPoint.component as Component;
|
|
127
|
+
slots = { _: slotFunctions[i + 1] };
|
|
128
|
+
} else {
|
|
129
|
+
slotFunctions[i] = (parent, anchor, _, context) =>
|
|
130
|
+
layoutEndPoint.component!(parent, anchor, $props, context, {
|
|
131
|
+
_: slotFunctions[i + 1],
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (firstTime) {
|
|
139
|
+
hydrate(parent, component, $props, slots);
|
|
140
|
+
} else {
|
|
141
|
+
// TODO: Clear ranges, reuse layouts etc
|
|
142
|
+
parent.textContent = "";
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
mount(parent, component, $props, slots);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
// TODO: Show a proper Error component
|
|
148
|
+
parent.innerHTML = '<span style="color: red">Script syntax error</span><p>' + error + "</p>";
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function loadClientAndServerData(
|
|
156
|
+
data: Record<string, any>,
|
|
157
|
+
location: string,
|
|
158
|
+
params: Record<string, string>,
|
|
159
|
+
clientEndPoint?: PageEndPoint,
|
|
160
|
+
serverEndPoint?: PageServerEndPoint,
|
|
161
|
+
) {
|
|
162
|
+
let newData = {};
|
|
163
|
+
if (clientEndPoint?.load) {
|
|
164
|
+
const clientUrl = new URL(document.location.href);
|
|
165
|
+
const clientParams = buildClientParams(clientUrl, params, data);
|
|
166
|
+
const clientData = await clientEndPoint.load(clientParams);
|
|
167
|
+
if (clientData) {
|
|
168
|
+
Object.assign(newData, clientData);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (serverEndPoint?.load) {
|
|
172
|
+
const serverUrl = location.replace(/\/$/, "") + "/~server";
|
|
173
|
+
const serverResponse = await fetch(serverUrl);
|
|
174
|
+
if (
|
|
175
|
+
serverResponse?.ok &&
|
|
176
|
+
serverResponse.headers.get("content-type")?.includes("application/json")
|
|
177
|
+
) {
|
|
178
|
+
const serverData = await serverResponse.json();
|
|
179
|
+
Object.assign(newData, serverData);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return newData;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function buildClientParams(url: URL, params: Record<string, string>, data: Record<string, any>) {
|
|
186
|
+
return {
|
|
187
|
+
url,
|
|
188
|
+
params,
|
|
189
|
+
data,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import torpor from "@torpor/unplugin/vite";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { AppOptions, RouterSchemaInput, createApp } from "vinxi";
|
|
4
|
+
import { config } from "vinxi/plugins/config";
|
|
5
|
+
import type UserConfig from "../types/UserConfig";
|
|
6
|
+
import FileSystemRouter from "./FileSystemRouter";
|
|
7
|
+
|
|
8
|
+
export default function defineSite(options?: UserConfig) {
|
|
9
|
+
// Set the port
|
|
10
|
+
process.env.PORT = (options?.port || 7059).toString();
|
|
11
|
+
|
|
12
|
+
// Get Vinxi to create and serve the app
|
|
13
|
+
return createApp({
|
|
14
|
+
routers: [
|
|
15
|
+
{
|
|
16
|
+
name: "assets",
|
|
17
|
+
type: "static",
|
|
18
|
+
dir: "./src/assets",
|
|
19
|
+
},
|
|
20
|
+
// Doesn't look like we can have two server routes?
|
|
21
|
+
// Have to put api in /routes
|
|
22
|
+
// Maybe we can support having /routes/api and routes/site and stripping /site from the start?
|
|
23
|
+
//{
|
|
24
|
+
// name: "api",
|
|
25
|
+
// type: "http",
|
|
26
|
+
// target: "server",
|
|
27
|
+
// // TODO: change this when published
|
|
28
|
+
// handler: "node_modules/@torpor/build/src/site/serverEntry.ts",
|
|
29
|
+
// routes: apiRoutes,
|
|
30
|
+
// plugins: () => [config("custom", viteOptions())],
|
|
31
|
+
//},
|
|
32
|
+
{
|
|
33
|
+
name: "server",
|
|
34
|
+
type: "http",
|
|
35
|
+
target: "server",
|
|
36
|
+
// TODO: change this when published
|
|
37
|
+
handler: "node_modules/@torpor/build/src/site/serverEntry.ts",
|
|
38
|
+
routes,
|
|
39
|
+
plugins: () => [config("custom", viteOptions()), torpor({ server: true })],
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: "client",
|
|
43
|
+
type: "client",
|
|
44
|
+
target: "browser",
|
|
45
|
+
// TODO: change this when published
|
|
46
|
+
handler: "node_modules/@torpor/build/src/site/clientEntry.ts",
|
|
47
|
+
base: "/_build",
|
|
48
|
+
routes,
|
|
49
|
+
plugins: () => [config("custom", viteOptions()), torpor()],
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function routes(router: RouterSchemaInput, app: AppOptions) {
|
|
56
|
+
return new FileSystemRouter(
|
|
57
|
+
{
|
|
58
|
+
dir: path.resolve("src/routes"),
|
|
59
|
+
extensions: ["js", "ts"],
|
|
60
|
+
},
|
|
61
|
+
router,
|
|
62
|
+
app,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function viteOptions() {
|
|
67
|
+
return {
|
|
68
|
+
resolve: {
|
|
69
|
+
alias: {
|
|
70
|
+
"@": path.resolve("src"),
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export default async function lazyRoute(
|
|
2
|
+
component: { src: string; import(): Promise<any> },
|
|
3
|
+
clientManifest: any,
|
|
4
|
+
serverManifest: any,
|
|
5
|
+
//exported = "default",
|
|
6
|
+
) {
|
|
7
|
+
if (import.meta.env.DEV) {
|
|
8
|
+
const manifest = import.meta.env.SSR ? serverManifest : clientManifest;
|
|
9
|
+
const mod = await manifest.inputs[component.src].import();
|
|
10
|
+
return mod;
|
|
11
|
+
} else {
|
|
12
|
+
const mod = await component.import();
|
|
13
|
+
return mod;
|
|
14
|
+
}
|
|
15
|
+
/*
|
|
16
|
+
return lazy(async () => {
|
|
17
|
+
if (import.meta.env.DEV) {
|
|
18
|
+
let manifest = import.meta.env.SSR ? serverManifest : clientManifest;
|
|
19
|
+
|
|
20
|
+
const mod = await manifest.inputs[component.src].import();
|
|
21
|
+
invariant(mod[exported], `Module ${component.src} does not export ${exported}`);
|
|
22
|
+
const Component = mod[exported];
|
|
23
|
+
let assets = await clientManifest.inputs?.[component.src].assets();
|
|
24
|
+
const styles = assets.filter((asset) => asset.tag === "style");
|
|
25
|
+
|
|
26
|
+
if (typeof window !== "undefined" && import.meta.hot) {
|
|
27
|
+
import.meta.hot.on("css-update", (data) => {
|
|
28
|
+
updateStyles(styles, data);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const Comp = forwardRef((props, ref) => {
|
|
33
|
+
if (typeof window !== "undefined") {
|
|
34
|
+
useLayoutEffect(() => {
|
|
35
|
+
return () => {
|
|
36
|
+
// remove style tags added by vite when a CSS file is imported
|
|
37
|
+
cleanupStyles(styles);
|
|
38
|
+
};
|
|
39
|
+
}, []);
|
|
40
|
+
}
|
|
41
|
+
return createElement(
|
|
42
|
+
Fragment,
|
|
43
|
+
null,
|
|
44
|
+
...assets.map((asset) => renderAsset(asset)),
|
|
45
|
+
createElement(Component, { ...props, ref: ref }),
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
return { default: Comp };
|
|
49
|
+
} else {
|
|
50
|
+
const mod = await component.import();
|
|
51
|
+
|
|
52
|
+
const Component = mod[exported];
|
|
53
|
+
let assets = await clientManifest.inputs?.[component.src].assets();
|
|
54
|
+
|
|
55
|
+
if (typeof window !== "undefined") {
|
|
56
|
+
const styles = assets.filter((asset) => asset.attrs.rel === "stylesheet");
|
|
57
|
+
preloadStyles(styles);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const Comp = forwardRef((props, ref) => {
|
|
61
|
+
return createElement(
|
|
62
|
+
Fragment,
|
|
63
|
+
null,
|
|
64
|
+
...assets.map((asset) => renderAsset(asset)),
|
|
65
|
+
createElement(Component, { ...props, ref: ref }),
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
return { default: Comp };
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
*/
|
|
72
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { getManifest } from "vinxi/manifest";
|
|
2
|
+
import fileRoutes, { RouteModule } from "vinxi/routes";
|
|
3
|
+
import type RouteHandler from "../types/RouteHandler";
|
|
4
|
+
import type RouteHandlerCollection from "../types/RouteHandlerCollection";
|
|
5
|
+
import type RouteLayoutHandler from "../types/RouteLayoutHandler";
|
|
6
|
+
import lazyRoute from "./lazyRoute";
|
|
7
|
+
|
|
8
|
+
const clientManifest = getManifest("client");
|
|
9
|
+
const serverManifest = getManifest("server");
|
|
10
|
+
|
|
11
|
+
const routeHandlers: RouteHandlerCollection = {
|
|
12
|
+
handlers: fileRoutes
|
|
13
|
+
// HACK: Is there a better way to filter? glob in the config?
|
|
14
|
+
.filter((route) => !route.path.startsWith("_"))
|
|
15
|
+
.map((route) => {
|
|
16
|
+
return {
|
|
17
|
+
...route,
|
|
18
|
+
regex: pathToRegExp(route.path),
|
|
19
|
+
// TODO: Implement the rest of the lazyRoute stuff from vinxi/react or vinxi/solid
|
|
20
|
+
endPoint: lazyRoute(route.$handler, clientManifest, serverManifest),
|
|
21
|
+
loaded: false,
|
|
22
|
+
};
|
|
23
|
+
}),
|
|
24
|
+
match(path, urlParams) {
|
|
25
|
+
console.log("matching", path);
|
|
26
|
+
|
|
27
|
+
// TODO: Lots of testing etc
|
|
28
|
+
for (let handler of (this as RouteHandlerCollection).handlers) {
|
|
29
|
+
let match = path.match(handler.regex);
|
|
30
|
+
if (match) {
|
|
31
|
+
// Lazy load server endpoints and layouts
|
|
32
|
+
// Partly because it makes sense to do it here, but also because
|
|
33
|
+
// it doesn't work if we try to do it upfront for some reason
|
|
34
|
+
if (!handler.loaded) {
|
|
35
|
+
loadHandler(handler);
|
|
36
|
+
handler.loaded = true;
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
handler,
|
|
40
|
+
routeParams: match.groups,
|
|
41
|
+
urlParams: urlParams ? Object.fromEntries(urlParams) : undefined,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
console.log("not found");
|
|
47
|
+
//for (let handler of (this as RouteHandlerCollection).handlers) {
|
|
48
|
+
// console.log(" have", handler.path, handler.regex);
|
|
49
|
+
//}
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
function pathToRegExp(path: string): RegExp {
|
|
54
|
+
const pattern = path
|
|
55
|
+
.split("/")
|
|
56
|
+
.map((p) => {
|
|
57
|
+
return p.replace(/\[([^\/]+?)\]/, "(?<$1>[^\\/]+?)");
|
|
58
|
+
})
|
|
59
|
+
.join("\\/");
|
|
60
|
+
return new RegExp(`^${pattern}$`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function loadHandler(handler: RouteHandler) {
|
|
64
|
+
handler.layouts = findLayouts(handler.path, fileRoutes);
|
|
65
|
+
handler.serverEndPoint = findServer(handler.path, fileRoutes);
|
|
66
|
+
handler.serverHook = findServerHook(handler.path, fileRoutes);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function findLayouts(path: string, fileRoutes: RouteModule[]): RouteLayoutHandler[] | undefined {
|
|
70
|
+
let layouts: RouteLayoutHandler[] = [];
|
|
71
|
+
let parts = path
|
|
72
|
+
// Strip the trailing`~/server` (so we don't look for `~/server/_layout`
|
|
73
|
+
// which will never exist)
|
|
74
|
+
.replace(/\/~server$/, "")
|
|
75
|
+
// The path will always start with / so splitting e.g. `/first/second`
|
|
76
|
+
// will result in ['', 'first', 'second'] and checks for `/_layout`,
|
|
77
|
+
// `/first/_layout` and `/second/_layout`
|
|
78
|
+
.split("/");
|
|
79
|
+
let basePath = "";
|
|
80
|
+
for (let i = 0; i < parts.length; i++) {
|
|
81
|
+
if (i > 0) {
|
|
82
|
+
basePath += "/" + parts[i];
|
|
83
|
+
}
|
|
84
|
+
const layoutPath = basePath + "/_layout";
|
|
85
|
+
const layoutRoute = fileRoutes.find((r) => r.path === layoutPath);
|
|
86
|
+
if (layoutRoute) {
|
|
87
|
+
layouts.push({
|
|
88
|
+
path: layoutPath,
|
|
89
|
+
endPoint: lazyRoute(layoutRoute.$handler, clientManifest, serverManifest),
|
|
90
|
+
serverEndPoint: findServer(layoutPath, fileRoutes),
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return layouts.length ? layouts : undefined;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function findServer(path: string, fileRoutes: RouteModule[]): Promise<any> | undefined {
|
|
98
|
+
const serverPath = path.replace(/\/$/, "") + "/~server";
|
|
99
|
+
const serverRoute = fileRoutes.find((r) => r.path === serverPath);
|
|
100
|
+
return serverRoute && lazyRoute(serverRoute.$handler, clientManifest, serverManifest);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function findServerHook(path: string, fileRoutes: RouteModule[]): Promise<any> | undefined {
|
|
104
|
+
// TODO: Should this be a collection, like layouts?
|
|
105
|
+
let hookPath = "/_hook.server";
|
|
106
|
+
if (path.startsWith("/api/")) {
|
|
107
|
+
hookPath = "/api" + hookPath;
|
|
108
|
+
}
|
|
109
|
+
const hookRoute = fileRoutes.find((r) => r.path === hookPath);
|
|
110
|
+
return hookRoute && lazyRoute(hookRoute.$handler, clientManifest, serverManifest);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export default routeHandlers;
|