@fluixi/start 0.1.0-alpha.74 → 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.
Files changed (119) hide show
  1. package/dist/adapter.cjs +5 -3
  2. package/dist/adapter.d.ts +9 -9
  3. package/dist/adapter.d.ts.map +1 -1
  4. package/dist/adapter.js +10 -10
  5. package/dist/adapter.mjs +5 -3
  6. package/dist/adapters/entry.cjs +1 -1
  7. package/dist/adapters/entry.js +1 -1
  8. package/dist/adapters/entry.mjs +1 -1
  9. package/dist/adapters/platforms.cjs +1 -1
  10. package/dist/adapters/platforms.d.ts +2 -2
  11. package/dist/adapters/platforms.js +8 -8
  12. package/dist/adapters/platforms.mjs +1 -1
  13. package/dist/{api-RH6E5UTH.mjs → api-HD7KU3BS.mjs} +1 -1
  14. package/dist/api.cjs +2 -2
  15. package/dist/api.js +1 -1
  16. package/dist/api.mjs +2 -2
  17. package/dist/commands/{api-RH6E5UTH.mjs → api-HD7KU3BS.mjs} +1 -1
  18. package/dist/commands/{api-4IYNQRKG.mjs → api-PBT4QZ34.mjs} +1 -1
  19. package/dist/commands/{api-23MJFSYL.mjs → api-PJL37LV3.mjs} +2 -2
  20. package/dist/commands/build.cjs +13 -6
  21. package/dist/commands/build.js +6 -6
  22. package/dist/commands/build.mjs +13 -6
  23. package/dist/commands/{chunk-OCPCK4ZJ.mjs → chunk-GFQ4MUIC.mjs} +2 -2
  24. package/dist/commands/{chunk-NS5GR2GX.mjs → chunk-MTM6M3TC.mjs} +1 -1
  25. package/dist/commands/dev.cjs +11 -4
  26. package/dist/commands/dev.js +3 -3
  27. package/dist/commands/dev.mjs +12 -5
  28. package/dist/commands/index.cjs +14 -7
  29. package/dist/commands/index.d.ts +1 -1
  30. package/dist/commands/index.js +1 -1
  31. package/dist/commands/index.mjs +15 -8
  32. package/dist/commands/prerender.cjs +2 -0
  33. package/dist/commands/prerender.d.ts +1 -1
  34. package/dist/commands/prerender.js +2 -2
  35. package/dist/commands/prerender.mjs +2 -0
  36. package/dist/commands/start.cjs +2 -0
  37. package/dist/commands/start.d.ts +1 -1
  38. package/dist/commands/start.js +2 -2
  39. package/dist/commands/start.mjs +2 -0
  40. package/dist/config.cjs +7 -1
  41. package/dist/config.d.ts +24 -7
  42. package/dist/config.d.ts.map +1 -1
  43. package/dist/config.js +8 -6
  44. package/dist/config.mjs +7 -1
  45. package/dist/define-config.cjs +32 -0
  46. package/dist/define-config.d.ts +12 -0
  47. package/dist/define-config.d.ts.map +1 -0
  48. package/dist/define-config.js +3 -0
  49. package/dist/define-config.mjs +7 -0
  50. package/dist/di.cjs +1 -1
  51. package/dist/di.d.ts +4 -4
  52. package/dist/di.d.ts.map +1 -1
  53. package/dist/di.js +9 -9
  54. package/dist/di.mjs +1 -1
  55. package/dist/document.d.ts +1 -1
  56. package/dist/document.js +1 -1
  57. package/dist/generated-api.cjs +1 -1
  58. package/dist/generated-api.js +1 -1
  59. package/dist/generated-api.mjs +1 -1
  60. package/dist/generated-server-fns.cjs +1 -1
  61. package/dist/generated-server-fns.mjs +1 -1
  62. package/dist/handler-core.cjs +1 -1
  63. package/dist/handler-core.d.ts +2 -2
  64. package/dist/handler-core.js +2 -2
  65. package/dist/handler-core.mjs +1 -1
  66. package/dist/handler.cjs +1 -0
  67. package/dist/handler.d.ts +3 -3
  68. package/dist/handler.js +3 -3
  69. package/dist/handler.mjs +2 -0
  70. package/dist/head.cjs +1 -1
  71. package/dist/head.d.ts +1 -1
  72. package/dist/head.js +1 -1
  73. package/dist/head.mjs +1 -1
  74. package/dist/image-loader.cjs +37 -0
  75. package/dist/image-loader.d.ts +25 -0
  76. package/dist/image-loader.d.ts.map +1 -0
  77. package/dist/image-loader.js +18 -0
  78. package/dist/image-loader.mjs +12 -0
  79. package/dist/image.cjs +13 -8
  80. package/dist/image.d.ts +2 -19
  81. package/dist/image.d.ts.map +1 -1
  82. package/dist/image.js +5 -21
  83. package/dist/image.mjs +14 -8
  84. package/dist/index.cjs +5 -738
  85. package/dist/index.d.ts +28 -8
  86. package/dist/index.d.ts.map +1 -1
  87. package/dist/index.js +33 -17
  88. package/dist/index.mjs +4 -710
  89. package/dist/interceptors.cjs +1 -0
  90. package/dist/interceptors.js +1 -1
  91. package/dist/interceptors.mjs +2 -0
  92. package/dist/internal.cjs +9 -4
  93. package/dist/internal.d.ts +6 -6
  94. package/dist/internal.d.ts.map +1 -1
  95. package/dist/internal.js +23 -15
  96. package/dist/internal.mjs +9 -4
  97. package/dist/middleware.d.ts +3 -3
  98. package/dist/middleware.d.ts.map +1 -1
  99. package/dist/middleware.js +1 -1
  100. package/dist/preload.cjs +1 -1
  101. package/dist/preload.js +1 -1
  102. package/dist/preload.mjs +1 -1
  103. package/dist/router.cjs +1 -1
  104. package/dist/router.d.ts +3 -3
  105. package/dist/router.js +3 -3
  106. package/dist/router.mjs +1 -1
  107. package/dist/server-fn-setup.cjs +1 -1
  108. package/dist/server-fn-setup.d.ts +1 -1
  109. package/dist/server-fn-setup.js +2 -2
  110. package/dist/server-fn-setup.mjs +1 -1
  111. package/dist/server-fn.cjs +1 -1
  112. package/dist/server-fn.d.ts +2 -2
  113. package/dist/server-fn.js +2 -2
  114. package/dist/server-fn.mjs +1 -1
  115. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  116. package/dist/ui.cjs +1 -1
  117. package/dist/ui.js +1 -1
  118. package/dist/ui.mjs +1 -1
  119. package/package.json +25 -7
