@torpor/build 0.0.6 → 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.
Files changed (106) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +1 -1
  3. package/dist/Server-CB99REgs.d.cts +74 -0
  4. package/dist/Server-CB99REgs.d.ts +74 -0
  5. package/dist/Site-CgoeDt8k.d.cts +29 -0
  6. package/dist/Site-D95nwrAg.d.ts +29 -0
  7. package/dist/adapters/cloudflare/index.cjs +125 -0
  8. package/dist/adapters/cloudflare/index.cjs.map +1 -0
  9. package/dist/adapters/cloudflare/index.d.cts +13 -0
  10. package/dist/adapters/cloudflare/index.d.ts +13 -0
  11. package/dist/adapters/cloudflare/index.js +80 -0
  12. package/dist/adapters/cloudflare/index.js.map +1 -0
  13. package/dist/adapters/node/index.cjs +152 -0
  14. package/dist/adapters/node/index.cjs.map +1 -0
  15. package/dist/adapters/node/index.d.cts +8 -0
  16. package/dist/adapters/node/index.d.ts +8 -0
  17. package/dist/adapters/node/index.js +8 -0
  18. package/dist/adapters/node/index.js.map +1 -0
  19. package/dist/bin/index.cjs +541 -93
  20. package/dist/bin/index.cjs.map +1 -1
  21. package/dist/bin/index.js +511 -22
  22. package/dist/bin/index.js.map +1 -1
  23. package/dist/chunk-5X4T3PZN.js +23 -0
  24. package/dist/chunk-5X4T3PZN.js.map +1 -0
  25. package/dist/chunk-ALHFYD7U.js +113 -0
  26. package/dist/chunk-ALHFYD7U.js.map +1 -0
  27. package/dist/chunk-V4XTDQEV.js +19 -0
  28. package/dist/chunk-V4XTDQEV.js.map +1 -0
  29. package/dist/chunk-VAGHGWM5.js +110 -0
  30. package/dist/chunk-VAGHGWM5.js.map +1 -0
  31. package/dist/index.cjs +200 -97
  32. package/dist/index.cjs.map +1 -1
  33. package/dist/index.d.cts +20 -28
  34. package/dist/index.d.ts +20 -28
  35. package/dist/index.js +85 -17
  36. package/dist/index.js.map +1 -1
  37. package/dist/metafile-cjs.json +1 -1
  38. package/dist/metafile-esm.json +1 -1
  39. package/dist/response.js +17 -91
  40. package/dist/response.js.map +1 -1
  41. package/package.json +24 -7
  42. package/src/adapters/cloudflare/_worker.ts +38 -0
  43. package/src/adapters/cloudflare/index.ts +98 -0
  44. package/src/adapters/node/constants.ts +1 -0
  45. package/src/adapters/node/createFetchHandler.ts +29 -0
  46. package/src/adapters/node/createMiddlewareHandler.ts +79 -0
  47. package/src/adapters/node/createNodeServer.ts +8 -0
  48. package/src/adapters/node/flattenHeaders.ts +44 -0
  49. package/src/adapters/node/index.ts +18 -0
  50. package/src/adapters/node/nodeMessageToNodeResponse.ts +89 -0
  51. package/src/adapters/node/nodeMessageToRequest.ts +43 -0
  52. package/src/adapters/node/readableToBuffer.ts +16 -0
  53. package/src/adapters/node/requestToNodeMessage.ts +26 -0
  54. package/src/adapters/node/responseToNodeResponse.ts +44 -0
  55. package/src/bin/index.ts +43 -36
  56. package/src/index.ts +6 -14
  57. package/src/server/CookieHelper.ts +36 -0
  58. package/src/server/HeaderHelper.ts +25 -0
  59. package/src/server/Server.ts +119 -0
  60. package/src/server/ServerEvent.ts +33 -0
  61. package/src/server/ServerFunction.ts +5 -0
  62. package/src/server/contentType.ts +85 -0
  63. package/src/site/Router.ts +136 -0
  64. package/src/site/Routerx.ts +67 -0
  65. package/src/site/Site.ts +114 -0
  66. package/src/site/clientEntry.ts +22 -20
  67. package/src/site/manifest.ts +49 -0
  68. package/src/site/pathToRegex.ts +10 -0
  69. package/src/site/prepareTemplate.ts +20 -0
  70. package/src/site/runBuild.ts +92 -0
  71. package/src/site/runDev.ts +84 -0
  72. package/src/site/runPreview.ts +83 -0
  73. package/src/site/serverEntry.ts +113 -200
  74. package/src/types/Adapter.ts +10 -0
  75. package/src/types/HttpMethod.ts +12 -0
  76. package/src/types/LayoutHandler.ts +7 -0
  77. package/src/types/ManifestRoute.ts +8 -0
  78. package/src/types/MiddlewareFunction.ts +8 -0
  79. package/src/types/PageEndPoint.ts +4 -3
  80. package/src/types/{PageEvent.ts → PageLoadEvent.ts} +3 -2
  81. package/src/types/PageServerAction.ts +5 -2
  82. package/src/types/PageServerEndPoint.ts +1 -0
  83. package/src/types/PageServerLoad.ts +5 -2
  84. package/src/types/RouteHandler.ts +6 -5
  85. package/src/types/RouteHandlerCollection.ts +1 -0
  86. package/src/types/RouteLayoutHandler.ts +3 -2
  87. package/src/types/RouteMatchResult.ts +3 -2
  88. package/src/types/RouteType.ts +7 -0
  89. package/src/types/ServerEndPoint.ts +1 -0
  90. package/src/types/ServerHook.ts +3 -2
  91. package/src/types/{ServerEvent.ts → ServerLoadEvent.ts} +13 -6
  92. package/src/types/ServerRequest.ts +5 -2
  93. package/src/types/manifest.d.ts +7 -0
  94. package/test/app.test.ts +47 -0
  95. package/tsconfig.json +1 -1
  96. package/tsup.config.ts +11 -1
  97. package/vitest.config.ts +3 -0
  98. package/dist/chunk-IE5EKBNX.js +0 -93
  99. package/dist/chunk-IE5EKBNX.js.map +0 -1
  100. package/src/site/FileSystemRouter.ts +0 -25
  101. package/src/site/buildRoutePath.ts +0 -25
  102. package/src/site/defineConfig.ts +0 -75
  103. package/src/site/lazyRoute.ts +0 -72
  104. package/src/site/routeHandlers.ts +0 -126
  105. package/src/types/CookieHelper.ts +0 -8
  106. package/src/types/UserConfig.ts +0 -4
@@ -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,10 @@
1
+ export default function pathToRegex(path: string): RegExp {
2
+ const pattern = path
3
+ .split("/")
4
+ .map((p) => {
5
+ return p.replace(/\[([^\/]+?)\]/, "(?<$1>[^\\/]+?)");
6
+ })
7
+ .join("\\/")
8
+ .replaceAll("*", ".*");
9
+ return new RegExp(`^${pattern}$`);
10
+ }
@@ -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
+ }