@fluixi/start 0.1.0-alpha.73 → 0.1.0-alpha.75
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/dist/adapter.cjs +750 -0
- package/dist/adapter.d.ts +9 -9
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +10 -10
- package/dist/adapter.mjs +719 -0
- package/dist/adapters/entry.cjs +122 -0
- package/dist/adapters/entry.js +1 -1
- package/dist/adapters/entry.mjs +95 -0
- package/dist/adapters/platforms.cjs +311 -0
- package/dist/adapters/platforms.d.ts +2 -2
- package/dist/adapters/platforms.js +8 -8
- package/dist/adapters/platforms.mjs +284 -0
- package/dist/api-HD7KU3BS.mjs +114 -0
- package/dist/api.cjs +145 -0
- package/dist/api.js +1 -1
- package/dist/api.mjs +116 -0
- package/dist/commands/api-HD7KU3BS.mjs +114 -0
- package/dist/commands/api-PBT4QZ34.mjs +16 -0
- package/dist/commands/api-PJL37LV3.mjs +17 -0
- package/dist/commands/build.cjs +877 -0
- package/dist/commands/build.js +6 -6
- package/dist/commands/build.mjs +717 -0
- package/dist/commands/chunk-GFQ4MUIC.mjs +117 -0
- package/dist/commands/chunk-MTM6M3TC.mjs +115 -0
- package/dist/commands/dev.cjs +670 -0
- package/dist/commands/dev.js +3 -3
- package/dist/commands/dev.mjs +512 -0
- package/dist/commands/index.cjs +1145 -0
- package/dist/commands/index.d.ts +1 -1
- package/dist/commands/index.js +1 -1
- package/dist/commands/index.mjs +982 -0
- package/dist/commands/prerender.cjs +404 -0
- package/dist/commands/prerender.d.ts +1 -1
- package/dist/commands/prerender.js +2 -2
- package/dist/commands/prerender.mjs +377 -0
- package/dist/commands/start.cjs +463 -0
- package/dist/commands/start.d.ts +1 -1
- package/dist/commands/start.js +2 -2
- package/dist/commands/start.mjs +428 -0
- package/dist/config.cjs +150 -0
- package/dist/config.d.ts +24 -7
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +8 -6
- package/dist/config.mjs +114 -0
- package/dist/define-config.cjs +32 -0
- package/dist/define-config.d.ts +12 -0
- package/dist/define-config.d.ts.map +1 -0
- package/dist/define-config.js +3 -0
- package/dist/define-config.mjs +7 -0
- package/dist/di.cjs +200 -0
- package/dist/di.d.ts +4 -4
- package/dist/di.d.ts.map +1 -1
- package/dist/di.js +9 -9
- package/dist/di.mjs +169 -0
- package/dist/document.cjs +115 -0
- package/dist/document.d.ts +1 -1
- package/dist/document.js +1 -1
- package/dist/document.mjs +86 -0
- package/dist/generated-api.cjs +43 -0
- package/dist/generated-api.js +1 -1
- package/dist/generated-api.mjs +18 -0
- package/dist/generated-server-fns.cjs +43 -0
- package/dist/generated-server-fns.mjs +18 -0
- package/dist/handler-core.cjs +211 -0
- package/dist/handler-core.d.ts +2 -2
- package/dist/handler-core.js +2 -2
- package/dist/handler-core.mjs +185 -0
- package/dist/handler.cjs +182 -0
- package/dist/handler.d.ts +3 -3
- package/dist/handler.js +3 -3
- package/dist/handler.mjs +152 -0
- package/dist/head.cjs +25 -0
- package/dist/head.d.ts +1 -1
- package/dist/head.js +1 -1
- package/dist/head.mjs +4 -0
- package/dist/image-loader.cjs +37 -0
- package/dist/image-loader.d.ts +25 -0
- package/dist/image-loader.d.ts.map +1 -0
- package/dist/image-loader.js +18 -0
- package/dist/image-loader.mjs +12 -0
- package/dist/image.cjs +151 -0
- package/dist/image.d.ts +2 -19
- package/dist/image.d.ts.map +1 -1
- package/dist/image.js +5 -21
- package/dist/image.mjs +126 -0
- package/dist/index.cjs +303 -0
- package/dist/index.d.ts +28 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +33 -17
- package/dist/index.mjs +262 -0
- package/dist/interceptors.cjs +72 -0
- package/dist/interceptors.js +1 -1
- package/dist/interceptors.mjs +44 -0
- package/dist/internal.cjs +393 -0
- package/dist/internal.d.ts +6 -6
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +23 -15
- package/dist/internal.mjs +223 -0
- package/dist/middleware.cjs +58 -0
- package/dist/middleware.d.ts +3 -3
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +1 -1
- package/dist/middleware.mjs +31 -0
- package/dist/preload.cjs +42 -0
- package/dist/preload.js +1 -1
- package/dist/preload.mjs +18 -0
- package/dist/router.cjs +25 -0
- package/dist/router.d.ts +3 -3
- package/dist/router.js +3 -3
- package/dist/router.mjs +4 -0
- package/dist/server-fn-setup.cjs +46 -0
- package/dist/server-fn-setup.d.ts +1 -1
- package/dist/server-fn-setup.js +2 -2
- package/dist/server-fn-setup.mjs +22 -0
- package/dist/server-fn.cjs +109 -0
- package/dist/server-fn.d.ts +2 -2
- package/dist/server-fn.js +2 -2
- package/dist/server-fn.mjs +79 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/dist/ui.cjs +123 -0
- package/dist/ui.js +1 -1
- package/dist/ui.mjs +93 -0
- package/package.json +63 -25
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/adapters/entry.ts
|
|
21
|
+
var entry_exports = {};
|
|
22
|
+
__export(entry_exports, {
|
|
23
|
+
cloudflareEntry: () => cloudflareEntry,
|
|
24
|
+
netlifyEntry: () => netlifyEntry,
|
|
25
|
+
vercelEntry: () => vercelEntry
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(entry_exports);
|
|
28
|
+
function preamble({ serverEntry, middleware, template, ssr, mountId, routeAssets }) {
|
|
29
|
+
return [
|
|
30
|
+
`import { createHandlerFrom } from '@fluixi/start/handler-core';`,
|
|
31
|
+
`import { createPreloader } from '@fluixi/start/preload';`,
|
|
32
|
+
`import * as server from ${JSON.stringify(serverEntry)};`,
|
|
33
|
+
middleware ? `import * as middleware from ${JSON.stringify(middleware)};` : "",
|
|
34
|
+
``,
|
|
35
|
+
`const template = ${JSON.stringify(template)};`,
|
|
36
|
+
// Inlined for the same reason as the template: this runs as an uploaded file, with
|
|
37
|
+
// no dist/server beside it to read.
|
|
38
|
+
`const routeAssets = ${JSON.stringify(routeAssets)};`,
|
|
39
|
+
`const handler = createHandlerFrom({`,
|
|
40
|
+
` template,`,
|
|
41
|
+
` mod: server,`,
|
|
42
|
+
` cfg: { ssr: ${ssr}, mountId: ${JSON.stringify(mountId)} },`,
|
|
43
|
+
` preload: createPreloader(routeAssets),`,
|
|
44
|
+
middleware ? ` middleware,` : "",
|
|
45
|
+
`});`
|
|
46
|
+
].filter(Boolean).join("\n");
|
|
47
|
+
}
|
|
48
|
+
function cloudflareEntry(source) {
|
|
49
|
+
return `${preamble(source)}
|
|
50
|
+
|
|
51
|
+
const prerendered = new Set(${JSON.stringify(source.prerendered)});
|
|
52
|
+
|
|
53
|
+
export default {
|
|
54
|
+
async fetch(request, env, ctx) {
|
|
55
|
+
// Static first, but only for what is static. ASSETS answers "/" with index.html
|
|
56
|
+
// whether that is a prerendered page or the shell, so asking about every path
|
|
57
|
+
// costs an SSR app its home page. Extension means a file, prerendered means a
|
|
58
|
+
// page, the rest is the handler's: the rule \`withStaticFiles\` already uses.
|
|
59
|
+
const { pathname } = new URL(request.url);
|
|
60
|
+
if (env && env.ASSETS && (prerendered.has(pathname) || /\\.[^/]+$/.test(pathname))) {
|
|
61
|
+
const asset = await env.ASSETS.fetch(request.clone());
|
|
62
|
+
if (asset.status !== 404) return asset;
|
|
63
|
+
}
|
|
64
|
+
return handler(request, { env, ctx });
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
`;
|
|
68
|
+
}
|
|
69
|
+
function netlifyEntry(source) {
|
|
70
|
+
return `${preamble(source)}
|
|
71
|
+
|
|
72
|
+
export default async (request, context) => handler(request, { context });
|
|
73
|
+
|
|
74
|
+
export const config = { path: '/*', preferStatic: true };
|
|
75
|
+
`;
|
|
76
|
+
}
|
|
77
|
+
function vercelEntry(source) {
|
|
78
|
+
return `${preamble(source)}
|
|
79
|
+
|
|
80
|
+
export default async (req, res) => {
|
|
81
|
+
const proto = req.headers['x-forwarded-proto'] || 'https';
|
|
82
|
+
const host = req.headers['x-forwarded-host'] || req.headers.host;
|
|
83
|
+
|
|
84
|
+
const headers = new Headers();
|
|
85
|
+
for (const [name, value] of Object.entries(req.headers)) {
|
|
86
|
+
if (Array.isArray(value)) for (const one of value) headers.append(name, one);
|
|
87
|
+
else if (value !== undefined) headers.set(name, value);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// A body has to be read off the stream before it can be handed to Request; only
|
|
91
|
+
// GET and HEAD are guaranteed not to have one.
|
|
92
|
+
let body;
|
|
93
|
+
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
94
|
+
const chunks = [];
|
|
95
|
+
for await (const chunk of req) chunks.push(chunk);
|
|
96
|
+
if (chunks.length) body = new Blob(chunks);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const response = await handler(
|
|
100
|
+
new Request(new URL(req.url, \`\${proto}://\${host}\`), { method: req.method, headers, body }),
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
res.statusCode = response.status;
|
|
104
|
+
// set-cookie is the one header that legitimately repeats; folding it into one line
|
|
105
|
+
// breaks the cookies.
|
|
106
|
+
for (const [name, value] of response.headers) {
|
|
107
|
+
if (name.toLowerCase() !== 'set-cookie') res.setHeader(name, value);
|
|
108
|
+
}
|
|
109
|
+
const cookies = response.headers.getSetCookie?.() ?? [];
|
|
110
|
+
if (cookies.length) res.setHeader('set-cookie', cookies);
|
|
111
|
+
|
|
112
|
+
if (response.body) for await (const chunk of response.body) res.write(chunk);
|
|
113
|
+
res.end();
|
|
114
|
+
};
|
|
115
|
+
`;
|
|
116
|
+
}
|
|
117
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
118
|
+
0 && (module.exports = {
|
|
119
|
+
cloudflareEntry,
|
|
120
|
+
netlifyEntry,
|
|
121
|
+
vercelEntry
|
|
122
|
+
});
|
package/dist/adapters/entry.js
CHANGED
|
@@ -49,7 +49,7 @@ export default {
|
|
|
49
49
|
// Static first, but only for what is static. ASSETS answers "/" with index.html
|
|
50
50
|
// whether that is a prerendered page or the shell, so asking about every path
|
|
51
51
|
// costs an SSR app its home page. Extension means a file, prerendered means a
|
|
52
|
-
// page, the rest is the handler's
|
|
52
|
+
// page, the rest is the handler's: the rule \`withStaticFiles\` already uses.
|
|
53
53
|
const { pathname } = new URL(request.url);
|
|
54
54
|
if (env && env.ASSETS && (prerendered.has(pathname) || /\\.[^/]+$/.test(pathname))) {
|
|
55
55
|
const asset = await env.ASSETS.fetch(request.clone());
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// src/adapters/entry.ts
|
|
2
|
+
function preamble({ serverEntry, middleware, template, ssr, mountId, routeAssets }) {
|
|
3
|
+
return [
|
|
4
|
+
`import { createHandlerFrom } from '@fluixi/start/handler-core';`,
|
|
5
|
+
`import { createPreloader } from '@fluixi/start/preload';`,
|
|
6
|
+
`import * as server from ${JSON.stringify(serverEntry)};`,
|
|
7
|
+
middleware ? `import * as middleware from ${JSON.stringify(middleware)};` : "",
|
|
8
|
+
``,
|
|
9
|
+
`const template = ${JSON.stringify(template)};`,
|
|
10
|
+
// Inlined for the same reason as the template: this runs as an uploaded file, with
|
|
11
|
+
// no dist/server beside it to read.
|
|
12
|
+
`const routeAssets = ${JSON.stringify(routeAssets)};`,
|
|
13
|
+
`const handler = createHandlerFrom({`,
|
|
14
|
+
` template,`,
|
|
15
|
+
` mod: server,`,
|
|
16
|
+
` cfg: { ssr: ${ssr}, mountId: ${JSON.stringify(mountId)} },`,
|
|
17
|
+
` preload: createPreloader(routeAssets),`,
|
|
18
|
+
middleware ? ` middleware,` : "",
|
|
19
|
+
`});`
|
|
20
|
+
].filter(Boolean).join("\n");
|
|
21
|
+
}
|
|
22
|
+
function cloudflareEntry(source) {
|
|
23
|
+
return `${preamble(source)}
|
|
24
|
+
|
|
25
|
+
const prerendered = new Set(${JSON.stringify(source.prerendered)});
|
|
26
|
+
|
|
27
|
+
export default {
|
|
28
|
+
async fetch(request, env, ctx) {
|
|
29
|
+
// Static first, but only for what is static. ASSETS answers "/" with index.html
|
|
30
|
+
// whether that is a prerendered page or the shell, so asking about every path
|
|
31
|
+
// costs an SSR app its home page. Extension means a file, prerendered means a
|
|
32
|
+
// page, the rest is the handler's: the rule \`withStaticFiles\` already uses.
|
|
33
|
+
const { pathname } = new URL(request.url);
|
|
34
|
+
if (env && env.ASSETS && (prerendered.has(pathname) || /\\.[^/]+$/.test(pathname))) {
|
|
35
|
+
const asset = await env.ASSETS.fetch(request.clone());
|
|
36
|
+
if (asset.status !== 404) return asset;
|
|
37
|
+
}
|
|
38
|
+
return handler(request, { env, ctx });
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
`;
|
|
42
|
+
}
|
|
43
|
+
function netlifyEntry(source) {
|
|
44
|
+
return `${preamble(source)}
|
|
45
|
+
|
|
46
|
+
export default async (request, context) => handler(request, { context });
|
|
47
|
+
|
|
48
|
+
export const config = { path: '/*', preferStatic: true };
|
|
49
|
+
`;
|
|
50
|
+
}
|
|
51
|
+
function vercelEntry(source) {
|
|
52
|
+
return `${preamble(source)}
|
|
53
|
+
|
|
54
|
+
export default async (req, res) => {
|
|
55
|
+
const proto = req.headers['x-forwarded-proto'] || 'https';
|
|
56
|
+
const host = req.headers['x-forwarded-host'] || req.headers.host;
|
|
57
|
+
|
|
58
|
+
const headers = new Headers();
|
|
59
|
+
for (const [name, value] of Object.entries(req.headers)) {
|
|
60
|
+
if (Array.isArray(value)) for (const one of value) headers.append(name, one);
|
|
61
|
+
else if (value !== undefined) headers.set(name, value);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// A body has to be read off the stream before it can be handed to Request; only
|
|
65
|
+
// GET and HEAD are guaranteed not to have one.
|
|
66
|
+
let body;
|
|
67
|
+
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
68
|
+
const chunks = [];
|
|
69
|
+
for await (const chunk of req) chunks.push(chunk);
|
|
70
|
+
if (chunks.length) body = new Blob(chunks);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const response = await handler(
|
|
74
|
+
new Request(new URL(req.url, \`\${proto}://\${host}\`), { method: req.method, headers, body }),
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
res.statusCode = response.status;
|
|
78
|
+
// set-cookie is the one header that legitimately repeats; folding it into one line
|
|
79
|
+
// breaks the cookies.
|
|
80
|
+
for (const [name, value] of response.headers) {
|
|
81
|
+
if (name.toLowerCase() !== 'set-cookie') res.setHeader(name, value);
|
|
82
|
+
}
|
|
83
|
+
const cookies = response.headers.getSetCookie?.() ?? [];
|
|
84
|
+
if (cookies.length) res.setHeader('set-cookie', cookies);
|
|
85
|
+
|
|
86
|
+
if (response.body) for await (const chunk of response.body) res.write(chunk);
|
|
87
|
+
res.end();
|
|
88
|
+
};
|
|
89
|
+
`;
|
|
90
|
+
}
|
|
91
|
+
export {
|
|
92
|
+
cloudflareEntry,
|
|
93
|
+
netlifyEntry,
|
|
94
|
+
vercelEntry
|
|
95
|
+
};
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/adapters/platforms.ts
|
|
21
|
+
var platforms_exports = {};
|
|
22
|
+
__export(platforms_exports, {
|
|
23
|
+
cloudflareAdapter: () => cloudflareAdapter,
|
|
24
|
+
netlifyAdapter: () => netlifyAdapter,
|
|
25
|
+
vercelAdapter: () => vercelAdapter
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(platforms_exports);
|
|
28
|
+
var import_promises2 = require("node:fs/promises");
|
|
29
|
+
var import_node_fs2 = require("node:fs");
|
|
30
|
+
var import_node_path2 = require("node:path");
|
|
31
|
+
|
|
32
|
+
// src/adapter.ts
|
|
33
|
+
var import_promises = require("node:fs/promises");
|
|
34
|
+
var import_node_fs = require("node:fs");
|
|
35
|
+
var import_node_path = require("node:path");
|
|
36
|
+
|
|
37
|
+
// src/document.ts
|
|
38
|
+
var import_head = require("@fluixi/head");
|
|
39
|
+
|
|
40
|
+
// src/preload.ts
|
|
41
|
+
var import_router = require("@fluixi/core/router");
|
|
42
|
+
|
|
43
|
+
// src/adapter.ts
|
|
44
|
+
async function readTemplate(clientDir, serverDir) {
|
|
45
|
+
const stashed = (0, import_node_path.join)(serverDir, "template.html");
|
|
46
|
+
return (0, import_promises.readFile)((0, import_node_fs.existsSync)(stashed) ? stashed : (0, import_node_path.join)(clientDir, "index.html"), "utf-8");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/adapters/entry.ts
|
|
50
|
+
function preamble({ serverEntry, middleware, template, ssr, mountId, routeAssets }) {
|
|
51
|
+
return [
|
|
52
|
+
`import { createHandlerFrom } from '@fluixi/start/handler-core';`,
|
|
53
|
+
`import { createPreloader } from '@fluixi/start/preload';`,
|
|
54
|
+
`import * as server from ${JSON.stringify(serverEntry)};`,
|
|
55
|
+
middleware ? `import * as middleware from ${JSON.stringify(middleware)};` : "",
|
|
56
|
+
``,
|
|
57
|
+
`const template = ${JSON.stringify(template)};`,
|
|
58
|
+
// Inlined for the same reason as the template: this runs as an uploaded file, with
|
|
59
|
+
// no dist/server beside it to read.
|
|
60
|
+
`const routeAssets = ${JSON.stringify(routeAssets)};`,
|
|
61
|
+
`const handler = createHandlerFrom({`,
|
|
62
|
+
` template,`,
|
|
63
|
+
` mod: server,`,
|
|
64
|
+
` cfg: { ssr: ${ssr}, mountId: ${JSON.stringify(mountId)} },`,
|
|
65
|
+
` preload: createPreloader(routeAssets),`,
|
|
66
|
+
middleware ? ` middleware,` : "",
|
|
67
|
+
`});`
|
|
68
|
+
].filter(Boolean).join("\n");
|
|
69
|
+
}
|
|
70
|
+
function cloudflareEntry(source) {
|
|
71
|
+
return `${preamble(source)}
|
|
72
|
+
|
|
73
|
+
const prerendered = new Set(${JSON.stringify(source.prerendered)});
|
|
74
|
+
|
|
75
|
+
export default {
|
|
76
|
+
async fetch(request, env, ctx) {
|
|
77
|
+
// Static first, but only for what is static. ASSETS answers "/" with index.html
|
|
78
|
+
// whether that is a prerendered page or the shell, so asking about every path
|
|
79
|
+
// costs an SSR app its home page. Extension means a file, prerendered means a
|
|
80
|
+
// page, the rest is the handler's: the rule \`withStaticFiles\` already uses.
|
|
81
|
+
const { pathname } = new URL(request.url);
|
|
82
|
+
if (env && env.ASSETS && (prerendered.has(pathname) || /\\.[^/]+$/.test(pathname))) {
|
|
83
|
+
const asset = await env.ASSETS.fetch(request.clone());
|
|
84
|
+
if (asset.status !== 404) return asset;
|
|
85
|
+
}
|
|
86
|
+
return handler(request, { env, ctx });
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
`;
|
|
90
|
+
}
|
|
91
|
+
function netlifyEntry(source) {
|
|
92
|
+
return `${preamble(source)}
|
|
93
|
+
|
|
94
|
+
export default async (request, context) => handler(request, { context });
|
|
95
|
+
|
|
96
|
+
export const config = { path: '/*', preferStatic: true };
|
|
97
|
+
`;
|
|
98
|
+
}
|
|
99
|
+
function vercelEntry(source) {
|
|
100
|
+
return `${preamble(source)}
|
|
101
|
+
|
|
102
|
+
export default async (req, res) => {
|
|
103
|
+
const proto = req.headers['x-forwarded-proto'] || 'https';
|
|
104
|
+
const host = req.headers['x-forwarded-host'] || req.headers.host;
|
|
105
|
+
|
|
106
|
+
const headers = new Headers();
|
|
107
|
+
for (const [name, value] of Object.entries(req.headers)) {
|
|
108
|
+
if (Array.isArray(value)) for (const one of value) headers.append(name, one);
|
|
109
|
+
else if (value !== undefined) headers.set(name, value);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// A body has to be read off the stream before it can be handed to Request; only
|
|
113
|
+
// GET and HEAD are guaranteed not to have one.
|
|
114
|
+
let body;
|
|
115
|
+
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
116
|
+
const chunks = [];
|
|
117
|
+
for await (const chunk of req) chunks.push(chunk);
|
|
118
|
+
if (chunks.length) body = new Blob(chunks);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const response = await handler(
|
|
122
|
+
new Request(new URL(req.url, \`\${proto}://\${host}\`), { method: req.method, headers, body }),
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
res.statusCode = response.status;
|
|
126
|
+
// set-cookie is the one header that legitimately repeats; folding it into one line
|
|
127
|
+
// breaks the cookies.
|
|
128
|
+
for (const [name, value] of response.headers) {
|
|
129
|
+
if (name.toLowerCase() !== 'set-cookie') res.setHeader(name, value);
|
|
130
|
+
}
|
|
131
|
+
const cookies = response.headers.getSetCookie?.() ?? [];
|
|
132
|
+
if (cookies.length) res.setHeader('set-cookie', cookies);
|
|
133
|
+
|
|
134
|
+
if (response.body) for await (const chunk of response.body) res.write(chunk);
|
|
135
|
+
res.end();
|
|
136
|
+
};
|
|
137
|
+
`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// src/adapters/platforms.ts
|
|
141
|
+
async function entrySource(ctx, from, prerendered) {
|
|
142
|
+
const template = await readTemplate(ctx.clientDir, ctx.serverDir);
|
|
143
|
+
const assetFile = (0, import_node_path2.join)(ctx.serverDir, "route-assets.json");
|
|
144
|
+
const routeAssets = (0, import_node_fs2.existsSync)(assetFile) ? JSON.parse(await (0, import_promises2.readFile)(assetFile, "utf-8")) : {};
|
|
145
|
+
const middleware = (0, import_node_path2.join)(ctx.serverDir, "middleware.js");
|
|
146
|
+
const rel = (file) => {
|
|
147
|
+
const path = (0, import_node_path2.join)(ctx.serverDir, file).replace(/\\/g, "/");
|
|
148
|
+
return path.startsWith("/") ? path : `./${path}`;
|
|
149
|
+
};
|
|
150
|
+
return {
|
|
151
|
+
serverEntry: rel("entry-server.js"),
|
|
152
|
+
middleware: (0, import_node_fs2.existsSync)(middleware) ? rel("middleware.js") : null,
|
|
153
|
+
template,
|
|
154
|
+
ssr: ctx.cfg.ssr,
|
|
155
|
+
mountId: ctx.cfg.mountId,
|
|
156
|
+
prerendered,
|
|
157
|
+
routeAssets
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function indexIsPage(template, mountId) {
|
|
161
|
+
const mount = new RegExp(`<([a-z-]+)[^>]*\\sid=["']${mountId}["'][^>]*>`, "i").exec(template);
|
|
162
|
+
if (!mount) return false;
|
|
163
|
+
return !template.slice(mount.index + mount[0].length).trimStart().startsWith("</");
|
|
164
|
+
}
|
|
165
|
+
function skipWhenStatic(ctx, name) {
|
|
166
|
+
if (ctx.hasServer) return false;
|
|
167
|
+
console.log(` fluixi: no server entry — ${name} gets the static output alone.`);
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
async function appendRedirect(clientDir, rule) {
|
|
171
|
+
const file = (0, import_node_path2.join)(clientDir, "_redirects");
|
|
172
|
+
const existing = await (0, import_promises2.readFile)(file, "utf-8").catch(() => "");
|
|
173
|
+
if (existing.includes(rule)) return;
|
|
174
|
+
const separator = existing && !existing.endsWith("\n") ? "\n" : "";
|
|
175
|
+
await (0, import_promises2.writeFile)(file, `${existing}${separator}${rule}
|
|
176
|
+
`);
|
|
177
|
+
}
|
|
178
|
+
var SPA_FALLBACK = "/* /index.html 200";
|
|
179
|
+
var CF_MAX_RULES = 100;
|
|
180
|
+
async function prerenderedRoutes(dir, mountId) {
|
|
181
|
+
const out = [];
|
|
182
|
+
const walk = async (current) => {
|
|
183
|
+
for (const entry of await (0, import_promises2.readdir)(current, { withFileTypes: true })) {
|
|
184
|
+
const path = (0, import_node_path2.join)(current, entry.name);
|
|
185
|
+
if (entry.isDirectory()) {
|
|
186
|
+
if (entry.name === "assets" || entry.name.startsWith(".")) continue;
|
|
187
|
+
await walk(path);
|
|
188
|
+
} else if (entry.name.endsWith(".html")) {
|
|
189
|
+
const rel = (0, import_node_path2.relative)(dir, path).replace(/\\/g, "/").replace(/index\.html$/, "").replace(/\.html$/, "");
|
|
190
|
+
out.push("/" + rel.replace(/\/$/, ""));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
await walk(dir);
|
|
195
|
+
const index = await (0, import_promises2.readFile)((0, import_node_path2.join)(dir, "index.html"), "utf-8").catch(() => "");
|
|
196
|
+
const root = indexIsPage(index, mountId);
|
|
197
|
+
return out.filter((r) => r !== "/" || root).sort();
|
|
198
|
+
}
|
|
199
|
+
var cloudflareAdapter = {
|
|
200
|
+
name: "cloudflare",
|
|
201
|
+
bundle: "inline",
|
|
202
|
+
serve({ handler }) {
|
|
203
|
+
return { fetch: handler };
|
|
204
|
+
},
|
|
205
|
+
async build(ctx) {
|
|
206
|
+
if (skipWhenStatic(ctx, "cloudflare")) return;
|
|
207
|
+
const out = (0, import_node_path2.join)(ctx.clientDir, "_worker.js");
|
|
208
|
+
const prerendered = await prerenderedRoutes(ctx.clientDir, ctx.cfg.mountId);
|
|
209
|
+
await ctx.bundleEntry(cloudflareEntry(await entrySource(ctx, out, prerendered)), out);
|
|
210
|
+
const base = ["/assets/*", "/_worker.js"];
|
|
211
|
+
const room = CF_MAX_RULES - base.length;
|
|
212
|
+
const exclude = [...base, ...prerendered.slice(0, room)];
|
|
213
|
+
if (prerendered.length > room) {
|
|
214
|
+
console.warn(
|
|
215
|
+
` fluixi: ${prerendered.length} prerendered routes exceed Cloudflare's ${CF_MAX_RULES}-rule limit for _routes.json; ${prerendered.length - room} will invoke the worker to serve a static file.`
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
await (0, import_promises2.writeFile)(
|
|
219
|
+
(0, import_node_path2.join)(ctx.clientDir, "_routes.json"),
|
|
220
|
+
JSON.stringify({ version: 1, include: ["/*"], exclude }, null, 2) + "\n"
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
var netlifyAdapter = {
|
|
225
|
+
name: "netlify",
|
|
226
|
+
bundle: "inline",
|
|
227
|
+
serve({ handler }) {
|
|
228
|
+
return { fetch: handler };
|
|
229
|
+
},
|
|
230
|
+
async build(ctx) {
|
|
231
|
+
if (skipWhenStatic(ctx, "netlify")) return appendRedirect(ctx.clientDir, SPA_FALLBACK);
|
|
232
|
+
const dir = (0, import_node_path2.join)(ctx.root, ".netlify", "functions-internal");
|
|
233
|
+
await (0, import_promises2.mkdir)(dir, { recursive: true });
|
|
234
|
+
const out = (0, import_node_path2.join)(dir, "fluixi-server.mjs");
|
|
235
|
+
const prerendered = await prerenderedRoutes(ctx.clientDir, ctx.cfg.mountId);
|
|
236
|
+
await ctx.bundleEntry(netlifyEntry(await entrySource(ctx, out, prerendered)), out);
|
|
237
|
+
await (0, import_promises2.writeFile)(
|
|
238
|
+
(0, import_node_path2.join)(dir, "fluixi-server.json"),
|
|
239
|
+
JSON.stringify({ config: { path: "/*", preferStatic: true }, version: 1 }, null, 2) + "\n"
|
|
240
|
+
);
|
|
241
|
+
if (!prerendered.includes("/")) {
|
|
242
|
+
await appendRedirect(ctx.clientDir, "/ /.netlify/functions/fluixi-server 200!");
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
var vercelAdapter = {
|
|
247
|
+
name: "vercel",
|
|
248
|
+
bundle: "inline",
|
|
249
|
+
serve({ handler }) {
|
|
250
|
+
return { fetch: handler };
|
|
251
|
+
},
|
|
252
|
+
async build(ctx) {
|
|
253
|
+
const output = (0, import_node_path2.join)(ctx.root, ".vercel", "output");
|
|
254
|
+
await (0, import_promises2.mkdir)(output, { recursive: true });
|
|
255
|
+
await (0, import_promises2.cp)(ctx.clientDir, (0, import_node_path2.join)(output, "static"), { recursive: true });
|
|
256
|
+
if (skipWhenStatic(ctx, "vercel")) {
|
|
257
|
+
await (0, import_promises2.writeFile)(
|
|
258
|
+
(0, import_node_path2.join)(output, "config.json"),
|
|
259
|
+
JSON.stringify(
|
|
260
|
+
{
|
|
261
|
+
version: 3,
|
|
262
|
+
// The filesystem phase alone answers 404 for a client route, since nothing
|
|
263
|
+
// is on disk at that path; the catch-all is what makes it a SPA.
|
|
264
|
+
routes: [{ handle: "filesystem" }, { src: "/(.*)", dest: "/index.html" }]
|
|
265
|
+
},
|
|
266
|
+
null,
|
|
267
|
+
2
|
|
268
|
+
) + "\n"
|
|
269
|
+
);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
const fn = (0, import_node_path2.join)(output, "functions", "index.func");
|
|
273
|
+
await (0, import_promises2.mkdir)(fn, { recursive: true });
|
|
274
|
+
const out = (0, import_node_path2.join)(fn, "index.mjs");
|
|
275
|
+
const prerendered = await prerenderedRoutes(ctx.clientDir, ctx.cfg.mountId);
|
|
276
|
+
await ctx.bundleEntry(vercelEntry(await entrySource(ctx, out, prerendered)), out);
|
|
277
|
+
await (0, import_promises2.writeFile)(
|
|
278
|
+
(0, import_node_path2.join)(fn, ".vc-config.json"),
|
|
279
|
+
JSON.stringify(
|
|
280
|
+
{ runtime: "nodejs20.x", handler: "index.mjs", launcherType: "Nodejs", shouldAddHelpers: false },
|
|
281
|
+
null,
|
|
282
|
+
2
|
|
283
|
+
) + "\n"
|
|
284
|
+
);
|
|
285
|
+
await (0, import_promises2.writeFile)(
|
|
286
|
+
(0, import_node_path2.join)(output, "config.json"),
|
|
287
|
+
JSON.stringify(
|
|
288
|
+
{
|
|
289
|
+
version: 3,
|
|
290
|
+
routes: [
|
|
291
|
+
// The filesystem holds a shell at `/index.html`, so `/` has to be claimed
|
|
292
|
+
// before that phase or it is served unrendered. Skipped when `/` really is
|
|
293
|
+
// a prerendered page, which the filesystem should answer.
|
|
294
|
+
...prerendered.includes("/") ? [] : [{ src: "/", dest: "/index" }],
|
|
295
|
+
// A prerendered page or an asset wins; only a miss reaches the function.
|
|
296
|
+
{ handle: "filesystem" },
|
|
297
|
+
{ src: "/(.*)", dest: "/index" }
|
|
298
|
+
]
|
|
299
|
+
},
|
|
300
|
+
null,
|
|
301
|
+
2
|
|
302
|
+
) + "\n"
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
307
|
+
0 && (module.exports = {
|
|
308
|
+
cloudflareAdapter,
|
|
309
|
+
netlifyAdapter,
|
|
310
|
+
vercelAdapter
|
|
311
|
+
});
|
|
@@ -3,7 +3,7 @@ import { type Adapter } from '../adapter.js';
|
|
|
3
3
|
* Cloudflare Pages. `_worker.js` at the root of the published directory takes every
|
|
4
4
|
* request; `_routes.json` is what decides whether a request costs an invocation.
|
|
5
5
|
*
|
|
6
|
-
* In advanced mode the worker sees everything not excluded
|
|
6
|
+
* In advanced mode the worker sees everything not excluded, so a prerendered page
|
|
7
7
|
* would wake it just to proxy the file back through the ASSETS binding. On a plan
|
|
8
8
|
* metered by request that is the whole page view budget spent on static files, so
|
|
9
9
|
* every prerendered path is excluded by name.
|
|
@@ -13,7 +13,7 @@ import { type Adapter } from '../adapter.js';
|
|
|
13
13
|
export declare const cloudflareAdapter: Adapter;
|
|
14
14
|
/**
|
|
15
15
|
* Netlify. The function is a v2 fetch handler under `.netlify/functions-internal/`,
|
|
16
|
-
* which is where a framework
|
|
16
|
+
* which is where a framework: as opposed to the user, puts one, so it cannot
|
|
17
17
|
* collide with the app's own `netlify/functions`.
|
|
18
18
|
*
|
|
19
19
|
* Publish `dist/client`.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Deploy adapters for the three hosts that expect a specific output layout.
|
|
3
3
|
*
|
|
4
4
|
* Each one bundles the same generated entry and then writes whatever the platform
|
|
5
|
-
* reads to route a request. What differs is only that layout
|
|
5
|
+
* reads to route a request. What differs is only that layout, the handler, the
|
|
6
6
|
* dispatch order and the render are shared.
|
|
7
7
|
*
|
|
8
8
|
* All three inline the framework: a worker or a serverless function is a file that
|
|
@@ -40,7 +40,7 @@ async function entrySource(ctx, from, prerendered) {
|
|
|
40
40
|
* Is `dist/client/index.html` a page, or the shell the server renders into?
|
|
41
41
|
*
|
|
42
42
|
* Vite writes the shell at the path a prerendered `/` would occupy, and every one of
|
|
43
|
-
* these platforms serves a matching file before invoking anything
|
|
43
|
+
* these platforms serves a matching file before invoking anything, so `/` is the one
|
|
44
44
|
* route an SSR app cannot render, and the CDN answers it with an empty mount element.
|
|
45
45
|
* No other route is affected: nothing exists at `/about` unless it was prerendered.
|
|
46
46
|
*/
|
|
@@ -59,7 +59,7 @@ function skipWhenStatic(ctx, name) {
|
|
|
59
59
|
}
|
|
60
60
|
/**
|
|
61
61
|
* Add a rule to `_redirects` without taking the file over. Apps ship their own (the
|
|
62
|
-
* docs site redirects `/` to its default locale), so ours goes last
|
|
62
|
+
* docs site redirects `/` to its default locale), so ours goes last, first match
|
|
63
63
|
* wins, so the app's rules are reached first.
|
|
64
64
|
*/
|
|
65
65
|
async function appendRedirect(clientDir, rule) {
|
|
@@ -112,7 +112,7 @@ async function prerenderedRoutes(dir, mountId) {
|
|
|
112
112
|
* Cloudflare Pages. `_worker.js` at the root of the published directory takes every
|
|
113
113
|
* request; `_routes.json` is what decides whether a request costs an invocation.
|
|
114
114
|
*
|
|
115
|
-
* In advanced mode the worker sees everything not excluded
|
|
115
|
+
* In advanced mode the worker sees everything not excluded, so a prerendered page
|
|
116
116
|
* would wake it just to proxy the file back through the ASSETS binding. On a plan
|
|
117
117
|
* metered by request that is the whole page view budget spent on static files, so
|
|
118
118
|
* every prerendered path is excluded by name.
|
|
@@ -133,13 +133,13 @@ export const cloudflareAdapter = {
|
|
|
133
133
|
await ctx.bundleEntry(cloudflareEntry(await entrySource(ctx, out, prerendered)), out);
|
|
134
134
|
// Everything under /assets is content-hashed and immutable, so it never needs the
|
|
135
135
|
// worker. `_worker.js` itself must be excluded or Pages would route it to itself.
|
|
136
|
-
// `/` is excluded only when it was prerendered
|
|
136
|
+
// `/` is excluded only when it was prerendered, otherwise the worker is the only
|
|
137
137
|
// thing that can answer it, and excluding it would serve the shell instead.
|
|
138
138
|
const base = ['/assets/*', '/_worker.js'];
|
|
139
139
|
const room = CF_MAX_RULES - base.length;
|
|
140
140
|
const exclude = [...base, ...prerendered.slice(0, room)];
|
|
141
141
|
if (prerendered.length > room) {
|
|
142
|
-
// Past the cap the remainder still works
|
|
142
|
+
// Past the cap the remainder still works: it just wakes the worker to hand
|
|
143
143
|
// back a file. Worth saying, because the symptom is a bill, not an error.
|
|
144
144
|
console.warn(` fluixi: ${prerendered.length} prerendered routes exceed Cloudflare's ${CF_MAX_RULES}-rule ` +
|
|
145
145
|
`limit for _routes.json; ${prerendered.length - room} will invoke the worker to serve a static file.`);
|
|
@@ -149,7 +149,7 @@ export const cloudflareAdapter = {
|
|
|
149
149
|
};
|
|
150
150
|
/**
|
|
151
151
|
* Netlify. The function is a v2 fetch handler under `.netlify/functions-internal/`,
|
|
152
|
-
* which is where a framework
|
|
152
|
+
* which is where a framework: as opposed to the user, puts one, so it cannot
|
|
153
153
|
* collide with the app's own `netlify/functions`.
|
|
154
154
|
*
|
|
155
155
|
* Publish `dist/client`.
|
|
@@ -173,7 +173,7 @@ export const netlifyAdapter = {
|
|
|
173
173
|
// load an internal function at all.
|
|
174
174
|
await writeFile(join(dir, 'fluixi-server.json'), JSON.stringify({ config: { path: '/*', preferStatic: true }, version: 1 }, null, 2) + '\n');
|
|
175
175
|
// `preferStatic` is what keeps assets, prerendered pages and files like
|
|
176
|
-
// `favicon.ico` off the function
|
|
176
|
+
// `favicon.ico` off the function: and it is also what would hand back the shell
|
|
177
177
|
// for `/`. A forced redirect is the one exception the CDN honours over a file, so
|
|
178
178
|
// it claims exactly that path and nothing else. Appended, so an app's own rules in
|
|
179
179
|
// `public/_redirects` still match first.
|