package/dist/index.mjs CHANGED
@@ -1,106 +1,9 @@
1
- /*! @fluixi/start v0.1.0-alpha.74 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
1
+ /*! @fluixi/start v0.1.0-alpha.75 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
2
2
 
3
- // src/config.ts
4
- import { pathToFileURL } from "node:url";
5
- import { existsSync } from "node:fs";
6
- import { writeFile, unlink } from "node:fs/promises";
7
- import { resolve, isAbsolute } from "node:path";
3
+ // src/define-config.ts
8
4
  function defineConfig(config) {
9
5
  return config;
10
6
  }
11
- function resolveConfig(config = {}) {
12
- const root = resolve(config.root ?? process.cwd());
13
- return {
14
- root,
15
- ssr: config.ssr ?? true,
16
- port: config.port ?? 3e3,
17
- serverEntry: config.serverEntry ?? "src/entry-server.ts",
18
- clientEntry: config.clientEntry ?? "src/entry-client.ts",
19
- template: config.template ?? "index.html",
20
- mountId: config.mountId ?? "root",
21
- routesDir: config.routesDir ?? "src/routes",
22
- apiDir: config.apiDir ?? "src/api",
23
- middleware: config.middleware ?? "src/middleware.ts",
24
- ssrNoExternal: config.ssrNoExternal ?? [],
25
- resolve: config.resolve ?? [],
26
- adapter: config.adapter,
27
- i18n: resolveI18n(config.i18n),
28
- prerender: resolvePrerender(config.prerender)
29
- };
30
- }
31
- function resolvePrerender(prerender) {
32
- if (!prerender) return false;
33
- const opts = typeof prerender === "object" ? prerender : {};
34
- return {
35
- routes: opts.routes?.length ? opts.routes : ["/"],
36
- crawl: opts.crawl ?? true
37
- };
38
- }
39
- function resolveI18n(i18n) {
40
- if (i18n === false) return false;
41
- const opts = typeof i18n === "object" ? i18n : {};
42
- return {
43
- dir: opts.dir ?? "src/i18n",
44
- defaultLocale: opts.defaultLocale,
45
- locales: opts.locales
46
- };
47
- }
48
- var CONFIG_NAMES = [
49
- "fluixi.config.ts",
50
- "fluixi.config.mts",
51
- "fluixi.config.mjs",
52
- "fluixi.config.js"
53
- ];
54
- async function loadConfig(root = process.cwd()) {
55
- for (const name of CONFIG_NAMES) {
56
- const file = resolve(root, name);
57
- if (existsSync(file)) return loadConfigFile(file);
58
- }
59
- return {};
60
- }
61
- async function loadConfigFile(file) {
62
- if (!/\.[mc]?ts$/.test(file)) {
63
- const mod = await import(
64
- /* @vite-ignore */
65
- pathToFileURL(file).href
66
- );
67
- return mod.default ?? mod;
68
- }
69
- const esbuild = await import("esbuild");
70
- const { outputFiles } = await esbuild.build({
71
- entryPoints: [file],
72
- bundle: true,
73
- write: false,
74
- format: "esm",
75
- platform: "node",
76
- target: `node${process.versions.node.split(".")[0]}`,
77
- sourcemap: "inline",
78
- plugins: [
79
- {
80
- name: "fluixi:externalize-bare",
81
- setup(b) {
82
- b.onResolve({ filter: /.*/ }, (args) => {
83
- if (args.kind === "entry-point") return null;
84
- if (args.path.startsWith(".") || isAbsolute(args.path)) return null;
85
- return { path: args.path, external: true };
86
- });
87
- }
88
- }
89
- ]
90
- });
91
- const tempFile = `${file}.timestamp-${Date.now()}-${Math.random().toString(16).slice(2)}.mjs`;
92
- await writeFile(tempFile, outputFiles[0].text);
93
- try {
94
- const mod = await import(
95
- /* @vite-ignore */
96
- pathToFileURL(tempFile).href
97
- );
98
- return mod.default ?? mod;
99
- } finally {
100
- await unlink(tempFile).catch(() => {
101
- });
102
- }
103
- }
104
7
 
