@torpor/build 0.4.10 → 0.4.11
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/package.json +4 -4
- package/src/bin/index.ts +11 -0
- package/src/index.ts +30 -0
- package/src/nav/formSubmit.ts +56 -0
- package/src/nav/load.ts +9 -0
- package/src/nav/loadData.ts +134 -0
- package/src/nav/navigate.ts +164 -0
- package/src/nav/reload.ts +9 -0
- package/src/nav.ts +4 -0
- package/src/response/badRequest.ts +19 -0
- package/src/response/created.ts +20 -0
- package/src/response/forbidden.ts +21 -0
- package/src/response/found.ts +30 -0
- package/src/response/methodNotAllowed.ts +18 -0
- package/src/response/movedPermanently.ts +25 -0
- package/src/response/notFound.ts +24 -0
- package/src/response/notModified.ts +34 -0
- package/src/response/ok.ts +29 -0
- package/src/response/permanentRedirect.ts +30 -0
- package/src/response/response.ts +22 -0
- package/src/response/seeOther.ts +19 -0
- package/src/response/serverError.ts +24 -0
- package/src/response/temporaryRedirect.ts +31 -0
- package/src/response/transfer.ts +14 -0
- package/src/response/unauthorized.ts +21 -0
- package/src/response/unprocessable.ts +18 -0
- package/src/response.ts +37 -0
- package/src/run/prepareTemplate.ts +39 -0
- package/src/run/run.ts +62 -0
- package/src/run/runBuild.ts +77 -0
- package/src/run/runDev.ts +89 -0
- package/src/run/runPreview.ts +93 -0
- package/src/run.ts +6 -0
- package/src/server/CookieHelper.ts +36 -0
- package/src/server/HeaderHelper.ts +25 -0
- package/src/server/Routerx.ts +72 -0
- package/src/server/Server.ts +144 -0
- package/src/server/ServerEvent.ts +39 -0
- package/src/server/connect/bufferToArrayBuffer.ts +8 -0
- package/src/server/connect/connectMiddleware.ts +81 -0
- package/src/server/connect/flattenHeaders.ts +44 -0
- package/src/server/connect/nodeMessageToNodeResponse.ts +89 -0
- package/src/server/connect/readableToBuffer.ts +16 -0
- package/src/server/connect/requestToNodeMessage.ts +26 -0
- package/src/server/contentType.ts +85 -0
- package/src/server/types/HttpMethod.ts +12 -0
- package/src/server/types/MiddlewareFunction.ts +8 -0
- package/src/server/types/ServerFunction.ts +7 -0
- package/src/server.ts +7 -0
- package/src/site/Router.ts +164 -0
- package/src/site/Site.ts +214 -0
- package/src/site/clientEntry.ts +108 -0
- package/src/site/clientEntryDev.ts +3 -0
- package/src/site/defaultAdapter.ts +11 -0
- package/src/site/manifest.ts +54 -0
- package/src/site/serverEntry.ts +417 -0
- package/src/state/$page.ts +24 -0
- package/src/state/client.ts +15 -0
- package/src/state.ts +3 -0
- package/src/test/runTest.ts +350 -0
- package/src/test.ts +3 -0
- package/src/types/Adapter.ts +8 -0
- package/src/types/ClientState.ts +8 -0
- package/src/types/InternalState.ts +7 -0
- package/src/types/LayoutHandler.ts +5 -0
- package/src/types/LayoutPath.ts +10 -0
- package/src/types/ManifestRoute.ts +6 -0
- package/src/types/PageEndPoint.ts +39 -0
- package/src/types/PageLoadEvent.ts +14 -0
- package/src/types/PageServerAction.ts +7 -0
- package/src/types/PageServerEndPoint.ts +16 -0
- package/src/types/PageServerLoad.ts +7 -0
- package/src/types/PageState.ts +8 -0
- package/src/types/Route.ts +11 -0
- package/src/types/RouteHandler.ts +16 -0
- package/src/types/RouteLayoutHandler.ts +5 -0
- package/src/types/RouteMatchResult.ts +7 -0
- package/src/types/RouteType.ts +19 -0
- package/src/types/ServerEndPoint.ts +37 -0
- package/src/types/ServerHook.ts +11 -0
- package/src/types/ServerLoadEvent.ts +37 -0
- package/src/types/ServerRequest.ts +7 -0
- package/src/utils/pathToRegex.ts +15 -0
- package/src/vite-env.d.ts +2 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import response from "./response";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 200 OK
|
|
5
|
+
*
|
|
6
|
+
* The HTTP 200 OK successful response status code indicates that a request has
|
|
7
|
+
* succeeded. A 200 OK response is cacheable by default.
|
|
8
|
+
*
|
|
9
|
+
* A 200 OK response has a different meaning and format depending on the HTTP
|
|
10
|
+
* request method. Here's how they vary for different methods:
|
|
11
|
+
*
|
|
12
|
+
* - GET: A resource was retrieved by the server and included in the response
|
|
13
|
+
* body.
|
|
14
|
+
* - POST: An action succeeded; the response has a message body describing the
|
|
15
|
+
* result.
|
|
16
|
+
* - HEAD: Identical to GET, except there is no message body.
|
|
17
|
+
* - TRACE: The response has a message body containing the request as received
|
|
18
|
+
* by the server.
|
|
19
|
+
*
|
|
20
|
+
* Although possible, successful PUT or DELETE requests often do not result in a
|
|
21
|
+
* 200 OK response. It is more common to see 201 Created if the resource is
|
|
22
|
+
* uploaded or created for the first time, or 204 No Content upon successful
|
|
23
|
+
* deletion of a resource.
|
|
24
|
+
*
|
|
25
|
+
* See https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/200
|
|
26
|
+
*/
|
|
27
|
+
export default function ok(body?: object | string): Response {
|
|
28
|
+
return response(200, body);
|
|
29
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import transfer from "./transfer";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 308 Permanent Redirect
|
|
5
|
+
*
|
|
6
|
+
* The HTTP 308 Permanent Redirect redirection response status code indicates
|
|
7
|
+
* that the requested resource has been permanently moved to the URL given by
|
|
8
|
+
* the Location header.
|
|
9
|
+
*
|
|
10
|
+
* A browser receiving this status will automatically request the resource at
|
|
11
|
+
* the URL in the Location header, redirecting the user to the new page. Search
|
|
12
|
+
* engines receiving this response will attribute links to the original URL to
|
|
13
|
+
* the redirected resource, passing the SEO ranking to the new URL.
|
|
14
|
+
*
|
|
15
|
+
* The request method and the body will not be modified by the client in the
|
|
16
|
+
* redirected request. A 301 Moved Permanently requires the request method and
|
|
17
|
+
* the body to remain unchanged when redirection is performed, but this is
|
|
18
|
+
* incorrectly handled by older clients to use the GET method instead.
|
|
19
|
+
*
|
|
20
|
+
* > Note: Some Web applications may use the 308 Permanent Redirect in a
|
|
21
|
+
* > non-standard way and for different purposes. For example, Google Drive uses
|
|
22
|
+
* > a 308 Resume Incomplete response to indicate to the client when an
|
|
23
|
+
* > unfinished upload has stalled. See Perform a resumable download on the
|
|
24
|
+
* > Google Drive documentation for more information.
|
|
25
|
+
*
|
|
26
|
+
* See https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/308
|
|
27
|
+
*/
|
|
28
|
+
export default function permanentRedirect(location: string): Response {
|
|
29
|
+
return transfer(308, location);
|
|
30
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a response with the supplied status code and optional body.
|
|
3
|
+
*
|
|
4
|
+
* If the body is an object, it will be converted to JSON and the content-type
|
|
5
|
+
* header set to "application/json". Otherwise, if the body is a string, the
|
|
6
|
+
* content-type header will be set to "text/plain".
|
|
7
|
+
*/
|
|
8
|
+
export default function response(status: number, body?: object | string): Response {
|
|
9
|
+
let headers: Record<string, string> | undefined = undefined;
|
|
10
|
+
if (body) {
|
|
11
|
+
if (typeof body === "object") {
|
|
12
|
+
headers = { "content-type": "application/json" };
|
|
13
|
+
body = JSON.stringify(body);
|
|
14
|
+
} else {
|
|
15
|
+
headers = { "content-type": "text/plain" };
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return new Response(body, {
|
|
19
|
+
status,
|
|
20
|
+
headers,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import transfer from "./transfer";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 303 See Other
|
|
5
|
+
*
|
|
6
|
+
* The HTTP 303 See Other redirection response status code indicates that the
|
|
7
|
+
* browser should redirect to the URL in the Location header instead of
|
|
8
|
+
* rendering the requested resource.
|
|
9
|
+
*
|
|
10
|
+
* This response code is often sent back as a result of PUT or POST methods so
|
|
11
|
+
* the client may retrieve a confirmation, or view a representation of a
|
|
12
|
+
* real-world object (see HTTP range-14). The method to retrieve the redirected
|
|
13
|
+
* resource is always GET.
|
|
14
|
+
*
|
|
15
|
+
* See https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/303
|
|
16
|
+
*/
|
|
17
|
+
export default function seeOther(location: string): Response {
|
|
18
|
+
return transfer(303, location);
|
|
19
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import response from "./response";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 500 Server Error
|
|
5
|
+
*
|
|
6
|
+
* The HTTP 500 Internal Server Error server error response status code
|
|
7
|
+
* indicates that the server encountered an unexpected condition that prevented
|
|
8
|
+
* it from fulfilling the request. This error is a generic "catch-all" response
|
|
9
|
+
* to server issues, indicating that the server cannot find a more appropriate
|
|
10
|
+
* 5XX error to respond with.
|
|
11
|
+
*
|
|
12
|
+
* If you're a visitor seeing 500 errors on a web page, these issues require
|
|
13
|
+
* investigation by server owners or administrators. There are many possible
|
|
14
|
+
* causes of 500 errors, including: improper server configuration, out-of-memory
|
|
15
|
+
* (OOM) issues, unhandled exceptions, improper file permissions, or other
|
|
16
|
+
* complex factors. Server administrators may proactively log occurrences of
|
|
17
|
+
* server error responses, like the 500 status code, with details about the
|
|
18
|
+
* initiating requests to improve the stability of a service in the future.
|
|
19
|
+
*
|
|
20
|
+
* See https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/500
|
|
21
|
+
*/
|
|
22
|
+
export default function serverError(body?: object | string): Response {
|
|
23
|
+
return response(500, body ?? "Server error");
|
|
24
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import transfer from "./transfer";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 307 Temporary Redirect
|
|
5
|
+
*
|
|
6
|
+
* The HTTP 307 Temporary Redirect redirection response status code indicates
|
|
7
|
+
* that the resource requested has been temporarily moved to the URL in the
|
|
8
|
+
* Location header.
|
|
9
|
+
*
|
|
10
|
+
* A browser receiving this status will automatically request the resource at
|
|
11
|
+
* the URL in the Location header, redirecting the user to the new page. Search
|
|
12
|
+
* engines receiving this response will not attribute links to the original URL
|
|
13
|
+
* to the new resource, meaning no SEO value is transferred to the new URL.
|
|
14
|
+
*
|
|
15
|
+
* The method and the body of the original request are reused to perform the
|
|
16
|
+
* redirected request. In the cases where you want the request method to be
|
|
17
|
+
* changed to GET, use 303 See Other instead. This is useful when you want to
|
|
18
|
+
* give an answer to a successful PUT request that is not the uploaded resource,
|
|
19
|
+
* but a status monitor or confirmation message like "You have successfully
|
|
20
|
+
* uploaded XYZ".
|
|
21
|
+
*
|
|
22
|
+
* The difference between 307 and 302 is that 307 guarantees that the client
|
|
23
|
+
* will not change the request method and body when the redirected request is
|
|
24
|
+
* made. With 302, older clients incorrectly changed the method to GET. 307 and
|
|
25
|
+
* 302 responses are identical when the request method is GET.
|
|
26
|
+
*
|
|
27
|
+
* See https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/307
|
|
28
|
+
*/
|
|
29
|
+
export default function temporaryRedirect(location: string): Response {
|
|
30
|
+
return transfer(307, location);
|
|
31
|
+
}
|
|
@@ -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,21 @@
|
|
|
1
|
+
import response from "./response";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 401 Unauthorized
|
|
5
|
+
*
|
|
6
|
+
* The HTTP 401 Unauthorized client error response status code indicates that a
|
|
7
|
+
* request was not successful because it lacks valid authentication credentials
|
|
8
|
+
* for the requested resource. This status code is sent with an HTTP
|
|
9
|
+
* WWW-Authenticate response header that contains information on the
|
|
10
|
+
* authentication scheme the server expects the client to include to make the
|
|
11
|
+
* request successfully.
|
|
12
|
+
*
|
|
13
|
+
* A 401 Unauthorized is similar to the 403 Forbidden response, except that a
|
|
14
|
+
* 403 is returned when a request contains valid credentials, but the client
|
|
15
|
+
* does not have permissions to perform a certain action.
|
|
16
|
+
*
|
|
17
|
+
* See https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/401
|
|
18
|
+
*/
|
|
19
|
+
export default function unauthorized(body?: object | string): Response {
|
|
20
|
+
return response(401, body ?? "Unauthorized");
|
|
21
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import response from "./response";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 422 Unprocessable Content
|
|
5
|
+
*
|
|
6
|
+
* The HTTP 422 Unprocessable Content client error response status code
|
|
7
|
+
* indicates that the server understood the content type of the request content,
|
|
8
|
+
* and the syntax of the request content was correct, but it was unable to
|
|
9
|
+
* process the contained instructions.
|
|
10
|
+
*
|
|
11
|
+
* Clients that receive a 422 response should expect that repeating the request
|
|
12
|
+
* without modification will fail with the same error.
|
|
13
|
+
*
|
|
14
|
+
* See https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/422
|
|
15
|
+
*/
|
|
16
|
+
export default function unprocessable(body?: object | string): Response {
|
|
17
|
+
return response(422, body ?? "Unprocessable");
|
|
18
|
+
}
|
package/src/response.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import badRequest from "./response/badRequest";
|
|
2
|
+
import created from "./response/created";
|
|
3
|
+
import forbidden from "./response/forbidden";
|
|
4
|
+
import found from "./response/found";
|
|
5
|
+
import methodNotAllowed from "./response/methodNotAllowed";
|
|
6
|
+
import movedPermanently from "./response/movedPermanently";
|
|
7
|
+
import notFound from "./response/notFound";
|
|
8
|
+
import notModified from "./response/notModified";
|
|
9
|
+
import ok from "./response/ok";
|
|
10
|
+
import permanentRedirect from "./response/permanentRedirect";
|
|
11
|
+
import response from "./response/response";
|
|
12
|
+
import seeOther from "./response/seeOther";
|
|
13
|
+
import serverError from "./response/serverError";
|
|
14
|
+
import temporaryRedirect from "./response/temporaryRedirect";
|
|
15
|
+
import transfer from "./response/transfer";
|
|
16
|
+
import unauthorized from "./response/unauthorized";
|
|
17
|
+
import unprocessable from "./response/unprocessable";
|
|
18
|
+
|
|
19
|
+
export {
|
|
20
|
+
response,
|
|
21
|
+
ok,
|
|
22
|
+
created,
|
|
23
|
+
transfer,
|
|
24
|
+
movedPermanently,
|
|
25
|
+
found,
|
|
26
|
+
seeOther,
|
|
27
|
+
notModified,
|
|
28
|
+
temporaryRedirect,
|
|
29
|
+
permanentRedirect,
|
|
30
|
+
badRequest,
|
|
31
|
+
unauthorized,
|
|
32
|
+
forbidden,
|
|
33
|
+
notFound,
|
|
34
|
+
methodNotAllowed,
|
|
35
|
+
unprocessable,
|
|
36
|
+
serverError,
|
|
37
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export default function prepareTemplate(
|
|
2
|
+
template: string,
|
|
3
|
+
clientScript: string,
|
|
4
|
+
clientDevScript?: string,
|
|
5
|
+
): string {
|
|
6
|
+
let result = template;
|
|
7
|
+
|
|
8
|
+
// Put %COMPONENT_HEAD% before </head>
|
|
9
|
+
// This is where styles will go
|
|
10
|
+
let headStart = result.indexOf("</head>");
|
|
11
|
+
if (headStart === -1) {
|
|
12
|
+
throw new Error(`Couldn't find <head> end tag`);
|
|
13
|
+
}
|
|
14
|
+
result = result.substring(0, headStart) + "%COMPONENT_HEAD%" + result.substring(headStart);
|
|
15
|
+
|
|
16
|
+
// Put %COMPONENT_BODY% inside <div id="app"></div>
|
|
17
|
+
// This is where the component's HTML will go
|
|
18
|
+
let bodyStart = regexIndexOf(result, /<div\s+id=("app"|'app'|app)\s+/);
|
|
19
|
+
bodyStart = result.indexOf(">", bodyStart) + 1;
|
|
20
|
+
let bodyEnd = result.indexOf("</div>", bodyStart);
|
|
21
|
+
if (bodyStart === -1 || bodyEnd === -1) {
|
|
22
|
+
throw new Error(`Couldn't find <div id="app"></div>`);
|
|
23
|
+
}
|
|
24
|
+
result = result.substring(0, bodyStart) + "%COMPONENT_BODY%" + result.substring(bodyEnd);
|
|
25
|
+
|
|
26
|
+
// Add the clientEntry script at the very end, for hydrating and navigating
|
|
27
|
+
if (clientDevScript) {
|
|
28
|
+
result += `<script type="module" src="${clientDevScript}"></script>`;
|
|
29
|
+
}
|
|
30
|
+
result += `<script type="module" src="${clientScript}"></script>`;
|
|
31
|
+
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// From https://stackoverflow.com/a/274094
|
|
36
|
+
function regexIndexOf(string: string, regex: RegExp, position?: number) {
|
|
37
|
+
var indexOf = string.substring(position || 0).search(regex);
|
|
38
|
+
return indexOf >= 0 ? indexOf + (position || 0) : indexOf;
|
|
39
|
+
}
|
package/src/run/run.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { transformWithEsbuild } from "vite";
|
|
4
|
+
import Site from "../site/Site";
|
|
5
|
+
import runBuild from "./runBuild";
|
|
6
|
+
import runDev from "./runDev";
|
|
7
|
+
import runPreview from "./runPreview";
|
|
8
|
+
|
|
9
|
+
export default async function run(
|
|
10
|
+
folder: string,
|
|
11
|
+
mode: "dev" | "build" | "preview",
|
|
12
|
+
): Promise<void> {
|
|
13
|
+
// Look for and load a site.config.js/ts file in the working directory
|
|
14
|
+
let configFile = "";
|
|
15
|
+
let deleteConfigFile = false;
|
|
16
|
+
const jsConfigFile = path.join(folder, "site.config.js");
|
|
17
|
+
const tsConfigFile = path.join(folder, "site.config.ts");
|
|
18
|
+
if (fs.existsSync(jsConfigFile)) {
|
|
19
|
+
// If it's JS, we can just import it straight up
|
|
20
|
+
configFile = jsConfigFile;
|
|
21
|
+
} else if (fs.existsSync(tsConfigFile)) {
|
|
22
|
+
// If it's TS, we need to convert it to JS
|
|
23
|
+
// TODO: Put the generated file somewhere better (dist?)
|
|
24
|
+
configFile = path.join(folder, "site.config.temp.js");
|
|
25
|
+
let source = fs.readFileSync(tsConfigFile, "utf-8");
|
|
26
|
+
let transformed = await transformWithEsbuild(source, tsConfigFile, {
|
|
27
|
+
loader: "ts",
|
|
28
|
+
});
|
|
29
|
+
fs.writeFileSync(configFile, transformed.code);
|
|
30
|
+
deleteConfigFile = true;
|
|
31
|
+
} else {
|
|
32
|
+
throw new Error("site.config file not found");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const site = (await import(configFile)).default as Site;
|
|
36
|
+
if (!site || !site.root || !site.routes) {
|
|
37
|
+
throw new Error("Invalid site in config file");
|
|
38
|
+
}
|
|
39
|
+
if (deleteConfigFile) {
|
|
40
|
+
fs.rmSync(configFile);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
switch (mode) {
|
|
44
|
+
case "dev": {
|
|
45
|
+
await runDev(site);
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
case "build": {
|
|
49
|
+
await runBuild(site);
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
case "preview": {
|
|
53
|
+
await runPreview(site);
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
default: {
|
|
57
|
+
console.log("No mode passed, running in dev");
|
|
58
|
+
await runDev(site);
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import torpor from "@torpor/unplugin/vite";
|
|
2
|
+
import { existsSync, promises as fs } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { build, defineConfig } from "vite";
|
|
5
|
+
import Site from "../site/Site";
|
|
6
|
+
import manifest from "../site/manifest.ts";
|
|
7
|
+
|
|
8
|
+
// TODO: Don't cache index.html in dev?
|
|
9
|
+
// TODO: Multiple hook.server locations
|
|
10
|
+
// TODO: Don't reload layouts during client routing
|
|
11
|
+
// TODO: Call the correct +page and +server routes when in the same folder
|
|
12
|
+
|
|
13
|
+
export default async function runBuild(site: Site): Promise<void> {
|
|
14
|
+
// Delete the dist folder if it exists
|
|
15
|
+
const distFolder = path.resolve(site.root, "dist");
|
|
16
|
+
if (existsSync(distFolder)) {
|
|
17
|
+
await fs.rm(distFolder, { recursive: true });
|
|
18
|
+
}
|
|
19
|
+
const clientFolder = path.join(distFolder, "client");
|
|
20
|
+
const serverFolder = path.join(distFolder, "server");
|
|
21
|
+
|
|
22
|
+
// HOOK: Prebuild
|
|
23
|
+
if (site.adapter.prebuild) {
|
|
24
|
+
await site.adapter.prebuild(site);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// TODO: From a setting
|
|
28
|
+
let siteHtml = path.resolve(site.root, "src/site.html");
|
|
29
|
+
|
|
30
|
+
const siteFolder = path.resolve(site.root, "./node_modules/@torpor/build/src/site/");
|
|
31
|
+
let clientScript = path.join(siteFolder, "clientEntry.ts");
|
|
32
|
+
let serverScript = path.join(siteFolder, "serverEntry.ts");
|
|
33
|
+
|
|
34
|
+
// Build the client assets, including site.html and the route files
|
|
35
|
+
// EXCLUDING anything with `server.js` in the name
|
|
36
|
+
const clientConfig = structuredClone(site.viteConfig ?? {});
|
|
37
|
+
clientConfig.plugins = [manifest(site), torpor(), ...site.plugins];
|
|
38
|
+
clientConfig.build ??= {};
|
|
39
|
+
clientConfig.build.outDir = clientFolder;
|
|
40
|
+
clientConfig.build.rollupOptions ??= {};
|
|
41
|
+
clientConfig.build.rollupOptions.input = [
|
|
42
|
+
siteHtml,
|
|
43
|
+
clientScript,
|
|
44
|
+
...site.routes
|
|
45
|
+
.filter((r) => !/server\.(ts|js)$/.test(r.file))
|
|
46
|
+
.map((r) => path.resolve(site.root, r.file)),
|
|
47
|
+
...site.inputs.filter((f) => !/server\.(ts|js)$/.test(f)),
|
|
48
|
+
];
|
|
49
|
+
clientConfig.build.ssrManifest = true;
|
|
50
|
+
await build(defineConfig(clientConfig));
|
|
51
|
+
|
|
52
|
+
// Build the server assets, including the server entry script and the route
|
|
53
|
+
// files
|
|
54
|
+
const serverConfig = structuredClone(site.viteConfig ?? {});
|
|
55
|
+
serverConfig.plugins = [manifest(site, true), torpor(), ...site.plugins];
|
|
56
|
+
serverConfig.build ??= {};
|
|
57
|
+
serverConfig.build.outDir = serverFolder;
|
|
58
|
+
serverConfig.build.rollupOptions ??= {};
|
|
59
|
+
serverConfig.build.rollupOptions.input = [
|
|
60
|
+
serverScript,
|
|
61
|
+
...site.routes.map((r) => path.resolve(site.root, r.file)),
|
|
62
|
+
...site.inputs,
|
|
63
|
+
];
|
|
64
|
+
serverConfig.build.ssr = serverScript;
|
|
65
|
+
await build(defineConfig(serverConfig));
|
|
66
|
+
|
|
67
|
+
// Move the site.html file into /client
|
|
68
|
+
// HACK: Should do this in Rollup if possible?
|
|
69
|
+
siteHtml = path.join(clientFolder, "site.html");
|
|
70
|
+
await fs.rename(path.join(clientFolder, "src", "site.html"), siteHtml);
|
|
71
|
+
await fs.rm(path.join(clientFolder, "src"), { recursive: true });
|
|
72
|
+
|
|
73
|
+
// HOOK: Postbuild
|
|
74
|
+
if (site.adapter.postbuild) {
|
|
75
|
+
await site.adapter.postbuild(site);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import estorpor from "@torpor/unplugin/esbuild";
|
|
2
|
+
import torpor from "@torpor/unplugin/vite";
|
|
3
|
+
import { configDotenv } from "dotenv";
|
|
4
|
+
import { promises as fs } from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { createServer as createViteServer } from "vite";
|
|
7
|
+
import { serverError } from "../response.ts";
|
|
8
|
+
import Server from "../server/Server.ts";
|
|
9
|
+
import connectMiddleware from "../server/connect/connectMiddleware.ts";
|
|
10
|
+
import Site from "../site/Site.ts";
|
|
11
|
+
import manifest from "../site/manifest.ts";
|
|
12
|
+
import prepareTemplate from "./prepareTemplate.ts";
|
|
13
|
+
|
|
14
|
+
export default async function runDev(site: Site): Promise<void> {
|
|
15
|
+
const server = new Server();
|
|
16
|
+
|
|
17
|
+
// Create the Vite server in middleware mode and configure the app type as
|
|
18
|
+
// "custom", disabling Vite's own HTML serving logic so the parent server
|
|
19
|
+
// can take control
|
|
20
|
+
const config = structuredClone(site.viteConfig ?? {});
|
|
21
|
+
config.server = { middlewareMode: true };
|
|
22
|
+
config.appType = "custom";
|
|
23
|
+
config.plugins = [manifest(site, true), torpor({ dev: true }), ...site.plugins];
|
|
24
|
+
|
|
25
|
+
// HACK: To be able to import `.torp` files from barrel files in
|
|
26
|
+
// node_modules, we need to add their libraries to `ssr.noExternal` in
|
|
27
|
+
// site.config.ts, and let esbuild know how to compile them here
|
|
28
|
+
config.optimizeDeps ??= {};
|
|
29
|
+
config.optimizeDeps.extensions ??= [];
|
|
30
|
+
config.optimizeDeps.extensions.push(".torp");
|
|
31
|
+
config.optimizeDeps.esbuildOptions ??= {};
|
|
32
|
+
config.optimizeDeps.esbuildOptions.plugins ??= [];
|
|
33
|
+
config.optimizeDeps.esbuildOptions.plugins.push(estorpor());
|
|
34
|
+
|
|
35
|
+
const vite = await createViteServer(config);
|
|
36
|
+
|
|
37
|
+
// Read site.html
|
|
38
|
+
// It's called site.html because @torpor/build builds the site (html, routes
|
|
39
|
+
// etc) while the user builds the app (components etc)
|
|
40
|
+
const templateFile = path.resolve(site.root, "src/site.html");
|
|
41
|
+
let template = await fs.readFile(templateFile, "utf-8");
|
|
42
|
+
|
|
43
|
+
// Apply Vite HTML transforms. This injects the Vite HMR client, and also
|
|
44
|
+
// applies HTML transforms from Vite plugins. Note that we only support a
|
|
45
|
+
// universal transform, not individual transforms for each route
|
|
46
|
+
template = await vite.transformIndexHtml("", template);
|
|
47
|
+
|
|
48
|
+
const siteFolder = path.resolve(site.root, "./node_modules/@torpor/build/src/site/");
|
|
49
|
+
let clientScript = path.join(siteFolder, "clientEntry.ts");
|
|
50
|
+
let clientDevScript = path.join(siteFolder, "clientEntryDev.ts");
|
|
51
|
+
let serverScript = path.join(siteFolder, "serverEntry.ts");
|
|
52
|
+
|
|
53
|
+
// Prepare site.html so that we can just splice components into it
|
|
54
|
+
template = prepareTemplate(template, clientScript, clientDevScript);
|
|
55
|
+
|
|
56
|
+
// Use Vite's Connect instance as middleware. We need to wrap it with
|
|
57
|
+
// createMiddlewareHandler that converts Connect middleware to
|
|
58
|
+
// Request/Response web handlers
|
|
59
|
+
server.use(connectMiddleware(vite.middlewares));
|
|
60
|
+
|
|
61
|
+
// Every request (GET, POST, etc) goes through loadEndPoint
|
|
62
|
+
server.add("*", async (ev) => {
|
|
63
|
+
try {
|
|
64
|
+
// Load the server entry. ssrLoadModule automatically transforms ESM
|
|
65
|
+
// source code to be usable in Node.js. No bundling is required, and
|
|
66
|
+
// it provides efficient invalidation similar to HMR
|
|
67
|
+
const { load } = await vite.ssrLoadModule(serverScript);
|
|
68
|
+
|
|
69
|
+
// Render the app HTML (or fetch server data etc) via serverEntry's
|
|
70
|
+
// exported `load` function
|
|
71
|
+
return await load(ev, template);
|
|
72
|
+
} catch (e: any) {
|
|
73
|
+
// If an error is caught, let Vite fix the stack trace so it maps
|
|
74
|
+
// back to your actual source code
|
|
75
|
+
vite.ssrFixStacktrace(e);
|
|
76
|
+
return serverError(e);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Load environment variables from a `.env` file, with defaults if not set
|
|
81
|
+
configDotenv();
|
|
82
|
+
|
|
83
|
+
process.env.PROTOCOL ??= "http:";
|
|
84
|
+
process.env.HOST ??= "localhost";
|
|
85
|
+
process.env.PORT ??= "7059";
|
|
86
|
+
|
|
87
|
+
// Serve the site
|
|
88
|
+
site.adapter.serve(server, site);
|
|
89
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
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/Site";
|
|
8
|
+
import prepareTemplate from "./prepareTemplate.ts";
|
|
9
|
+
import runBuild from "./runBuild";
|
|
10
|
+
|
|
11
|
+
export default async function runPreview(site: Site): Promise<void> {
|
|
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
|
+
const body = bufferToArrayBuffer(await fs.readFile(file));
|
|
48
|
+
return new Response(body, {
|
|
49
|
+
status: 200,
|
|
50
|
+
headers: {
|
|
51
|
+
"Content-Type": contentType(path.extname(file)),
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
} catch (e: any) {
|
|
56
|
+
return serverError(e);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Every request (GET, POST, etc) goes through loadEndPoint
|
|
61
|
+
const serverScript = path.resolve(serverFolder, "serverEntry.js");
|
|
62
|
+
server.add("*", async (ev) => {
|
|
63
|
+
try {
|
|
64
|
+
// Load the server entry
|
|
65
|
+
const { load } = await import(serverScript);
|
|
66
|
+
|
|
67
|
+
// Render the app HTML (or fetch server data etc) via serverEntry's
|
|
68
|
+
// exported `load` function
|
|
69
|
+
return await load(ev, template);
|
|
70
|
+
} catch (e: any) {
|
|
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
|
+
}
|
|
85
|
+
|
|
86
|
+
// From https://stackoverflow.com/a/79345620
|
|
87
|
+
// No idea why it is needed
|
|
88
|
+
function bufferToArrayBuffer(buffer: Buffer): ArrayBuffer {
|
|
89
|
+
return buffer.buffer.slice(
|
|
90
|
+
buffer.byteOffset,
|
|
91
|
+
buffer.byteOffset + buffer.byteLength,
|
|
92
|
+
) as ArrayBuffer;
|
|
93
|
+
}
|
package/src/run.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { SerializeOptions, parse, serialize } from "cookie";
|
|
2
|
+
|
|
3
|
+
export default class CookieHelper {
|
|
4
|
+
request: Request;
|
|
5
|
+
cookies: Map<string, string> = new Map();
|
|
6
|
+
|
|
7
|
+
constructor(request: Request) {
|
|
8
|
+
this.request = request;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
get(name: string): string | undefined {
|
|
12
|
+
// If it's been set, return the value from the map
|
|
13
|
+
if (this.cookies.has(name)) {
|
|
14
|
+
return parse(this.cookies.get(name)!)[name];
|
|
15
|
+
}
|
|
16
|
+
// Otherwise, return the value from the `cookie` header
|
|
17
|
+
const header = this.request.headers.get("cookie");
|
|
18
|
+
return parse(header ?? "")[name];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
set(name: string, value: string, options?: SerializeOptions): void {
|
|
22
|
+
// Set a cookie header with some default values for security
|
|
23
|
+
options ??= {};
|
|
24
|
+
options.httpOnly ??= true;
|
|
25
|
+
options.secure ??= true;
|
|
26
|
+
options.sameSite ??= "lax";
|
|
27
|
+
this.cookies.set(name, serialize(name, value, options));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
delete(name: string, options?: SerializeOptions): void {
|
|
31
|
+
// Set a cookie header with an immediate expiry date
|
|
32
|
+
options ??= {};
|
|
33
|
+
options.expires = new Date(0);
|
|
34
|
+
this.cookies.set(name, serialize(name, "", options));
|
|
35
|
+
}
|
|
36
|
+
}
|