@farm.js/core 0.1.0-beta.10 → 0.1.0-beta.13

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 (79) hide show
  1. package/dist/{chunk-3QDB3IA6.mjs → chunk-3WIRO3N7.mjs} +2 -2
  2. package/dist/chunk-3WIRO3N7.mjs.map +1 -0
  3. package/dist/chunk-53MVSX3R.mjs +168 -0
  4. package/dist/chunk-53MVSX3R.mjs.map +1 -0
  5. package/dist/{chunk-AW6I64AP.mjs → chunk-6ZXIQSXE.mjs} +16 -1
  6. package/dist/chunk-6ZXIQSXE.mjs.map +1 -0
  7. package/dist/chunk-7ZVN3YZI.mjs +333 -0
  8. package/dist/chunk-7ZVN3YZI.mjs.map +1 -0
  9. package/dist/{chunk-7N62DNSO.mjs → chunk-H3TE3LOV.mjs} +2 -2
  10. package/dist/{chunk-QTZZ2ISQ.mjs → chunk-KLA67WKG.mjs} +1 -1
  11. package/dist/chunk-KLA67WKG.mjs.map +1 -0
  12. package/dist/{chunk-K5ZTUF22.mjs → chunk-L2PE36GQ.mjs} +7 -3
  13. package/dist/chunk-L2PE36GQ.mjs.map +1 -0
  14. package/dist/{chunk-4LND3PXF.mjs → chunk-OPHK6OJN.mjs} +513 -48
  15. package/dist/chunk-OPHK6OJN.mjs.map +1 -0
  16. package/dist/chunk-USLFXFFH.mjs +46 -0
  17. package/dist/chunk-USLFXFFH.mjs.map +1 -0
  18. package/dist/{chunk-DTOWEQRV.mjs → chunk-VSDZEB5M.mjs} +368 -103
  19. package/dist/chunk-VSDZEB5M.mjs.map +1 -0
  20. package/dist/{chunk-E2AI2CW7.mjs → chunk-Z3YG4OZT.mjs} +727 -148
  21. package/dist/chunk-Z3YG4OZT.mjs.map +1 -0
  22. package/dist/client.cjs.map +1 -1
  23. package/dist/client.mjs +1 -1
  24. package/dist/{devtools-A22KUJZT.mjs → devtools-GAZ7F3KL.mjs} +4 -3
  25. package/dist/{devtools-A22KUJZT.mjs.map → devtools-GAZ7F3KL.mjs.map} +1 -1
  26. package/dist/docs.cjs +622 -124
  27. package/dist/docs.cjs.map +1 -1
  28. package/dist/docs.mjs +5 -3
  29. package/dist/font.cjs +19 -2
  30. package/dist/font.cjs.map +1 -1
  31. package/dist/font.mjs +7 -3
  32. package/dist/index.cjs +2208 -681
  33. package/dist/index.cjs.map +1 -1
  34. package/dist/index.mjs +27 -19
  35. package/dist/index.mjs.map +1 -1
  36. package/dist/internal/build-runtime.cjs +2035 -517
  37. package/dist/internal/build-runtime.cjs.map +1 -1
  38. package/dist/internal/build-runtime.mjs +13 -10
  39. package/dist/internal/client-runtime.cjs +169 -2
  40. package/dist/internal/client-runtime.cjs.map +1 -1
  41. package/dist/internal/client-runtime.mjs +170 -2
  42. package/dist/internal/client-runtime.mjs.map +1 -1
  43. package/dist/internal/config-runtime.cjs +22 -0
  44. package/dist/internal/config-runtime.cjs.map +1 -1
  45. package/dist/internal/config-runtime.mjs +3 -2
  46. package/dist/internal/metadata-image-runtime.cjs +199 -0
  47. package/dist/internal/metadata-image-runtime.cjs.map +1 -0
  48. package/dist/internal/metadata-image-runtime.mjs +8 -0
  49. package/dist/internal/metadata-image-runtime.mjs.map +1 -0
  50. package/dist/internal/production-runtime.cjs +310 -0
  51. package/dist/internal/production-runtime.cjs.map +1 -1
  52. package/dist/internal/production-runtime.mjs +10 -0
  53. package/dist/internal/production-runtime.mjs.map +1 -1
  54. package/dist/navigation.cjs.map +1 -1
  55. package/dist/navigation.mjs +2 -2
  56. package/dist/plugin.cjs +1523 -340
  57. package/dist/plugin.cjs.map +1 -1
  58. package/dist/plugin.mjs +9 -5
  59. package/dist/server.cjs +1636 -411
  60. package/dist/server.cjs.map +1 -1
  61. package/dist/server.mjs +17 -19
  62. package/dist/server.mjs.map +1 -1
  63. package/dist/version.cjs +1 -1
  64. package/dist/version.cjs.map +1 -1
  65. package/dist/version.mjs +1 -1
  66. package/dist/vite.cjs +1577 -350
  67. package/dist/vite.cjs.map +1 -1
  68. package/dist/vite.mjs +9 -5
  69. package/package.json +10 -1
  70. package/dist/chunk-3QDB3IA6.mjs.map +0 -1
  71. package/dist/chunk-4LND3PXF.mjs.map +0 -1
  72. package/dist/chunk-AW6I64AP.mjs.map +0 -1
  73. package/dist/chunk-DTOWEQRV.mjs.map +0 -1
  74. package/dist/chunk-E2AI2CW7.mjs.map +0 -1
  75. package/dist/chunk-K5ZTUF22.mjs.map +0 -1
  76. package/dist/chunk-QM746KUO.mjs +0 -56
  77. package/dist/chunk-QM746KUO.mjs.map +0 -1
  78. package/dist/chunk-QTZZ2ISQ.mjs.map +0 -1
  79. /package/dist/{chunk-7N62DNSO.mjs.map → chunk-H3TE3LOV.mjs.map} +0 -0
@@ -6,6 +6,12 @@ import {
6
6
  mergeMetadata,
7
7
  renderMetadataHead
8
8
  } from "./chunk-FRPWCMMG.mjs";
9
+ import {
10
+ createFarmMetadataImageResponse
11
+ } from "./chunk-53MVSX3R.mjs";
12
+ import {
13
+ resolveFarmLayoutFonts
14
+ } from "./chunk-6ZXIQSXE.mjs";
9
15
  import {
10
16
  farmEnvironmentFunctionsPlugin
11
17
  } from "./chunk-V6WE6JSZ.mjs";
@@ -24,9 +30,18 @@ import {
24
30
  isFarmNotFoundError,
25
31
  isFarmRedirectError
26
32
  } from "./chunk-7HO6AEOP.mjs";
33
+ import {
34
+ resolveFarmDocsFontAssets,
35
+ toFarmDocsPublicFontAssets
36
+ } from "./chunk-USLFXFFH.mjs";
27
37
  import {
28
38
  resolveFarmAuthConfig
29
39
  } from "./chunk-I4CUBGVF.mjs";
40
+ import {
41
+ FARM_DEVTOOLS_LAUNCH_PARAM,
42
+ FARM_DEVTOOLS_PATH,
43
+ resolveFarmDevtoolsConfig
44
+ } from "./chunk-7MABZJIM.mjs";
30
45
  import {
31
46
  normalizeRouteRules
32
47
  } from "./chunk-YW7OBE6Q.mjs";
@@ -36,10 +51,8 @@ import {
36
51
  getFarmSourceRoots
37
52
  } from "./chunk-74YTBPTY.mjs";
38
53
  import {
39
- FARM_DEVTOOLS_LAUNCH_PARAM,
40
- FARM_DEVTOOLS_PATH,
41
- resolveFarmDevtoolsConfig
42
- } from "./chunk-7MABZJIM.mjs";
54
+ resolveFarmPerformanceConfig
55
+ } from "./chunk-7ZVN3YZI.mjs";
43
56
  import {
44
57
  getPublicFarmImageConfig,
45
58
  resolveFarmImageConfig
@@ -147,7 +160,7 @@ import {
147
160
  } from "./chunk-RHIFZZUI.mjs";
148
161
  import {
149
162
  FARM_VERSION
150
- } from "./chunk-3QDB3IA6.mjs";
163
+ } from "./chunk-3WIRO3N7.mjs";
151
164
  import {
152
165
  FARM_DEPLOYMENT_ID_HEADER,
153
166
  createFarmDeploymentCookie,
@@ -185,6 +198,22 @@ import { readFile } from "fs/promises";
185
198
  var OPTIONAL_CATCH_ALL_SEGMENT = /^\[\[\.\.\.([^\]]+)\]\]$/;
186
199
  var REQUIRED_CATCH_ALL_SEGMENT = /^\[\.\.\.([^\]]+)\]$/;
187
200
  var REQUIRED_DYNAMIC_SEGMENT = /^\[([^\]]+)\]$/;