105
8
  // src/index.ts
106
9
  import { html, svg } from "@fluixi/core";
@@ -186,11 +89,6 @@ function guardHandler(handler, onError) {
186
89
  }
187
90
 
188
91
  // src/handler.ts
189
- import { createServer as createHttpServer } from "node:http";
190
- import { stat } from "node:fs/promises";
191
- import { createReadStream } from "node:fs";
192
- import { Readable } from "node:stream";
193
- import { join, extname } from "node:path";
194
92
  function streamDocument(head, body, tail) {
195
93
  const enc = new TextEncoder();
196
94
  const stream = new ReadableStream({
@@ -232,107 +130,6 @@ function createRequestHandler(render) {
232
130
  }
233
131
  };
234
132
  }
235
- function nodeToRequest(req) {
236
- const host = req.headers.host ?? "localhost";
237
- const url = `http://${host}${req.url ?? "/"}`;
238
- const headers = new Headers();
239
- for (const [k, v] of Object.entries(req.headers)) {
240
- if (Array.isArray(v)) v.forEach((x) => headers.append(k, x));
241
- else if (v != null) headers.set(k, v);
242
- }
243
- const method = req.method ?? "GET";
244
- const init = { method, headers };
245
- if (method !== "GET" && method !== "HEAD") {
246
- init.body = req;
247
- init.duplex = "half";
248
- }
249
- return new Request(url, init);
250
- }
251
- async function sendResponse(res, response) {
252
- res.statusCode = response.status;
253
- response.headers.forEach((value, key) => res.setHeader(key, value));
254
- const body = response.body;
255
- if (!body) {
256
- res.end(await response.text().catch(() => ""));
257
- return;
258
- }
259
- const reader = body.getReader();
260
- try {
261
- for (; ; ) {
262
- const { done, value } = await reader.read();
263
- if (done) break;
264
- res.write(value);
265
- }
266
- } finally {
267
- res.end();
268
- }
269
- }
270
- function toNodeHandler(handler) {
271
- return async (req, res) => {
272
- const response = await handler(nodeToRequest(req));
273
- await sendResponse(res, response);
274
- };
275
- }
276
- function serveNode(handler, opts = {}) {
277
- const port = opts.port ?? 3e3;
278
- const node = toNodeHandler(handler);
279
- const server = createHttpServer((req, res) => {
280
- node(req, res).catch((e) => {
281
- res.statusCode = 500;
282
- res.end(String(e?.stack || e));
283
- });
284
- });
285
- server.listen(
286
- port,
287
- () => opts.onListen ? opts.onListen(port) : console.log(` fluixi → http://localhost:${port}`)
288
- );
289
- return server;
290
- }
291
- var MIME = {
292
- ".js": "text/javascript",
293
- ".mjs": "text/javascript",
294
- ".css": "text/css",
295
- ".html": "text/html",
296
- ".json": "application/json",
297
- ".svg": "image/svg+xml",
298
- ".png": "image/png",
299
- ".jpg": "image/jpeg",
300
- ".jpeg": "image/jpeg",
301
- ".webp": "image/webp",
302
- ".ico": "image/x-icon",
303
- ".woff": "font/woff",
304
- ".woff2": "font/woff2",
305
- ".map": "application/json"
306
- };
307
- function withStaticFiles(handler, dir) {
308
- return async (request) => {
309
- const { pathname } = new URL(request.url);
310
- if (pathname !== "/" && extname(pathname)) {
311
- const file = join(dir, decodeURIComponent(pathname));
312
- try {
313
- const s = await stat(file);
314
- if (s.isFile()) {
315
- const body = Readable.toWeb(createReadStream(file));
316
- return new Response(body, {
317
- headers: {
318
- "content-type": MIME[extname(file).toLowerCase()] ?? "application/octet-stream",
319
- "content-length": String(s.size)
320
- }
321
- });
322
- }
323
- } catch {
324
- }
325
- }
326
- return handler(request);
327
- };
328
- }
329
-
330
- // src/adapter.ts
331
- import { readFile as readFile2 } from "node:fs/promises";
332
- import { existsSync as existsSync3, readFileSync } from "node:fs";
333
- import { resolve as resolve2, join as join3, dirname } from "node:path";
334
- import { pathToFileURL as pathToFileURL2 } from "node:url";
335
- import { createRequire } from "node:module";
336
133
 
337
134
  // src/middleware.ts
338
135
  function defineMiddleware(mw) {
@@ -395,385 +192,6 @@ function createHandlerFrom({ template, mod, cfg, middleware, preload }) {
395
192
  return guardHandler(composeMiddleware(normalizeMiddleware(middleware), core));
396
193
  }
397
194
 
398
- // src/preload.ts
399
- import { matchRoutes } from "@fluixi/core/router";
400
- function createPreloader(assets) {
401
- const patterns = Object.keys(assets ?? {});
402
- if (!patterns.length) return () => [];
403
- const routes = patterns.map((path) => ({ path, meta: { path } }));
404
- return (pathname) => {
405
- const matched = matchRoutes(routes, pathname)?.matched;
406
- const leaf = matched?.[matched.length - 1];
407
- const pattern = leaf?.route.meta?.path;
408
- return pattern ? assets[pattern] ?? [] : [];
409
- };
410
- }
411
-
412
- // src/adapters/platforms.ts
413
- import { mkdir, writeFile as writeFile2, readFile, cp, readdir } from "node:fs/promises";
414
- import { existsSync as existsSync2 } from "node:fs";
415
- import { join as join2, relative } from "node:path";
416
-
417
- // src/adapters/entry.ts
418
- function preamble({ serverEntry, middleware, template, ssr, mountId, routeAssets }) {
419
- return [
420
- `import { createHandlerFrom } from '@fluixi/start/handler-core';`,
421
- `import { createPreloader } from '@fluixi/start/preload';`,
422
- `import * as server from ${JSON.stringify(serverEntry)};`,
423
- middleware ? `import * as middleware from ${JSON.stringify(middleware)};` : "",
424
- ``,
425
- `const template = ${JSON.stringify(template)};`,
426
- // Inlined for the same reason as the template: this runs as an uploaded file, with
427
- // no dist/server beside it to read.
428
- `const routeAssets = ${JSON.stringify(routeAssets)};`,
429
- `const handler = createHandlerFrom({`,
430
- ` template,`,
431
- ` mod: server,`,
432
- ` cfg: { ssr: ${ssr}, mountId: ${JSON.stringify(mountId)} },`,
433
- ` preload: createPreloader(routeAssets),`,
434
- middleware ? ` middleware,` : "",
435
- `});`
436
- ].filter(Boolean).join("\n");
437
- }
438
- function cloudflareEntry(source) {
439
- return `${preamble(source)}
440
-
441
- const prerendered = new Set(${JSON.stringify(source.prerendered)});
442
-
443
- export default {
444
- async fetch(request, env, ctx) {
445
- // Static first, but only for what is static. ASSETS answers "/" with index.html
446
- // whether that is a prerendered page or the shell, so asking about every path
447
- // costs an SSR app its home page. Extension means a file, prerendered means a
448
- // page, the rest is the handler's — the rule \`withStaticFiles\` already uses.
449
- const { pathname } = new URL(request.url);
450
- if (env && env.ASSETS && (prerendered.has(pathname) || /\\.[^/]+$/.test(pathname))) {
451
- const asset = await env.ASSETS.fetch(request.clone());
452
- if (asset.status !== 404) return asset;
453
- }
454
- return handler(request, { env, ctx });
455
- },
456
- };
457
- `;
458
- }
459
- function netlifyEntry(source) {
460
- return `${preamble(source)}
461
-
462
- export default async (request, context) => handler(request, { context });
463
-
464
- export const config = { path: '/*', preferStatic: true };
465
- `;
466
- }
467
- function vercelEntry(source) {
468
- return `${preamble(source)}
469
-
470
- export default async (req, res) => {
471
- const proto = req.headers['x-forwarded-proto'] || 'https';
472
- const host = req.headers['x-forwarded-host'] || req.headers.host;
473
-
474
- const headers = new Headers();
475
- for (const [name, value] of Object.entries(req.headers)) {
476
- if (Array.isArray(value)) for (const one of value) headers.append(name, one);
477
- else if (value !== undefined) headers.set(name, value);
478
- }
479
-
480
- // A body has to be read off the stream before it can be handed to Request; only
481
- // GET and HEAD are guaranteed not to have one.
482
- let body;
483
- if (req.method !== 'GET' && req.method !== 'HEAD') {
484
- const chunks = [];
485
- for await (const chunk of req) chunks.push(chunk);
486
- if (chunks.length) body = new Blob(chunks);
487
- }
488
-
489
- const response = await handler(
490
- new Request(new URL(req.url, \`\${proto}://\${host}\`), { method: req.method, headers, body }),
491
- );
492
-
493
- res.statusCode = response.status;
494
- // set-cookie is the one header that legitimately repeats; folding it into one line
495
- // breaks the cookies.
496
- for (const [name, value] of response.headers) {
497
- if (name.toLowerCase() !== 'set-cookie') res.setHeader(name, value);
498
- }
499
- const cookies = response.headers.getSetCookie?.() ?? [];
500
- if (cookies.length) res.setHeader('set-cookie', cookies);
501
-
502
- if (response.body) for await (const chunk of response.body) res.write(chunk);
503
- res.end();
504
- };
505
- `;
506
- }
507
-
508
- // src/adapters/platforms.ts
509
- async function entrySource(ctx, from, prerendered) {
510
- const template = await readTemplate(ctx.clientDir, ctx.serverDir);
511
- const assetFile = join2(ctx.serverDir, "route-assets.json");
512
- const routeAssets = existsSync2(assetFile) ? JSON.parse(await readFile(assetFile, "utf-8")) : {};
513
- const middleware = join2(ctx.serverDir, "middleware.js");
514
- const rel = (file) => {
515
- const path = join2(ctx.serverDir, file).replace(/\\/g, "/");
516
- return path.startsWith("/") ? path : `./${path}`;
517
- };
518
- return {
519
- serverEntry: rel("entry-server.js"),
520
- middleware: existsSync2(middleware) ? rel("middleware.js") : null,
521
- template,
522
- ssr: ctx.cfg.ssr,
523
- mountId: ctx.cfg.mountId,
524
- prerendered,
525
- routeAssets
526
- };
527
- }
528
- function indexIsPage(template, mountId) {
529
- const mount = new RegExp(`<([a-z-]+)[^>]*\\sid=["']${mountId}["'][^>]*>`, "i").exec(template);
530
- if (!mount) return false;
531
- return !template.slice(mount.index + mount[0].length).trimStart().startsWith("</");
532
- }
533
- function skipWhenStatic(ctx, name) {
534
- if (ctx.hasServer) return false;
535
- console.log(` fluixi: no server entry — ${name} gets the static output alone.`);
536
- return true;
537
- }
538
- async function appendRedirect(clientDir, rule) {
539
- const file = join2(clientDir, "_redirects");
540
- const existing = await readFile(file, "utf-8").catch(() => "");
541
- if (existing.includes(rule)) return;
542
- const separator = existing && !existing.endsWith("\n") ? "\n" : "";
543
- await writeFile2(file, `${existing}${separator}${rule}
544
- `);
545
- }
546
- var SPA_FALLBACK = "/* /index.html 200";
547
- var CF_MAX_RULES = 100;
548
- async function prerenderedRoutes(dir, mountId) {
549
- const out = [];
550
- const walk = async (current) => {
551
- for (const entry of await readdir(current, { withFileTypes: true })) {
552
- const path = join2(current, entry.name);
553
- if (entry.isDirectory()) {
554
- if (entry.name === "assets" || entry.name.startsWith(".")) continue;
555
- await walk(path);
556
- } else if (entry.name.endsWith(".html")) {
557
- const rel = relative(dir, path).replace(/\\/g, "/").replace(/index\.html$/, "").replace(/\.html$/, "");
558
- out.push("/" + rel.replace(/\/$/, ""));
559
- }
560
- }
561
- };
562
- await walk(dir);
563
- const index = await readFile(join2(dir, "index.html"), "utf-8").catch(() => "");
564
- const root = indexIsPage(index, mountId);
565
- return out.filter((r) => r !== "/" || root).sort();
566
- }
567
- var cloudflareAdapter = {
568
- name: "cloudflare",
569
- bundle: "inline",
570
- serve({ handler }) {
571
- return { fetch: handler };
572
- },
573
- async build(ctx) {
574
- if (skipWhenStatic(ctx, "cloudflare")) return;
575
- const out = join2(ctx.clientDir, "_worker.js");
576
- const prerendered = await prerenderedRoutes(ctx.clientDir, ctx.cfg.mountId);
577
- await ctx.bundleEntry(cloudflareEntry(await entrySource(ctx, out, prerendered)), out);
578
- const base = ["/assets/*", "/_worker.js"];
579
- const room = CF_MAX_RULES - base.length;
580
- const exclude = [...base, ...prerendered.slice(0, room)];
581
- if (prerendered.length > room) {
582
- console.warn(
583
- ` 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.`
584
- );
585
- }
586
- await writeFile2(
587
- join2(ctx.clientDir, "_routes.json"),
588
- JSON.stringify({ version: 1, include: ["/*"], exclude }, null, 2) + "\n"
589
- );
590
- }
591
- };
592
- var netlifyAdapter = {
593
- name: "netlify",
594
- bundle: "inline",
595
- serve({ handler }) {
596
- return { fetch: handler };
597
- },
598
- async build(ctx) {
599
- if (skipWhenStatic(ctx, "netlify")) return appendRedirect(ctx.clientDir, SPA_FALLBACK);
600
- const dir = join2(ctx.root, ".netlify", "functions-internal");
601
- await mkdir(dir, { recursive: true });
602
- const out = join2(dir, "fluixi-server.mjs");
603
- const prerendered = await prerenderedRoutes(ctx.clientDir, ctx.cfg.mountId);
604
- await ctx.bundleEntry(netlifyEntry(await entrySource(ctx, out, prerendered)), out);
605
- await writeFile2(
606
- join2(dir, "fluixi-server.json"),
607
- JSON.stringify({ config: { path: "/*", preferStatic: true }, version: 1 }, null, 2) + "\n"
608
- );
609
- if (!prerendered.includes("/")) {
610
- await appendRedirect(ctx.clientDir, "/ /.netlify/functions/fluixi-server 200!");
611
- }
612
- }
613
- };
614
- var vercelAdapter = {
615
- name: "vercel",
616
- bundle: "inline",
617
- serve({ handler }) {
618
- return { fetch: handler };
619
- },
620
- async build(ctx) {
621
- const output = join2(ctx.root, ".vercel", "output");
622
- await mkdir(output, { recursive: true });
623
- await cp(ctx.clientDir, join2(output, "static"), { recursive: true });
624
- if (skipWhenStatic(ctx, "vercel")) {
625
- await writeFile2(
626
- join2(output, "config.json"),
627
- JSON.stringify(
628
- {
629
- version: 3,
630
- // The filesystem phase alone answers 404 for a client route, since nothing
631
- // is on disk at that path; the catch-all is what makes it a SPA.
632
- routes: [{ handle: "filesystem" }, { src: "/(.*)", dest: "/index.html" }]
633
- },
634
- null,
635
- 2
636
- ) + "\n"
637
- );
638
- return;
639
- }
640
- const fn = join2(output, "functions", "index.func");
641
- await mkdir(fn, { recursive: true });
642
- const out = join2(fn, "index.mjs");
643
- const prerendered = await prerenderedRoutes(ctx.clientDir, ctx.cfg.mountId);
644
- await ctx.bundleEntry(vercelEntry(await entrySource(ctx, out, prerendered)), out);
645
- await writeFile2(
646
- join2(fn, ".vc-config.json"),
647
- JSON.stringify(
648
- { runtime: "nodejs20.x", handler: "index.mjs", launcherType: "Nodejs", shouldAddHelpers: false },
649
- null,
650
- 2
651
- ) + "\n"
652
- );
653
- await writeFile2(
654
- join2(output, "config.json"),
655
- JSON.stringify(
656
- {
657
- version: 3,
658
- routes: [
659
- // The filesystem holds a shell at `/index.html`, so `/` has to be claimed
660
- // before that phase or it is served unrendered. Skipped when `/` really is
661
- // a prerendered page, which the filesystem should answer.
662
- ...prerendered.includes("/") ? [] : [{ src: "/", dest: "/index" }],
663
- // A prerendered page or an asset wins; only a miss reaches the function.
664
- { handle: "filesystem" },
665
- { src: "/(.*)", dest: "/index" }
666
- ]
667
- },
668
- null,
669
- 2
670
- ) + "\n"
671
- );
672
- }
673
- };
674
-
675
- // src/adapter.ts
676
- async function createProdHandler(config = {}) {
677
- const cfg = resolveConfig(config);
678
- const clientDir = resolve2(cfg.root, "dist/client");
679
- const template = await readTemplate(clientDir, resolve2(cfg.root, "dist/server"));
680
- const serverEntry = resolve2(cfg.root, "dist/server", "entry-server.js");
681
- const mod = existsSync3(serverEntry) ? await import(
682
- /* @vite-ignore */
683
- pathToFileURL2(serverEntry).href
684
- ) : {};
685
- const mwFile = resolve2(cfg.root, "dist/server", "middleware.js");
686
- const middleware = existsSync3(mwFile) ? await import(
687
- /* @vite-ignore */
688
- pathToFileURL2(mwFile).href
689
- ) : void 0;
690
- return createHandlerFrom({ template, mod, cfg, middleware, preload: await loadPreloader(cfg) });
691
- }
692
- async function readTemplate(clientDir, serverDir) {
693
- const stashed = join3(serverDir, "template.html");
694
- return readFile2(existsSync3(stashed) ? stashed : join3(clientDir, "index.html"), "utf-8");
695
- }
696
- async function loadPreloader(cfg) {
697
- const file = resolve2(cfg.root, "dist/server", "route-assets.json");
698
- if (!existsSync3(file)) return void 0;
699
- try {
700
- return createPreloader(JSON.parse(await readFile2(file, "utf-8")));
701
- } catch {
702
- return void 0;
703
- }
704
- }
705
- var nodeAdapter = {
706
- name: "node",
707
- // A Node server runs from the app directory, so node_modules is right there: keep the
708
- // framework as imports instead of copying it into the bundle.
709
- bundle: "external",
710
- async serve({ handler, cfg, clientDir }) {
711
- serveNode(withStaticFiles(handler, clientDir), {
712
- port: cfg.port,
713
- onListen: (p) => console.log(` fluixi start (node) → http://localhost:${p}`)
714
- });
715
- }
716
- };
717
- var webAdapter = {
718
- name: "web",
719
- // An edge/serverless runtime uploads a file, not an install: nothing may be left to
720
- // resolve at runtime, so everything goes in the bundle.
721
- bundle: "inline",
722
- serve({ handler }) {
723
- return { fetch: handler };
724
- }
725
- };
726
- var GENERATED_SUBPATHS = /* @__PURE__ */ new Set(["./routes", "./server-fns", "./api-routes"]);
727
- function ssrBuildOptions(cfg) {
728
- const noExternal = cfg.ssrNoExternal.length ? cfg.ssrNoExternal : void 0;
729
- const mode = cfg.adapter?.bundle;
730
- if (mode === "inline") return { noExternal: true };
731
- if (mode !== "external") return { noExternal };
732
- const external = frameworkDeps(cfg.root).filter((name) => !cfg.ssrNoExternal.includes(name)).flatMap((name) => withSubpaths(cfg.root, name));
733
- if (!external.length) {
734
- console.warn(
735
- ` fluixi: adapter "${cfg.adapter?.name}" builds an externalized server, but no fluixi or @fluixi/* dependency was found in ${join3(cfg.root, "package.json")} — bundling everything.`
736
- );
737
- }
738
- return { noExternal, external: external.length ? external : void 0 };
739
- }
740
- function frameworkDeps(root) {
741
- const pkg = readPackage(join3(root, "package.json"));
742
- if (!pkg) return [];
743
- const declared = { ...pkg.dependencies, ...pkg.peerDependencies, ...pkg.optionalDependencies };
744
- return Object.keys(declared).filter((id) => id === "@fluixi/core" || id.startsWith("@fluixi/"));
745
- }
746
- function withSubpaths(root, name) {
747
- const exports = resolvePackage(root, name)?.exports;
748
- if (!exports || typeof exports !== "object") return [name];
749
- const ids = [name];
750
- for (const key of Object.keys(exports)) {
751
- if (!key.startsWith("./") || key.includes("*")) continue;
752
- if (key === "./package.json" || GENERATED_SUBPATHS.has(key)) continue;
753
- ids.push(`${name}/${key.slice(2)}`);
754
- }
755
- return ids;
756
- }
757
- function resolvePackage(root, name) {
758
- try {
759
- return readPackage(createRequire(join3(root, "index.js")).resolve(`${name}/package.json`));
760
- } catch {
761
- }
762
- for (let dir = root; ; dir = dirname(dir)) {
763
- const pkg = readPackage(join3(dir, "node_modules", name, "package.json"));
764
- if (pkg) return pkg;
765
- if (dirname(dir) === dir) return void 0;
766
- }
767
- }
768
- function readPackage(file) {
769
- if (!existsSync3(file)) return void 0;
770
- try {
771
- return JSON.parse(readFileSync(file, "utf-8"));
772
- } catch {
773
- return void 0;
774
- }
775
- }
776
-
777
195
  // src/server-fn.ts
778
196
  var _fetch = (...a) => fetch(...a);
779
197
  function setServerFnFetch(f) {
@@ -812,114 +230,7 @@ async function fetchWithInterceptors(input, init) {
812
230
  return response;
813
231
  }
814
232
 
815
- // src/image.ts
816
- import { readFile as readFile3 } from "node:fs/promises";
817
- import { resolve as resolvePath, join as join4, extname as extname2, sep } from "node:path";
818
- var DEFAULT_SIZES = [16, 32, 48, 64, 96, 128, 256, 384, 640, 750, 828, 1080, 1200, 1920, 2048, 3840];
819
- var MIME2 = {
820
- ".jpg": "image/jpeg",
821
- ".jpeg": "image/jpeg",
822
- ".png": "image/png",
823
- ".webp": "image/webp",
824
- ".avif": "image/avif",
825
- ".gif": "image/gif",
826
- ".svg": "image/svg+xml"
827
- };
828
- var sharpModule;
829
- async function loadSharp() {
830
- if (sharpModule !== void 0) return sharpModule;
831
- try {
832
- const name = "sharp";
833
- sharpModule = (await import(
834
- /* @vite-ignore */
835
- name
836
- )).default;
837
- } catch {
838
- sharpModule = null;
839
- }
840
- return sharpModule;
841
- }
842
- var defaultTransform = async (input, { width, quality, format }) => {
843
- const sharp = await loadSharp();
844
- if (!sharp) return null;
845
- const pipe = sharp(input).rotate().resize({ width, withoutEnlargement: true });
846
- if (format === "avif") return { data: new Uint8Array(await pipe.avif({ quality }).toBuffer()), contentType: "image/avif" };
847
- if (format === "webp") return { data: new Uint8Array(await pipe.webp({ quality }).toBuffer()), contentType: "image/webp" };
848
- if (format === "png") return { data: new Uint8Array(await pipe.png().toBuffer()), contentType: "image/png" };
849
- return { data: new Uint8Array(await pipe.jpeg({ quality, mozjpeg: true }).toBuffer()), contentType: "image/jpeg" };
850
- };
851
- function imageResponse(body, contentType, maxAge) {
852
- return new Response(body, {
853
- headers: {
854
- "content-type": contentType,
855
- "cache-control": `public, max-age=${maxAge}, immutable`,
856
- vary: "Accept"
857
- }
858
- });
859
- }
860
- function createImageHandler(options = {}) {
861
- const root = resolvePath(options.root ?? join4(process.cwd(), "public"));
862
- const domains = new Set((options.domains ?? []).map((d) => d.toLowerCase()));
863
- const sizes = options.sizes ?? DEFAULT_SIZES;
864
- const maxQuality = options.quality ?? 80;
865
- const maxAge = options.cacheMaxAge ?? 60 * 60 * 24 * 365;
866
- const transform = options.transform ?? defaultTransform;
867
- const cacheMax = options.cache === false ? 0 : typeof options.cache === "number" ? options.cache : 100;
868
- const cache = /* @__PURE__ */ new Map();
869
- return async (request) => {
870
- const url = new URL(request.url);
871
- const src = url.searchParams.get("url") ?? url.searchParams.get("src");
872
- const width = Number(url.searchParams.get("w"));
873
- const quality = Math.min(Math.max(Number(url.searchParams.get("q")) || maxQuality, 1), 100);
874
- if (!src) return new Response('missing "url"', { status: 400 });
875
- if (!Number.isInteger(width) || !sizes.includes(width)) return new Response('invalid "w"', { status: 400 });
876
- const accept = request.headers.get("accept") ?? "";
877
- const format = accept.includes("image/avif") ? "avif" : accept.includes("image/webp") ? "webp" : "original";
878
- const key = `${src}|${width}|${quality}|${format}`;
879
- const hit = cache.get(key);
880
- if (hit) return imageResponse(hit.body, hit.contentType, maxAge);
881
- let input;
882
- let sourceType;
883
- if (/^https?:\/\//i.test(src)) {
884
- let host;
885
- try {
886
- host = new URL(src).hostname.toLowerCase();
887
- } catch {
888
- return new Response("bad url", { status: 400 });
889
- }
890
- if (!domains.has(host)) return new Response("domain not allowed", { status: 403 });
891
- const res = await fetch(src);
892
- if (!res.ok) return new Response("upstream error", { status: 502 });
893
- input = new Uint8Array(await res.arrayBuffer());
894
- sourceType = res.headers.get("content-type") ?? "application/octet-stream";
895
- } else {
896
- const filePath = resolvePath(root, src.startsWith("/") ? src.slice(1) : src);
897
- if (filePath !== root && !filePath.startsWith(root + sep)) return new Response("forbidden", { status: 403 });
898
- try {
899
- input = new Uint8Array(await readFile3(filePath));
900
- } catch {
901
- return new Response("not found", { status: 404 });
902
- }
903
- sourceType = MIME2[extname2(filePath).toLowerCase()] ?? "application/octet-stream";
904
- }
905
- if (sourceType === "image/svg+xml" || sourceType === "image/gif") {
906
- return imageResponse(input, sourceType, maxAge);
907
- }
908
- let out = null;
909
- try {
910
- out = await transform(input, { width, quality, format, contentType: sourceType });
911
- } catch {
912
- out = null;
913
- }
914
- const body = out?.data ?? input;
915
- const contentType = out?.contentType ?? sourceType;
916
- if (cacheMax > 0) {
917
- if (cache.size >= cacheMax) cache.delete(cache.keys().next().value);
918
- cache.set(key, { body, contentType });
919
- }
920
- return imageResponse(body, contentType, maxAge);
921
- };
922
- }
233
+ // src/image-loader.ts
923
234
  function imageLoader(options = {}) {
924
235
  const path = options.path ?? "/api/image";
925
236
  return ({ src, width, quality }) => {
@@ -934,12 +245,8 @@ import { getRequestLocals, getLocals } from "@fluixi/server";
934
245
  export {
935
246
  addInterceptor,
936
247
  clearInterceptors,
937
- cloudflareAdapter,
938
248
  composeMiddleware,
939
249
  createHandlerFrom,
940
- createImageHandler,
941
- createProdHandler,
942
- createRequestHandler,
943
250
  defineConfig,
944
251
  defineMiddleware,
945
252
  fetchWithInterceptors,
@@ -949,20 +256,7 @@ export {
949
256
  imageLoader,
950
257
  injectApp,
951
258
  injectAppAndHead,
952
- loadConfig,
953
- netlifyAdapter,
954
- nodeAdapter,
955
- nodeToRequest,
956
259
  normalizeMiddleware,
957
- resolveConfig,
958
- sendResponse,
959
- serveNode,
960
260
  splitTemplate,
961
- ssrBuildOptions,
962
- streamDocument,
963
- svg,
964
- toNodeHandler,
965
- vercelAdapter,
966
- webAdapter,
967
- withStaticFiles
261
+ svg
968
262
  };