@torpor/build 0.0.5 → 0.1.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 +14 -0
- package/README.md +1 -1
- package/dist/Server-CB99REgs.d.cts +74 -0
- package/dist/Server-CB99REgs.d.ts +74 -0
- package/dist/Site-CgoeDt8k.d.cts +29 -0
- package/dist/Site-D95nwrAg.d.ts +29 -0
- package/dist/adapters/cloudflare/index.cjs +125 -0
- package/dist/adapters/cloudflare/index.cjs.map +1 -0
- package/dist/adapters/cloudflare/index.d.cts +13 -0
- package/dist/adapters/cloudflare/index.d.ts +13 -0
- package/dist/adapters/cloudflare/index.js +80 -0
- package/dist/adapters/cloudflare/index.js.map +1 -0
- package/dist/adapters/node/index.cjs +152 -0
- package/dist/adapters/node/index.cjs.map +1 -0
- package/dist/adapters/node/index.d.cts +8 -0
- package/dist/adapters/node/index.d.ts +8 -0
- package/dist/adapters/node/index.js +8 -0
- package/dist/adapters/node/index.js.map +1 -0
- package/dist/bin/index.cjs +541 -93
- package/dist/bin/index.cjs.map +1 -1
- package/dist/bin/index.js +511 -22
- package/dist/bin/index.js.map +1 -1
- package/dist/chunk-5X4T3PZN.js +23 -0
- package/dist/chunk-5X4T3PZN.js.map +1 -0
- package/dist/chunk-ALHFYD7U.js +113 -0
- package/dist/chunk-ALHFYD7U.js.map +1 -0
- package/dist/chunk-V4XTDQEV.js +19 -0
- package/dist/chunk-V4XTDQEV.js.map +1 -0
- package/dist/chunk-VAGHGWM5.js +110 -0
- package/dist/chunk-VAGHGWM5.js.map +1 -0
- package/dist/index.cjs +200 -97
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +33 -36
- package/dist/index.d.ts +33 -36
- package/dist/index.js +85 -17
- package/dist/index.js.map +1 -1
- package/dist/metafile-cjs.json +1 -1
- package/dist/metafile-esm.json +1 -1
- package/dist/response.js +17 -91
- package/dist/response.js.map +1 -1
- package/package.json +26 -9
- package/src/adapters/cloudflare/_worker.ts +38 -0
- package/src/adapters/cloudflare/index.ts +98 -0
- package/src/adapters/node/constants.ts +1 -0
- package/src/adapters/node/createFetchHandler.ts +29 -0
- package/src/adapters/node/createMiddlewareHandler.ts +79 -0
- package/src/adapters/node/createNodeServer.ts +8 -0
- package/src/adapters/node/flattenHeaders.ts +44 -0
- package/src/adapters/node/index.ts +18 -0
- package/src/adapters/node/nodeMessageToNodeResponse.ts +89 -0
- package/src/adapters/node/nodeMessageToRequest.ts +43 -0
- package/src/adapters/node/readableToBuffer.ts +16 -0
- package/src/adapters/node/requestToNodeMessage.ts +26 -0
- package/src/adapters/node/responseToNodeResponse.ts +44 -0
- package/src/bin/index.ts +43 -36
- package/src/index.ts +6 -14
- package/src/server/CookieHelper.ts +36 -0
- package/src/server/HeaderHelper.ts +25 -0
- package/src/server/Server.ts +119 -0
- package/src/server/ServerEvent.ts +33 -0
- package/src/server/ServerFunction.ts +5 -0
- package/src/server/contentType.ts +85 -0
- package/src/site/Router.ts +136 -0
- package/src/site/Routerx.ts +67 -0
- package/src/site/Site.ts +114 -0
- package/src/site/clientEntry.ts +22 -20
- package/src/site/manifest.ts +49 -0
- package/src/site/pathToRegex.ts +10 -0
- package/src/site/prepareTemplate.ts +20 -0
- package/src/site/runBuild.ts +92 -0
- package/src/site/runDev.ts +84 -0
- package/src/site/runPreview.ts +83 -0
- package/src/site/serverEntry.ts +113 -200
- package/src/types/Adapter.ts +10 -0
- package/src/types/HttpMethod.ts +12 -0
- package/src/types/LayoutHandler.ts +7 -0
- package/src/types/ManifestRoute.ts +8 -0
- package/src/types/MiddlewareFunction.ts +8 -0
- package/src/types/PageEndPoint.ts +4 -3
- package/src/types/{PageEvent.ts → PageLoadEvent.ts} +3 -2
- package/src/types/PageServerAction.ts +5 -2
- package/src/types/PageServerEndPoint.ts +1 -0
- package/src/types/PageServerLoad.ts +5 -2
- package/src/types/RouteHandler.ts +6 -5
- package/src/types/RouteHandlerCollection.ts +1 -0
- package/src/types/RouteLayoutHandler.ts +3 -2
- package/src/types/RouteMatchResult.ts +3 -2
- package/src/types/RouteType.ts +7 -0
- package/src/types/ServerEndPoint.ts +1 -0
- package/src/types/ServerHook.ts +3 -2
- package/src/types/{ServerEvent.ts → ServerLoadEvent.ts} +13 -6
- package/src/types/ServerRequest.ts +5 -2
- package/src/types/manifest.d.ts +7 -0
- package/test/app.test.ts +47 -0
- package/tsconfig.json +1 -1
- package/tsup.config.ts +11 -1
- package/vitest.config.ts +3 -0
- package/dist/chunk-L7GOYKUG.js +0 -93
- package/dist/chunk-L7GOYKUG.js.map +0 -1
- package/src/site/FileSystemRouter.ts +0 -25
- package/src/site/buildRoutePath.ts +0 -25
- package/src/site/defineConfig.ts +0 -75
- package/src/site/lazyRoute.ts +0 -72
- package/src/site/routeHandlers.ts +0 -126
- package/src/types/CookieHelper.ts +0 -8
- package/src/types/UserConfig.ts +0 -4
package/src/site/Site.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { Plugin } from "vite";
|
|
4
|
+
import tsconfigPaths from "vite-tsconfig-paths";
|
|
5
|
+
import node from "../adapters/node";
|
|
6
|
+
import Adapter from "../types/Adapter";
|
|
7
|
+
import {
|
|
8
|
+
HOOK_ROUTE,
|
|
9
|
+
HOOK_SERVER_ROUTE,
|
|
10
|
+
LAYOUT_ROUTE,
|
|
11
|
+
LAYOUT_SERVER_ROUTE,
|
|
12
|
+
PAGE_ROUTE,
|
|
13
|
+
PAGE_SERVER_ROUTE,
|
|
14
|
+
SERVER_ROUTE,
|
|
15
|
+
} from "../types/RouteType";
|
|
16
|
+
|
|
17
|
+
// 1. The user sets this up
|
|
18
|
+
// 2. It is passed into serverEntry and clientEntry through a Vite plugin
|
|
19
|
+
// (defined in manifest.ts)
|
|
20
|
+
// 3. In those files, it is used to build a Router
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The Site class contains the information necessary to setup a @torpor/build
|
|
24
|
+
* site, including routes, plugins, and options.
|
|
25
|
+
*/
|
|
26
|
+
export default class Site {
|
|
27
|
+
root: string;
|
|
28
|
+
routes: { path: string; file: string; type: number }[] = [];
|
|
29
|
+
// Is default plugins a bad idea?
|
|
30
|
+
// HACK: Set loose because otherwise it uses a list of allowed extensions
|
|
31
|
+
// TODO: Should probably just do this ourselves and let the user pass in
|
|
32
|
+
// tsconfigPaths themselves if they want to do something funky
|
|
33
|
+
plugins: Plugin[] = [tsconfigPaths({ loose: true })];
|
|
34
|
+
// Is default adapter a bad idea?
|
|
35
|
+
adapter: Adapter = node;
|
|
36
|
+
|
|
37
|
+
constructor() {
|
|
38
|
+
this.root = process.cwd();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async addRouteFolder(folder: string) {
|
|
42
|
+
const routeFolder = path.join(this.root, folder);
|
|
43
|
+
const routeFiles = await fs.readdir(routeFolder, { recursive: true });
|
|
44
|
+
|
|
45
|
+
for (let file of routeFiles) {
|
|
46
|
+
// The file must start with `+` or `_` and end with `.js` or `.ts`
|
|
47
|
+
if (/^(\+|_).+(\.js|\.ts)$/.test(path.basename(file))) {
|
|
48
|
+
let routePath = file
|
|
49
|
+
.replace(/^\//, "")
|
|
50
|
+
.replace(/(\.ts|\.js)$/, "")
|
|
51
|
+
.replace(/_hook.server$/, "_hook/~server")
|
|
52
|
+
.replace(/_layout.server$/, "_layout/~server")
|
|
53
|
+
.replace(/\+page.server$/, "~server")
|
|
54
|
+
.replace(/\+server$/, "")
|
|
55
|
+
.replace(/\+page$/, "")
|
|
56
|
+
.replace(/\/$/, "");
|
|
57
|
+
routePath = routePath.length > 0 ? `/${routePath}` : "/";
|
|
58
|
+
let type = this.#routeType(file);
|
|
59
|
+
file = path.relative(this.root, path.resolve(routeFolder, file));
|
|
60
|
+
this.routes.push({ path: routePath, file, type });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
this.#sortRoutes();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
addRouteFile(path: string, file: string) {
|
|
67
|
+
let type = this.#routeType(file);
|
|
68
|
+
this.routes.push({ path, file, type });
|
|
69
|
+
this.#sortRoutes();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
#routeType(file: string): number {
|
|
73
|
+
let routePath = file.replace(/^\//, "").replace(/(\.ts|\.js)$/, "");
|
|
74
|
+
if (routePath.endsWith("+page")) {
|
|
75
|
+
return PAGE_ROUTE;
|
|
76
|
+
} else if (routePath.endsWith("+page.server")) {
|
|
77
|
+
return PAGE_SERVER_ROUTE;
|
|
78
|
+
} else if (routePath.endsWith("+server")) {
|
|
79
|
+
return SERVER_ROUTE;
|
|
80
|
+
} else if (routePath.endsWith("_layout")) {
|
|
81
|
+
return LAYOUT_ROUTE;
|
|
82
|
+
} else if (routePath.endsWith("_layout.server")) {
|
|
83
|
+
return LAYOUT_SERVER_ROUTE;
|
|
84
|
+
} else if (routePath.endsWith("_hook")) {
|
|
85
|
+
return HOOK_ROUTE;
|
|
86
|
+
} else if (routePath.endsWith("_hook.server")) {
|
|
87
|
+
return HOOK_SERVER_ROUTE;
|
|
88
|
+
}
|
|
89
|
+
return -1;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// TODO: You could make your site in a single file, like e.g. Hono...
|
|
93
|
+
//addPage(path: string, endPoint: PageEndPoint, serverEndPoint?: PageServerEndPoint) {}
|
|
94
|
+
//addEndPoint(path: string, endPoint: ServerEndPoint) {}
|
|
95
|
+
|
|
96
|
+
#sortRoutes() {
|
|
97
|
+
this.routes = this.routes.sort((a, b) => {
|
|
98
|
+
// Sort [param]s after paths
|
|
99
|
+
// There might be a quicker/easier way to do this
|
|
100
|
+
for (let i = 0; i < Math.min(a.path.length, b.path.length); i++) {
|
|
101
|
+
if (a.path[i] === "[" && b.path[i] !== "[") {
|
|
102
|
+
return 1;
|
|
103
|
+
} else if (b.path[i] === "[" && a.path[i] !== "[") {
|
|
104
|
+
return -1;
|
|
105
|
+
} else if (a.path[i] === b.path[i]) {
|
|
106
|
+
// Keep going...
|
|
107
|
+
} else {
|
|
108
|
+
return a.path[i].localeCompare(b.path[i]);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return a.path.length - b.path.length;
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
package/src/site/clientEntry.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
import manifest from "@torpor/build/manifest";
|
|
1
2
|
import { hydrate, mount } from "@torpor/view";
|
|
2
3
|
import { type Component, type SlotRender } from "@torpor/view";
|
|
3
|
-
import "
|
|
4
|
-
import
|
|
5
|
-
import type
|
|
6
|
-
import
|
|
7
|
-
|
|
4
|
+
import $page from "../state/$page.ts";
|
|
5
|
+
import type PageEndPoint from "../types/PageEndPoint.ts";
|
|
6
|
+
import type PageServerEndPoint from "../types/PageServerEndPoint.ts";
|
|
7
|
+
import Router from "./Router.ts";
|
|
8
|
+
|
|
9
|
+
// Build the router from the Site object created by the user
|
|
10
|
+
const router = new Router();
|
|
11
|
+
router.addPages(manifest.routes);
|
|
8
12
|
|
|
9
13
|
// Intercept clicks on links
|
|
10
14
|
window.addEventListener("click", async (e) => {
|
|
@@ -38,12 +42,8 @@ async function navigateToLocation(location: Location, firstTime = false) {
|
|
|
38
42
|
return await navigate(location.pathname, new URLSearchParams(location.search), firstTime);
|
|
39
43
|
}
|
|
40
44
|
|
|
41
|
-
async function navigate(
|
|
42
|
-
path:
|
|
43
|
-
urlParams: URLSearchParams,
|
|
44
|
-
firstTime = false,
|
|
45
|
-
): Promise<boolean> {
|
|
46
|
-
console.log("navigating client to", path, urlParams.size ? `with ${urlParams}` : "");
|
|
45
|
+
async function navigate(path: string, query: URLSearchParams, firstTime = false): Promise<boolean> {
|
|
46
|
+
console.log(`navigating to '${path}'${query.size ? ` with ${query}` : ""}`);
|
|
47
47
|
|
|
48
48
|
const parent = document.getElementById("app");
|
|
49
49
|
if (!parent) {
|
|
@@ -52,7 +52,7 @@ async function navigate(
|
|
|
52
52
|
return false;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
const route =
|
|
55
|
+
const route = router.match(path, query);
|
|
56
56
|
if (!route) {
|
|
57
57
|
// TODO: 404
|
|
58
58
|
console.log("404");
|
|
@@ -64,10 +64,10 @@ async function navigate(
|
|
|
64
64
|
//$page.url = new URL(document.location.href);
|
|
65
65
|
|
|
66
66
|
const handler = route.handler;
|
|
67
|
-
const params = route.
|
|
67
|
+
const params = route.params || {};
|
|
68
68
|
|
|
69
69
|
// There must be a client endpoint with a component
|
|
70
|
-
const clientEndPoint: PageEndPoint | undefined = (await handler.endPoint).default;
|
|
70
|
+
const clientEndPoint: PageEndPoint | undefined = (await handler.endPoint()).default;
|
|
71
71
|
if (!clientEndPoint?.component) {
|
|
72
72
|
// TODO: 404
|
|
73
73
|
console.log("404");
|
|
@@ -75,7 +75,8 @@ async function navigate(
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
// There may be a server endpoint
|
|
78
|
-
const serverEndPoint: PageServerEndPoint | undefined =
|
|
78
|
+
const serverEndPoint: PageServerEndPoint | undefined =
|
|
79
|
+
handler.serverEndPoint && (await handler.serverEndPoint())?.default;
|
|
79
80
|
|
|
80
81
|
// Pass the data into $props
|
|
81
82
|
// TODO: Don't load if this is the first time -- it should have been passed to us, somehow...
|
|
@@ -86,9 +87,9 @@ async function navigate(
|
|
|
86
87
|
for (let key in params) {
|
|
87
88
|
layoutPath = layoutPath.replace(`[${key}]`, params[key]);
|
|
88
89
|
}
|
|
89
|
-
const layoutEndPoint: PageEndPoint | undefined = (await layout.endPoint)?.default;
|
|
90
|
-
const layoutServerEndPoint: PageServerEndPoint | undefined =
|
|
91
|
-
?.default;
|
|
90
|
+
const layoutEndPoint: PageEndPoint | undefined = (await layout.endPoint())?.default;
|
|
91
|
+
const layoutServerEndPoint: PageServerEndPoint | undefined =
|
|
92
|
+
layout.serverEndPoint && (await layout.serverEndPoint())?.default;
|
|
92
93
|
const layoutData = await loadClientAndServerData(
|
|
93
94
|
data,
|
|
94
95
|
document.location.origin + layoutPath,
|
|
@@ -120,7 +121,8 @@ async function navigate(
|
|
|
120
121
|
slotFunctions[handler.layouts.length] = (parent, anchor, _, context) =>
|
|
121
122
|
clientEndPoint.component!(parent, anchor, $props, context);
|
|
122
123
|
for (let i = handler.layouts.length - 1; i >= 0; i--) {
|
|
123
|
-
const layoutEndPoint: PageEndPoint | undefined = (await handler.layouts[i].endPoint)
|
|
124
|
+
const layoutEndPoint: PageEndPoint | undefined = (await handler.layouts[i].endPoint())
|
|
125
|
+
?.default;
|
|
124
126
|
if (layoutEndPoint?.component) {
|
|
125
127
|
if (i === 0) {
|
|
126
128
|
component = layoutEndPoint.component as Component;
|
|
@@ -174,7 +176,7 @@ async function loadClientAndServerData(
|
|
|
174
176
|
const serverResponse = await fetch(serverUrl);
|
|
175
177
|
if (
|
|
176
178
|
serverResponse?.ok &&
|
|
177
|
-
serverResponse.headers.get("
|
|
179
|
+
serverResponse.headers.get("Content-Type")?.includes("application/json")
|
|
178
180
|
) {
|
|
179
181
|
const serverData = await serverResponse.json();
|
|
180
182
|
Object.assign(newData, serverData);
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { Plugin } from "vite";
|
|
4
|
+
import Site from "./Site";
|
|
5
|
+
|
|
6
|
+
const moduleId = "@torpor/build/manifest";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A Vite plugin that provides information about the app to both clientEntry and
|
|
10
|
+
* serverEntry.
|
|
11
|
+
*/
|
|
12
|
+
export default function manifest(site: Site, server = false): Plugin {
|
|
13
|
+
return {
|
|
14
|
+
name: "torpor-manifest",
|
|
15
|
+
resolveId: {
|
|
16
|
+
order: "pre",
|
|
17
|
+
handler(id) {
|
|
18
|
+
if (id === moduleId) {
|
|
19
|
+
return id;
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
load(id) {
|
|
24
|
+
if (id === moduleId) {
|
|
25
|
+
return `
|
|
26
|
+
${!server ? "const load = { default: { load: true } };" : ""}
|
|
27
|
+
export default {
|
|
28
|
+
routes: [
|
|
29
|
+
${site.routes
|
|
30
|
+
.map((r) => {
|
|
31
|
+
if (server || !/server\.(ts|js)$/.test(r.file)) {
|
|
32
|
+
return `{ path: "${r.path}", file: "${r.file}", type: ${r.type}, endPoint: ${server || !/server\.(ts|js)$/.test(r.file) ? `() => import(/* @vite-ignore */ "${path.join(site.root, r.file)}")` : "undefined"} }`;
|
|
33
|
+
} else {
|
|
34
|
+
// On the client, for a server route, we need to check
|
|
35
|
+
// whether there's a load function and set a dummy endPoint
|
|
36
|
+
// if so. The correct endpoint will be hit in clientEntry
|
|
37
|
+
// HACK: Should do this better than checking for `load:` in source...
|
|
38
|
+
const haveLoad = readFileSync(path.join(site.root, r.file)).includes("load:");
|
|
39
|
+
return `{ path: "${r.path}", file: "${r.file}", type: ${r.type}, endPoint: ${haveLoad ? "() => load" : "undefined"} }`;
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
.join(",\n ")}
|
|
43
|
+
],
|
|
44
|
+
};
|
|
45
|
+
`;
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export default function prepareTemplate(template: string, clientScript: string) {
|
|
2
|
+
let contentStart = regexIndexOf(template, /\<div\s+id=("app"|'app'|app)\s+/);
|
|
3
|
+
contentStart = template.indexOf(">", contentStart) + 1;
|
|
4
|
+
let contentEnd = template.indexOf("</div>", contentStart);
|
|
5
|
+
if (contentStart === -1 || contentEnd === -1) {
|
|
6
|
+
throw new Error(`Couldn't find <div id="app"></div>`);
|
|
7
|
+
}
|
|
8
|
+
return (
|
|
9
|
+
template.substring(0, contentStart) +
|
|
10
|
+
"%COMPONENT_HTML%" +
|
|
11
|
+
template.substring(contentEnd) +
|
|
12
|
+
`<script type="module" src="${clientScript}"></script>`
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// From https://stackoverflow.com/a/274094
|
|
17
|
+
function regexIndexOf(string: string, regex: RegExp, position?: number) {
|
|
18
|
+
var indexOf = string.substring(position || 0).search(regex);
|
|
19
|
+
return indexOf >= 0 ? indexOf + (position || 0) : indexOf;
|
|
20
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import torpor from "@torpor/unplugin/vite";
|
|
2
|
+
import { existsSync, promises as fs } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { build, defineConfig } from "vite";
|
|
6
|
+
import Site from "./Site.ts";
|
|
7
|
+
import manifest from "./manifest.ts";
|
|
8
|
+
|
|
9
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
|
|
11
|
+
// TODO: Don't cache index.html in dev?
|
|
12
|
+
// TODO: Multiple hook.server locations
|
|
13
|
+
// TODO: Don't reload layouts during client routing
|
|
14
|
+
// TODO: Call the correct +page and +server routes when in the same folder
|
|
15
|
+
|
|
16
|
+
export default async function runBuild(site: Site) {
|
|
17
|
+
// Delete the dist folder if it exists
|
|
18
|
+
const distFolder = path.resolve(site.root, "dist");
|
|
19
|
+
if (existsSync(distFolder)) {
|
|
20
|
+
await fs.rm(distFolder, { recursive: true });
|
|
21
|
+
}
|
|
22
|
+
const clientFolder = path.join(distFolder, "client");
|
|
23
|
+
const serverFolder = path.join(distFolder, "server");
|
|
24
|
+
|
|
25
|
+
// HOOK: Prebuild
|
|
26
|
+
if (site.adapter.prebuild) {
|
|
27
|
+
await site.adapter.prebuild(site);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// TODO: From a setting
|
|
31
|
+
let siteHtml = path.resolve(site.root, "src/site.html");
|
|
32
|
+
|
|
33
|
+
let clientScript = path.resolve(__dirname, "../../src/site/clientEntry.ts");
|
|
34
|
+
let serverScript = path.resolve(__dirname, "../../src/site/serverEntry.ts");
|
|
35
|
+
|
|
36
|
+
// Build the client assets, including site.html and the route files
|
|
37
|
+
// EXCLUDING anything with `server.js` in the name
|
|
38
|
+
await build(
|
|
39
|
+
defineConfig({
|
|
40
|
+
plugins: [
|
|
41
|
+
manifest(site),
|
|
42
|
+
// @ts-ignore
|
|
43
|
+
torpor(),
|
|
44
|
+
...site.plugins,
|
|
45
|
+
],
|
|
46
|
+
build: {
|
|
47
|
+
outDir: clientFolder,
|
|
48
|
+
rollupOptions: {
|
|
49
|
+
input: [
|
|
50
|
+
siteHtml,
|
|
51
|
+
clientScript,
|
|
52
|
+
...site.routes
|
|
53
|
+
.filter((r) => !/server\.(ts|js)$/.test(r.file))
|
|
54
|
+
.map((r) => path.resolve(site.root, r.file)),
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
ssrManifest: true,
|
|
58
|
+
},
|
|
59
|
+
}),
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
// Build the server assets, including the server entry script and the route
|
|
63
|
+
// files
|
|
64
|
+
await build(
|
|
65
|
+
defineConfig({
|
|
66
|
+
plugins: [
|
|
67
|
+
manifest(site, true),
|
|
68
|
+
// @ts-ignore
|
|
69
|
+
torpor(),
|
|
70
|
+
...site.plugins,
|
|
71
|
+
],
|
|
72
|
+
build: {
|
|
73
|
+
outDir: serverFolder,
|
|
74
|
+
rollupOptions: {
|
|
75
|
+
input: [serverScript, ...site.routes.map((r) => path.resolve(site.root, r.file))],
|
|
76
|
+
},
|
|
77
|
+
ssr: serverScript,
|
|
78
|
+
},
|
|
79
|
+
}),
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
// Move the site.html file into /client
|
|
83
|
+
// HACK: Should do this in Rollup if possible?
|
|
84
|
+
siteHtml = path.join(clientFolder, "site.html");
|
|
85
|
+
await fs.rename(path.join(clientFolder, "src", "site.html"), siteHtml);
|
|
86
|
+
await fs.rm(path.join(clientFolder, "src"), { recursive: true });
|
|
87
|
+
|
|
88
|
+
// HOOK: Postbuild
|
|
89
|
+
if (site.adapter.postbuild) {
|
|
90
|
+
await site.adapter.postbuild(site);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import torpor from "@torpor/unplugin/vite";
|
|
2
|
+
import { configDotenv } from "dotenv";
|
|
3
|
+
import { promises as fs } from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { createServer as createViteServer } from "vite";
|
|
7
|
+
import createMiddlewareHandler from "../adapters/node/createMiddlewareHandler.ts";
|
|
8
|
+
import { serverError } from "../response.ts";
|
|
9
|
+
import Server from "../server/Server.ts";
|
|
10
|
+
import Site from "./Site.ts";
|
|
11
|
+
import manifest from "./manifest.ts";
|
|
12
|
+
import prepareTemplate from "./prepareTemplate.ts";
|
|
13
|
+
|
|
14
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
|
|
16
|
+
export default async function runDev(site: Site) {
|
|
17
|
+
const server = new Server();
|
|
18
|
+
|
|
19
|
+
// Create the Vite server in middleware mode and configure the app type as
|
|
20
|
+
// "custom", disabling Vite's own HTML serving logic so the parent server
|
|
21
|
+
// can take control
|
|
22
|
+
const vite = await createViteServer({
|
|
23
|
+
server: { middlewareMode: true },
|
|
24
|
+
appType: "custom",
|
|
25
|
+
plugins: [
|
|
26
|
+
manifest(site, true),
|
|
27
|
+
// @ts-ignore not sure...
|
|
28
|
+
torpor(),
|
|
29
|
+
...site.plugins,
|
|
30
|
+
],
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Read site.html
|
|
34
|
+
// It's called site.html because @torpor/build builds the site (html, routes
|
|
35
|
+
// etc) while the user builds the app (components etc)
|
|
36
|
+
const templateFile = path.resolve(site.root, "src/site.html");
|
|
37
|
+
let template = await fs.readFile(templateFile, "utf-8");
|
|
38
|
+
|
|
39
|
+
// Apply Vite HTML transforms. This injects the Vite HMR client, and also
|
|
40
|
+
// applies HTML transforms from Vite plugins. Note that we only support a
|
|
41
|
+
// universal transform, not individual transforms for each route
|
|
42
|
+
template = await vite.transformIndexHtml("", template);
|
|
43
|
+
|
|
44
|
+
// TODO: Is this going to be the correct path after installing from npm?
|
|
45
|
+
const clientScript = path.resolve(__dirname, "../../src/site/clientEntry.ts");
|
|
46
|
+
const serverScript = path.resolve(__dirname, "../../src/site/serverEntry.ts");
|
|
47
|
+
|
|
48
|
+
// Prepare site.html so that we can just splice components into it
|
|
49
|
+
template = prepareTemplate(template, clientScript);
|
|
50
|
+
|
|
51
|
+
// Use vite's Connect instance as middleware. We need to wrap it with
|
|
52
|
+
// createMiddlewareHandler that converts Connect middleware to
|
|
53
|
+
// Request/Response web handlers
|
|
54
|
+
server.use(createMiddlewareHandler(vite.middlewares));
|
|
55
|
+
|
|
56
|
+
// Every request (GET, POST, etc) goes through loadEndPoint
|
|
57
|
+
server.add("*", async (ev) => {
|
|
58
|
+
try {
|
|
59
|
+
// Load the server entry. ssrLoadModule automatically transforms ESM
|
|
60
|
+
// source code to be usable in Node.js. No bundling is required, and
|
|
61
|
+
// it provides efficient invalidation similar to HMR
|
|
62
|
+
const { load } = await vite.ssrLoadModule(serverScript);
|
|
63
|
+
|
|
64
|
+
// Render the app HTML (or fetch server data etc) via serverEntry's
|
|
65
|
+
// exported `load` function
|
|
66
|
+
return await load(ev, template);
|
|
67
|
+
} catch (e: any) {
|
|
68
|
+
// If an error is caught, let Vite fix the stack trace so it maps
|
|
69
|
+
// back to your actual source code
|
|
70
|
+
vite.ssrFixStacktrace(e);
|
|
71
|
+
return serverError(e);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Load environment variables from a `.env` file, with defaults if not set
|
|
76
|
+
configDotenv();
|
|
77
|
+
|
|
78
|
+
process.env.PROTOCOL ??= "http:";
|
|
79
|
+
process.env.HOST ??= "localhost";
|
|
80
|
+
process.env.PORT ??= "7059";
|
|
81
|
+
|
|
82
|
+
// Serve the site
|
|
83
|
+
site.adapter.serve(server, site);
|
|
84
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { configDotenv } from "dotenv";
|
|
2
|
+
import { existsSync, promises as fs } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { serverError } from "../response.ts";
|
|
5
|
+
import Server from "../server/Server.ts";
|
|
6
|
+
import contentType from "../server/contentType.ts";
|
|
7
|
+
import Site from "./Site";
|
|
8
|
+
import prepareTemplate from "./prepareTemplate.ts";
|
|
9
|
+
import runBuild from "./runBuild";
|
|
10
|
+
|
|
11
|
+
export default async function runPreview(site: Site) {
|
|
12
|
+
// Build the site
|
|
13
|
+
await runBuild(site);
|
|
14
|
+
|
|
15
|
+
const distFolder = path.resolve(site.root, "dist");
|
|
16
|
+
const clientFolder = path.join(distFolder, "client");
|
|
17
|
+
const serverFolder = path.join(distFolder, "server");
|
|
18
|
+
|
|
19
|
+
// Find the client script file in /assets
|
|
20
|
+
let clientScript = (await fs.readdir(path.join(clientFolder, "assets"))).find((f) =>
|
|
21
|
+
f.startsWith("clientEntry-"),
|
|
22
|
+
);
|
|
23
|
+
if (!clientScript) {
|
|
24
|
+
throw new Error("clientEntry.js not found");
|
|
25
|
+
}
|
|
26
|
+
clientScript = `/assets/${clientScript}`;
|
|
27
|
+
|
|
28
|
+
// Read site.html
|
|
29
|
+
// It's called site.html because @torpor/build builds the site (html, routes
|
|
30
|
+
// etc) while the user builds the app (components etc)
|
|
31
|
+
let siteHtml = path.join(clientFolder, "site.html");
|
|
32
|
+
let template = await fs.readFile(siteHtml, "utf-8");
|
|
33
|
+
|
|
34
|
+
// Prepare site.html so that we can just splice components into it
|
|
35
|
+
template = prepareTemplate(template, clientScript);
|
|
36
|
+
|
|
37
|
+
// Configure the server
|
|
38
|
+
const server = new Server();
|
|
39
|
+
|
|
40
|
+
// Serve dist/client/assets statically
|
|
41
|
+
server.add("/assets/*", async (ev) => {
|
|
42
|
+
try {
|
|
43
|
+
const url = new URL(ev.request.url);
|
|
44
|
+
const file = path.join(site.root, "dist", "client", url.pathname);
|
|
45
|
+
if (existsSync(file)) {
|
|
46
|
+
// TODO: Stream the data?
|
|
47
|
+
return new Response(await fs.readFile(file), {
|
|
48
|
+
status: 200,
|
|
49
|
+
headers: {
|
|
50
|
+
"Content-Type": contentType(path.extname(file)),
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
} catch (e: any) {
|
|
55
|
+
return serverError(e);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Every request (GET, POST, etc) goes through loadEndPoint
|
|
60
|
+
const serverScript = path.resolve(serverFolder, "serverEntry.js");
|
|
61
|
+
server.add("*", async (ev) => {
|
|
62
|
+
try {
|
|
63
|
+
// Load the server entry
|
|
64
|
+
const { load } = await import(serverScript);
|
|
65
|
+
|
|
66
|
+
// Render the app HTML (or fetch server data etc) via serverEntry's
|
|
67
|
+
// exported `load` function
|
|
68
|
+
return await load(ev, template);
|
|
69
|
+
} catch (e: any) {
|
|
70
|
+
return serverError(e);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Load environment variables from a `.env` file, with defaults if not set
|
|
75
|
+
configDotenv();
|
|
76
|
+
|
|
77
|
+
process.env.PROTOCOL ??= "http:";
|
|
78
|
+
process.env.HOST ??= "localhost";
|
|
79
|
+
process.env.PORT ??= "7059";
|
|
80
|
+
|
|
81
|
+
// Serve the site
|
|
82
|
+
site.adapter.serve(server, site);
|
|
83
|
+
}
|