@fluixi/start 0.1.0-alpha.73 → 0.1.0-alpha.74
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 +748 -0
- package/dist/adapter.mjs +717 -0
- package/dist/adapters/entry.cjs +122 -0
- package/dist/adapters/entry.mjs +95 -0
- package/dist/adapters/platforms.cjs +311 -0
- package/dist/adapters/platforms.mjs +284 -0
- package/dist/api-RH6E5UTH.mjs +114 -0
- package/dist/api.cjs +145 -0
- package/dist/api.mjs +116 -0
- package/dist/commands/api-23MJFSYL.mjs +17 -0
- package/dist/commands/api-4IYNQRKG.mjs +16 -0
- package/dist/commands/api-RH6E5UTH.mjs +114 -0
- package/dist/commands/build.cjs +870 -0
- package/dist/commands/build.mjs +710 -0
- package/dist/commands/chunk-NS5GR2GX.mjs +115 -0
- package/dist/commands/chunk-OCPCK4ZJ.mjs +117 -0
- package/dist/commands/dev.cjs +663 -0
- package/dist/commands/dev.mjs +505 -0
- package/dist/commands/index.cjs +1138 -0
- package/dist/commands/index.mjs +975 -0
- package/dist/commands/prerender.cjs +402 -0
- package/dist/commands/prerender.mjs +375 -0
- package/dist/commands/start.cjs +461 -0
- package/dist/commands/start.mjs +426 -0
- package/dist/config.cjs +144 -0
- package/dist/config.mjs +108 -0
- package/dist/di.cjs +200 -0
- package/dist/di.mjs +169 -0
- package/dist/document.cjs +115 -0
- package/dist/document.mjs +86 -0
- package/dist/generated-api.cjs +43 -0
- 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.mjs +185 -0
- package/dist/handler.cjs +181 -0
- package/dist/handler.mjs +150 -0
- package/dist/head.cjs +25 -0
- package/dist/head.mjs +4 -0
- package/dist/image.cjs +146 -0
- package/dist/image.mjs +120 -0
- package/dist/index.cjs +1036 -0
- package/dist/index.mjs +968 -0
- package/dist/interceptors.cjs +71 -0
- package/dist/interceptors.mjs +42 -0
- package/dist/internal.cjs +388 -0
- package/dist/internal.mjs +218 -0
- package/dist/middleware.cjs +58 -0
- package/dist/middleware.mjs +31 -0
- package/dist/preload.cjs +42 -0
- package/dist/preload.mjs +18 -0
- package/dist/router.cjs +25 -0
- package/dist/router.mjs +4 -0
- package/dist/server-fn-setup.cjs +46 -0
- package/dist/server-fn-setup.mjs +22 -0
- package/dist/server-fn.cjs +109 -0
- package/dist/server-fn.mjs +79 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/dist/ui.cjs +123 -0
- package/dist/ui.mjs +93 -0
- package/package.json +45 -25
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1036 @@
|
|
|
1
|
+
/*! @fluixi/start v0.1.0-alpha.74 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
+
|
|
31
|
+
// src/index.ts
|
|
32
|
+
var index_exports = {};
|
|
33
|
+
__export(index_exports, {
|
|
34
|
+
addInterceptor: () => addInterceptor,
|
|
35
|
+
clearInterceptors: () => clearInterceptors,
|
|
36
|
+
cloudflareAdapter: () => cloudflareAdapter,
|
|
37
|
+
composeMiddleware: () => composeMiddleware,
|
|
38
|
+
createHandlerFrom: () => createHandlerFrom,
|
|
39
|
+
createImageHandler: () => createImageHandler,
|
|
40
|
+
createProdHandler: () => createProdHandler,
|
|
41
|
+
createRequestHandler: () => createRequestHandler,
|
|
42
|
+
defineConfig: () => defineConfig,
|
|
43
|
+
defineMiddleware: () => defineMiddleware,
|
|
44
|
+
fetchWithInterceptors: () => fetchWithInterceptors,
|
|
45
|
+
getLocals: () => import_server.getLocals,
|
|
46
|
+
getRequestLocals: () => import_server.getRequestLocals,
|
|
47
|
+
html: () => import_core.html,
|
|
48
|
+
imageLoader: () => imageLoader,
|
|
49
|
+
injectApp: () => injectApp,
|
|
50
|
+
injectAppAndHead: () => injectAppAndHead,
|
|
51
|
+
loadConfig: () => loadConfig,
|
|
52
|
+
netlifyAdapter: () => netlifyAdapter,
|
|
53
|
+
nodeAdapter: () => nodeAdapter,
|
|
54
|
+
nodeToRequest: () => nodeToRequest,
|
|
55
|
+
normalizeMiddleware: () => normalizeMiddleware,
|
|
56
|
+
resolveConfig: () => resolveConfig,
|
|
57
|
+
sendResponse: () => sendResponse,
|
|
58
|
+
serveNode: () => serveNode,
|
|
59
|
+
splitTemplate: () => splitTemplate,
|
|
60
|
+
ssrBuildOptions: () => ssrBuildOptions,
|
|
61
|
+
streamDocument: () => streamDocument,
|
|
62
|
+
svg: () => import_core.svg,
|
|
63
|
+
toNodeHandler: () => toNodeHandler,
|
|
64
|
+
vercelAdapter: () => vercelAdapter,
|
|
65
|
+
webAdapter: () => webAdapter,
|
|
66
|
+
withStaticFiles: () => withStaticFiles
|
|
67
|
+
});
|
|
68
|
+
module.exports = __toCommonJS(index_exports);
|
|
69
|
+
|
|
70
|
+
// src/config.ts
|
|
71
|
+
var import_node_url = require("node:url");
|
|
72
|
+
var import_node_fs = require("node:fs");
|
|
73
|
+
var import_promises = require("node:fs/promises");
|
|
74
|
+
var import_node_path = require("node:path");
|
|
75
|
+
function defineConfig(config) {
|
|
76
|
+
return config;
|
|
77
|
+
}
|
|
78
|
+
function resolveConfig(config = {}) {
|
|
79
|
+
const root = (0, import_node_path.resolve)(config.root ?? process.cwd());
|
|
80
|
+
return {
|
|
81
|
+
root,
|
|
82
|
+
ssr: config.ssr ?? true,
|
|
83
|
+
port: config.port ?? 3e3,
|
|
84
|
+
serverEntry: config.serverEntry ?? "src/entry-server.ts",
|
|
85
|
+
clientEntry: config.clientEntry ?? "src/entry-client.ts",
|
|
86
|
+
template: config.template ?? "index.html",
|
|
87
|
+
mountId: config.mountId ?? "root",
|
|
88
|
+
routesDir: config.routesDir ?? "src/routes",
|
|
89
|
+
apiDir: config.apiDir ?? "src/api",
|
|
90
|
+
middleware: config.middleware ?? "src/middleware.ts",
|
|
91
|
+
ssrNoExternal: config.ssrNoExternal ?? [],
|
|
92
|
+
resolve: config.resolve ?? [],
|
|
93
|
+
adapter: config.adapter,
|
|
94
|
+
i18n: resolveI18n(config.i18n),
|
|
95
|
+
prerender: resolvePrerender(config.prerender)
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function resolvePrerender(prerender) {
|
|
99
|
+
if (!prerender) return false;
|
|
100
|
+
const opts = typeof prerender === "object" ? prerender : {};
|
|
101
|
+
return {
|
|
102
|
+
routes: opts.routes?.length ? opts.routes : ["/"],
|
|
103
|
+
crawl: opts.crawl ?? true
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function resolveI18n(i18n) {
|
|
107
|
+
if (i18n === false) return false;
|
|
108
|
+
const opts = typeof i18n === "object" ? i18n : {};
|
|
109
|
+
return {
|
|
110
|
+
dir: opts.dir ?? "src/i18n",
|
|
111
|
+
defaultLocale: opts.defaultLocale,
|
|
112
|
+
locales: opts.locales
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
var CONFIG_NAMES = [
|
|
116
|
+
"fluixi.config.ts",
|
|
117
|
+
"fluixi.config.mts",
|
|
118
|
+
"fluixi.config.mjs",
|
|
119
|
+
"fluixi.config.js"
|
|
120
|
+
];
|
|
121
|
+
async function loadConfig(root = process.cwd()) {
|
|
122
|
+
for (const name of CONFIG_NAMES) {
|
|
123
|
+
const file = (0, import_node_path.resolve)(root, name);
|
|
124
|
+
if ((0, import_node_fs.existsSync)(file)) return loadConfigFile(file);
|
|
125
|
+
}
|
|
126
|
+
return {};
|
|
127
|
+
}
|
|
128
|
+
async function loadConfigFile(file) {
|
|
129
|
+
if (!/\.[mc]?ts$/.test(file)) {
|
|
130
|
+
const mod = await import(
|
|
131
|
+
/* @vite-ignore */
|
|
132
|
+
(0, import_node_url.pathToFileURL)(file).href
|
|
133
|
+
);
|
|
134
|
+
return mod.default ?? mod;
|
|
135
|
+
}
|
|
136
|
+
const esbuild = await import("esbuild");
|
|
137
|
+
const { outputFiles } = await esbuild.build({
|
|
138
|
+
entryPoints: [file],
|
|
139
|
+
bundle: true,
|
|
140
|
+
write: false,
|
|
141
|
+
format: "esm",
|
|
142
|
+
platform: "node",
|
|
143
|
+
target: `node${process.versions.node.split(".")[0]}`,
|
|
144
|
+
sourcemap: "inline",
|
|
145
|
+
plugins: [
|
|
146
|
+
{
|
|
147
|
+
name: "fluixi:externalize-bare",
|
|
148
|
+
setup(b) {
|
|
149
|
+
b.onResolve({ filter: /.*/ }, (args) => {
|
|
150
|
+
if (args.kind === "entry-point") return null;
|
|
151
|
+
if (args.path.startsWith(".") || (0, import_node_path.isAbsolute)(args.path)) return null;
|
|
152
|
+
return { path: args.path, external: true };
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
]
|
|
157
|
+
});
|
|
158
|
+
const tempFile = `${file}.timestamp-${Date.now()}-${Math.random().toString(16).slice(2)}.mjs`;
|
|
159
|
+
await (0, import_promises.writeFile)(tempFile, outputFiles[0].text);
|
|
160
|
+
try {
|
|
161
|
+
const mod = await import(
|
|
162
|
+
/* @vite-ignore */
|
|
163
|
+
(0, import_node_url.pathToFileURL)(tempFile).href
|
|
164
|
+
);
|
|
165
|
+
return mod.default ?? mod;
|
|
166
|
+
} finally {
|
|
167
|
+
await (0, import_promises.unlink)(tempFile).catch(() => {
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// src/index.ts
|
|
173
|
+
var import_core = require("@fluixi/core");
|
|
174
|
+
|
|
175
|
+
// src/document.ts
|
|
176
|
+
var import_head = require("@fluixi/head");
|
|
177
|
+
function injectApp(template, appHtml, mountId = "root") {
|
|
178
|
+
const mount = new RegExp(`<div id=["']${mountId}["']\\s*>\\s*</div>`);
|
|
179
|
+
if (mount.test(template)) {
|
|
180
|
+
return template.replace(mount, `<div id="${mountId}">${appHtml}</div>`);
|
|
181
|
+
}
|
|
182
|
+
if (template.includes("<!--ssr-outlet-->")) {
|
|
183
|
+
return template.replace("<!--ssr-outlet-->", appHtml);
|
|
184
|
+
}
|
|
185
|
+
return template.replace("</body>", `<div id="${mountId}">${appHtml}</div></body>`);
|
|
186
|
+
}
|
|
187
|
+
function splitTemplate(template, mountId = "root") {
|
|
188
|
+
const mount = new RegExp(`<div id=["']${mountId}["']\\s*>\\s*</div>`);
|
|
189
|
+
const m = template.match(mount);
|
|
190
|
+
if (m && m.index !== void 0) {
|
|
191
|
+
return {
|
|
192
|
+
head: template.slice(0, m.index) + `<div id="${mountId}">`,
|
|
193
|
+
tail: `</div>` + template.slice(m.index + m[0].length)
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
const outlet = template.indexOf("<!--ssr-outlet-->");
|
|
197
|
+
if (outlet !== -1) {
|
|
198
|
+
return {
|
|
199
|
+
head: template.slice(0, outlet),
|
|
200
|
+
tail: template.slice(outlet + "<!--ssr-outlet-->".length)
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
const body = template.indexOf("</body>");
|
|
204
|
+
if (body !== -1) {
|
|
205
|
+
return {
|
|
206
|
+
head: template.slice(0, body) + `<div id="${mountId}">`,
|
|
207
|
+
tail: `</div>` + template.slice(body)
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
return { head: template, tail: "" };
|
|
211
|
+
}
|
|
212
|
+
function injectPreload(template, files) {
|
|
213
|
+
const tags = files.filter((file) => !template.includes(file)).map(
|
|
214
|
+
(file) => file.endsWith(".css") ? `<link rel="stylesheet" href="${file}">` : (
|
|
215
|
+
// `modulepreload` also fetches the chunk's own static imports.
|
|
216
|
+
`<link rel="modulepreload" href="${file}">`
|
|
217
|
+
)
|
|
218
|
+
);
|
|
219
|
+
if (tags.length === 0) return template;
|
|
220
|
+
const head = template.indexOf("</head>");
|
|
221
|
+
if (head === -1) return template;
|
|
222
|
+
return template.slice(0, head) + tags.join("") + template.slice(head);
|
|
223
|
+
}
|
|
224
|
+
function setHtmlAttr(html2, name, value) {
|
|
225
|
+
const existing = new RegExp(`(<html\\b[^>]*?)\\s${name}="[^"]*"`, "i");
|
|
226
|
+
if (existing.test(html2)) return html2.replace(existing, `$1 ${name}="${value}"`);
|
|
227
|
+
return html2.replace(/<html\b/i, `<html ${name}="${value}"`);
|
|
228
|
+
}
|
|
229
|
+
function injectAppAndHead(template, rendered, mountId = "root") {
|
|
230
|
+
const { head, body } = (0, import_head.extractHeadMarker)(rendered);
|
|
231
|
+
let html2 = injectApp(template, body, mountId);
|
|
232
|
+
if (head) {
|
|
233
|
+
if (head.headHtml) {
|
|
234
|
+
if (/<title[\s>]/i.test(head.headHtml)) html2 = html2.replace(/<title>[\s\S]*?<\/title>/i, "");
|
|
235
|
+
html2 = html2.replace("</head>", `${head.headHtml}</head>`);
|
|
236
|
+
}
|
|
237
|
+
for (const [k, v] of Object.entries(head.htmlAttrs)) html2 = setHtmlAttr(html2, k, v);
|
|
238
|
+
}
|
|
239
|
+
return html2;
|
|
240
|
+
}
|
|
241
|
+
function guardHandler(handler, onError) {
|
|
242
|
+
return async (request) => {
|
|
243
|
+
try {
|
|
244
|
+
return await handler(request);
|
|
245
|
+
} catch (e) {
|
|
246
|
+
onError?.(e);
|
|
247
|
+
return new Response(String(e?.stack || e), {
|
|
248
|
+
status: 500,
|
|
249
|
+
headers: { "content-type": "text/plain; charset=utf-8" }
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// src/handler.ts
|
|
256
|
+
var import_node_http = require("node:http");
|
|
257
|
+
var import_promises2 = require("node:fs/promises");
|
|
258
|
+
var import_node_fs2 = require("node:fs");
|
|
259
|
+
var import_node_stream = require("node:stream");
|
|
260
|
+
var import_node_path2 = require("node:path");
|
|
261
|
+
function streamDocument(head, body, tail) {
|
|
262
|
+
const enc = new TextEncoder();
|
|
263
|
+
const stream = new ReadableStream({
|
|
264
|
+
async start(controller) {
|
|
265
|
+
try {
|
|
266
|
+
controller.enqueue(enc.encode(head));
|
|
267
|
+
const reader = body.getReader();
|
|
268
|
+
for (; ; ) {
|
|
269
|
+
const { done, value } = await reader.read();
|
|
270
|
+
if (done) break;
|
|
271
|
+
controller.enqueue(value);
|
|
272
|
+
}
|
|
273
|
+
controller.enqueue(enc.encode(tail));
|
|
274
|
+
controller.close();
|
|
275
|
+
} catch (err) {
|
|
276
|
+
controller.error(err);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
return new Response(stream, {
|
|
281
|
+
status: 200,
|
|
282
|
+
headers: { "content-type": "text/html; charset=utf-8" }
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
function createRequestHandler(render) {
|
|
286
|
+
return async (request) => {
|
|
287
|
+
const url = new URL(request.url);
|
|
288
|
+
try {
|
|
289
|
+
const html2 = await render(url.pathname + url.search, request);
|
|
290
|
+
return new Response(html2, {
|
|
291
|
+
status: 200,
|
|
292
|
+
headers: { "content-type": "text/html; charset=utf-8" }
|
|
293
|
+
});
|
|
294
|
+
} catch (e) {
|
|
295
|
+
return new Response(String(e?.stack || e), {
|
|
296
|
+
status: 500,
|
|
297
|
+
headers: { "content-type": "text/plain; charset=utf-8" }
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
function nodeToRequest(req) {
|
|
303
|
+
const host = req.headers.host ?? "localhost";
|
|
304
|
+
const url = `http://${host}${req.url ?? "/"}`;
|
|
305
|
+
const headers = new Headers();
|
|
306
|
+
for (const [k, v] of Object.entries(req.headers)) {
|
|
307
|
+
if (Array.isArray(v)) v.forEach((x) => headers.append(k, x));
|
|
308
|
+
else if (v != null) headers.set(k, v);
|
|
309
|
+
}
|
|
310
|
+
const method = req.method ?? "GET";
|
|
311
|
+
const init = { method, headers };
|
|
312
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
313
|
+
init.body = req;
|
|
314
|
+
init.duplex = "half";
|
|
315
|
+
}
|
|
316
|
+
return new Request(url, init);
|
|
317
|
+
}
|
|
318
|
+
async function sendResponse(res, response) {
|
|
319
|
+
res.statusCode = response.status;
|
|
320
|
+
response.headers.forEach((value, key) => res.setHeader(key, value));
|
|
321
|
+
const body = response.body;
|
|
322
|
+
if (!body) {
|
|
323
|
+
res.end(await response.text().catch(() => ""));
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
const reader = body.getReader();
|
|
327
|
+
try {
|
|
328
|
+
for (; ; ) {
|
|
329
|
+
const { done, value } = await reader.read();
|
|
330
|
+
if (done) break;
|
|
331
|
+
res.write(value);
|
|
332
|
+
}
|
|
333
|
+
} finally {
|
|
334
|
+
res.end();
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
function toNodeHandler(handler) {
|
|
338
|
+
return async (req, res) => {
|
|
339
|
+
const response = await handler(nodeToRequest(req));
|
|
340
|
+
await sendResponse(res, response);
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
function serveNode(handler, opts = {}) {
|
|
344
|
+
const port = opts.port ?? 3e3;
|
|
345
|
+
const node = toNodeHandler(handler);
|
|
346
|
+
const server = (0, import_node_http.createServer)((req, res) => {
|
|
347
|
+
node(req, res).catch((e) => {
|
|
348
|
+
res.statusCode = 500;
|
|
349
|
+
res.end(String(e?.stack || e));
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
server.listen(
|
|
353
|
+
port,
|
|
354
|
+
() => opts.onListen ? opts.onListen(port) : console.log(` fluixi → http://localhost:${port}`)
|
|
355
|
+
);
|
|
356
|
+
return server;
|
|
357
|
+
}
|
|
358
|
+
var MIME = {
|
|
359
|
+
".js": "text/javascript",
|
|
360
|
+
".mjs": "text/javascript",
|
|
361
|
+
".css": "text/css",
|
|
362
|
+
".html": "text/html",
|
|
363
|
+
".json": "application/json",
|
|
364
|
+
".svg": "image/svg+xml",
|
|
365
|
+
".png": "image/png",
|
|
366
|
+
".jpg": "image/jpeg",
|
|
367
|
+
".jpeg": "image/jpeg",
|
|
368
|
+
".webp": "image/webp",
|
|
369
|
+
".ico": "image/x-icon",
|
|
370
|
+
".woff": "font/woff",
|
|
371
|
+
".woff2": "font/woff2",
|
|
372
|
+
".map": "application/json"
|
|
373
|
+
};
|
|
374
|
+
function withStaticFiles(handler, dir) {
|
|
375
|
+
return async (request) => {
|
|
376
|
+
const { pathname } = new URL(request.url);
|
|
377
|
+
if (pathname !== "/" && (0, import_node_path2.extname)(pathname)) {
|
|
378
|
+
const file = (0, import_node_path2.join)(dir, decodeURIComponent(pathname));
|
|
379
|
+
try {
|
|
380
|
+
const s = await (0, import_promises2.stat)(file);
|
|
381
|
+
if (s.isFile()) {
|
|
382
|
+
const body = import_node_stream.Readable.toWeb((0, import_node_fs2.createReadStream)(file));
|
|
383
|
+
return new Response(body, {
|
|
384
|
+
headers: {
|
|
385
|
+
"content-type": MIME[(0, import_node_path2.extname)(file).toLowerCase()] ?? "application/octet-stream",
|
|
386
|
+
"content-length": String(s.size)
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
} catch {
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
return handler(request);
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// src/adapter.ts
|
|
398
|
+
var import_promises4 = require("node:fs/promises");
|
|
399
|
+
var import_node_fs4 = require("node:fs");
|
|
400
|
+
var import_node_path4 = require("node:path");
|
|
401
|
+
var import_node_url2 = require("node:url");
|
|
402
|
+
var import_node_module = require("node:module");
|
|
403
|
+
|
|
404
|
+
// src/middleware.ts
|
|
405
|
+
function defineMiddleware(mw) {
|
|
406
|
+
return Array.isArray(mw) ? mw.slice() : [mw];
|
|
407
|
+
}
|
|
408
|
+
function normalizeMiddleware(mod) {
|
|
409
|
+
const def = mod?.default ?? mod;
|
|
410
|
+
if (def == null) return [];
|
|
411
|
+
if (Array.isArray(def)) return def.filter((m) => typeof m === "function");
|
|
412
|
+
return typeof def === "function" ? [def] : [];
|
|
413
|
+
}
|
|
414
|
+
function composeMiddleware(middlewares, core) {
|
|
415
|
+
if (middlewares.length === 0) return core;
|
|
416
|
+
return (request) => {
|
|
417
|
+
let lastCalled = -1;
|
|
418
|
+
const dispatch = (i) => {
|
|
419
|
+
if (i <= lastCalled) {
|
|
420
|
+
return Promise.reject(new Error("middleware called next() more than once"));
|
|
421
|
+
}
|
|
422
|
+
lastCalled = i;
|
|
423
|
+
const mw = middlewares[i];
|
|
424
|
+
if (!mw) return Promise.resolve(core(request));
|
|
425
|
+
return Promise.resolve(mw(request, () => dispatch(i + 1)));
|
|
426
|
+
};
|
|
427
|
+
return dispatch(0);
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// src/handler-core.ts
|
|
432
|
+
function createHandlerFrom({ template, mod, cfg, middleware, preload }) {
|
|
433
|
+
const withPreload = (html2, pathname) => preload ? injectPreload(html2, preload(pathname)) : html2;
|
|
434
|
+
let core;
|
|
435
|
+
if (!cfg.ssr) {
|
|
436
|
+
core = createRequestHandler(async (url) => withPreload(template, url.split("?")[0]));
|
|
437
|
+
} else if (typeof mod.renderStream === "function") {
|
|
438
|
+
core = async (request) => {
|
|
439
|
+
const url = new URL(request.url);
|
|
440
|
+
const { head, tail } = splitTemplate(withPreload(template, url.pathname), cfg.mountId);
|
|
441
|
+
const body = await mod.renderStream(url.pathname + url.search, request);
|
|
442
|
+
return streamDocument(head, body, tail);
|
|
443
|
+
};
|
|
444
|
+
} else {
|
|
445
|
+
const render = mod.render ?? mod.default;
|
|
446
|
+
core = createRequestHandler(
|
|
447
|
+
async (url, request) => (
|
|
448
|
+
// `url` here is `pathname + search`; matching only ever wants the path.
|
|
449
|
+
injectAppAndHead(withPreload(template, url.split("?")[0]), await render(url, request), cfg.mountId)
|
|
450
|
+
)
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
if (typeof mod.handleServerFn === "function" && typeof mod.isServerFnRequest === "function") {
|
|
454
|
+
const render = core;
|
|
455
|
+
core = async (request) => mod.isServerFnRequest(request) ? mod.handleServerFn(request) : render(request);
|
|
456
|
+
}
|
|
457
|
+
if (typeof mod.handleApiRequest === "function" && typeof mod.isApiRequest === "function") {
|
|
458
|
+
const next = core;
|
|
459
|
+
core = async (request) => mod.isApiRequest(request) ? mod.handleApiRequest(request) : next(request);
|
|
460
|
+
}
|
|
461
|
+
if (!middleware) return guardHandler(core);
|
|
462
|
+
return guardHandler(composeMiddleware(normalizeMiddleware(middleware), core));
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// src/preload.ts
|
|
466
|
+
var import_router = require("@fluixi/core/router");
|
|
467
|
+
function createPreloader(assets) {
|
|
468
|
+
const patterns = Object.keys(assets ?? {});
|
|
469
|
+
if (!patterns.length) return () => [];
|
|
470
|
+
const routes = patterns.map((path) => ({ path, meta: { path } }));
|
|
471
|
+
return (pathname) => {
|
|
472
|
+
const matched = (0, import_router.matchRoutes)(routes, pathname)?.matched;
|
|
473
|
+
const leaf = matched?.[matched.length - 1];
|
|
474
|
+
const pattern = leaf?.route.meta?.path;
|
|
475
|
+
return pattern ? assets[pattern] ?? [] : [];
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// src/adapters/platforms.ts
|
|
480
|
+
var import_promises3 = require("node:fs/promises");
|
|
481
|
+
var import_node_fs3 = require("node:fs");
|
|
482
|
+
var import_node_path3 = require("node:path");
|
|
483
|
+
|
|
484
|
+
// src/adapters/entry.ts
|
|
485
|
+
function preamble({ serverEntry, middleware, template, ssr, mountId, routeAssets }) {
|
|
486
|
+
return [
|
|
487
|
+
`import { createHandlerFrom } from '@fluixi/start/handler-core';`,
|
|
488
|
+
`import { createPreloader } from '@fluixi/start/preload';`,
|
|
489
|
+
`import * as server from ${JSON.stringify(serverEntry)};`,
|
|
490
|
+
middleware ? `import * as middleware from ${JSON.stringify(middleware)};` : "",
|
|
491
|
+
``,
|
|
492
|
+
`const template = ${JSON.stringify(template)};`,
|
|
493
|
+
// Inlined for the same reason as the template: this runs as an uploaded file, with
|
|
494
|
+
// no dist/server beside it to read.
|
|
495
|
+
`const routeAssets = ${JSON.stringify(routeAssets)};`,
|
|
496
|
+
`const handler = createHandlerFrom({`,
|
|
497
|
+
` template,`,
|
|
498
|
+
` mod: server,`,
|
|
499
|
+
` cfg: { ssr: ${ssr}, mountId: ${JSON.stringify(mountId)} },`,
|
|
500
|
+
` preload: createPreloader(routeAssets),`,
|
|
501
|
+
middleware ? ` middleware,` : "",
|
|
502
|
+
`});`
|
|
503
|
+
].filter(Boolean).join("\n");
|
|
504
|
+
}
|
|
505
|
+
function cloudflareEntry(source) {
|
|
506
|
+
return `${preamble(source)}
|
|
507
|
+
|
|
508
|
+
const prerendered = new Set(${JSON.stringify(source.prerendered)});
|
|
509
|
+
|
|
510
|
+
export default {
|
|
511
|
+
async fetch(request, env, ctx) {
|
|
512
|
+
// Static first, but only for what is static. ASSETS answers "/" with index.html
|
|
513
|
+
// whether that is a prerendered page or the shell, so asking about every path
|
|
514
|
+
// costs an SSR app its home page. Extension means a file, prerendered means a
|
|
515
|
+
// page, the rest is the handler's — the rule \`withStaticFiles\` already uses.
|
|
516
|
+
const { pathname } = new URL(request.url);
|
|
517
|
+
if (env && env.ASSETS && (prerendered.has(pathname) || /\\.[^/]+$/.test(pathname))) {
|
|
518
|
+
const asset = await env.ASSETS.fetch(request.clone());
|
|
519
|
+
if (asset.status !== 404) return asset;
|
|
520
|
+
}
|
|
521
|
+
return handler(request, { env, ctx });
|
|
522
|
+
},
|
|
523
|
+
};
|
|
524
|
+
`;
|
|
525
|
+
}
|
|
526
|
+
function netlifyEntry(source) {
|
|
527
|
+
return `${preamble(source)}
|
|
528
|
+
|
|
529
|
+
export default async (request, context) => handler(request, { context });
|
|
530
|
+
|
|
531
|
+
export const config = { path: '/*', preferStatic: true };
|
|
532
|
+
`;
|
|
533
|
+
}
|
|
534
|
+
function vercelEntry(source) {
|
|
535
|
+
return `${preamble(source)}
|
|
536
|
+
|
|
537
|
+
export default async (req, res) => {
|
|
538
|
+
const proto = req.headers['x-forwarded-proto'] || 'https';
|
|
539
|
+
const host = req.headers['x-forwarded-host'] || req.headers.host;
|
|
540
|
+
|
|
541
|
+
const headers = new Headers();
|
|
542
|
+
for (const [name, value] of Object.entries(req.headers)) {
|
|
543
|
+
if (Array.isArray(value)) for (const one of value) headers.append(name, one);
|
|
544
|
+
else if (value !== undefined) headers.set(name, value);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// A body has to be read off the stream before it can be handed to Request; only
|
|
548
|
+
// GET and HEAD are guaranteed not to have one.
|
|
549
|
+
let body;
|
|
550
|
+
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
551
|
+
const chunks = [];
|
|
552
|
+
for await (const chunk of req) chunks.push(chunk);
|
|
553
|
+
if (chunks.length) body = new Blob(chunks);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
const response = await handler(
|
|
557
|
+
new Request(new URL(req.url, \`\${proto}://\${host}\`), { method: req.method, headers, body }),
|
|
558
|
+
);
|
|
559
|
+
|
|
560
|
+
res.statusCode = response.status;
|
|
561
|
+
// set-cookie is the one header that legitimately repeats; folding it into one line
|
|
562
|
+
// breaks the cookies.
|
|
563
|
+
for (const [name, value] of response.headers) {
|
|
564
|
+
if (name.toLowerCase() !== 'set-cookie') res.setHeader(name, value);
|
|
565
|
+
}
|
|
566
|
+
const cookies = response.headers.getSetCookie?.() ?? [];
|
|
567
|
+
if (cookies.length) res.setHeader('set-cookie', cookies);
|
|
568
|
+
|
|
569
|
+
if (response.body) for await (const chunk of response.body) res.write(chunk);
|
|
570
|
+
res.end();
|
|
571
|
+
};
|
|
572
|
+
`;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// src/adapters/platforms.ts
|
|
576
|
+
async function entrySource(ctx, from, prerendered) {
|
|
577
|
+
const template = await readTemplate(ctx.clientDir, ctx.serverDir);
|
|
578
|
+
const assetFile = (0, import_node_path3.join)(ctx.serverDir, "route-assets.json");
|
|
579
|
+
const routeAssets = (0, import_node_fs3.existsSync)(assetFile) ? JSON.parse(await (0, import_promises3.readFile)(assetFile, "utf-8")) : {};
|
|
580
|
+
const middleware = (0, import_node_path3.join)(ctx.serverDir, "middleware.js");
|
|
581
|
+
const rel = (file) => {
|
|
582
|
+
const path = (0, import_node_path3.join)(ctx.serverDir, file).replace(/\\/g, "/");
|
|
583
|
+
return path.startsWith("/") ? path : `./${path}`;
|
|
584
|
+
};
|
|
585
|
+
return {
|
|
586
|
+
serverEntry: rel("entry-server.js"),
|
|
587
|
+
middleware: (0, import_node_fs3.existsSync)(middleware) ? rel("middleware.js") : null,
|
|
588
|
+
template,
|
|
589
|
+
ssr: ctx.cfg.ssr,
|
|
590
|
+
mountId: ctx.cfg.mountId,
|
|
591
|
+
prerendered,
|
|
592
|
+
routeAssets
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
function indexIsPage(template, mountId) {
|
|
596
|
+
const mount = new RegExp(`<([a-z-]+)[^>]*\\sid=["']${mountId}["'][^>]*>`, "i").exec(template);
|
|
597
|
+
if (!mount) return false;
|
|
598
|
+
return !template.slice(mount.index + mount[0].length).trimStart().startsWith("</");
|
|
599
|
+
}
|
|
600
|
+
function skipWhenStatic(ctx, name) {
|
|
601
|
+
if (ctx.hasServer) return false;
|
|
602
|
+
console.log(` fluixi: no server entry — ${name} gets the static output alone.`);
|
|
603
|
+
return true;
|
|
604
|
+
}
|
|
605
|
+
async function appendRedirect(clientDir, rule) {
|
|
606
|
+
const file = (0, import_node_path3.join)(clientDir, "_redirects");
|
|
607
|
+
const existing = await (0, import_promises3.readFile)(file, "utf-8").catch(() => "");
|
|
608
|
+
if (existing.includes(rule)) return;
|
|
609
|
+
const separator = existing && !existing.endsWith("\n") ? "\n" : "";
|
|
610
|
+
await (0, import_promises3.writeFile)(file, `${existing}${separator}${rule}
|
|
611
|
+
`);
|
|
612
|
+
}
|
|
613
|
+
var SPA_FALLBACK = "/* /index.html 200";
|
|
614
|
+
var CF_MAX_RULES = 100;
|
|
615
|
+
async function prerenderedRoutes(dir, mountId) {
|
|
616
|
+
const out = [];
|
|
617
|
+
const walk = async (current) => {
|
|
618
|
+
for (const entry of await (0, import_promises3.readdir)(current, { withFileTypes: true })) {
|
|
619
|
+
const path = (0, import_node_path3.join)(current, entry.name);
|
|
620
|
+
if (entry.isDirectory()) {
|
|
621
|
+
if (entry.name === "assets" || entry.name.startsWith(".")) continue;
|
|
622
|
+
await walk(path);
|
|
623
|
+
} else if (entry.name.endsWith(".html")) {
|
|
624
|
+
const rel = (0, import_node_path3.relative)(dir, path).replace(/\\/g, "/").replace(/index\.html$/, "").replace(/\.html$/, "");
|
|
625
|
+
out.push("/" + rel.replace(/\/$/, ""));
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
};
|
|
629
|
+
await walk(dir);
|
|
630
|
+
const index = await (0, import_promises3.readFile)((0, import_node_path3.join)(dir, "index.html"), "utf-8").catch(() => "");
|
|
631
|
+
const root = indexIsPage(index, mountId);
|
|
632
|
+
return out.filter((r) => r !== "/" || root).sort();
|
|
633
|
+
}
|
|
634
|
+
var cloudflareAdapter = {
|
|
635
|
+
name: "cloudflare",
|
|
636
|
+
bundle: "inline",
|
|
637
|
+
serve({ handler }) {
|
|
638
|
+
return { fetch: handler };
|
|
639
|
+
},
|
|
640
|
+
async build(ctx) {
|
|
641
|
+
if (skipWhenStatic(ctx, "cloudflare")) return;
|
|
642
|
+
const out = (0, import_node_path3.join)(ctx.clientDir, "_worker.js");
|
|
643
|
+
const prerendered = await prerenderedRoutes(ctx.clientDir, ctx.cfg.mountId);
|
|
644
|
+
await ctx.bundleEntry(cloudflareEntry(await entrySource(ctx, out, prerendered)), out);
|
|
645
|
+
const base = ["/assets/*", "/_worker.js"];
|
|
646
|
+
const room = CF_MAX_RULES - base.length;
|
|
647
|
+
const exclude = [...base, ...prerendered.slice(0, room)];
|
|
648
|
+
if (prerendered.length > room) {
|
|
649
|
+
console.warn(
|
|
650
|
+
` 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.`
|
|
651
|
+
);
|
|
652
|
+
}
|
|
653
|
+
await (0, import_promises3.writeFile)(
|
|
654
|
+
(0, import_node_path3.join)(ctx.clientDir, "_routes.json"),
|
|
655
|
+
JSON.stringify({ version: 1, include: ["/*"], exclude }, null, 2) + "\n"
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
};
|
|
659
|
+
var netlifyAdapter = {
|
|
660
|
+
name: "netlify",
|
|
661
|
+
bundle: "inline",
|
|
662
|
+
serve({ handler }) {
|
|
663
|
+
return { fetch: handler };
|
|
664
|
+
},
|
|
665
|
+
async build(ctx) {
|
|
666
|
+
if (skipWhenStatic(ctx, "netlify")) return appendRedirect(ctx.clientDir, SPA_FALLBACK);
|
|
667
|
+
const dir = (0, import_node_path3.join)(ctx.root, ".netlify", "functions-internal");
|
|
668
|
+
await (0, import_promises3.mkdir)(dir, { recursive: true });
|
|
669
|
+
const out = (0, import_node_path3.join)(dir, "fluixi-server.mjs");
|
|
670
|
+
const prerendered = await prerenderedRoutes(ctx.clientDir, ctx.cfg.mountId);
|
|
671
|
+
await ctx.bundleEntry(netlifyEntry(await entrySource(ctx, out, prerendered)), out);
|
|
672
|
+
await (0, import_promises3.writeFile)(
|
|
673
|
+
(0, import_node_path3.join)(dir, "fluixi-server.json"),
|
|
674
|
+
JSON.stringify({ config: { path: "/*", preferStatic: true }, version: 1 }, null, 2) + "\n"
|
|
675
|
+
);
|
|
676
|
+
if (!prerendered.includes("/")) {
|
|
677
|
+
await appendRedirect(ctx.clientDir, "/ /.netlify/functions/fluixi-server 200!");
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
};
|
|
681
|
+
var vercelAdapter = {
|
|
682
|
+
name: "vercel",
|
|
683
|
+
bundle: "inline",
|
|
684
|
+
serve({ handler }) {
|
|
685
|
+
return { fetch: handler };
|
|
686
|
+
},
|
|
687
|
+
async build(ctx) {
|
|
688
|
+
const output = (0, import_node_path3.join)(ctx.root, ".vercel", "output");
|
|
689
|
+
await (0, import_promises3.mkdir)(output, { recursive: true });
|
|
690
|
+
await (0, import_promises3.cp)(ctx.clientDir, (0, import_node_path3.join)(output, "static"), { recursive: true });
|
|
691
|
+
if (skipWhenStatic(ctx, "vercel")) {
|
|
692
|
+
await (0, import_promises3.writeFile)(
|
|
693
|
+
(0, import_node_path3.join)(output, "config.json"),
|
|
694
|
+
JSON.stringify(
|
|
695
|
+
{
|
|
696
|
+
version: 3,
|
|
697
|
+
// The filesystem phase alone answers 404 for a client route, since nothing
|
|
698
|
+
// is on disk at that path; the catch-all is what makes it a SPA.
|
|
699
|
+
routes: [{ handle: "filesystem" }, { src: "/(.*)", dest: "/index.html" }]
|
|
700
|
+
},
|
|
701
|
+
null,
|
|
702
|
+
2
|
|
703
|
+
) + "\n"
|
|
704
|
+
);
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
const fn = (0, import_node_path3.join)(output, "functions", "index.func");
|
|
708
|
+
await (0, import_promises3.mkdir)(fn, { recursive: true });
|
|
709
|
+
const out = (0, import_node_path3.join)(fn, "index.mjs");
|
|
710
|
+
const prerendered = await prerenderedRoutes(ctx.clientDir, ctx.cfg.mountId);
|
|
711
|
+
await ctx.bundleEntry(vercelEntry(await entrySource(ctx, out, prerendered)), out);
|
|
712
|
+
await (0, import_promises3.writeFile)(
|
|
713
|
+
(0, import_node_path3.join)(fn, ".vc-config.json"),
|
|
714
|
+
JSON.stringify(
|
|
715
|
+
{ runtime: "nodejs20.x", handler: "index.mjs", launcherType: "Nodejs", shouldAddHelpers: false },
|
|
716
|
+
null,
|
|
717
|
+
2
|
|
718
|
+
) + "\n"
|
|
719
|
+
);
|
|
720
|
+
await (0, import_promises3.writeFile)(
|
|
721
|
+
(0, import_node_path3.join)(output, "config.json"),
|
|
722
|
+
JSON.stringify(
|
|
723
|
+
{
|
|
724
|
+
version: 3,
|
|
725
|
+
routes: [
|
|
726
|
+
// The filesystem holds a shell at `/index.html`, so `/` has to be claimed
|
|
727
|
+
// before that phase or it is served unrendered. Skipped when `/` really is
|
|
728
|
+
// a prerendered page, which the filesystem should answer.
|
|
729
|
+
...prerendered.includes("/") ? [] : [{ src: "/", dest: "/index" }],
|
|
730
|
+
// A prerendered page or an asset wins; only a miss reaches the function.
|
|
731
|
+
{ handle: "filesystem" },
|
|
732
|
+
{ src: "/(.*)", dest: "/index" }
|
|
733
|
+
]
|
|
734
|
+
},
|
|
735
|
+
null,
|
|
736
|
+
2
|
|
737
|
+
) + "\n"
|
|
738
|
+
);
|
|
739
|
+
}
|
|
740
|
+
};
|
|
741
|
+
|
|
742
|
+
// src/adapter.ts
|
|
743
|
+
async function createProdHandler(config = {}) {
|
|
744
|
+
const cfg = resolveConfig(config);
|
|
745
|
+
const clientDir = (0, import_node_path4.resolve)(cfg.root, "dist/client");
|
|
746
|
+
const template = await readTemplate(clientDir, (0, import_node_path4.resolve)(cfg.root, "dist/server"));
|
|
747
|
+
const serverEntry = (0, import_node_path4.resolve)(cfg.root, "dist/server", "entry-server.js");
|
|
748
|
+
const mod = (0, import_node_fs4.existsSync)(serverEntry) ? await import(
|
|
749
|
+
/* @vite-ignore */
|
|
750
|
+
(0, import_node_url2.pathToFileURL)(serverEntry).href
|
|
751
|
+
) : {};
|
|
752
|
+
const mwFile = (0, import_node_path4.resolve)(cfg.root, "dist/server", "middleware.js");
|
|
753
|
+
const middleware = (0, import_node_fs4.existsSync)(mwFile) ? await import(
|
|
754
|
+
/* @vite-ignore */
|
|
755
|
+
(0, import_node_url2.pathToFileURL)(mwFile).href
|
|
756
|
+
) : void 0;
|
|
757
|
+
return createHandlerFrom({ template, mod, cfg, middleware, preload: await loadPreloader(cfg) });
|
|
758
|
+
}
|
|
759
|
+
async function readTemplate(clientDir, serverDir) {
|
|
760
|
+
const stashed = (0, import_node_path4.join)(serverDir, "template.html");
|
|
761
|
+
return (0, import_promises4.readFile)((0, import_node_fs4.existsSync)(stashed) ? stashed : (0, import_node_path4.join)(clientDir, "index.html"), "utf-8");
|
|
762
|
+
}
|
|
763
|
+
async function loadPreloader(cfg) {
|
|
764
|
+
const file = (0, import_node_path4.resolve)(cfg.root, "dist/server", "route-assets.json");
|
|
765
|
+
if (!(0, import_node_fs4.existsSync)(file)) return void 0;
|
|
766
|
+
try {
|
|
767
|
+
return createPreloader(JSON.parse(await (0, import_promises4.readFile)(file, "utf-8")));
|
|
768
|
+
} catch {
|
|
769
|
+
return void 0;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
var nodeAdapter = {
|
|
773
|
+
name: "node",
|
|
774
|
+
// A Node server runs from the app directory, so node_modules is right there: keep the
|
|
775
|
+
// framework as imports instead of copying it into the bundle.
|
|
776
|
+
bundle: "external",
|
|
777
|
+
async serve({ handler, cfg, clientDir }) {
|
|
778
|
+
serveNode(withStaticFiles(handler, clientDir), {
|
|
779
|
+
port: cfg.port,
|
|
780
|
+
onListen: (p) => console.log(` fluixi start (node) → http://localhost:${p}`)
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
};
|
|
784
|
+
var webAdapter = {
|
|
785
|
+
name: "web",
|
|
786
|
+
// An edge/serverless runtime uploads a file, not an install: nothing may be left to
|
|
787
|
+
// resolve at runtime, so everything goes in the bundle.
|
|
788
|
+
bundle: "inline",
|
|
789
|
+
serve({ handler }) {
|
|
790
|
+
return { fetch: handler };
|
|
791
|
+
}
|
|
792
|
+
};
|
|
793
|
+
var GENERATED_SUBPATHS = /* @__PURE__ */ new Set(["./routes", "./server-fns", "./api-routes"]);
|
|
794
|
+
function ssrBuildOptions(cfg) {
|
|
795
|
+
const noExternal = cfg.ssrNoExternal.length ? cfg.ssrNoExternal : void 0;
|
|
796
|
+
const mode = cfg.adapter?.bundle;
|
|
797
|
+
if (mode === "inline") return { noExternal: true };
|
|
798
|
+
if (mode !== "external") return { noExternal };
|
|
799
|
+
const external = frameworkDeps(cfg.root).filter((name) => !cfg.ssrNoExternal.includes(name)).flatMap((name) => withSubpaths(cfg.root, name));
|
|
800
|
+
if (!external.length) {
|
|
801
|
+
console.warn(
|
|
802
|
+
` fluixi: adapter "${cfg.adapter?.name}" builds an externalized server, but no fluixi or @fluixi/* dependency was found in ${(0, import_node_path4.join)(cfg.root, "package.json")} — bundling everything.`
|
|
803
|
+
);
|
|
804
|
+
}
|
|
805
|
+
return { noExternal, external: external.length ? external : void 0 };
|
|
806
|
+
}
|
|
807
|
+
function frameworkDeps(root) {
|
|
808
|
+
const pkg = readPackage((0, import_node_path4.join)(root, "package.json"));
|
|
809
|
+
if (!pkg) return [];
|
|
810
|
+
const declared = { ...pkg.dependencies, ...pkg.peerDependencies, ...pkg.optionalDependencies };
|
|
811
|
+
return Object.keys(declared).filter((id) => id === "@fluixi/core" || id.startsWith("@fluixi/"));
|
|
812
|
+
}
|
|
813
|
+
function withSubpaths(root, name) {
|
|
814
|
+
const exports2 = resolvePackage(root, name)?.exports;
|
|
815
|
+
if (!exports2 || typeof exports2 !== "object") return [name];
|
|
816
|
+
const ids = [name];
|
|
817
|
+
for (const key of Object.keys(exports2)) {
|
|
818
|
+
if (!key.startsWith("./") || key.includes("*")) continue;
|
|
819
|
+
if (key === "./package.json" || GENERATED_SUBPATHS.has(key)) continue;
|
|
820
|
+
ids.push(`${name}/${key.slice(2)}`);
|
|
821
|
+
}
|
|
822
|
+
return ids;
|
|
823
|
+
}
|
|
824
|
+
function resolvePackage(root, name) {
|
|
825
|
+
try {
|
|
826
|
+
return readPackage((0, import_node_module.createRequire)((0, import_node_path4.join)(root, "index.js")).resolve(`${name}/package.json`));
|
|
827
|
+
} catch {
|
|
828
|
+
}
|
|
829
|
+
for (let dir = root; ; dir = (0, import_node_path4.dirname)(dir)) {
|
|
830
|
+
const pkg = readPackage((0, import_node_path4.join)(dir, "node_modules", name, "package.json"));
|
|
831
|
+
if (pkg) return pkg;
|
|
832
|
+
if ((0, import_node_path4.dirname)(dir) === dir) return void 0;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
function readPackage(file) {
|
|
836
|
+
if (!(0, import_node_fs4.existsSync)(file)) return void 0;
|
|
837
|
+
try {
|
|
838
|
+
return JSON.parse((0, import_node_fs4.readFileSync)(file, "utf-8"));
|
|
839
|
+
} catch {
|
|
840
|
+
return void 0;
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// src/server-fn.ts
|
|
845
|
+
var _fetch = (...a) => fetch(...a);
|
|
846
|
+
function setServerFnFetch(f) {
|
|
847
|
+
_fetch = f;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// src/interceptors.ts
|
|
851
|
+
var interceptors = [];
|
|
852
|
+
function addInterceptor(interceptor) {
|
|
853
|
+
if (interceptors.length === 0) setServerFnFetch(fetchWithInterceptors);
|
|
854
|
+
interceptors.push(interceptor);
|
|
855
|
+
return () => {
|
|
856
|
+
const i = interceptors.indexOf(interceptor);
|
|
857
|
+
if (i >= 0) interceptors.splice(i, 1);
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
function clearInterceptors() {
|
|
861
|
+
interceptors.length = 0;
|
|
862
|
+
setServerFnFetch(((...a) => fetch(...a)));
|
|
863
|
+
}
|
|
864
|
+
async function fetchWithInterceptors(input, init) {
|
|
865
|
+
let request = input instanceof Request && !init ? input : new Request(input, init);
|
|
866
|
+
for (const i of interceptors) if (i.request) request = await i.request(request);
|
|
867
|
+
let response;
|
|
868
|
+
try {
|
|
869
|
+
response = await fetch(request);
|
|
870
|
+
} catch (error) {
|
|
871
|
+
for (const i of interceptors) {
|
|
872
|
+
if (!i.error) continue;
|
|
873
|
+
const recovered = await i.error(error, request);
|
|
874
|
+
if (recovered) return recovered;
|
|
875
|
+
}
|
|
876
|
+
throw error;
|
|
877
|
+
}
|
|
878
|
+
for (const i of interceptors) if (i.response) response = await i.response(response, request);
|
|
879
|
+
return response;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// src/image.ts
|
|
883
|
+
var import_promises5 = require("node:fs/promises");
|
|
884
|
+
var import_node_path5 = require("node:path");
|
|
885
|
+
var DEFAULT_SIZES = [16, 32, 48, 64, 96, 128, 256, 384, 640, 750, 828, 1080, 1200, 1920, 2048, 3840];
|
|
886
|
+
var MIME2 = {
|
|
887
|
+
".jpg": "image/jpeg",
|
|
888
|
+
".jpeg": "image/jpeg",
|
|
889
|
+
".png": "image/png",
|
|
890
|
+
".webp": "image/webp",
|
|
891
|
+
".avif": "image/avif",
|
|
892
|
+
".gif": "image/gif",
|
|
893
|
+
".svg": "image/svg+xml"
|
|
894
|
+
};
|
|
895
|
+
var sharpModule;
|
|
896
|
+
async function loadSharp() {
|
|
897
|
+
if (sharpModule !== void 0) return sharpModule;
|
|
898
|
+
try {
|
|
899
|
+
const name = "sharp";
|
|
900
|
+
sharpModule = (await import(
|
|
901
|
+
/* @vite-ignore */
|
|
902
|
+
name
|
|
903
|
+
)).default;
|
|
904
|
+
} catch {
|
|
905
|
+
sharpModule = null;
|
|
906
|
+
}
|
|
907
|
+
return sharpModule;
|
|
908
|
+
}
|
|
909
|
+
var defaultTransform = async (input, { width, quality, format }) => {
|
|
910
|
+
const sharp = await loadSharp();
|
|
911
|
+
if (!sharp) return null;
|
|
912
|
+
const pipe = sharp(input).rotate().resize({ width, withoutEnlargement: true });
|
|
913
|
+
if (format === "avif") return { data: new Uint8Array(await pipe.avif({ quality }).toBuffer()), contentType: "image/avif" };
|
|
914
|
+
if (format === "webp") return { data: new Uint8Array(await pipe.webp({ quality }).toBuffer()), contentType: "image/webp" };
|
|
915
|
+
if (format === "png") return { data: new Uint8Array(await pipe.png().toBuffer()), contentType: "image/png" };
|
|
916
|
+
return { data: new Uint8Array(await pipe.jpeg({ quality, mozjpeg: true }).toBuffer()), contentType: "image/jpeg" };
|
|
917
|
+
};
|
|
918
|
+
function imageResponse(body, contentType, maxAge) {
|
|
919
|
+
return new Response(body, {
|
|
920
|
+
headers: {
|
|
921
|
+
"content-type": contentType,
|
|
922
|
+
"cache-control": `public, max-age=${maxAge}, immutable`,
|
|
923
|
+
vary: "Accept"
|
|
924
|
+
}
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
function createImageHandler(options = {}) {
|
|
928
|
+
const root = (0, import_node_path5.resolve)(options.root ?? (0, import_node_path5.join)(process.cwd(), "public"));
|
|
929
|
+
const domains = new Set((options.domains ?? []).map((d) => d.toLowerCase()));
|
|
930
|
+
const sizes = options.sizes ?? DEFAULT_SIZES;
|
|
931
|
+
const maxQuality = options.quality ?? 80;
|
|
932
|
+
const maxAge = options.cacheMaxAge ?? 60 * 60 * 24 * 365;
|
|
933
|
+
const transform = options.transform ?? defaultTransform;
|
|
934
|
+
const cacheMax = options.cache === false ? 0 : typeof options.cache === "number" ? options.cache : 100;
|
|
935
|
+
const cache = /* @__PURE__ */ new Map();
|
|
936
|
+
return async (request) => {
|
|
937
|
+
const url = new URL(request.url);
|
|
938
|
+
const src = url.searchParams.get("url") ?? url.searchParams.get("src");
|
|
939
|
+
const width = Number(url.searchParams.get("w"));
|
|
940
|
+
const quality = Math.min(Math.max(Number(url.searchParams.get("q")) || maxQuality, 1), 100);
|
|
941
|
+
if (!src) return new Response('missing "url"', { status: 400 });
|
|
942
|
+
if (!Number.isInteger(width) || !sizes.includes(width)) return new Response('invalid "w"', { status: 400 });
|
|
943
|
+
const accept = request.headers.get("accept") ?? "";
|
|
944
|
+
const format = accept.includes("image/avif") ? "avif" : accept.includes("image/webp") ? "webp" : "original";
|
|
945
|
+
const key = `${src}|${width}|${quality}|${format}`;
|
|
946
|
+
const hit = cache.get(key);
|
|
947
|
+
if (hit) return imageResponse(hit.body, hit.contentType, maxAge);
|
|
948
|
+
let input;
|
|
949
|
+
let sourceType;
|
|
950
|
+
if (/^https?:\/\//i.test(src)) {
|
|
951
|
+
let host;
|
|
952
|
+
try {
|
|
953
|
+
host = new URL(src).hostname.toLowerCase();
|
|
954
|
+
} catch {
|
|
955
|
+
return new Response("bad url", { status: 400 });
|
|
956
|
+
}
|
|
957
|
+
if (!domains.has(host)) return new Response("domain not allowed", { status: 403 });
|
|
958
|
+
const res = await fetch(src);
|
|
959
|
+
if (!res.ok) return new Response("upstream error", { status: 502 });
|
|
960
|
+
input = new Uint8Array(await res.arrayBuffer());
|
|
961
|
+
sourceType = res.headers.get("content-type") ?? "application/octet-stream";
|
|
962
|
+
} else {
|
|
963
|
+
const filePath = (0, import_node_path5.resolve)(root, src.startsWith("/") ? src.slice(1) : src);
|
|
964
|
+
if (filePath !== root && !filePath.startsWith(root + import_node_path5.sep)) return new Response("forbidden", { status: 403 });
|
|
965
|
+
try {
|
|
966
|
+
input = new Uint8Array(await (0, import_promises5.readFile)(filePath));
|
|
967
|
+
} catch {
|
|
968
|
+
return new Response("not found", { status: 404 });
|
|
969
|
+
}
|
|
970
|
+
sourceType = MIME2[(0, import_node_path5.extname)(filePath).toLowerCase()] ?? "application/octet-stream";
|
|
971
|
+
}
|
|
972
|
+
if (sourceType === "image/svg+xml" || sourceType === "image/gif") {
|
|
973
|
+
return imageResponse(input, sourceType, maxAge);
|
|
974
|
+
}
|
|
975
|
+
let out = null;
|
|
976
|
+
try {
|
|
977
|
+
out = await transform(input, { width, quality, format, contentType: sourceType });
|
|
978
|
+
} catch {
|
|
979
|
+
out = null;
|
|
980
|
+
}
|
|
981
|
+
const body = out?.data ?? input;
|
|
982
|
+
const contentType = out?.contentType ?? sourceType;
|
|
983
|
+
if (cacheMax > 0) {
|
|
984
|
+
if (cache.size >= cacheMax) cache.delete(cache.keys().next().value);
|
|
985
|
+
cache.set(key, { body, contentType });
|
|
986
|
+
}
|
|
987
|
+
return imageResponse(body, contentType, maxAge);
|
|
988
|
+
};
|
|
989
|
+
}
|
|
990
|
+
function imageLoader(options = {}) {
|
|
991
|
+
const path = options.path ?? "/api/image";
|
|
992
|
+
return ({ src, width, quality }) => {
|
|
993
|
+
const params = new URLSearchParams({ url: src, w: String(width) });
|
|
994
|
+
if (quality != null) params.set("q", String(quality));
|
|
995
|
+
return `${path}?${params.toString()}`;
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
// src/index.ts
|
|
1000
|
+
var import_server = require("@fluixi/server");
|
|
1001
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1002
|
+
0 && (module.exports = {
|
|
1003
|
+
addInterceptor,
|
|
1004
|
+
clearInterceptors,
|
|
1005
|
+
cloudflareAdapter,
|
|
1006
|
+
composeMiddleware,
|
|
1007
|
+
createHandlerFrom,
|
|
1008
|
+
createImageHandler,
|
|
1009
|
+
createProdHandler,
|
|
1010
|
+
createRequestHandler,
|
|
1011
|
+
defineConfig,
|
|
1012
|
+
defineMiddleware,
|
|
1013
|
+
fetchWithInterceptors,
|
|
1014
|
+
getLocals,
|
|
1015
|
+
getRequestLocals,
|
|
1016
|
+
html,
|
|
1017
|
+
imageLoader,
|
|
1018
|
+
injectApp,
|
|
1019
|
+
injectAppAndHead,
|
|
1020
|
+
loadConfig,
|
|
1021
|
+
netlifyAdapter,
|
|
1022
|
+
nodeAdapter,
|
|
1023
|
+
nodeToRequest,
|
|
1024
|
+
normalizeMiddleware,
|
|
1025
|
+
resolveConfig,
|
|
1026
|
+
sendResponse,
|
|
1027
|
+
serveNode,
|
|
1028
|
+
splitTemplate,
|
|
1029
|
+
ssrBuildOptions,
|
|
1030
|
+
streamDocument,
|
|
1031
|
+
svg,
|
|
1032
|
+
toNodeHandler,
|
|
1033
|
+
vercelAdapter,
|
|
1034
|
+
webAdapter,
|
|
1035
|
+
withStaticFiles
|
|
1036
|
+
});
|