201
+ var REQUEST_API_LABELS = /* @__PURE__ */ new Map([
202
+ ["cookies", "request cookies"],
203
+ ["headers", "request headers"],
204
+ ["getCurrentRequest", "the current Request"],
205
+ ["useSearchParams", "URL search parameters"],
206
+ ["auth", "request authentication"],
207
+ ["getServerSession", "request authentication"],
208
+ ["getSession", "request authentication"],
209
+ ["currentUser", "request authentication"]
210
+ ]);
211
+ var ROUTE_PROP_LABELS = /* @__PURE__ */ new Map([
212
+ ["searchParams", "URL search parameters"],
213
+ ["middleware", "middleware request data"],
214
+ ["middlewareData", "middleware request data"],
215
+ ["context", "request context"]
216
+ ]);
188
217
  function resolveRouteRenderingConfig(mod, source) {
189
218
  const directiveConfig = parseRouteRenderingDirective(source);
190
219
  let ssg = directiveConfig?.ssg ?? false;
@@ -237,6 +266,221 @@ async function resolveRouteRenderingConfigFromFile(mod, filePath) {
237
266
  return resolveRouteRenderingConfig(mod, source);
238
267
  }
239
268
  __name(resolveRouteRenderingConfigFromFile, "resolveRouteRenderingConfigFromFile");
269
+ function analyzeStaticRouteCandidate(mod, source, options = {}) {
270
+ const blockers = [];
271
+ if (!source) {
272
+ blockers.push("route source is unavailable");
273
+ }
274
+ if (options.isDynamic) {
275
+ blockers.push("the route has dynamic path segments");
276
+ }
277
+ if (hasExplicitRenderingConfig(mod, source)) {
278
+ blockers.push("the route already has explicit rendering config");
279
+ }
280
+ if (source) {
281
+ blockers.push(...findRequestBoundSourceBlockers(source));
282
+ }
283
+ return {
284
+ candidate: blockers.length === 0,
285
+ blockers
286
+ };
287
+ }
288
+ __name(analyzeStaticRouteCandidate, "analyzeStaticRouteCandidate");
289
+ function findRequestBoundSourceBlockers(source) {
290
+ const blockers = /* @__PURE__ */ new Set();
291
+ const code = stripCommentsAndLiteralContents(source);
292
+ const sourceWithoutComments = stripComments(source);
293
+ for (const binding of readRequestApiImportBindings(sourceWithoutComments)) {
294
+ if (new RegExp(`\\b${escapeRegExp(binding.local)}\\s*(?:\\?\\.\\s*)?\\(`).test(code)) {
295
+ blockers.add(`the route reads ${binding.label}`);
296
+ }
297
+ }
298
+ for (const namespace of readNamespaceImportBindings(sourceWithoutComments)) {
299
+ for (const [name, label] of REQUEST_API_LABELS) {
300
+ if (new RegExp(
301
+ `\\b${escapeRegExp(namespace)}\\s*(?:\\?\\.\\s*|\\.\\s*)${name}\\s*(?:\\?\\.\\s*)?\\(`
302
+ ).test(code)) {
303
+ blockers.add(`the route reads ${label}`);
304
+ }
305
+ }
306
+ }
307
+ const params = readDefaultRouteParameters(code);
308
+ if (params) {
309
+ for (const [name, label] of ROUTE_PROP_LABELS) {
310
+ if (new RegExp(`\\b${name}\\b`).test(params)) {
311
+ blockers.add(`the route reads ${label}`);
312
+ }
313
+ }
314
+ const propsName = params.match(/^\s*([A-Za-z_$][\w$]*)\s*(?::|,|$)/)?.[1];
315
+ if (propsName) {
316
+ for (const [name, label] of ROUTE_PROP_LABELS) {
317
+ if (new RegExp(`\\b${escapeRegExp(propsName)}\\s*\\.\\s*${name}\\b`).test(code)) {
318
+ blockers.add(`the route reads ${label}`);
319
+ }
320
+ if (new RegExp(`\\{[^}]*\\b${name}\\b[^}]*\\}\\s*=\\s*${escapeRegExp(propsName)}\\b`).test(
321
+ code
322
+ )) {
323
+ blockers.add(`the route reads ${label}`);
324
+ }
325
+ }
326
+ }
327
+ }
328
+ return [...blockers];
329
+ }
330
+ __name(findRequestBoundSourceBlockers, "findRequestBoundSourceBlockers");
331
+ function readRequestApiImportBindings(source) {
332
+ const bindings = [];
333
+ for (const clause of readStaticImportClauses(source)) {
334
+ const namedGroup = clause.match(/\{([\s\S]*?)\}/)?.[1];
335
+ for (const rawSpecifier of (namedGroup ?? "").split(",")) {
336
+ const specifier = rawSpecifier.trim().replace(/^type\s+/, "");
337
+ const imported = specifier.match(/^([A-Za-z_$][\w$]*)(?:\s+as\s+([A-Za-z_$][\w$]*))?$/);
338
+ if (!imported?.[1]) continue;
339
+ const label = REQUEST_API_LABELS.get(imported[1]);
340
+ if (label) bindings.push({ local: imported[2] ?? imported[1], label });
341
+ }
342
+ const local = clause.match(/^\s*([A-Za-z_$][\w$]*)\s*(?:,|$)/)?.[1];
343
+ if (local) {
344
+ const label = REQUEST_API_LABELS.get(local);
345
+ if (label) bindings.push({ local, label });
346
+ }
347
+ }
348
+ return bindings;
349
+ }
350
+ __name(readRequestApiImportBindings, "readRequestApiImportBindings");
351
+ function readNamespaceImportBindings(source) {
352
+ return readStaticImportClauses(source).map((clause) => clause.match(/\*\s*as\s*([A-Za-z_$][\w$]*)/)?.[1]).filter((binding) => Boolean(binding));
353
+ }
354
+ __name(readNamespaceImportBindings, "readNamespaceImportBindings");
355
+ function readStaticImportClauses(source) {
356
+ return [...source.matchAll(/\bimport\s+(?!\()([\s\S]*?)\s+from\s*["'][^"']+["']/g)].map((match) => match[1]).filter((clause) => Boolean(clause));
357
+ }
358
+ __name(readStaticImportClauses, "readStaticImportClauses");
359
+ function readDefaultRouteParameters(code) {
360
+ const inlineParams = code.match(/\bexport\s+default\s+(?:async\s+)?function\b[^(]*\(([^)]*)\)/)?.[1] ?? code.match(/\bexport\s+default\s+(?:async\s+)?\(([^)]*)\)\s*=>/)?.[1];
361
+ if (inlineParams !== void 0) return inlineParams;
362
+ const binding = code.match(/\bexport\s+default\s+([A-Za-z_$][\w$]*)\s*;?/)?.[1] ?? code.match(/\bexport\s*\{\s*([A-Za-z_$][\w$]*)\s+as\s+default\s*\}/)?.[1];
363
+ if (!binding) return void 0;
364
+ const escapedBinding = escapeRegExp(binding);
365
+ return code.match(new RegExp(`\\bfunction\\s+${escapedBinding}\\s*\\(([^)]*)\\)`))?.[1] ?? code.match(
366
+ new RegExp(
367
+ `\\b(?:const|let|var)\\s+${escapedBinding}\\s*=\\s*(?:async\\s+)?\\(([^)]*)\\)\\s*=>`
368
+ )
369
+ )?.[1] ?? code.match(
370
+ new RegExp(`\\b(?:const|let|var)\\s+${escapedBinding}\\s*=\\s*function\\s*\\(([^)]*)\\)`)
371
+ )?.[1];
372
+ }
373
+ __name(readDefaultRouteParameters, "readDefaultRouteParameters");
374
+ function stripCommentsAndLiteralContents(source) {
375
+ return sanitizeSource(source, true);
376
+ }
377
+ __name(stripCommentsAndLiteralContents, "stripCommentsAndLiteralContents");
378
+ function stripComments(source) {
379
+ return sanitizeSource(source, false);
380
+ }
381
+ __name(stripComments, "stripComments");
382
+ function sanitizeSource(source, maskLiterals) {
383
+ const output = [...source];
384
+ const mask = /* @__PURE__ */ __name((index) => {
385
+ if (output[index] !== "\n" && output[index] !== "\r") output[index] = " ";
386
+ }, "mask");
387
+ const scanQuoted = /* @__PURE__ */ __name((start, quote) => {
388
+ let index = start;
389
+ if (maskLiterals) mask(index);
390
+ index += 1;
391
+ while (index < source.length) {
392
+ if (source[index] === "\\") {
393
+ if (maskLiterals) mask(index);
394
+ if (index + 1 < source.length && maskLiterals) mask(index + 1);
395
+ index += 2;
396
+ continue;
397
+ }
398
+ const character = source[index];
399
+ if (maskLiterals) mask(index);
400
+ index += 1;
401
+ if (character === quote) break;
402
+ }
403
+ return index;
404
+ }, "scanQuoted");
405
+ const scanCode = /* @__PURE__ */ __name((start, stopAtTemplateBrace) => {
406
+ let index = start;
407
+ let braceDepth = 0;
408
+ while (index < source.length) {
409
+ const character = source[index];
410
+ const next = source[index + 1];
411
+ if (character === "/" && next === "/") {
412
+ while (index < source.length && source[index] !== "\n" && source[index] !== "\r") {
413
+ mask(index++);
414
+ }
415
+ continue;
416
+ }
417
+ if (character === "/" && next === "*") {
418
+ mask(index++);
419
+ mask(index++);
420
+ while (index < source.length) {
421
+ const closesComment = source[index] === "*" && source[index + 1] === "/";
422
+ mask(index++);
423
+ if (closesComment) {
424
+ mask(index++);
425
+ break;
426
+ }
427
+ }
428
+ continue;
429
+ }
430
+ if (character === "'" || character === '"') {
431
+ index = scanQuoted(index, character);
432
+ continue;
433
+ }
434
+ if (character === "`") {
435
+ if (maskLiterals) mask(index);
436
+ index += 1;
437
+ while (index < source.length) {
438
+ if (source[index] === "\\") {
439
+ if (maskLiterals) mask(index);
440
+ if (index + 1 < source.length && maskLiterals) mask(index + 1);
441
+ index += 2;
442
+ } else if (source[index] === "`") {
443
+ if (maskLiterals) mask(index);
444
+ index += 1;
445
+ break;
446
+ } else if (source[index] === "$" && source[index + 1] === "{") {
447
+ if (maskLiterals) {
448
+ mask(index);
449
+ mask(index + 1);
450
+ }
451
+ index = scanCode(index + 2, true);
452
+ } else {
453
+ if (maskLiterals) mask(index);
454
+ index += 1;
455
+ }
456
+ }
457
+ continue;
458
+ }
459
+ if (character === "{") {
460
+ braceDepth += 1;
461
+ } else if (character === "}") {
462
+ if (stopAtTemplateBrace && braceDepth === 0) {
463
+ if (maskLiterals) mask(index);
464
+ return index + 1;
465
+ }
466
+ braceDepth = Math.max(0, braceDepth - 1);
467
+ }
468
+ index += 1;
469
+ }
470
+ return index;
471
+ }, "scanCode");
472
+ scanCode(0, false);
473
+ return output.join("");
474
+ }
475
+ __name(sanitizeSource, "sanitizeSource");
476
+ function escapeRegExp(value) {
477
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
478
+ }
479
+ __name(escapeRegExp, "escapeRegExp");
480
+ function hasExplicitRenderingConfig(mod, source) {
481
+ return typeof mod?.ssg === "boolean" || typeof mod?.ppr === "boolean" || typeof mod?.experimental_ppr === "boolean" || typeof mod?.revalidate !== "undefined" || typeof mod?.dynamic !== "undefined" || typeof parseRouteRenderingDirective(source) !== "undefined";
482
+ }
483
+ __name(hasExplicitRenderingConfig, "hasExplicitRenderingConfig");
240
484
  function parseRouteRenderingDirective(source) {
241
485
  if (!source) {
242
486
  return void 0;
@@ -368,9 +612,10 @@ function skipHorizontalWhitespace(source, start) {
368
612
  return index;
369
613
  }
370
614
  __name(skipHorizontalWhitespace, "skipHorizontalWhitespace");
371
- async function collectSSGPages(routes, loadModule) {
615
+ async function collectSSGPages(routes, loadModule, options = {}) {
372
616
  const ssgPages = [];
373
617
  const ssrRoutes = [];
618
+ const staticCandidateRoutes = [];
374
619
  for (const route of routes) {
375
620
  try {
376
621
  const mod = await loadModule(route.filePath);
@@ -378,7 +623,8 @@ async function collectSSGPages(routes, loadModule) {
378
623
  ssrRoutes.push(route.path);
379
624
  continue;
380
625
  }
381
- const rendering = await resolveRouteRenderingConfigFromFile(mod, route.filePath);
626
+ const source = await readFile(route.filePath, "utf8").catch(() => void 0);
627
+ const rendering = resolveRouteRenderingConfig(mod, source);
382
628
  if (rendering.ssg) {
383
629
  if (route.isDynamic) {
384
630
  const getStaticPaths = mod.getStaticPaths || mod.generateStaticParams;
@@ -405,12 +651,26 @@ async function collectSSGPages(routes, loadModule) {
405
651
  }
406
652
  } else {
407
653
  ssrRoutes.push(route.path);
654
+ if (options.suggestStaticRendering !== false && analyzeStaticRouteCandidate(mod, source, { isDynamic: route.isDynamic }).candidate) {
655
+ staticCandidateRoutes.push(route.path);
656
+ }
408
657
  }
409
658
  } catch (error) {
410
659
  console.error(`Error processing route ${route.path}:`, error);
411
660
  ssrRoutes.push(route.path);
412
661
  }
413
662
  }
663
+ if (staticCandidateRoutes.length > 0) {
664
+ const visibleRoutes = staticCandidateRoutes.slice(0, 10);
665
+ const remainingCount = staticCandidateRoutes.length - visibleRoutes.length;
666
+ const remainingSuffix = remainingCount > 0 ? `
667
+ \u2026and ${remainingCount} more` : "";
668
+ console.warn(
669
+ `[Farm.js] ${staticCandidateRoutes.length} route${staticCandidateRoutes.length === 1 ? " appears" : "s appear"} eligible for static rendering:
670
+ ${visibleRoutes.join("\n ")}${remainingSuffix}
671
+ Add \`export const dynamic = "force-static";\` after verifying that imported code does not read cookies, headers, authentication, or other request data.`
672
+ );
673
+ }
414
674
  return { ssg: ssgPages, ssr: ssrRoutes };
415
675
  }
416
676
  __name(collectSSGPages, "collectSSGPages");
@@ -558,6 +818,15 @@ import path3 from "path";
558
818
  import fs from "fs";
559
819
  import path from "path";
560
820
  import { fileURLToPath } from "url";
821
+
822
+ // src/island.ts
823
+ var FARM_ISLAND_STRATEGIES = ["load", "interaction", "visible", "idle"];
824
+ function isFarmIslandStrategy(value) {
825
+ return FARM_ISLAND_STRATEGIES.includes(value);
826
+ }
827
+ __name(isFarmIslandStrategy, "isFarmIslandStrategy");
828
+
829
+ // src/utils/client-component.ts
561
830
  function readIfExists(filePath) {
562
831
  try {
563
832
  if (!filePath || !fs.existsSync(filePath)) {
@@ -628,56 +897,186 @@ function hasHydrateExport(content) {
628
897
  return /\bexport\s+const\s+hydrate\s*=\s*true\b/.test(content) || /\bexport\s*\{\s*hydrate\s*\}\b/.test(content);
629
898
  }
630
899
  __name(hasHydrateExport, "hasHydrateExport");
900
+ function tokenizeModuleSource(content) {
901
+ const tokens = [];
902
+ let index = 0;
903
+ let line = 1;
904
+ while (index < content.length) {
905
+ const character = content[index];
906
+ if (/\s/.test(character)) {
907
+ if (character === "\n") line++;
908
+ index++;
909
+ continue;
910
+ }
911
+ if (character === "/" && content[index + 1] === "/") {
912
+ index += 2;
913
+ while (index < content.length && content[index] !== "\n") index++;
914
+ continue;
915
+ }
916
+ if (character === "/" && content[index + 1] === "*") {
917
+ index += 2;
918
+ while (index < content.length) {
919
+ if (content[index] === "\n") line++;
920
+ if (content[index] === "*" && content[index + 1] === "/") {
921
+ index += 2;
922
+ break;
923
+ }
924
+ index++;
925
+ }
926
+ continue;
927
+ }
928
+ if (character === '"' || character === "'") {
929
+ const quote = character;
930
+ const tokenLine = line;
931
+ let value = "";
932
+ index++;
933
+ while (index < content.length) {
934
+ const next = content[index];
935
+ if (next === "\\") {
936
+ value += next;
937
+ if (index + 1 < content.length) value += content[index + 1];
938
+ index += 2;
939
+ continue;
940
+ }
941
+ if (next === quote) {
942
+ index++;
943
+ break;
944
+ }
945
+ if (next === "\n") line++;
946
+ value += next;
947
+ index++;
948
+ }
949
+ tokens.push({ kind: "string", value, line: tokenLine });
950
+ continue;
951
+ }
952
+ if (character === "`") {
953
+ index++;
954
+ while (index < content.length) {
955
+ const next = content[index];
956
+ if (next === "\\") {
957
+ index += 2;
958
+ continue;
959
+ }
960
+ if (next === "`") {
961
+ index++;
962
+ break;
963
+ }
964
+ if (next === "\n") line++;
965
+ index++;
966
+ }
967
+ continue;
968
+ }
969
+ if (/[A-Za-z_$]/.test(character)) {
970
+ const start = index++;
971
+ while (index < content.length && /[\w$]/.test(content[index])) index++;
972
+ tokens.push({ kind: "identifier", value: content.slice(start, index), line });
973
+ continue;
974
+ }
975
+ tokens.push({ kind: "punctuation", value: character, line });
976
+ index++;
977
+ }
978
+ return tokens;
979
+ }
980
+ __name(tokenizeModuleSource, "tokenizeModuleSource");
981
+ function isIslandExportStart(tokens, index) {
982
+ const token = tokens[index];
983
+ const previous = tokens[index - 1];
984
+ const startsStatement = !previous || previous.value === ";" || previous.value === "{" || previous.value === "}" || token.line > previous.line;
985
+ return startsStatement && token.value === "export" && tokens[index + 1]?.value === "const" && tokens[index + 2]?.value === "island";
986
+ }
987
+ __name(isIslandExportStart, "isIslandExportStart");
988
+ function getIslandStrategyExport(content) {
989
+ if (!content) return null;
990
+ const tokens = tokenizeModuleSource(content);
991
+ for (let index = 0; index < tokens.length - 2; index++) {
992
+ if (!isIslandExportStart(tokens, index)) continue;
993
+ let valueIndex = index + 3;
994
+ if (tokens[valueIndex]?.value === ":") {
995
+ while (valueIndex < tokens.length && tokens[valueIndex].value !== "=") valueIndex++;
996
+ }
997
+ if (tokens[valueIndex]?.value !== "=") break;
998
+ valueIndex++;
999
+ const literal = tokens[valueIndex];
1000
+ if (!literal || literal.kind !== "string" || !isFarmIslandStrategy(literal.value)) break;
1001
+ let trailingIndex = valueIndex + 1;
1002
+ if (tokens[trailingIndex]?.value === "as" && tokens[trailingIndex + 1]?.value === "const") {
1003
+ trailingIndex += 2;
1004
+ }
1005
+ const trailing = tokens[trailingIndex];
1006
+ if (trailing && trailing.value !== ";" && trailing.line === literal.line) break;
1007
+ return literal.value;
1008
+ }
1009
+ if (tokens.some((_token, index) => isIslandExportStart(tokens, index))) {
1010
+ throw new Error(
1011
+ 'Farm island configuration must be a static "load", "interaction", "visible", or "idle" string literal.'
1012
+ );
1013
+ }
1014
+ return null;
1015
+ }
1016
+ __name(getIslandStrategyExport, "getIslandStrategyExport");
631
1017
  function stripUseClientDirective(content) {
632
1018
  return content.replace(/^\s*(["'])use client\1\s*;?\s*/, "");
633
1019
  }
634
1020
  __name(stripUseClientDirective, "stripUseClientDirective");
635
- function parseClientModuleMetadata(content) {
1021
+ function parseClientModuleMetadata(content, inspectIslandExport) {
636
1022
  if (!content) {
637
1023
  return {
638
1024
  isClientComponent: false,
639
- hasHydrateExport: false
1025
+ hasHydrateExport: false,
1026
+ islandStrategy: null
640
1027
  };
641
1028
  }
1029
+ const isClientComponent = hasUseClientDirective(content);
1030
+ const hasHydrate = hasHydrateExport(content);
642
1031
  return {
643
- isClientComponent: hasUseClientDirective(content),
644
- hasHydrateExport: hasHydrateExport(content)
1032
+ isClientComponent,
1033
+ hasHydrateExport: hasHydrate,
1034
+ islandStrategy: inspectIslandExport || isClientComponent || hasHydrate ? getIslandStrategyExport(content) : null
645
1035
  };
646
1036
  }
647
1037
  __name(parseClientModuleMetadata, "parseClientModuleMetadata");
648
1038
  function getClientModuleMetadata(modulePath, root) {
649
1039
  const resolvedPath = resolveModuleSourcePath(modulePath, root);
650
- return inspectClientModuleMetadata(resolvedPath, root, /* @__PURE__ */ new Set());
1040
+ return inspectClientModuleMetadata(resolvedPath, root, /* @__PURE__ */ new Set(), true);
651
1041
  }
652
1042
  __name(getClientModuleMetadata, "getClientModuleMetadata");
653
- function inspectClientModuleMetadata(resolvedPath, root, visited) {
1043
+ function inspectClientModuleMetadata(resolvedPath, root, visited, inspectIslandExport) {
654
1044
  if (!resolvedPath || visited.has(resolvedPath)) {
655
1045
  return {
656
1046
  isClientComponent: false,
657
- shouldHydrate: false
1047
+ shouldHydrate: false,
1048
+ islandStrategy: null
658
1049
  };
659
1050
  }
660
1051
  visited.add(resolvedPath);
661
1052
  const content = readIfExists(resolvedPath);
662
- const parsed = parseClientModuleMetadata(content);
1053
+ const parsed = parseClientModuleMetadata(content, inspectIslandExport);
663
1054
  if (parsed.isClientComponent) {
664
1055
  return {
665
1056
  isClientComponent: true,
666
- shouldHydrate: true
1057
+ shouldHydrate: true,
1058
+ islandStrategy: parsed.islandStrategy ?? "load"
667
1059
  };
668
1060
  }
669
1061
  let importsClientBoundary = false;
1062
+ let importedIslandStrategy = null;
670
1063
  for (const specifier of getRelativeImportSpecifiers(content)) {
671
1064
  const importedPath = resolveImportedModuleSourcePath(resolvedPath, specifier, root);
672
- const importedMetadata = inspectClientModuleMetadata(importedPath, root, visited);
1065
+ const importedMetadata = inspectClientModuleMetadata(importedPath, root, visited, false);
673
1066
  if (importedMetadata.isClientComponent || importedMetadata.shouldHydrate) {
674
1067
  importsClientBoundary = true;
675
- break;
1068
+ if (importedIslandStrategy === null) {
1069
+ importedIslandStrategy = importedMetadata.islandStrategy;
1070
+ } else if (importedIslandStrategy !== importedMetadata.islandStrategy) {
1071
+ importedIslandStrategy = "load";
1072
+ }
676
1073
  }
677
1074
  }
1075
+ const shouldHydrate = parsed.hasHydrateExport || importsClientBoundary;
678
1076
  return {
679
1077
  isClientComponent: false,
680
- shouldHydrate: parsed.hasHydrateExport || importsClientBoundary
1078
+ shouldHydrate,
1079
+ islandStrategy: shouldHydrate ? parsed.islandStrategy ?? importedIslandStrategy ?? "load" : null
681
1080
  };
682
1081
  }
683
1082
  __name(inspectClientModuleMetadata, "inspectClientModuleMetadata");
@@ -855,6 +1254,10 @@ function parseInterceptionMarker(segment) {
855
1254
  __name(parseInterceptionMarker, "parseInterceptionMarker");
856
1255
 
857
1256
  // src/routing/route-manager.ts
1257
+ function shouldSuggestStaticRenderingForI18n(i18n) {
1258
+ return !i18n || i18n.routing === "prefix-always" || i18n.routing === "prefix-except-default" && i18n.detection.every((signal) => signal === "url");
1259
+ }
1260
+ __name(shouldSuggestStaticRenderingForI18n, "shouldSuggestStaticRenderingForI18n");
858
1261
  function routeSpecificity(entry) {
859
1262
  return entry.route.segments.reduce((score, segment) => {
860
1263
  if (!segment.isDynamic) return score + 100;
@@ -1098,6 +1501,7 @@ var _RouteManager = class _RouteManager {
1098
1501
  modulePath: toUrlPath(entry.modulePath),
1099
1502
  shouldHydrate: metadata.shouldHydrate,
1100
1503
  isClientComponent: metadata.isClientComponent,
1504
+ islandStrategy: metadata.islandStrategy,
1101
1505
  search: getProgrammaticRouteSearchClientOptions(programmaticPage?.search),
1102
1506
  segments: entry.route.segments.map((seg) => ({
1103
1507
  segment: seg.segment,
@@ -1560,8 +1964,10 @@ var _RouteManager = class _RouteManager {
1560
1964
  isDynamic: entry.route.segments.some((seg) => seg.isDynamic),
1561
1965
  pattern: entry.pattern
1562
1966
  }));
1563
- const result = await collectSSGPages(routes, (filePath) => this.loadRouteModule(filePath));
1564
1967
  const i18n = this.getI18nConfig();
1968
+ const result = await collectSSGPages(routes, (filePath) => this.loadRouteModule(filePath), {
1969
+ suggestStaticRendering: shouldSuggestStaticRenderingForI18n(i18n)
1970
+ });
1565
1971
  if (!i18n) {
1566
1972
  return result;
1567
1973
  }
@@ -1693,7 +2099,7 @@ __name(safeGlobFiles, "safeGlobFiles");
1693
2099
 
1694
2100
  // src/server/renderer.ts
1695
2101
  import React from "react";
1696
- import { renderToPipeableStream, renderToStaticMarkup } from "react-dom/server";
2102
+ import { renderToPipeableStream } from "react-dom/server";
1697
2103
  import * as fs2 from "fs";
1698
2104
  import * as path5 from "path";
1699
2105
  import { Writable } from "stream";
@@ -1844,7 +2250,8 @@ __name(transformFarmFontCalls, "transformFarmFontCalls");
1844
2250
  function renderFarmFontDevHead(root) {
1845
2251
  const registry = registries.get(normalizeRoot(root));
1846
2252
  if (!registry || registry.production || registry.size === 0) return "";
1847
- return registry.renderPreloadTags(false);
2253
+ return `${registry.renderPreloadTags(false)}
2254
+ <link rel="stylesheet" href="/@farm/fonts.css">`;
1848
2255
  }
1849
2256
  __name(renderFarmFontDevHead, "renderFarmFontDevHead");
1850
2257
  var _FarmFontRegistry = class _FarmFontRegistry {
@@ -2026,6 +2433,10 @@ ${FONT_SECTION_END}
2026
2433
  fallback,
2027
2434
  display,
2028
2435
  preload: raw.preload !== false,
2436
+ preloadResources: raw.preload === false ? [] : normalizedSources.map(({ resource }) => ({
2437
+ href: resourceUrl(resource, production, this.basePath),
2438
+ type: fontContentType(resource.extension)
2439
+ })),
2029
2440
  weight: defaultWeight,
2030
2441
  style: raw.style === void 0 ? void 0 : defaultStyle,
2031
2442
  sources: normalizedSources
@@ -2122,7 +2533,7 @@ ${FONT_SECTION_END}
2122
2533
  const normalizedExtension = extension.toLowerCase();
2123
2534
  const hash = createHash2("sha256").update(bytes).digest("hex").slice(0, 12);
2124
2535
  const baseName = sanitizeFileName(path4.basename(fileName, extension)) || "font";
2125
- const outputFileName = `assets/fonts/${baseName}-${hash}${normalizedExtension}`;
2536
+ const outputFileName = `assets/fonts/${baseName}-h${hash}${normalizedExtension}`;
2126
2537
  const key = `${hash}${normalizedExtension}`;
2127
2538
  const existing = this.resources.get(key);
2128
2539
  if (existing) return existing;
@@ -2392,7 +2803,8 @@ function serializeFontResult(font) {
2392
2803
  fontFamily: stack,
2393
2804
  ...font.style ? { fontStyle: font.style } : {},
2394
2805
  ...font.weight !== void 0 && !String(font.weight).includes(" ") ? { fontWeight: font.weight } : {}
2395
- }
2806
+ },
2807
+ preloads: font.preloadResources
2396
2808
  });
2397
2809
  }
2398
2810
  __name(serializeFontResult, "serializeFontResult");
@@ -2812,7 +3224,7 @@ function createPPRRefreshScript() {
2812
3224
  }
2813
3225
  __name(createPPRRefreshScript, "createPPRRefreshScript");
2814
3226
  function createPreHydrationClickQueueScript() {
2815
- return `<script>(function(){if(window.__FARM_PREHYDRATION_CLICK_QUEUE__)return;var queue=[];window.__FARM_PREHYDRATION_CLICK_QUEUE__=queue;window.__FARM_HYDRATED__=false;document.documentElement.dataset.farmHydrated="false";function isModified(event){return !!(event.metaKey||event.altKey||event.ctrlKey||event.shiftKey)}function closestQueuedTarget(target){while(target&&target!==document.documentElement){if(target.matches&&target.matches('button,[role="button"],input[type="button"],input[type="submit"],input[type="reset"]'))return target;target=target.parentElement}return null}document.addEventListener("click",function(event){if(window.__FARM_HYDRATED__)return;if(event.defaultPrevented||event.button!==0||isModified(event))return;var target=closestQueuedTarget(event.target);if(!target||target.closest&&target.closest("a[href]"))return;if(queue.some(function(item){return item.target===target}))return;queue.push({target:target,createdAt:Date.now()});event.preventDefault();event.stopImmediatePropagation()},true);})();</script>`;
3227
+ return `<script>(function(){if(window.__FARM_PREHYDRATION_CLICK_QUEUE__)return;var queue=[];window.__FARM_PREHYDRATION_CLICK_QUEUE__=queue;window.__FARM_HYDRATED__=false;document.documentElement.dataset.farmHydrated="false";function isModified(event){return !!(event.metaKey||event.altKey||event.ctrlKey||event.shiftKey)}function closestQueuedTarget(target){while(target&&target!==document.documentElement){if(target.matches&&target.matches('button,[role="button"],input[type="button"],input[type="submit"],input[type="reset"]'))return target;target=target.parentElement}return null}document.addEventListener("click",function(event){if(window.__FARM_HYDRATED__)return;if(event.defaultPrevented||event.button!==0||isModified(event))return;var target=closestQueuedTarget(event.target);if(!target||target.closest&&target.closest("a[href]")||target.closest&&target.closest('[data-farm-island-hydrated="true"]'))return;if(queue.some(function(item){return item.target===target}))return;queue.push({target:target,createdAt:Date.now()});document.dispatchEvent(new CustomEvent("farm:island-interaction",{detail:{target:target}}));event.preventDefault();event.stopImmediatePropagation()},true);})();</script>`;
2816
3228
  }
2817
3229
  __name(createPreHydrationClickQueueScript, "createPreHydrationClickQueueScript");
2818
3230
  function searchParamsToObject(searchParams) {
@@ -3423,6 +3835,7 @@ var _ServerRenderer = class _ServerRenderer {
3423
3835
  req.__FARM_ROUTE__ = pathname;
3424
3836
  req.__FARM_IS_CLIENT_COMPONENT__ = isClientComponent;
3425
3837
  req.__FARM_SHOULD_HYDRATE__ = shouldHydrate;
3838
+ req.__FARM_ISLAND_STRATEGY__ = moduleMetadata.islandStrategy;
3426
3839
  req.__FARM_HAS_HYDRATABLE_ROUTE_SLOTS__ = hasHydratableRouteSlots;
3427
3840
  req.__FARM_LOADING_MODULE_PATH__ = loadingBoundaryEntry?.modulePath ? loadingBoundaryEntry.modulePath.substring(
3428
3841
  loadingBoundaryEntry.modulePath.indexOf("/src/app/")
@@ -3498,7 +3911,12 @@ var _ServerRenderer = class _ServerRenderer {
3498
3911
  if (isClientComponent || shouldHydrate) {
3499
3912
  pageElement = React.createElement(
3500
3913
  "div",
3501
- { id: "__farm_page__", "data-farm-client": "true" },
3914
+ {
3915
+ id: "__farm_page__",
3916
+ "data-farm-client": "true",
3917
+ "data-farm-island": "page",
3918
+ "data-farm-island-strategy": moduleMetadata.islandStrategy ?? "load"
3919
+ },
3502
3920
  pageElement
3503
3921
  );
3504
3922
  }
@@ -3743,6 +4161,13 @@ var _ServerRenderer = class _ServerRenderer {
3743
4161
  return reference;
3744
4162
  }
3745
4163
  async renderMetadataImage(req, res, options) {
4164
+ const method = (req.method || "GET").toUpperCase();
4165
+ if (method !== "GET" && method !== "HEAD") {
4166
+ res.statusCode = 405;
4167
+ res.setHeader("Allow", "GET, HEAD");
4168
+ res.end();
4169
+ return;
4170
+ }
3746
4171
  if (options.image.sourceType === "static") {
3747
4172
  if (!options.image.staticInfo) {
3748
4173
  throw new Error(`Static metadata image ${options.image.modulePath} is missing file info`);
@@ -3768,42 +4193,12 @@ var _ServerRenderer = class _ServerRenderer {
3768
4193
  await this.writeMetadataImageResponse(req, res, handlerResult, imageModule);
3769
4194
  }
3770
4195
  async writeMetadataImageResponse(req, res, value, imageModule) {
3771
- if (isWebResponse(value)) {
3772
- res.statusCode = value.status;
3773
- value.headers.forEach((headerValue, key) => {
3774
- res.setHeader(key, headerValue);
3775
- });
3776
- if (req.method === "HEAD") {
3777
- res.end();
3778
- return;
3779
- }
3780
- const body2 = Buffer.from(await value.arrayBuffer());
3781
- res.write(body2);
3782
- res.end();
3783
- return;
3784
- }
3785
- const contentType = imageModule.contentType || "image/svg+xml; charset=utf-8";
3786
- let body;
3787
- if (typeof value === "string") {
3788
- body = value;
3789
- } else if (value instanceof ArrayBuffer) {
3790
- body = Buffer.from(value);
3791
- } else if (ArrayBuffer.isView(value)) {
3792
- body = Buffer.from(value.buffer, value.byteOffset, value.byteLength);
3793
- } else if (React.isValidElement(value)) {
3794
- body = renderToStaticMarkup(value);
3795
- } else {
3796
- throw new Error("Metadata image must return a Response, string, bytes, or React element");
3797
- }
3798
- res.statusCode = res.statusCode || 200;
3799
- res.setHeader("Content-Type", contentType);
3800
- res.setHeader("Cache-Control", "public, max-age=0, must-revalidate");
3801
- if (req.method === "HEAD") {
3802
- res.end();
3803
- return;
3804
- }
3805
- res.write(body);
3806
- res.end();
4196
+ const ifNoneMatch = req.headers["if-none-match"];
4197
+ const response = await createFarmMetadataImageResponse(value, imageModule, {
4198
+ method: req.method,
4199
+ ifNoneMatch: Array.isArray(ifNoneMatch) ? ifNoneMatch[0] : ifNoneMatch
4200
+ });
4201
+ await sendWebResponse(res, response);
3807
4202
  }
3808
4203
  async writeStaticMetadataImageResponse(req, res, image) {
3809
4204
  const method = (req.method || "GET").toUpperCase();
@@ -3933,6 +4328,7 @@ var _ServerRenderer = class _ServerRenderer {
3933
4328
  search: routeEntry.search,
3934
4329
  isClientComponent: routeEntry.isClientComponent,
3935
4330
  shouldHydrate: routeEntry.shouldHydrate,
4331
+ islandStrategy: routeEntry.islandStrategy,
3936
4332
  preloads: [routeEntry.modulePath],
3937
4333
  assets: []
3938
4334
  };
@@ -3969,6 +4365,7 @@ window.__FARM_DEPLOYMENT_ID__ = ${serializeInlineValue(deploymentId)};
3969
4365
  window.__FARM_PATH__ = ${JSON.stringify(req.__FARM_ROUTE__ || req.url || "/")};
3970
4366
  window.__FARM_IS_CLIENT__ = ${JSON.stringify(isClientComponent)};
3971
4367
  window.__FARM_SHOULD_HYDRATE__ = ${JSON.stringify(req.__FARM_SHOULD_HYDRATE__ === true)};
4368
+ window.__FARM_ISLAND_STRATEGY__ = ${JSON.stringify(req.__FARM_ISLAND_STRATEGY__ || "load")};
3972
4369
  window.__FARM_PAGE_MODULE__ = ${JSON.stringify(relativePath)};
3973
4370
  window.__FARM_LOADING_MODULE__ = ${JSON.stringify(
3974
4371
  req.__FARM_LOADING_MODULE_PATH__ || null
@@ -4329,6 +4726,7 @@ var _FarmApp = class _FarmApp {
4329
4726
  context: config.context || (() => void 0),
4330
4727
  serverActions: resolveServerActionsConfig(config.serverActions),
4331
4728
  images: resolveFarmImageConfig(config.images),
4729
+ performance: resolveFarmPerformanceConfig(config.performance),
4332
4730
  i18n: isResolvedI18nConfig(config.i18n) ? config.i18n : resolveFarmI18nConfig(config.i18n, {
4333
4731
  root,
4334
4732
  mode: process.env.NODE_ENV === "production" ? "production" : "development"
@@ -5075,7 +5473,7 @@ __name(removeLegacyTypeArtifacts, "removeLegacyTypeArtifacts");
5075
5473
 
5076
5474
  // src/vite.ts
5077
5475
  import * as fs4 from "fs";
5078
- import * as path10 from "path";
5476
+ import * as path11 from "path";
5079
5477
 
5080
5478
  // src/image-vite.ts
5081
5479
  import { readFile as readFile4 } from "fs/promises";
@@ -5146,6 +5544,100 @@ async function createBlurDataURL(bytes) {
5146
5544
  }
5147
5545
  __name(createBlurDataURL, "createBlurDataURL");
5148
5546
 
5547
+ // src/server/vite-config.ts
5548
+ import path10 from "path";
5549
+ var FARM_CLIENT_ENTRY_PATTERN = "**/{page,layout,loading,error,not-found,default}.{js,jsx,ts,tsx}";
5550
+ var FARM_CLIENT_OPTIMIZE_DEPS_INCLUDE = [
5551
+ "react",
5552
+ "react-dom",
5553
+ "react-dom/client",
5554
+ "react/jsx-runtime",
5555
+ "react/jsx-dev-runtime",
5556
+ "@farm.js/core/client",
5557
+ "@farm.js/core/plugin/client",
5558
+ "@farm.js/core/deferred",
5559
+ "@farm.js/core/deployment",
5560
+ "@farm.js/core/i18n/client",
5561
+ "@farm.js/core/query/client",
5562
+ "@farm.js/core/server-fn/client",
5563
+ "@farm.js/core/server-query/client"
5564
+ ];
5565
+ function createFarmClientOptimizeDepsEntries(projectRoot, appDirectories) {
5566
+ const resolvedProjectRoot = path10.resolve(projectRoot);
5567
+ return appDirectories.map((appDirectory) => {
5568
+ const relativeAppDirectory = path10.relative(resolvedProjectRoot, path10.resolve(appDirectory)).replace(/\\/g, "/");
5569
+ const prefix = relativeAppDirectory || ".";
5570
+ return `${prefix}/${FARM_CLIENT_ENTRY_PATTERN}`;
5571
+ });
5572
+ }
5573
+ __name(createFarmClientOptimizeDepsEntries, "createFarmClientOptimizeDepsEntries");
5574
+ function createFarmClientOptimizeDepsConfig(entries) {
5575
+ return {
5576
+ // Keep normal application dependency discovery for CJS-only packages, but
5577
+ // seed every Farm browser runtime so React is present in the first crawl.
5578
+ noDiscovery: false,
5579
+ // Waiting for the crawl avoids serving a partial optimizer generation and
5580
+ // then changing browser hashes while hydration is already in progress.
5581
+ holdUntilCrawlEnd: true,
5582
+ include: [...FARM_CLIENT_OPTIMIZE_DEPS_INCLUDE],
5583
+ // Farm's HTML boots through a virtual module and imports route UI lazily.
5584
+ // Explicit entries let Vite finish that crawl before a navigation can
5585
+ // rotate the optimizer hash underneath a hydrated React runtime.
5586
+ ...entries?.length ? { entries: [...entries] } : {}
5587
+ };
5588
+ }
5589
+ __name(createFarmClientOptimizeDepsConfig, "createFarmClientOptimizeDepsConfig");
5590
+ function mergeNoExternal(farmValue, userValue) {
5591
+ if (farmValue === true || userValue === true) return true;
5592
+ const normalize = /* @__PURE__ */ __name((value) => value == null || value === true ? [] : Array.isArray(value) ? value : [value], "normalize");
5593
+ return Array.from(/* @__PURE__ */ new Set([...normalize(farmValue), ...normalize(userValue)]));
5594
+ }
5595
+ __name(mergeNoExternal, "mergeNoExternal");
5596
+ function mergeFarmViteConfig(farmConfig, userConfig = {}) {
5597
+ const noDiscovery = userConfig.optimizeDeps?.noDiscovery ?? farmConfig.optimizeDeps?.noDiscovery;
5598
+ const holdUntilCrawlEnd = userConfig.optimizeDeps?.holdUntilCrawlEnd ?? (userConfig.optimizeDeps?.noDiscovery === true ? false : farmConfig.optimizeDeps?.holdUntilCrawlEnd);
5599
+ return {
5600
+ ...farmConfig,
5601
+ ...userConfig,
5602
+ plugins: [...farmConfig.plugins || [], ...userConfig.plugins || []],
5603
+ server: {
5604
+ ...farmConfig.server,
5605
+ ...userConfig.server
5606
+ },
5607
+ resolve: {
5608
+ ...farmConfig.resolve,
5609
+ ...userConfig.resolve,
5610
+ dedupe: Array.from(
5611
+ /* @__PURE__ */ new Set([...farmConfig.resolve?.dedupe || [], ...userConfig.resolve?.dedupe || []])
5612
+ )
5613
+ },
5614
+ optimizeDeps: {
5615
+ ...farmConfig.optimizeDeps,
5616
+ ...userConfig.optimizeDeps,
5617
+ noDiscovery,
5618
+ holdUntilCrawlEnd,
5619
+ include: Array.from(
5620
+ /* @__PURE__ */ new Set([
5621
+ ...farmConfig.optimizeDeps?.include || [],
5622
+ ...userConfig.optimizeDeps?.include || []
5623
+ ])
5624
+ ),
5625
+ exclude: Array.from(
5626
+ /* @__PURE__ */ new Set([
5627
+ ...farmConfig.optimizeDeps?.exclude || [],
5628
+ ...userConfig.optimizeDeps?.exclude || []
5629
+ ])
5630
+ )
5631
+ },
5632
+ ssr: {
5633
+ ...farmConfig.ssr,
5634
+ ...userConfig.ssr,
5635
+ noExternal: mergeNoExternal(farmConfig.ssr?.noExternal, userConfig.ssr?.noExternal)
5636
+ }
5637
+ };
5638
+ }
5639
+ __name(mergeFarmViteConfig, "mergeFarmViteConfig");
5640
+
5149
5641
  // src/vite.ts
5150
5642
  var ALL_TYPE_ARTIFACTS = {
5151
5643
  routes: true,
@@ -5188,7 +5680,7 @@ function loadFarmWorkflowsDevRuntime() {
5188
5680
  }
5189
5681
  __name(loadFarmWorkflowsDevRuntime, "loadFarmWorkflowsDevRuntime");
5190
5682
  function loadFarmDevtoolsSnapshotRuntime() {
5191
- return import("./devtools-A22KUJZT.mjs");
5683
+ return import("./devtools-GAZ7F3KL.mjs");
5192
5684
  }
5193
5685
  __name(loadFarmDevtoolsSnapshotRuntime, "loadFarmDevtoolsSnapshotRuntime");
5194
5686
  function loadFarmDevtoolsUIRuntime() {
@@ -5367,7 +5859,7 @@ function fileContainsProgrammaticPageRoute(file) {
5367
5859
  __name(fileContainsProgrammaticPageRoute, "fileContainsProgrammaticPageRoute");
5368
5860
  function rewriteEarlySsrRelativeImports(options) {
5369
5861
  const cleanId = options.id.split("?", 1)[0];
5370
- if (!path10.isAbsolute(cleanId) || cleanId.replace(/\\/g, "/").includes("/node_modules/")) {
5862
+ if (!path11.isAbsolute(cleanId) || cleanId.replace(/\\/g, "/").includes("/node_modules/")) {
5371
5863
  return null;
5372
5864
  }
5373
5865
  const replacements = [];
@@ -5387,7 +5879,7 @@ function rewriteEarlySsrRelativeImports(options) {
5387
5879
  const suffixIndex = source.value.search(/[?#]/);
5388
5880
  const sourcePath = suffixIndex === -1 ? source.value : source.value.slice(0, suffixIndex);
5389
5881
  const suffix = suffixIndex === -1 ? "" : source.value.slice(suffixIndex);
5390
- const resolvedPath = path10.resolve(path10.dirname(cleanId), sourcePath);
5882
+ const resolvedPath = path11.resolve(path11.dirname(cleanId), sourcePath);
5391
5883
  replacements.push({
5392
5884
  start: source.start,
5393
5885
  end: source.end,
@@ -5472,7 +5964,7 @@ function farmPlugin(options = {}, initialPluginManager) {
5472
5964
  ...layerRoots.length ? {
5473
5965
  server: {
5474
5966
  fs: {
5475
- allow: [path10.resolve(options.root || process.cwd()), ...layerRoots]
5967
+ allow: [path11.resolve(options.root || process.cwd()), ...layerRoots]
5476
5968
  }
5477
5969
  }
5478
5970
  } : {}
@@ -5503,9 +5995,9 @@ function farmPlugin(options = {}, initialPluginManager) {
5503
5995
  },
5504
5996
  server
5505
5997
  );
5506
- const globalsCSSPath = path10.join(server.config.root, "src/app/globals.css");
5998
+ const globalsCSSPath = path11.join(server.config.root, "src/app/globals.css");
5507
5999
  if (!fs4.existsSync(globalsCSSPath)) {
5508
- const appDir = path10.join(server.config.root, "src/app");
6000
+ const appDir = path11.join(server.config.root, "src/app");
5509
6001
  if (!fs4.existsSync(appDir)) {
5510
6002
  fs4.mkdirSync(appDir, { recursive: true });
5511
6003
  }
@@ -5527,10 +6019,10 @@ function farmPlugin(options = {}, initialPluginManager) {
5527
6019
  });
5528
6020
  }
5529
6021
  const sourceRoots = getFarmSourceRoots(farmConfig);
5530
- server.watcher.add(sourceRoots.map((source) => path10.join(source.root, source.srcDir)));
6022
+ server.watcher.add(sourceRoots.map((source) => path11.join(source.root, source.srcDir)));
5531
6023
  if (farmConfig.i18n.enabled) {
5532
6024
  server.watcher.add(
5533
- farmConfig.i18n.messages.includes("{locale}") ? path10.dirname(farmConfig.i18n.messages) : farmConfig.i18n.messages
6025
+ farmConfig.i18n.messages.includes("{locale}") ? path11.dirname(farmConfig.i18n.messages) : farmConfig.i18n.messages
5534
6026
  );
5535
6027
  }
5536
6028
  const workflowConfig = farmConfig.workflows;
@@ -5540,7 +6032,7 @@ function farmPlugin(options = {}, initialPluginManager) {
5540
6032
  ...farmDocsDevRuntime?.getFarmDocsRouteTypeEntries(farmConfig.docs) ?? []
5541
6033
  ], "getExtraRouteTypes");
5542
6034
  const appDirSlugs = sourceRoots.map(
5543
- (source) => path10.join(source.root, source.srcDir, "app").replace(/\\/g, "/")
6035
+ (source) => path11.join(source.root, source.srcDir, "app").replace(/\\/g, "/")
5544
6036
  );
5545
6037
  const generateTypeArtifacts = /* @__PURE__ */ __name(async (reason, selection = ALL_TYPE_ARTIFACTS, log = false) => {
5546
6038
  try {
@@ -5580,7 +6072,7 @@ function farmPlugin(options = {}, initialPluginManager) {
5580
6072
  }, "generateTypeArtifacts");
5581
6073
  await generateTypeArtifacts("startup");
5582
6074
  const srcDirSlugs = sourceRoots.map(
5583
- (source) => path10.join(source.root, source.srcDir).replace(/\\/g, "/")
6075
+ (source) => path11.join(source.root, source.srcDir).replace(/\\/g, "/")
5584
6076
  );
5585
6077
  const layerConfigFiles = new Set(
5586
6078
  farmConfig.layers.map((layer) => layer.configFile?.replace(/\\/g, "/")).filter((file) => Boolean(file))
@@ -5698,7 +6190,7 @@ function farmPlugin(options = {}, initialPluginManager) {
5698
6190
  await apiRouteManager.discoverRoutes();
5699
6191
  let discoveredWorkflows = [];
5700
6192
  const hasWorkflowDirectory = workflowConfig.enabled && workflowConfig.dirs.some(
5701
- (dir) => fs4.existsSync(path10.isAbsolute(dir) ? dir : path10.join(farmConfig.root, dir))
6193
+ (dir) => fs4.existsSync(path11.isAbsolute(dir) ? dir : path11.join(farmConfig.root, dir))
5702
6194
  );
5703
6195
  if (hasWorkflowDirectory) {
5704
6196
  const { createFarmWorkflowRequestHandler, discoverFarmWorkflows } = await loadFarmWorkflowsDevRuntime();
@@ -5762,10 +6254,20 @@ function farmPlugin(options = {}, initialPluginManager) {
5762
6254
  logger.info(`Refreshed routes: ${reason}`);
5763
6255
  }
5764
6256
  }, "refreshRouteDiscovery");
6257
+ const farmDocsFontAssetList = farmDocsDevRuntime ? resolveFarmDocsFontAssets(farmConfig.root) : [];
5765
6258
  const farmDocsHandler = farmDocsDevRuntime ? farmDocsDevRuntime.createFarmDocsHandler(farmConfig.docs, {
5766
6259
  root: farmConfig.root,
5767
6260
  srcDir: farmConfig.srcDir,
5768
- clientEntry: "/@farm/client.js"
6261
+ clientEntry: "/@farm/client.js",
6262
+ fontAssets: toFarmDocsPublicFontAssets(farmDocsFontAssetList),
6263
+ resolveLayoutFonts: /* @__PURE__ */ __name(async (pathname) => {
6264
+ const layouts = routeManager.matchRoute(pathname).layouts;
6265
+ const layoutModules = await Promise.all(
6266
+ layouts.map((layout) => routeManager.loadLayoutModule(layout.modulePath))
6267
+ );
6268
+ return resolveFarmLayoutFonts(layoutModules);
6269
+ }, "resolveLayoutFonts"),
6270
+ fontStylesheetHref: "/@farm/fonts.css"
5769
6271
  }) : null;
5770
6272
  const farmDocsAPIHandler = farmDocsDevRuntime ? farmDocsDevRuntime.createFarmDocsAPIHandler({
5771
6273
  rootDir: farmConfig.root,
@@ -5773,22 +6275,7 @@ function farmPlugin(options = {}, initialPluginManager) {
5773
6275
  docs: farmConfig.docs
5774
6276
  }) : null;
5775
6277
  const farmDocsFontAssets = new Map(
5776
- farmDocsDevRuntime ? [
5777
- [
5778
- "/assets/Geist-Variable-CrgPqtmy.woff2",
5779
- path10.join(
5780
- farmConfig.root,
5781
- "node_modules/geist/dist/fonts/geist-sans/Geist-Variable.woff2"
5782
- )
5783
- ],
5784
- [
5785
- "/assets/GeistMono-Variable-BNLlm6Cd.woff2",
5786
- path10.join(
5787
- farmConfig.root,
5788
- "node_modules/geist/dist/fonts/geist-mono/GeistMono-Variable.woff2"
5789
- )
5790
- ]
5791
- ] : []
6278
+ farmDocsFontAssetList.map(({ url, sourcePath }) => [url, sourcePath])
5792
6279
  );
5793
6280
  const logRequest = /* @__PURE__ */ __name((method, urlPath, tag) => {
5794
6281
  try {
@@ -5852,11 +6339,12 @@ function farmPlugin(options = {}, initialPluginManager) {
5852
6339
  }
5853
6340
  }
5854
6341
  const farmDocsFontPath = farmDocsFontAssets.get(requestPathname);
5855
- if (farmDocsFontPath && fs4.existsSync(farmDocsFontPath)) {
6342
+ if ((requestMethod === "GET" || requestMethod === "HEAD") && farmDocsFontPath && fs4.existsSync(farmDocsFontPath)) {
5856
6343
  res.statusCode = 200;
5857
6344
  res.setHeader("Content-Type", "font/woff2");
5858
6345
  res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
5859
- fs4.createReadStream(farmDocsFontPath).pipe(res);
6346
+ if (requestMethod === "HEAD") res.end();
6347
+ else fs4.createReadStream(farmDocsFontPath).pipe(res);
5860
6348
  return;
5861
6349
  }
5862
6350
  if (requestMethod === "GET" && (requestPathname === FARM_DEVTOOLS_PATH || requestPathname === `${FARM_DEVTOOLS_PATH}.json`)) {
@@ -6462,6 +6950,7 @@ function farmPlugin(options = {}, initialPluginManager) {
6462
6950
  modulePath: toUrlPath(route.modulePath),
6463
6951
  isClientComponent: routeSlots.length > 0 ? false : isClientComponent,
6464
6952
  shouldHydrate: shouldHydrate || routeSlots.some((slot) => slot.isClientComponent || slot.shouldHydrate),
6953
+ islandStrategy: moduleMetadata.islandStrategy,
6465
6954
  metadata: {
6466
6955
  title: mergedMetadata.title,
6467
6956
  description: mergedMetadata.description
@@ -6846,6 +7335,7 @@ export const getManifest = () => ({
6846
7335
  search: route.search,
6847
7336
  isClientComponent: moduleMetadata.isClientComponent,
6848
7337
  shouldHydrate: moduleMetadata.shouldHydrate,
7338
+ islandStrategy: moduleMetadata.islandStrategy,
6849
7339
  preloads: [route.modulePath],
6850
7340
  // In dev, preload is just the module
6851
7341
  assets: []
@@ -6972,8 +7462,8 @@ if (import.meta.hot) {
6972
7462
  }
6973
7463
  logger.info(`Found ${ssgPages.length} SSG pages, ${ssrRoutes.length} SSR routes`);
6974
7464
  logger.info("Pre-rendering SSG pages...");
6975
- const outDir = path10.join(server?.config.root || process.cwd(), options.outDir || "dist");
6976
- const clientDir = path10.join(outDir, "client");
7465
+ const outDir = path11.join(server?.config.root || process.cwd(), options.outDir || "dist");
7466
+ const clientDir = path11.join(outDir, "client");
6977
7467
  for (const page of ssgPages) {
6978
7468
  try {
6979
7469
  const staticRequest = new Request(new URL(page.urlPath, "http://farm.static"));
@@ -7025,8 +7515,8 @@ if (import.meta.hot) {
7025
7515
  <script type="module" src="/assets/client.js"></script>
7026
7516
  </body>
7027
7517
  </html>`;
7028
- const outputPath = page.urlPath === "/" ? path10.join(clientDir, "index.html") : path10.join(clientDir, page.urlPath + ".html");
7029
- fs4.mkdirSync(path10.dirname(outputPath), { recursive: true });
7518
+ const outputPath = page.urlPath === "/" ? path11.join(clientDir, "index.html") : path11.join(clientDir, page.urlPath + ".html");
7519
+ fs4.mkdirSync(path11.dirname(outputPath), { recursive: true });
7030
7520
  fs4.writeFileSync(outputPath, fullHtml);
7031
7521
  const revalidateInfo = page.revalidate ? ` (revalidate: ${page.revalidate}s)` : "";
7032
7522
  logger.success(` \u2713 ${page.urlPath}${revalidateInfo}`);
@@ -7035,7 +7525,7 @@ if (import.meta.hot) {
7035
7525
  logger.error(` \u2717 ${page.urlPath}: ${error}`);
7036
7526
  }
7037
7527
  }
7038
- const manifestPath = path10.join(outDir, "__ssg_manifest.json");
7528
+ const manifestPath = path11.join(outDir, "__ssg_manifest.json");
7039
7529
  fs4.writeFileSync(
7040
7530
  manifestPath,
7041
7531
  JSON.stringify(
@@ -7071,9 +7561,9 @@ if (import.meta.hot) {
7071
7561
  }
7072
7562
  const currentFarmConfig = farmApp?.getConfig();
7073
7563
  const normalizedFile = file.replace(/\\/g, "/");
7074
- const currentSrcRoot = currentFarmConfig ? getFarmSourceRoots(currentFarmConfig).map((source) => path10.join(source.root, source.srcDir).replace(/\\/g, "/")).find((sourceRoot) => normalizedFile.startsWith(`${sourceRoot}/`)) || null : null;
7564
+ const currentSrcRoot = currentFarmConfig ? getFarmSourceRoots(currentFarmConfig).map((source) => path11.join(source.root, source.srcDir).replace(/\\/g, "/")).find((sourceRoot) => normalizedFile.startsWith(`${sourceRoot}/`)) || null : null;
7075
7565
  if (currentSrcRoot && normalizedFile.startsWith(`${currentSrcRoot}/`) && isProgrammaticRoutesFileName(normalizedFile)) {
7076
- logUpdate("PAGE", `updated ${path10.basename(file)}`);
7566
+ logUpdate("PAGE", `updated ${path11.basename(file)}`);
7077
7567
  for (const mod of modules) {
7078
7568
  server2.moduleGraph.invalidateModule(mod);
7079
7569
  }
@@ -7470,6 +7960,7 @@ import React from 'react'
7470
7960
  import { hydrateRoot, createRoot } from 'react-dom/client'
7471
7961
  import { installChunkErrorRecovery, SPARouter } from '@farm.js/core/client'
7472
7962
  import { createClientPluginManager } from '@farm.js/core/plugin/client'
7963
+ import { scheduleFarmIslandHydration } from '@farm.js/core/internal/client-runtime'
7473
7964
  import { reviveDeferredData } from '@farm.js/core/deferred'
7474
7965
  import {
7475
7966
  createFarmDeploymentMismatchError,
@@ -7722,6 +8213,7 @@ class LegacyManifestSPARouter {
7722
8213
 
7723
8214
  // Only prefetch routes that will hydrate on the client.
7724
8215
  if (!match.route.isClientComponent && !match.route.shouldHydrate) return;
8216
+ if (match.route.islandStrategy && match.route.islandStrategy !== 'load') return;
7725
8217
 
7726
8218
  const modulePath = match.route.modulePath;
7727
8219
  if (this.moduleCache.has(modulePath)) return;
@@ -7866,6 +8358,12 @@ let hasClientTakenOver = false;
7866
8358
  const routeSlotRoots = new Map();
7867
8359
  const routeSlotDefinitions = new Map();
7868
8360
  let activeRouteInterception = null;
8361
+ let pendingPageHydrationController = null;
8362
+
8363
+ function cancelPendingPageHydration() {
8364
+ pendingPageHydrationController?.abort();
8365
+ pendingPageHydrationController = null;
8366
+ }
7869
8367
 
7870
8368
  function normalizeServerProps(rawProps) {
7871
8369
  const props = rawProps && typeof rawProps === 'object' ? { ...rawProps } : {};
@@ -7934,7 +8432,11 @@ async function hydrateInitialRouteSlots() {
7934
8432
  routeSlotDefinitions.set(getRouteSlotKey(slot), slot);
7935
8433
  if (!slot.isClientComponent && !slot.shouldHydrate) continue;
7936
8434
  try {
7937
- hydrated = (await renderRouteSlot(slot, 'hydrate')) || hydrated;
8435
+ const slotHydrated = await renderRouteSlot(slot, 'hydrate');
8436
+ hydrated = slotHydrated || hydrated;
8437
+ if (slotHydrated) {
8438
+ replayPreHydrationClicks(document.getElementById(slot.containerId));
8439
+ }
7938
8440
  } catch (error) {
7939
8441
  console.warn('[Farm.js] Could not hydrate route slot:', slot.name, error);
7940
8442
  }
@@ -8005,16 +8507,29 @@ function applyCanonicalPathFromProps(props) {
8005
8507
  window.history.replaceState({ ...(window.history.state || {}), path: canonicalPath }, '', canonicalPath);
8006
8508
  }
8007
8509
 
8008
- function replayPreHydrationClicks() {
8009
- window.__FARM_HYDRATED__ = true;
8010
- document.documentElement.dataset.farmHydrated = 'true';
8510
+ function replayPreHydrationClicks(container = null) {
8511
+ if (!container) {
8512
+ markFarmHydrated();
8513
+ } else {
8514
+ container.dataset.farmIslandHydrated = 'true';
8515
+ }
8011
8516
 
8012
8517
  const queue = Array.isArray(window.__FARM_PREHYDRATION_CLICK_QUEUE__)
8013
8518
  ? window.__FARM_PREHYDRATION_CLICK_QUEUE__
8014
8519
  : [];
8015
8520
  if (queue.length === 0) return;
8016
8521
 
8017
- const queuedClicks = queue.splice(0, queue.length);
8522
+ const queuedClicks = [];
8523
+ const remainingClicks = [];
8524
+ for (const queuedClick of queue) {
8525
+ const target = queuedClick?.target;
8526
+ if (!container || (target instanceof Node && container.contains(target))) {
8527
+ queuedClicks.push(queuedClick);
8528
+ } else {
8529
+ remainingClicks.push(queuedClick);
8530
+ }
8531
+ }
8532
+ queue.splice(0, queue.length, ...remainingClicks);
8018
8533
  for (const queuedClick of queuedClicks) {
8019
8534
  const target = queuedClick?.target;
8020
8535
  if (!target || typeof target.click !== 'function') continue;
@@ -8023,6 +8538,11 @@ function replayPreHydrationClicks() {
8023
8538
  }
8024
8539
  }
8025
8540
 
8541
+ function markFarmHydrated() {
8542
+ window.__FARM_HYDRATED__ = true;
8543
+ document.documentElement.dataset.farmHydrated = 'true';
8544
+ }
8545
+
8026
8546
  async function buildClientHydrationElement(PageComponent, pageProps) {
8027
8547
  let element = React.createElement(PageComponent, pageProps);
8028
8548
  const loadingModulePath = window.__FARM_LOADING_MODULE__;
@@ -8165,9 +8685,10 @@ async function tryHydrateImportedPage(
8165
8685
  layouts,
8166
8686
  useHydrate = false,
8167
8687
  existingProps = null,
8688
+ signal = null,
8168
8689
  ) {
8169
8690
  const modulePath = route?.modulePath;
8170
- if (!modulePath) {
8691
+ if (!modulePath || signal?.aborted) {
8171
8692
  return false;
8172
8693
  }
8173
8694
 
@@ -8176,6 +8697,7 @@ async function tryHydrateImportedPage(
8176
8697
  pageModule = await import(/* @vite-ignore */ modulePath);
8177
8698
  pageModuleCache.set(modulePath, pageModule);
8178
8699
  }
8700
+ if (signal?.aborted || !container?.isConnected) return false;
8179
8701
 
8180
8702
  const PageComponent = pageModule?.default;
8181
8703
  if (!PageComponent) {
@@ -8190,6 +8712,7 @@ async function tryHydrateImportedPage(
8190
8712
  window.location.pathname,
8191
8713
  existingProps,
8192
8714
  );
8715
+ if (signal?.aborted || !container?.isConnected) return false;
8193
8716
 
8194
8717
  const wrappedElement = useHydrate && container?.id === '__farm_page__'
8195
8718
  ? wrapWithIntegrationProviders(
@@ -8200,6 +8723,7 @@ async function tryHydrateImportedPage(
8200
8723
  currentPageProps,
8201
8724
  layouts,
8202
8725
  );
8726
+ if (signal?.aborted || !container?.isConnected) return false;
8203
8727
 
8204
8728
  if (useHydrate) {
8205
8729
  try {
@@ -8230,6 +8754,7 @@ async function tryHydrateImportedPage(
8230
8754
  async function renderPage(pageData) {
8231
8755
  const container = document.getElementById('root');
8232
8756
  if (!container) return;
8757
+ cancelPendingPageHydration();
8233
8758
  const destination = window.location.pathname + window.location.search;
8234
8759
 
8235
8760
  if (pageData.interception) {
@@ -8246,6 +8771,7 @@ async function renderPage(pageData) {
8246
8771
  modulePath: pageData.modulePath,
8247
8772
  isClientComponent: pageData.isClientComponent === true,
8248
8773
  shouldHydrate: pageData.shouldHydrate === true,
8774
+ islandStrategy: pageData.islandStrategy || 'load',
8249
8775
  };
8250
8776
  const params = pageData.props?.params || {};
8251
8777
  const layouts = (pageData.layoutModules || []).map((modulePath, index) => ({
@@ -8445,14 +8971,6 @@ async function hydrate() {
8445
8971
  }
8446
8972
 
8447
8973
  try {
8448
- // Pre-load layout for SPA navigation
8449
- try {
8450
- const layoutModule = await import(/* @vite-ignore */ '/src/app/layout.tsx');
8451
- LayoutComponent = layoutModule.default;
8452
- } catch (e) {
8453
- console.warn('[Farm.js] Could not preload layout:', e);
8454
- }
8455
-
8456
8974
  // Check if this is a client component (set by SSR)
8457
8975
  const isClientComponent = window.__FARM_IS_CLIENT__ === true;
8458
8976
  const modulePath = window.__FARM_PAGE_MODULE__;
@@ -8500,34 +9018,88 @@ async function hydrate() {
8500
9018
  return
8501
9019
  }
8502
9020
 
8503
- const hydrationSession = await farmClientRuntime.beginHydration({
8504
- container: pageContainer,
8505
- mode: shouldHydrate ? 'hydrate' : 'render',
8506
- });
9021
+ const currentRoute = findRoute(window.location.pathname)?.route;
9022
+ const islandStrategy = window.__FARM_ISLAND_STRATEGY__ || currentRoute?.islandStrategy || 'load';
9023
+ pageContainer.dataset.farmIsland = 'page';
9024
+ pageContainer.dataset.farmIslandStrategy = islandStrategy;
8507
9025
 
8508
- let hydrated = false;
9026
+ const hydrationPathname = window.location.pathname;
9027
+ const hydrationController = new AbortController();
9028
+ pendingPageHydrationController = hydrationController;
8509
9029
  try {
8510
- hydrated = await tryHydrateImportedPage(
8511
- pageContainer,
8512
- { modulePath },
8513
- currentPageProps.params || {},
8514
- layouts,
8515
- shouldHydrate,
8516
- currentPageProps,
8517
- );
8518
- await farmClientRuntime.completeHydration(hydrationSession);
8519
- } catch (error) {
8520
- await farmClientRuntime.failHydration(hydrationSession, error);
8521
- throw error;
8522
- }
9030
+ await scheduleFarmIslandHydration({
9031
+ container: pageContainer,
9032
+ strategy: islandStrategy,
9033
+ signal: hydrationController.signal,
9034
+ hydrate: async () => {
9035
+ if (
9036
+ hydrationController.signal.aborted ||
9037
+ !pageContainer.isConnected ||
9038
+ window.location.pathname !== hydrationPathname
9039
+ ) {
9040
+ return;
9041
+ }
8523
9042
 
8524
- if (hydrated) {
8525
- replayPreHydrationClicks();
8526
- console.log('[Farm.js] \u2705 Hydrated:', modulePath);
8527
- return;
8528
- }
9043
+ // Layout code is only needed when this route boundary actually hydrates.
9044
+ try {
9045
+ const layoutModule = await import(/* @vite-ignore */ '/src/app/layout.tsx');
9046
+ LayoutComponent = layoutModule.default;
9047
+ } catch (e) {
9048
+ console.warn('[Farm.js] Could not preload layout:', e);
9049
+ }
9050
+ if (hydrationController.signal.aborted || !pageContainer.isConnected) return;
8529
9051
 
8530
- console.log('[Farm.js] Server component - SPA router ready')
9052
+ const hydrationSession = await farmClientRuntime.beginHydration({
9053
+ container: pageContainer,
9054
+ mode: shouldHydrate ? 'hydrate' : 'render',
9055
+ });
9056
+
9057
+ let hydrated = false;
9058
+ try {
9059
+ if (hydrationController.signal.aborted || !pageContainer.isConnected) {
9060
+ await farmClientRuntime.failHydration(
9061
+ hydrationSession,
9062
+ new DOMException('Route hydration was cancelled', 'AbortError'),
9063
+ );
9064
+ return;
9065
+ }
9066
+ hydrated = await tryHydrateImportedPage(
9067
+ pageContainer,
9068
+ { modulePath },
9069
+ currentPageProps.params || {},
9070
+ layouts,
9071
+ shouldHydrate,
9072
+ currentPageProps,
9073
+ hydrationController.signal,
9074
+ );
9075
+ if (hydrationController.signal.aborted || !pageContainer.isConnected) {
9076
+ await farmClientRuntime.failHydration(
9077
+ hydrationSession,
9078
+ new DOMException('Route hydration was cancelled', 'AbortError'),
9079
+ );
9080
+ return;
9081
+ }
9082
+ await farmClientRuntime.completeHydration(hydrationSession);
9083
+ } catch (error) {
9084
+ await farmClientRuntime.failHydration(hydrationSession, error);
9085
+ throw error;
9086
+ }
9087
+
9088
+ if (hydrated) {
9089
+ // The scheduler owns queue draining and exactly-once click replay.
9090
+ markFarmHydrated();
9091
+ console.log('[Farm.js] \u2705 Hydrated:', modulePath, '(' + islandStrategy + ')');
9092
+ return;
9093
+ }
9094
+
9095
+ console.log('[Farm.js] Server component - SPA router ready')
9096
+ },
9097
+ });
9098
+ } finally {
9099
+ if (pendingPageHydrationController === hydrationController) {
9100
+ pendingPageHydrationController = null;
9101
+ }
9102
+ }
8531
9103
  } catch (error) {
8532
9104
  console.error('[Farm.js] Hydration error:', error)
8533
9105
  }
@@ -8633,7 +9205,7 @@ function generateClientManifest(bundle) {
8633
9205
  __name(generateClientManifest, "generateClientManifest");
8634
9206
  async function defineConfig(config = {}) {
8635
9207
  const tailwindcss = (await import("@tailwindcss/vite")).default;
8636
- const appRoot = path10.resolve(config.root || process.cwd());
9208
+ const appRoot = path11.resolve(config.root || process.cwd());
8637
9209
  if (config.extends?.length) {
8638
9210
  const { config: layeredConfig } = await import("./layers-VWVDTPUZ.mjs").then(
8639
9211
  ({ resolveFarmLayers }) => resolveFarmLayers(config, {
@@ -8778,13 +9350,16 @@ async function defineConfig(config = {}) {
8778
9350
  customLogger: farmLogger,
8779
9351
  clearScreen: false,
8780
9352
  optimizeDeps: {
8781
- include: [
8782
- "react",
8783
- "react-dom",
8784
- "react-dom/client",
8785
- "react/jsx-runtime",
8786
- "react/jsx-dev-runtime"
8787
- ],
9353
+ ...createFarmClientOptimizeDepsConfig(
9354
+ createFarmClientOptimizeDepsEntries(
9355
+ appRoot,
9356
+ getFarmAppDirectories({ ...config, root: appRoot })
9357
+ )
9358
+ ),
9359
+ // Pre-bundle every framework client-runtime entry with React. Without
9360
+ // this, Vite can discover a linked Farm entry after the page has loaded,
9361
+ // regenerate the optimizer browser hash, and leave React DOM holding a
9362
+ // different React module instance from a client component.
8788
9363
  // Exclude server-side packages from browser bundling
8789
9364
  exclude: [
8790
9365
  "@farm.js/core/server",
@@ -8819,7 +9394,7 @@ async function defineConfig(config = {}) {
8819
9394
  // Stub out problematic server-only modules during dev mode
8820
9395
  alias: {
8821
9396
  ...getFarmLayerAliases(config.layers),
8822
- "@": path10.resolve(appRoot, "src"),
9397
+ "@": path11.resolve(appRoot, "src"),
8823
9398
  // Nitro internals that should not be resolved in browser
8824
9399
  "supports-color": "data:text/javascript,export default false; export const supportsColor = false; export const stdout = false; export const stderr = false;",
8825
9400
  "@poppinss/dumper": "data:text/javascript,export default {};",
@@ -8841,6 +9416,7 @@ __name(defineConfig, "defineConfig");
8841
9416
  export {
8842
9417
  resolveRouteRenderingConfig,
8843
9418
  resolveRouteRenderingConfigFromFile,
9419
+ analyzeStaticRouteCandidate,
8844
9420
  parseRouteRenderingDirective,
8845
9421
  collectSSGPages,
8846
9422
  isSSGModule,
@@ -8863,9 +9439,12 @@ export {
8863
9439
  createFarmImageTypeDeclarations,
8864
9440
  generateFarmI18nTypes,
8865
9441
  generateFarmTypeArtifacts,
9442
+ createFarmClientOptimizeDepsEntries,
9443
+ createFarmClientOptimizeDepsConfig,
9444
+ mergeFarmViteConfig,
8866
9445
  farmI18nClientBridgePlugin,
8867
9446
  rewriteEarlySsrRelativeImports,
8868
9447
  farmPlugin,
8869
9448
  defineConfig
8870
9449
  };
8871
- //# sourceMappingURL=chunk-E2AI2CW7.mjs.map
9450
+ //# sourceMappingURL=chunk-Z3YG4OZT.mjs.map