@pajecawav/yamf 0.0.7 → 0.0.9

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 (84) hide show
  1. package/README.md +63 -7
  2. package/dist/client/index.js +39 -1
  3. package/dist/client/index.js.map +1 -0
  4. package/dist/components/Head.d.ts +3 -4
  5. package/dist/components/Head.d.ts.map +1 -1
  6. package/dist/components/Head.js.map +1 -1
  7. package/dist/context/ssr.d.ts +1 -2
  8. package/dist/context/ssr.d.ts.map +1 -1
  9. package/dist/hooks/isPrerendering.d.ts +12 -0
  10. package/dist/hooks/isPrerendering.d.ts.map +1 -0
  11. package/dist/hooks/isPrerendering.js +17 -0
  12. package/dist/hooks/isPrerendering.js.map +1 -0
  13. package/dist/hooks/useEvent.d.ts +1 -2
  14. package/dist/hooks/useEvent.d.ts.map +1 -1
  15. package/dist/hooks/useHead.d.ts +10 -5
  16. package/dist/hooks/useHead.d.ts.map +1 -1
  17. package/dist/hooks/useHead.js +46 -4
  18. package/dist/hooks/useHead.js.map +1 -1
  19. package/dist/index.d.ts +6 -2
  20. package/dist/index.js +3 -1
  21. package/dist/island/client.js +44 -36
  22. package/dist/island/client.js.map +1 -1
  23. package/dist/island/types.d.ts +2 -3
  24. package/dist/island/types.d.ts.map +1 -1
  25. package/dist/page.d.ts +32 -6
  26. package/dist/page.d.ts.map +1 -1
  27. package/dist/page.js +60 -6
  28. package/dist/page.js.map +1 -1
  29. package/dist/safeAsync.d.ts +19 -0
  30. package/dist/safeAsync.d.ts.map +1 -0
  31. package/dist/safeAsync.js +34 -0
  32. package/dist/safeAsync.js.map +1 -0
  33. package/dist/server/entry.d.mts +12 -4
  34. package/dist/server/entry.d.mts.map +1 -1
  35. package/dist/server/entry.mjs +9 -3
  36. package/dist/server/entry.mjs.map +1 -1
  37. package/dist/server/island/server.d.mts +2 -3
  38. package/dist/server/island/server.d.mts.map +1 -1
  39. package/dist/server/island/server.mjs +13 -3
  40. package/dist/server/island/server.mjs.map +1 -1
  41. package/dist/server/island/types.d.mts +1 -2
  42. package/dist/server/island/types.d.mts.map +1 -1
  43. package/dist/server/shared/assets.d.mts +1 -2
  44. package/dist/server/shared/assets.d.mts.map +1 -1
  45. package/dist/server/shared/head.d.mts +1 -2
  46. package/dist/server/shared/head.d.mts.map +1 -1
  47. package/dist/shared/assets.d.ts +2 -3
  48. package/dist/shared/assets.d.ts.map +1 -1
  49. package/dist/shared/head.d.ts +1 -2
  50. package/dist/shared/head.d.ts.map +1 -1
  51. package/dist/vite/index.d.mts +2 -2
  52. package/dist/vite/index.d.mts.map +1 -1
  53. package/dist/vite/index.mjs +13 -5
  54. package/dist/vite/index.mjs.map +1 -1
  55. package/dist/vite/islands.mjs +34 -5
  56. package/dist/vite/islands.mjs.map +1 -1
  57. package/dist/vite/prerender.mjs +16 -0
  58. package/dist/vite/prerender.mjs.map +1 -0
  59. package/dist/vite/virtual-assets.mjs +1 -1
  60. package/dist/vite/virtual-assets.mjs.map +1 -1
  61. package/dist/vite/virtual-error-handler.mjs +1 -1
  62. package/dist/vite/virtual-error-handler.mjs.map +1 -1
  63. package/dist/vite/virtual-pages.mjs +1 -1
  64. package/dist/vite/virtual-pages.mjs.map +1 -1
  65. package/dist/vite/virtual-root.mjs +12 -5
  66. package/dist/vite/virtual-root.mjs.map +1 -1
  67. package/dist/vite/virtual-template.mjs +9 -4
  68. package/dist/vite/virtual-template.mjs.map +1 -1
  69. package/package.json +27 -20
  70. package/src/client/index.ts +51 -1
  71. package/src/components/Head.tsx +2 -2
  72. package/src/hooks/isPrerendering.ts +13 -0
  73. package/src/hooks/useHead.tsx +98 -9
  74. package/src/index.ts +10 -1
  75. package/src/island/client.tsx +73 -65
  76. package/src/island/server.tsx +31 -6
  77. package/src/page.tsx +176 -11
  78. package/src/safeAsync.tsx +46 -0
  79. package/src/server/entry.tsx +25 -1
  80. package/src/vite/index.ts +30 -3
  81. package/src/vite/islands.ts +68 -8
  82. package/src/vite/prerender.ts +27 -0
  83. package/src/vite/virtual-root.ts +14 -4
  84. package/src/vite/virtual-template.ts +15 -3
@@ -2,6 +2,20 @@ import { withMagicString } from "rolldown-string";
2
2
  import { Visitor } from "vite";
3
3
  //#region src/vite/islands.ts
4
4
  const ISLAND_REGEX = /\.island\.(j|t)sx?$/;
5
+ const WRAPPABLE_INIT_TYPES = /* @__PURE__ */ new Set([
6
+ "ArrowFunctionExpression",
7
+ "FunctionExpression",
8
+ "CallExpression",
9
+ "OptionalCallExpression",
10
+ "Identifier",
11
+ "ConditionalExpression",
12
+ "SequenceExpression",
13
+ "TSAsExpression",
14
+ "NonNullExpression"
15
+ ]);
16
+ const isWrappableInit = (init) => {
17
+ return WRAPPABLE_INIT_TYPES.has(init.type);
18
+ };
5
19
  function langFromId(id) {
6
20
  if (id.endsWith(".tsx")) return "tsx";
7
21
  if (id.endsWith(".ts")) return "ts";
@@ -19,23 +33,37 @@ const islands = () => {
19
33
  s.prepend(`import * as __runtime from "@pajecawav/yamf/server";\n`);
20
34
  s.prepend(`import __assets from "${id}?assets=client";\n`);
21
35
  const seenExports = /* @__PURE__ */ new Set();
36
+ const warnings = [];
22
37
  new Visitor({
23
38
  ExportDefaultDeclaration(node) {
24
39
  const declaration = node.declaration;
25
- if (declaration.type !== "FunctionDeclaration" && declaration.type !== "FunctionExpression" && declaration.type !== "ArrowFunctionExpression") return;
40
+ if (declaration.type !== "FunctionDeclaration" && declaration.type !== "FunctionExpression" && declaration.type !== "ArrowFunctionExpression") {
41
+ warnings.push(`${id}: export default <${declaration.type === "Identifier" ? "identifier" : declaration.type}> is not wrapped as an island — only function components are supported`);
42
+ return;
43
+ }
26
44
  s.overwrite(node.start, declaration.start, "const __ISLAND__ = ");
27
45
  s.appendRight(declaration.end, `; export default __runtime.createIsland(__ISLAND__, "default", __assets)`);
28
46
  },
29
47
  ExportNamedDeclaration(node) {
30
48
  const declaration = node.declaration;
31
- if (declaration?.type === "VariableDeclaration") {
32
- const declarator = declaration.declarations[0];
49
+ if (!declaration) {
50
+ if (node.exportKind !== "type") warnings.push(`${id}: export { … } statements are not wrapped as islands — export the component with \`export const\` or \`export function\``);
51
+ return;
52
+ }
53
+ if (declaration.type === "VariableDeclaration") {
54
+ const declarators = declaration.declarations;
55
+ const declarator = declarators[0];
33
56
  if (!declarator || declarator.id.type !== "Identifier" || !declarator.init || seenExports.has(declarator.id.name)) return;
34
57
  const exportName = declarator.id.name;
58
+ if (!isWrappableInit(declarator.init)) {
59
+ warnings.push(`${id}: export "${exportName}" does not look like a component — left as a plain export, not an island`);
60
+ return;
61
+ }
62
+ if (declarators.length > 1) warnings.push(`${id}: only the first declarator of a multi-declarator export is wrapped as an island ("${exportName}") — split the declaration`);
35
63
  seenExports.add(exportName);
36
64
  s.overwrite(node.start, declarator.init.start, `const __wrap_${exportName} = `);
37
65
  s.appendRight(declarator.init.end, `; export const ${exportName} = __runtime.createIsland(__wrap_${exportName}, "${exportName}", __assets)`);
38
- } else if (declaration?.type === "FunctionDeclaration") {
66
+ } else if (declaration.type === "FunctionDeclaration") {
39
67
  const fnId = declaration.id;
40
68
  if (!fnId || fnId.type !== "Identifier" || seenExports.has(fnId.name)) return;
41
69
  const exportName = fnId.name;
@@ -46,6 +74,7 @@ const islands = () => {
46
74
  }
47
75
  }
48
76
  }).visit(program);
77
+ for (const warning of warnings) this.warn(warning);
49
78
  })
50
79
  }
51
80
  }, {
@@ -53,7 +82,7 @@ const islands = () => {
53
82
  transform: {
54
83
  order: "post",
55
84
  handler: withMagicString(function(s) {
56
- if (s.original.includes("__island_raw_import__")) s.replaceAll("__island_raw_import__", "import");
85
+ if (s.original.includes("__yamf_raw_import__")) s.replaceAll("__yamf_raw_import__", "import");
57
86
  })
58
87
  }
59
88
  }];
@@ -1 +1 @@
1
- {"version":3,"file":"islands.mjs","names":[],"sources":["../../src/vite/islands.ts"],"sourcesContent":["// reference https://github.com/hi-ogawa/vite-plugin-fullstack/blob/28e9540a68529c58842e9a3bf17d2193a065d524/examples/island/src/framework/island/plugin.ts\nimport { withMagicString } from \"rolldown-string\";\nimport type { ParserOptions, Plugin } from \"vite\";\nimport { Visitor } from \"vite\";\n\nconst ISLAND_REGEX = /\\.island\\.(j|t)sx?$/;\n\ntype Lang = NonNullable<ParserOptions[\"lang\"]>;\n\nfunction langFromId(id: string): Lang {\n\tif (id.endsWith(\".tsx\")) return \"tsx\";\n\tif (id.endsWith(\".ts\")) return \"ts\";\n\tif (id.endsWith(\".jsx\")) return \"jsx\";\n\treturn \"js\";\n}\n\nexport const islands = (): Plugin[] => {\n\treturn [\n\t\t{\n\t\t\tname: \"yamf:islands\",\n\t\t\ttransform: {\n\t\t\t\tfilter: { id: ISLAND_REGEX },\n\t\t\t\thandler: withMagicString(function (this, s, id) {\n\t\t\t\t\tif (this.environment.name !== \"ssr\") {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst program = this.parse(s.original, {\n\t\t\t\t\t\tlang: langFromId(id),\n\t\t\t\t\t});\n\n\t\t\t\t\t// prepend server island runtime + asset imports:\n\t\t\t\t\t// import * as __runtime from \"@pajecawav/yamf/server\";\n\t\t\t\t\t// import __assets from \"<id>?assets=client\";\n\t\t\t\t\ts.prepend(`import * as __runtime from \"@pajecawav/yamf/server\";\\n`);\n\t\t\t\t\ts.prepend(`import __assets from \"${id}?assets=client\";\\n`);\n\n\t\t\t\t\tconst seenExports = new Set<string>();\n\n\t\t\t\t\tnew Visitor({\n\t\t\t\t\t\tExportDefaultDeclaration(node) {\n\t\t\t\t\t\t\tconst declaration = node.declaration;\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\tdeclaration.type !== \"FunctionDeclaration\" &&\n\t\t\t\t\t\t\t\tdeclaration.type !== \"FunctionExpression\" &&\n\t\t\t\t\t\t\t\tdeclaration.type !== \"ArrowFunctionExpression\"\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// export default <fn> ->\n\t\t\t\t\t\t\t// const __ISLAND__ = <fn>; export default __runtime.createIsland(__ISLAND__, \"default\", __assets)\n\t\t\t\t\t\t\ts.overwrite(node.start, declaration.start, \"const __ISLAND__ = \");\n\t\t\t\t\t\t\ts.appendRight(\n\t\t\t\t\t\t\t\tdeclaration.end,\n\t\t\t\t\t\t\t\t`; export default __runtime.createIsland(__ISLAND__, \"default\", __assets)`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t},\n\t\t\t\t\t\tExportNamedDeclaration(node) {\n\t\t\t\t\t\t\tconst declaration = node.declaration;\n\t\t\t\t\t\t\tif (declaration?.type === \"VariableDeclaration\") {\n\t\t\t\t\t\t\t\tconst declarator = declaration.declarations[0];\n\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\t!declarator ||\n\t\t\t\t\t\t\t\t\tdeclarator.id.type !== \"Identifier\" ||\n\t\t\t\t\t\t\t\t\t!declarator.init ||\n\t\t\t\t\t\t\t\t\tseenExports.has(declarator.id.name)\n\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst exportName = declarator.id.name;\n\t\t\t\t\t\t\t\tseenExports.add(exportName);\n\n\t\t\t\t\t\t\t\t// export const <name> = <init> ->\n\t\t\t\t\t\t\t\t// const __wrap_<name> = <init>; export const <name> = __runtime.createIsland(__wrap_<name>, \"<name>\", __assets)\n\t\t\t\t\t\t\t\ts.overwrite(\n\t\t\t\t\t\t\t\t\tnode.start,\n\t\t\t\t\t\t\t\t\tdeclarator.init.start,\n\t\t\t\t\t\t\t\t\t`const __wrap_${exportName} = `,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\ts.appendRight(\n\t\t\t\t\t\t\t\t\tdeclarator.init.end,\n\t\t\t\t\t\t\t\t\t`; export const ${exportName} = __runtime.createIsland(__wrap_${exportName}, \"${exportName}\", __assets)`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t} else if (declaration?.type === \"FunctionDeclaration\") {\n\t\t\t\t\t\t\t\tconst fnId = declaration.id;\n\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\t!fnId ||\n\t\t\t\t\t\t\t\t\tfnId.type !== \"Identifier\" ||\n\t\t\t\t\t\t\t\t\tseenExports.has(fnId.name)\n\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst exportName = fnId.name;\n\t\t\t\t\t\t\t\tseenExports.add(exportName);\n\n\t\t\t\t\t\t\t\t// export function <name>() {} ->\n\t\t\t\t\t\t\t\t// function __wrap_<name>() {}; export const <name> = __runtime.createIsland(__wrap_<name>, \"<name>\", __assets)\n\t\t\t\t\t\t\t\ts.remove(node.start, declaration.start);\n\t\t\t\t\t\t\t\ts.overwrite(fnId.start, fnId.end, `__wrap_${exportName}`);\n\t\t\t\t\t\t\t\ts.appendRight(\n\t\t\t\t\t\t\t\t\tdeclaration.end,\n\t\t\t\t\t\t\t\t\t`; export const ${exportName} = __runtime.createIsland(__wrap_${exportName}, \"${exportName}\", __assets)`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t}).visit(program);\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"yamf:islands:raw-import\",\n\t\t\ttransform: {\n\t\t\t\torder: \"post\",\n\t\t\t\thandler: withMagicString(function (s) {\n\t\t\t\t\tif (s.original.includes(\"__island_raw_import__\")) {\n\t\t\t\t\t\ts.replaceAll(\"__island_raw_import__\", \"import\");\n\t\t\t\t\t}\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t];\n};\n"],"mappings":";;;AAKA,MAAM,eAAe;AAIrB,SAAS,WAAW,IAAkB;CACrC,IAAI,GAAG,SAAS,MAAM,GAAG,OAAO;CAChC,IAAI,GAAG,SAAS,KAAK,GAAG,OAAO;CAC/B,IAAI,GAAG,SAAS,MAAM,GAAG,OAAO;CAChC,OAAO;AACR;AAEA,MAAa,gBAA0B;CACtC,OAAO,CACN;EACC,MAAM;EACN,WAAW;GACV,QAAQ,EAAE,IAAI,aAAa;GAC3B,SAAS,gBAAgB,SAAgB,GAAG,IAAI;IAC/C,IAAI,KAAK,YAAY,SAAS,OAC7B;IAGD,MAAM,UAAU,KAAK,MAAM,EAAE,UAAU,EACtC,MAAM,WAAW,EAAE,EACpB,CAAC;IAKD,EAAE,QAAQ,wDAAwD;IAClE,EAAE,QAAQ,yBAAyB,GAAG,mBAAmB;IAEzD,MAAM,8BAAc,IAAI,IAAY;IAEpC,IAAI,QAAQ;KACX,yBAAyB,MAAM;MAC9B,MAAM,cAAc,KAAK;MACzB,IACC,YAAY,SAAS,yBACrB,YAAY,SAAS,wBACrB,YAAY,SAAS,2BAErB;MAKD,EAAE,UAAU,KAAK,OAAO,YAAY,OAAO,qBAAqB;MAChE,EAAE,YACD,YAAY,KACZ,0EACD;KACD;KACA,uBAAuB,MAAM;MAC5B,MAAM,cAAc,KAAK;MACzB,IAAI,aAAa,SAAS,uBAAuB;OAChD,MAAM,aAAa,YAAY,aAAa;OAC5C,IACC,CAAC,cACD,WAAW,GAAG,SAAS,gBACvB,CAAC,WAAW,QACZ,YAAY,IAAI,WAAW,GAAG,IAAI,GAElC;OAED,MAAM,aAAa,WAAW,GAAG;OACjC,YAAY,IAAI,UAAU;OAI1B,EAAE,UACD,KAAK,OACL,WAAW,KAAK,OAChB,gBAAgB,WAAW,IAC5B;OACA,EAAE,YACD,WAAW,KAAK,KAChB,kBAAkB,WAAW,mCAAmC,WAAW,KAAK,WAAW,aAC5F;MACD,OAAO,IAAI,aAAa,SAAS,uBAAuB;OACvD,MAAM,OAAO,YAAY;OACzB,IACC,CAAC,QACD,KAAK,SAAS,gBACd,YAAY,IAAI,KAAK,IAAI,GAEzB;OAED,MAAM,aAAa,KAAK;OACxB,YAAY,IAAI,UAAU;OAI1B,EAAE,OAAO,KAAK,OAAO,YAAY,KAAK;OACtC,EAAE,UAAU,KAAK,OAAO,KAAK,KAAK,UAAU,YAAY;OACxD,EAAE,YACD,YAAY,KACZ,kBAAkB,WAAW,mCAAmC,WAAW,KAAK,WAAW,aAC5F;MACD;KACD;IACD,CAAC,CAAC,CAAC,MAAM,OAAO;GACjB,CAAC;EACF;CACD,GACA;EACC,MAAM;EACN,WAAW;GACV,OAAO;GACP,SAAS,gBAAgB,SAAU,GAAG;IACrC,IAAI,EAAE,SAAS,SAAS,uBAAuB,GAC9C,EAAE,WAAW,yBAAyB,QAAQ;GAEhD,CAAC;EACF;CACD,CACD;AACD"}
1
+ {"version":3,"file":"islands.mjs","names":[],"sources":["../../src/vite/islands.ts"],"sourcesContent":["// reference https://github.com/hi-ogawa/vite-plugin-fullstack/blob/28e9540a68529c58842e9a3bf17d2193a065d524/examples/island/src/framework/island/plugin.ts\nimport { withMagicString } from \"rolldown-string\";\nimport type { ParserOptions, Plugin } from \"vite\";\nimport { Visitor } from \"vite\";\n\nconst ISLAND_REGEX = /\\.island\\.(j|t)sx?$/;\n\n// inits that can plausibly evaluate to a component; anything else (object,\n// array, literal, class…) is left as a plain export with a warning\nconst WRAPPABLE_INIT_TYPES = new Set([\n\t\"ArrowFunctionExpression\",\n\t\"FunctionExpression\",\n\t\"CallExpression\",\n\t\"OptionalCallExpression\",\n\t\"Identifier\",\n\t\"ConditionalExpression\",\n\t\"SequenceExpression\",\n\t\"TSAsExpression\",\n\t\"NonNullExpression\",\n]);\n\nconst isWrappableInit = (init: { type: string }): boolean => {\n\treturn WRAPPABLE_INIT_TYPES.has(init.type);\n};\n\ntype Lang = NonNullable<ParserOptions[\"lang\"]>;\n\nfunction langFromId(id: string): Lang {\n\tif (id.endsWith(\".tsx\")) {\n\t\treturn \"tsx\";\n\t}\n\tif (id.endsWith(\".ts\")) {\n\t\treturn \"ts\";\n\t}\n\tif (id.endsWith(\".jsx\")) {\n\t\treturn \"jsx\";\n\t}\n\treturn \"js\";\n}\n\nexport const islands = (): Plugin[] => {\n\treturn [\n\t\t{\n\t\t\tname: \"yamf:islands\",\n\t\t\ttransform: {\n\t\t\t\tfilter: { id: ISLAND_REGEX },\n\t\t\t\thandler: withMagicString(function (this, s, id) {\n\t\t\t\t\tif (this.environment.name !== \"ssr\") {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst program = this.parse(s.original, {\n\t\t\t\t\t\tlang: langFromId(id),\n\t\t\t\t\t});\n\n\t\t\t\t\t// prepend server island runtime + asset imports:\n\t\t\t\t\t// import * as __runtime from \"@pajecawav/yamf/server\";\n\t\t\t\t\t// import __assets from \"<id>?assets=client\";\n\t\t\t\t\ts.prepend(`import * as __runtime from \"@pajecawav/yamf/server\";\\n`);\n\t\t\t\t\ts.prepend(`import __assets from \"${id}?assets=client\";\\n`);\n\n\t\t\t\t\tconst seenExports = new Set<string>();\n\t\t\t\t\tconst warnings: string[] = [];\n\n\t\t\t\t\tnew Visitor({\n\t\t\t\t\t\tExportDefaultDeclaration(node) {\n\t\t\t\t\t\t\tconst declaration = node.declaration;\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\tdeclaration.type !== \"FunctionDeclaration\" &&\n\t\t\t\t\t\t\t\tdeclaration.type !== \"FunctionExpression\" &&\n\t\t\t\t\t\t\t\tdeclaration.type !== \"ArrowFunctionExpression\"\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\twarnings.push(\n\t\t\t\t\t\t\t\t\t`${id}: export default <${declaration.type === \"Identifier\" ? \"identifier\" : declaration.type}> is not wrapped as an island — only function components are supported`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// export default <fn> ->\n\t\t\t\t\t\t\t// const __ISLAND__ = <fn>; export default __runtime.createIsland(__ISLAND__, \"default\", __assets)\n\t\t\t\t\t\t\ts.overwrite(node.start, declaration.start, \"const __ISLAND__ = \");\n\t\t\t\t\t\t\ts.appendRight(\n\t\t\t\t\t\t\t\tdeclaration.end,\n\t\t\t\t\t\t\t\t`; export default __runtime.createIsland(__ISLAND__, \"default\", __assets)`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t},\n\t\t\t\t\t\tExportNamedDeclaration(node) {\n\t\t\t\t\t\t\tconst declaration = node.declaration;\n\n\t\t\t\t\t\t\tif (!declaration) {\n\t\t\t\t\t\t\t\tif (node.exportKind !== \"type\") {\n\t\t\t\t\t\t\t\t\twarnings.push(\n\t\t\t\t\t\t\t\t\t\t`${id}: export { … } statements are not wrapped as islands — export the component with \\`export const\\` or \\`export function\\``,\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (declaration.type === \"VariableDeclaration\") {\n\t\t\t\t\t\t\t\tconst declarators = declaration.declarations;\n\t\t\t\t\t\t\t\tconst declarator = declarators[0];\n\n\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\t!declarator ||\n\t\t\t\t\t\t\t\t\tdeclarator.id.type !== \"Identifier\" ||\n\t\t\t\t\t\t\t\t\t!declarator.init ||\n\t\t\t\t\t\t\t\t\tseenExports.has(declarator.id.name)\n\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tconst exportName = declarator.id.name;\n\n\t\t\t\t\t\t\t\tif (!isWrappableInit(declarator.init)) {\n\t\t\t\t\t\t\t\t\twarnings.push(\n\t\t\t\t\t\t\t\t\t\t`${id}: export \"${exportName}\" does not look like a component — left as a plain export, not an island`,\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tif (declarators.length > 1) {\n\t\t\t\t\t\t\t\t\twarnings.push(\n\t\t\t\t\t\t\t\t\t\t`${id}: only the first declarator of a multi-declarator export is wrapped as an island (\"${exportName}\") — split the declaration`,\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tseenExports.add(exportName);\n\n\t\t\t\t\t\t\t\t// export const <name> = <init> ->\n\t\t\t\t\t\t\t\t// const __wrap_<name> = <init>; export const <name> = __runtime.createIsland(__wrap_<name>, \"<name>\", __assets)\n\t\t\t\t\t\t\t\ts.overwrite(\n\t\t\t\t\t\t\t\t\tnode.start,\n\t\t\t\t\t\t\t\t\tdeclarator.init.start,\n\t\t\t\t\t\t\t\t\t`const __wrap_${exportName} = `,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\ts.appendRight(\n\t\t\t\t\t\t\t\t\tdeclarator.init.end,\n\t\t\t\t\t\t\t\t\t`; export const ${exportName} = __runtime.createIsland(__wrap_${exportName}, \"${exportName}\", __assets)`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t} else if (declaration.type === \"FunctionDeclaration\") {\n\t\t\t\t\t\t\t\tconst fnId = declaration.id;\n\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\t!fnId ||\n\t\t\t\t\t\t\t\t\tfnId.type !== \"Identifier\" ||\n\t\t\t\t\t\t\t\t\tseenExports.has(fnId.name)\n\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst exportName = fnId.name;\n\t\t\t\t\t\t\t\tseenExports.add(exportName);\n\n\t\t\t\t\t\t\t\t// export function <name>() {} ->\n\t\t\t\t\t\t\t\t// function __wrap_<name>() {}; export const <name> = __runtime.createIsland(__wrap_<name>, \"<name>\", __assets)\n\t\t\t\t\t\t\t\ts.remove(node.start, declaration.start);\n\t\t\t\t\t\t\t\ts.overwrite(fnId.start, fnId.end, `__wrap_${exportName}`);\n\t\t\t\t\t\t\t\ts.appendRight(\n\t\t\t\t\t\t\t\t\tdeclaration.end,\n\t\t\t\t\t\t\t\t\t`; export const ${exportName} = __runtime.createIsland(__wrap_${exportName}, \"${exportName}\", __assets)`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t}).visit(program);\n\n\t\t\t\t\tfor (const warning of warnings) {\n\t\t\t\t\t\tthis.warn(warning);\n\t\t\t\t\t}\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"yamf:islands:raw-import\",\n\t\t\ttransform: {\n\t\t\t\torder: \"post\",\n\t\t\t\thandler: withMagicString(function (s) {\n\t\t\t\t\t// the token is namespaced to avoid colliding with user code\n\t\t\t\t\tif (s.original.includes(\"__yamf_raw_import__\")) {\n\t\t\t\t\t\ts.replaceAll(\"__yamf_raw_import__\", \"import\");\n\t\t\t\t\t}\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t];\n};\n"],"mappings":";;;AAKA,MAAM,eAAe;AAIrB,MAAM,uCAAuB,IAAI,IAAI;CACpC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACD,CAAC;AAED,MAAM,mBAAmB,SAAoC;CAC5D,OAAO,qBAAqB,IAAI,KAAK,IAAI;AAC1C;AAIA,SAAS,WAAW,IAAkB;CACrC,IAAI,GAAG,SAAS,MAAM,GACrB,OAAO;CAER,IAAI,GAAG,SAAS,KAAK,GACpB,OAAO;CAER,IAAI,GAAG,SAAS,MAAM,GACrB,OAAO;CAER,OAAO;AACR;AAEA,MAAa,gBAA0B;CACtC,OAAO,CACN;EACC,MAAM;EACN,WAAW;GACV,QAAQ,EAAE,IAAI,aAAa;GAC3B,SAAS,gBAAgB,SAAgB,GAAG,IAAI;IAC/C,IAAI,KAAK,YAAY,SAAS,OAC7B;IAGD,MAAM,UAAU,KAAK,MAAM,EAAE,UAAU,EACtC,MAAM,WAAW,EAAE,EACpB,CAAC;IAKD,EAAE,QAAQ,wDAAwD;IAClE,EAAE,QAAQ,yBAAyB,GAAG,mBAAmB;IAEzD,MAAM,8BAAc,IAAI,IAAY;IACpC,MAAM,WAAqB,CAAC;IAE5B,IAAI,QAAQ;KACX,yBAAyB,MAAM;MAC9B,MAAM,cAAc,KAAK;MACzB,IACC,YAAY,SAAS,yBACrB,YAAY,SAAS,wBACrB,YAAY,SAAS,2BACpB;OACD,SAAS,KACR,GAAG,GAAG,oBAAoB,YAAY,SAAS,eAAe,eAAe,YAAY,KAAK,uEAC/F;OACA;MACD;MAIA,EAAE,UAAU,KAAK,OAAO,YAAY,OAAO,qBAAqB;MAChE,EAAE,YACD,YAAY,KACZ,0EACD;KACD;KACA,uBAAuB,MAAM;MAC5B,MAAM,cAAc,KAAK;MAEzB,IAAI,CAAC,aAAa;OACjB,IAAI,KAAK,eAAe,QACvB,SAAS,KACR,GAAG,GAAG,yHACP;OAED;MACD;MAEA,IAAI,YAAY,SAAS,uBAAuB;OAC/C,MAAM,cAAc,YAAY;OAChC,MAAM,aAAa,YAAY;OAE/B,IACC,CAAC,cACD,WAAW,GAAG,SAAS,gBACvB,CAAC,WAAW,QACZ,YAAY,IAAI,WAAW,GAAG,IAAI,GAElC;OAGD,MAAM,aAAa,WAAW,GAAG;OAEjC,IAAI,CAAC,gBAAgB,WAAW,IAAI,GAAG;QACtC,SAAS,KACR,GAAG,GAAG,YAAY,WAAW,yEAC9B;QACA;OACD;OAEA,IAAI,YAAY,SAAS,GACxB,SAAS,KACR,GAAG,GAAG,qFAAqF,WAAW,2BACvG;OAGD,YAAY,IAAI,UAAU;OAI1B,EAAE,UACD,KAAK,OACL,WAAW,KAAK,OAChB,gBAAgB,WAAW,IAC5B;OACA,EAAE,YACD,WAAW,KAAK,KAChB,kBAAkB,WAAW,mCAAmC,WAAW,KAAK,WAAW,aAC5F;MACD,OAAO,IAAI,YAAY,SAAS,uBAAuB;OACtD,MAAM,OAAO,YAAY;OACzB,IACC,CAAC,QACD,KAAK,SAAS,gBACd,YAAY,IAAI,KAAK,IAAI,GAEzB;OAED,MAAM,aAAa,KAAK;OACxB,YAAY,IAAI,UAAU;OAI1B,EAAE,OAAO,KAAK,OAAO,YAAY,KAAK;OACtC,EAAE,UAAU,KAAK,OAAO,KAAK,KAAK,UAAU,YAAY;OACxD,EAAE,YACD,YAAY,KACZ,kBAAkB,WAAW,mCAAmC,WAAW,KAAK,WAAW,aAC5F;MACD;KACD;IACD,CAAC,CAAC,CAAC,MAAM,OAAO;IAEhB,KAAK,MAAM,WAAW,UACrB,KAAK,KAAK,OAAO;GAEnB,CAAC;EACF;CACD,GACA;EACC,MAAM;EACN,WAAW;GACV,OAAO;GACP,SAAS,gBAAgB,SAAU,GAAG;IAErC,IAAI,EAAE,SAAS,SAAS,qBAAqB,GAC5C,EAAE,WAAW,uBAAuB,QAAQ;GAE9C,CAAC;EACF;CACD,CACD;AACD"}
@@ -0,0 +1,16 @@
1
+ //#region src/vite/prerender.ts
2
+ const prerenderModule = {
3
+ name: "yamf:prerender",
4
+ setup(nitro) {
5
+ nitro.hooks.hook("prerender:config", (config) => {
6
+ config.serverEntry = false;
7
+ });
8
+ nitro.hooks.hook("prerender:generate", (route) => {
9
+ if (route.route === "/404" && !route.error && !route.skip) route.fileName = "404.html";
10
+ });
11
+ }
12
+ };
13
+ //#endregion
14
+ export { prerenderModule };
15
+
16
+ //# sourceMappingURL=prerender.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prerender.mjs","names":[],"sources":["../../src/vite/prerender.ts"],"sourcesContent":["import type { NitroModule } from \"nitro/types\";\n\n// Nitro's prerenderer is a standalone rolldown build (`nitro-prerender` preset)\n// that cannot run yamf's vite plugins, so `virtual:yamf:*` modules, the islands\n// transform, and the vite asset manifests do not exist there. Its auto-detected\n// serverEntry (src/server.tsx) would be bundled by rolldown and crash the\n// worker at init (`Received protocol 'virtual:'`). Disabling it lets prerender\n// requests fall through to the inherited renderer — nitro's vite ssr-renderer —\n// which dispatches them into the vite-built ssr service bundle: the exact code\n// path the production server uses.\nexport const prerenderModule: NitroModule = {\n\tname: \"yamf:prerender\",\n\tsetup(nitro) {\n\t\tnitro.hooks.hook(\"prerender:config\", config => {\n\t\t\tconfig.serverEntry = false;\n\t\t});\n\n\t\tnitro.hooks.hook(\"prerender:generate\", route => {\n\t\t\t// static hosts (github-pages & friends) expect the not-found document\n\t\t\t// at exactly /404.html; nitro would write /404 as 404/index.html\n\t\t\t// (autoSubfolderIndex)\n\t\t\tif (route.route === \"/404\" && !route.error && !route.skip) {\n\t\t\t\troute.fileName = \"404.html\";\n\t\t\t}\n\t\t});\n\t},\n};\n"],"mappings":";AAUA,MAAa,kBAA+B;CAC3C,MAAM;CACN,MAAM,OAAO;EACZ,MAAM,MAAM,KAAK,qBAAoB,WAAU;GAC9C,OAAO,cAAc;EACtB,CAAC;EAED,MAAM,MAAM,KAAK,uBAAsB,UAAS;GAI/C,IAAI,MAAM,UAAU,UAAU,CAAC,MAAM,SAAS,CAAC,MAAM,MACpD,MAAM,WAAW;EAEnB,CAAC;CACF;AACD"}
@@ -2,7 +2,7 @@ import { js } from "./shared/utils.mjs";
2
2
  //#region src/vite/virtual-assets.ts
3
3
  const virtualAssets = () => {
4
4
  const virtualModuleId = "virtual:yamf:assets";
5
- const resolvedVirtualModuleId = "\0virtual:yamf:assets";
5
+ const resolvedVirtualModuleId = "\0" + virtualModuleId;
6
6
  return {
7
7
  name: "yamf:virtual-assets",
8
8
  resolveId(id) {
@@ -1 +1 @@
1
- {"version":3,"file":"virtual-assets.mjs","names":[],"sources":["../../src/vite/virtual-assets.ts"],"sourcesContent":["import type { Plugin } from \"vite\";\nimport { js } from \"../shared/utils\";\n\nexport const virtualAssets = (): Plugin => {\n\tconst virtualModuleId = \"virtual:yamf:assets\";\n\tconst resolvedVirtualModuleId = \"\\0\" + virtualModuleId;\n\n\treturn {\n\t\tname: \"yamf:virtual-assets\",\n\t\tresolveId(id) {\n\t\t\tif (id === virtualModuleId) {\n\t\t\t\treturn resolvedVirtualModuleId;\n\t\t\t}\n\n\t\t\treturn undefined;\n\t\t},\n\t\tload(id) {\n\t\t\tif (id !== resolvedVirtualModuleId) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\treturn js`\nimport clientAssets from \"./src/client?assets=client\";\n\nexport { clientAssets };\n`.trim();\n\t\t},\n\t};\n};\n"],"mappings":";;AAGA,MAAa,sBAA8B;CAC1C,MAAM,kBAAkB;CACxB,MAAM,0BAA0B;CAEhC,OAAO;EACN,MAAM;EACN,UAAU,IAAI;GACb,IAAI,OAAO,iBACV,OAAO;EAIT;EACA,KAAK,IAAI;GACR,IAAI,OAAO,yBACV;GAGD,OAAO,EAAE;;;;EAIV,KAAK;EACL;CACD;AACD"}
1
+ {"version":3,"file":"virtual-assets.mjs","names":[],"sources":["../../src/vite/virtual-assets.ts"],"sourcesContent":["import type { Plugin } from \"vite\";\nimport { js } from \"../shared/utils\";\n\nexport const virtualAssets = (): Plugin => {\n\tconst virtualModuleId = \"virtual:yamf:assets\";\n\tconst resolvedVirtualModuleId = \"\\0\" + virtualModuleId;\n\n\treturn {\n\t\tname: \"yamf:virtual-assets\",\n\t\tresolveId(id) {\n\t\t\tif (id === virtualModuleId) {\n\t\t\t\treturn resolvedVirtualModuleId;\n\t\t\t}\n\n\t\t\treturn undefined;\n\t\t},\n\t\tload(id) {\n\t\t\tif (id !== resolvedVirtualModuleId) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\treturn js`\nimport clientAssets from \"./src/client?assets=client\";\n\nexport { clientAssets };\n`.trim();\n\t\t},\n\t};\n};\n"],"mappings":";;AAGA,MAAa,sBAA8B;CAC1C,MAAM,kBAAkB;CACxB,MAAM,0BAA0B,OAAO;CAEvC,OAAO;EACN,MAAM;EACN,UAAU,IAAI;GACb,IAAI,OAAO,iBACV,OAAO;EAIT;EACA,KAAK,IAAI;GACR,IAAI,OAAO,yBACV;GAGD,OAAO,EAAE;;;;EAIV,KAAK;EACL;CACD;AACD"}
@@ -2,7 +2,7 @@ import { js } from "./shared/utils.mjs";
2
2
  //#region src/vite/virtual-error-handler.ts
3
3
  const virtualErrorHandler = (options) => {
4
4
  const virtualModuleId = "virtual:yamf:error-handler";
5
- const resolvedVirtualModuleId = "\0virtual:yamf:error-handler";
5
+ const resolvedVirtualModuleId = "\0" + virtualModuleId;
6
6
  return {
7
7
  name: "yamf:virtual-error-handler",
8
8
  resolveId(id) {
@@ -1 +1 @@
1
- {"version":3,"file":"virtual-error-handler.mjs","names":[],"sources":["../../src/vite/virtual-error-handler.ts"],"sourcesContent":["import type { NitroPluginConfig } from \"nitro/vite\";\nimport type { Plugin } from \"vite\";\nimport { js } from \"../shared/utils\";\n\nexport interface VirtualErrorHandlerOptions {\n\tnitro?: NitroPluginConfig;\n}\n\nexport const virtualErrorHandler = (options?: VirtualErrorHandlerOptions): Plugin => {\n\tconst virtualModuleId = \"virtual:yamf:error-handler\";\n\tconst resolvedVirtualModuleId = \"\\0\" + virtualModuleId;\n\n\treturn {\n\t\tname: \"yamf:virtual-error-handler\",\n\t\tresolveId(id) {\n\t\t\tif (id === virtualModuleId) {\n\t\t\t\treturn resolvedVirtualModuleId;\n\t\t\t}\n\n\t\t\treturn undefined;\n\t\t},\n\t\tload(id) {\n\t\t\tif (id !== resolvedVirtualModuleId) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst errorHandler = options?.nitro?.errorHandler;\n\n\t\t\tif (!errorHandler) {\n\t\t\t\treturn js`export const errorHandler = null;`;\n\t\t\t}\n\n\t\t\tif (Array.isArray(errorHandler)) {\n\t\t\t\tthrow new Error(\"Multiple error handlers are not supported\");\n\t\t\t}\n\n\t\t\treturn js`export { default as errorHandler } from \"${errorHandler}\";`;\n\t\t},\n\t};\n};\n"],"mappings":";;AAQA,MAAa,uBAAuB,YAAiD;CACpF,MAAM,kBAAkB;CACxB,MAAM,0BAA0B;CAEhC,OAAO;EACN,MAAM;EACN,UAAU,IAAI;GACb,IAAI,OAAO,iBACV,OAAO;EAIT;EACA,KAAK,IAAI;GACR,IAAI,OAAO,yBACV;GAGD,MAAM,eAAe,SAAS,OAAO;GAErC,IAAI,CAAC,cACJ,OAAO,EAAE;GAGV,IAAI,MAAM,QAAQ,YAAY,GAC7B,MAAM,IAAI,MAAM,2CAA2C;GAG5D,OAAO,EAAE,4CAA4C,aAAa;EACnE;CACD;AACD"}
1
+ {"version":3,"file":"virtual-error-handler.mjs","names":[],"sources":["../../src/vite/virtual-error-handler.ts"],"sourcesContent":["import type { NitroPluginConfig } from \"nitro/vite\";\nimport type { Plugin } from \"vite\";\nimport { js } from \"../shared/utils\";\n\nexport interface VirtualErrorHandlerOptions {\n\tnitro?: NitroPluginConfig;\n}\n\nexport const virtualErrorHandler = (options?: VirtualErrorHandlerOptions): Plugin => {\n\tconst virtualModuleId = \"virtual:yamf:error-handler\";\n\tconst resolvedVirtualModuleId = \"\\0\" + virtualModuleId;\n\n\treturn {\n\t\tname: \"yamf:virtual-error-handler\",\n\t\tresolveId(id) {\n\t\t\tif (id === virtualModuleId) {\n\t\t\t\treturn resolvedVirtualModuleId;\n\t\t\t}\n\n\t\t\treturn undefined;\n\t\t},\n\t\tload(id) {\n\t\t\tif (id !== resolvedVirtualModuleId) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst errorHandler = options?.nitro?.errorHandler;\n\n\t\t\tif (!errorHandler) {\n\t\t\t\treturn js`export const errorHandler = null;`;\n\t\t\t}\n\n\t\t\tif (Array.isArray(errorHandler)) {\n\t\t\t\tthrow new Error(\"Multiple error handlers are not supported\");\n\t\t\t}\n\n\t\t\treturn js`export { default as errorHandler } from \"${errorHandler}\";`;\n\t\t},\n\t};\n};\n"],"mappings":";;AAQA,MAAa,uBAAuB,YAAiD;CACpF,MAAM,kBAAkB;CACxB,MAAM,0BAA0B,OAAO;CAEvC,OAAO;EACN,MAAM;EACN,UAAU,IAAI;GACb,IAAI,OAAO,iBACV,OAAO;EAIT;EACA,KAAK,IAAI;GACR,IAAI,OAAO,yBACV;GAGD,MAAM,eAAe,SAAS,OAAO;GAErC,IAAI,CAAC,cACJ,OAAO,EAAE;GAGV,IAAI,MAAM,QAAQ,YAAY,GAC7B,MAAM,IAAI,MAAM,2CAA2C;GAG5D,OAAO,EAAE,4CAA4C,aAAa;EACnE;CACD;AACD"}
@@ -2,7 +2,7 @@ import { js } from "./shared/utils.mjs";
2
2
  //#region src/vite/virtual-pages.ts
3
3
  const virtualPages = () => {
4
4
  const virtualModuleId = "virtual:yamf:pages";
5
- const resolvedVirtualModuleId = "\0virtual:yamf:pages";
5
+ const resolvedVirtualModuleId = "\0" + virtualModuleId;
6
6
  return {
7
7
  name: "yamf:virtual-pages",
8
8
  resolveId(id) {
@@ -1 +1 @@
1
- {"version":3,"file":"virtual-pages.mjs","names":[],"sources":["../../src/vite/virtual-pages.ts"],"sourcesContent":["import type { Plugin } from \"vite\";\nimport { js } from \"../shared/utils\";\n\nexport const virtualPages = (): Plugin => {\n\tconst virtualModuleId = \"virtual:yamf:pages\";\n\tconst resolvedVirtualModuleId = \"\\0\" + virtualModuleId;\n\n\treturn {\n\t\tname: \"yamf:virtual-pages\",\n\t\tresolveId(id) {\n\t\t\tif (id === virtualModuleId) {\n\t\t\t\treturn resolvedVirtualModuleId;\n\t\t\t}\n\n\t\t\treturn undefined;\n\t\t},\n\t\tload(id) {\n\t\t\tif (id !== resolvedVirtualModuleId) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\treturn js`\nconst pages = import.meta.glob(\"/src/pages/**/*.page.{js,mjs,cjs,ts,mts,cts,tsx,jsx}\", {\n\timport: \"default\",\n});\n\nconst assets = import.meta.glob(\n\t\"/src/pages/**/*.page.{js,mjs,cjs,ts,mts,cts,tsx,jsx}\",\n\t{\n\t\timport: \"default\",\n\t\tquery: \"?assets=ssr\",\n\t\teager: true,\n\t},\n);\n\nexport { pages, assets };\n`.trim();\n\t\t},\n\t};\n};\n"],"mappings":";;AAGA,MAAa,qBAA6B;CACzC,MAAM,kBAAkB;CACxB,MAAM,0BAA0B;CAEhC,OAAO;EACN,MAAM;EACN,UAAU,IAAI;GACb,IAAI,OAAO,iBACV,OAAO;EAIT;EACA,KAAK,IAAI;GACR,IAAI,OAAO,yBACV;GAGD,OAAO,EAAE;;;;;;;;;;;;;;;EAeV,KAAK;EACL;CACD;AACD"}
1
+ {"version":3,"file":"virtual-pages.mjs","names":[],"sources":["../../src/vite/virtual-pages.ts"],"sourcesContent":["import type { Plugin } from \"vite\";\nimport { js } from \"../shared/utils\";\n\nexport const virtualPages = (): Plugin => {\n\tconst virtualModuleId = \"virtual:yamf:pages\";\n\tconst resolvedVirtualModuleId = \"\\0\" + virtualModuleId;\n\n\treturn {\n\t\tname: \"yamf:virtual-pages\",\n\t\tresolveId(id) {\n\t\t\tif (id === virtualModuleId) {\n\t\t\t\treturn resolvedVirtualModuleId;\n\t\t\t}\n\n\t\t\treturn undefined;\n\t\t},\n\t\tload(id) {\n\t\t\tif (id !== resolvedVirtualModuleId) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\treturn js`\nconst pages = import.meta.glob(\"/src/pages/**/*.page.{js,mjs,cjs,ts,mts,cts,tsx,jsx}\", {\n\timport: \"default\",\n});\n\nconst assets = import.meta.glob(\n\t\"/src/pages/**/*.page.{js,mjs,cjs,ts,mts,cts,tsx,jsx}\",\n\t{\n\t\timport: \"default\",\n\t\tquery: \"?assets=ssr\",\n\t\teager: true,\n\t},\n);\n\nexport { pages, assets };\n`.trim();\n\t\t},\n\t};\n};\n"],"mappings":";;AAGA,MAAa,qBAA6B;CACzC,MAAM,kBAAkB;CACxB,MAAM,0BAA0B,OAAO;CAEvC,OAAO;EACN,MAAM;EACN,UAAU,IAAI;GACb,IAAI,OAAO,iBACV,OAAO;EAIT;EACA,KAAK,IAAI;GACR,IAAI,OAAO,yBACV;GAGD,OAAO,EAAE;;;;;;;;;;;;;;;EAeV,KAAK;EACL;CACD;AACD"}
@@ -1,18 +1,24 @@
1
1
  import { js } from "./shared/utils.mjs";
2
+ import { resolve } from "node:path";
2
3
  import { resolveModulePath } from "exsolve";
3
4
  //#region src/vite/virtual-root.ts
4
5
  const ROOT_PATH = "./src/root";
5
6
  const virtualRoot = () => {
6
7
  const virtualModuleId = "virtual:yamf:root";
7
- const resolvedVirtualModuleId = "\0virtual:yamf:root";
8
+ const resolvedVirtualModuleId = "\0" + virtualModuleId;
9
+ let root = process.cwd();
8
10
  return {
9
11
  name: "yamf:virtual-root",
12
+ configResolved(config) {
13
+ root = config.root;
14
+ },
10
15
  resolveId(id) {
11
16
  if (id === virtualModuleId) return resolvedVirtualModuleId;
12
17
  },
13
18
  load(id) {
14
19
  if (id !== resolvedVirtualModuleId) return;
15
- if (!resolveModulePath(ROOT_PATH, {
20
+ const rootPath = resolve(root, ROOT_PATH);
21
+ const rootModule = resolveModulePath(rootPath, {
16
22
  try: true,
17
23
  suffixes: ["", "/index"],
18
24
  extensions: [
@@ -25,15 +31,16 @@ const virtualRoot = () => {
25
31
  ".mts",
26
32
  ".cts"
27
33
  ]
28
- })) return js`
34
+ });
35
+ if (!rootModule) return js`
29
36
  const Root = null;
30
37
  const rootAssets = null;
31
38
 
32
39
  export { Root, rootAssets };
33
40
  `;
34
41
  return js`
35
- import Root from "${ROOT_PATH}";
36
- import rootAssets from "${ROOT_PATH}?assets=ssr";
42
+ import Root from "${rootModule}";
43
+ import rootAssets from "${rootModule}?assets=ssr";
37
44
 
38
45
  export { Root, rootAssets };
39
46
  `.trim();
@@ -1 +1 @@
1
- {"version":3,"file":"virtual-root.mjs","names":[],"sources":["../../src/vite/virtual-root.ts"],"sourcesContent":["import { resolveModulePath } from \"exsolve\";\nimport type { Plugin } from \"vite\";\nimport { js } from \"../shared/utils\";\n\nconst ROOT_PATH = \"./src/root\";\n\nexport const virtualRoot = (): Plugin => {\n\tconst virtualModuleId = \"virtual:yamf:root\";\n\tconst resolvedVirtualModuleId = \"\\0\" + virtualModuleId;\n\n\treturn {\n\t\tname: \"yamf:virtual-root\",\n\t\tresolveId(id) {\n\t\t\tif (id === virtualModuleId) {\n\t\t\t\treturn resolvedVirtualModuleId;\n\t\t\t}\n\n\t\t\treturn undefined;\n\t\t},\n\t\tload(id) {\n\t\t\tif (id !== resolvedVirtualModuleId) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst root = resolveModulePath(ROOT_PATH, {\n\t\t\t\ttry: true,\n\t\t\t\tsuffixes: [\"\", \"/index\"],\n\t\t\t\textensions: [\".tsx\", \".jsx\", \".ts\", \".js\", \".mjs\", \".cjs\", \".mts\", \".cts\"],\n\t\t\t});\n\n\t\t\tif (!root) {\n\t\t\t\treturn js`\nconst Root = null;\nconst rootAssets = null;\n\nexport { Root, rootAssets };\n `;\n\t\t\t}\n\n\t\t\treturn js`\nimport Root from \"${ROOT_PATH}\";\nimport rootAssets from \"${ROOT_PATH}?assets=ssr\";\n\nexport { Root, rootAssets };\n`.trim();\n\t\t},\n\t};\n};\n"],"mappings":";;;AAIA,MAAM,YAAY;AAElB,MAAa,oBAA4B;CACxC,MAAM,kBAAkB;CACxB,MAAM,0BAA0B;CAEhC,OAAO;EACN,MAAM;EACN,UAAU,IAAI;GACb,IAAI,OAAO,iBACV,OAAO;EAIT;EACA,KAAK,IAAI;GACR,IAAI,OAAO,yBACV;GASD,IAAI,CANS,kBAAkB,WAAW;IACzC,KAAK;IACL,UAAU,CAAC,IAAI,QAAQ;IACvB,YAAY;KAAC;KAAQ;KAAQ;KAAO;KAAO;KAAQ;KAAQ;KAAQ;IAAM;GAC1E,CAEQ,GACP,OAAO,EAAE;;;;;;GAQV,OAAO,EAAE;oBACQ,UAAU;0BACJ,UAAU;;;EAGlC,KAAK;EACL;CACD;AACD"}
1
+ {"version":3,"file":"virtual-root.mjs","names":[],"sources":["../../src/vite/virtual-root.ts"],"sourcesContent":["import { resolve } from \"node:path\";\nimport { resolveModulePath } from \"exsolve\";\nimport type { Plugin } from \"vite\";\nimport { js } from \"../shared/utils\";\n\nconst ROOT_PATH = \"./src/root\";\n\nexport const virtualRoot = (): Plugin => {\n\tconst virtualModuleId = \"virtual:yamf:root\";\n\tconst resolvedVirtualModuleId = \"\\0\" + virtualModuleId;\n\n\t// resolved against the vite root (not cwd) so running vite from another\n\t// working directory does not silently skip the root layout\n\tlet root = process.cwd();\n\n\treturn {\n\t\tname: \"yamf:virtual-root\",\n\t\tconfigResolved(config) {\n\t\t\troot = config.root;\n\t\t},\n\t\tresolveId(id) {\n\t\t\tif (id === virtualModuleId) {\n\t\t\t\treturn resolvedVirtualModuleId;\n\t\t\t}\n\n\t\t\treturn undefined;\n\t\t},\n\t\tload(id) {\n\t\t\tif (id !== resolvedVirtualModuleId) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst rootPath = resolve(root, ROOT_PATH);\n\n\t\t\tconst rootModule = resolveModulePath(rootPath, {\n\t\t\t\ttry: true,\n\t\t\t\tsuffixes: [\"\", \"/index\"],\n\t\t\t\textensions: [\".tsx\", \".jsx\", \".ts\", \".js\", \".mjs\", \".cjs\", \".mts\", \".cts\"],\n\t\t\t});\n\n\t\t\tif (!rootModule) {\n\t\t\t\treturn js`\nconst Root = null;\nconst rootAssets = null;\n\nexport { Root, rootAssets };\n `;\n\t\t\t}\n\n\t\t\treturn js`\nimport Root from \"${rootModule}\";\nimport rootAssets from \"${rootModule}?assets=ssr\";\n\nexport { Root, rootAssets };\n`.trim();\n\t\t},\n\t};\n};\n"],"mappings":";;;;AAKA,MAAM,YAAY;AAElB,MAAa,oBAA4B;CACxC,MAAM,kBAAkB;CACxB,MAAM,0BAA0B,OAAO;CAIvC,IAAI,OAAO,QAAQ,IAAI;CAEvB,OAAO;EACN,MAAM;EACN,eAAe,QAAQ;GACtB,OAAO,OAAO;EACf;EACA,UAAU,IAAI;GACb,IAAI,OAAO,iBACV,OAAO;EAIT;EACA,KAAK,IAAI;GACR,IAAI,OAAO,yBACV;GAGD,MAAM,WAAW,QAAQ,MAAM,SAAS;GAExC,MAAM,aAAa,kBAAkB,UAAU;IAC9C,KAAK;IACL,UAAU,CAAC,IAAI,QAAQ;IACvB,YAAY;KAAC;KAAQ;KAAQ;KAAO;KAAO;KAAQ;KAAQ;KAAQ;IAAM;GAC1E,CAAC;GAED,IAAI,CAAC,YACJ,OAAO,EAAE;;;;;;GAQV,OAAO,EAAE;oBACQ,WAAW;0BACL,WAAW;;;EAGnC,KAAK;EACL;CACD;AACD"}
@@ -1,6 +1,6 @@
1
1
  import { js } from "./shared/utils.mjs";
2
+ import { isAbsolute, relative, resolve } from "node:path";
2
3
  import { readFile } from "node:fs/promises";
3
- import { relative } from "node:path";
4
4
  //#region src/vite/virtual-template.ts
5
5
  const TEMPLATE_PATH = "./src/template.html";
6
6
  const DEFAULT_TEMPLATE = `
@@ -14,16 +14,20 @@ const DEFAULT_TEMPLATE = `
14
14
  `.trim();
15
15
  const virtualTemplate = () => {
16
16
  const virtualModuleId = "virtual:yamf:template";
17
- const resolvedVirtualModuleId = "\0virtual:yamf:template";
17
+ const resolvedVirtualModuleId = "\0" + virtualModuleId;
18
+ let root = process.cwd();
18
19
  return {
19
20
  name: "yamf:virtual-template",
21
+ configResolved(config) {
22
+ root = config.root;
23
+ },
20
24
  resolveId(id) {
21
25
  if (id === virtualModuleId) return resolvedVirtualModuleId;
22
26
  },
23
27
  async load(id) {
24
28
  if (id !== resolvedVirtualModuleId) return;
25
29
  try {
26
- const template = await readFile(TEMPLATE_PATH, "utf8");
30
+ const template = await readFile(resolve(root, TEMPLATE_PATH), "utf8");
27
31
  return js`export const template = ${JSON.stringify(template)};`;
28
32
  } catch (error) {
29
33
  if (error instanceof Error && "code" in error && error.code === "ENOENT") return js`export const template = ${JSON.stringify(DEFAULT_TEMPLATE)};`;
@@ -31,7 +35,8 @@ const virtualTemplate = () => {
31
35
  }
32
36
  },
33
37
  handleHotUpdate({ file, server }) {
34
- if (relative(file, TEMPLATE_PATH) === "") {
38
+ if (!isAbsolute(file)) return;
39
+ if (relative(resolve(root, TEMPLATE_PATH), file) === "") {
35
40
  const mod = server.moduleGraph.getModuleById(resolvedVirtualModuleId);
36
41
  if (mod) server.moduleGraph.invalidateModule(mod);
37
42
  server.ws.send({ type: "full-reload" });
@@ -1 +1 @@
1
- {"version":3,"file":"virtual-template.mjs","names":[],"sources":["../../src/vite/virtual-template.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { relative } from \"node:path\";\nimport type { Plugin } from \"vite\";\nimport { js } from \"../shared/utils\";\n\nconst TEMPLATE_PATH = \"./src/template.html\";\n\nconst DEFAULT_TEMPLATE = /* html */ `\n<!DOCTYPE html>\n<html>\n <head></head>\n <body>\n <!--ssr-outlet-->\n </body>\n</html>\n`.trim();\n\nexport const virtualTemplate = (): Plugin => {\n\tconst virtualModuleId = \"virtual:yamf:template\";\n\tconst resolvedVirtualModuleId = \"\\0\" + virtualModuleId;\n\n\treturn {\n\t\tname: \"yamf:virtual-template\",\n\t\tresolveId(id) {\n\t\t\tif (id === virtualModuleId) {\n\t\t\t\treturn resolvedVirtualModuleId;\n\t\t\t}\n\n\t\t\treturn undefined;\n\t\t},\n\t\tasync load(id) {\n\t\t\tif (id !== resolvedVirtualModuleId) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst template = await readFile(TEMPLATE_PATH, \"utf8\");\n\n\t\t\t\treturn js`export const template = ${JSON.stringify(template)};`;\n\t\t\t} catch (error) {\n\t\t\t\tif (error instanceof Error && \"code\" in error && error.code === \"ENOENT\") {\n\t\t\t\t\treturn js`export const template = ${JSON.stringify(DEFAULT_TEMPLATE)};`;\n\t\t\t\t}\n\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t},\n\t\thandleHotUpdate({ file, server }) {\n\t\t\tif (relative(file, TEMPLATE_PATH) === \"\") {\n\t\t\t\tconst mod = server.moduleGraph.getModuleById(resolvedVirtualModuleId);\n\n\t\t\t\tif (mod) {\n\t\t\t\t\tserver.moduleGraph.invalidateModule(mod);\n\t\t\t\t}\n\n\t\t\t\tserver.ws.send({ type: \"full-reload\" });\n\t\t\t}\n\t\t},\n\t};\n};\n"],"mappings":";;;;AAKA,MAAM,gBAAgB;AAEtB,MAAM,mBAA8B;;;;;;;;EAQlC,KAAK;AAEP,MAAa,wBAAgC;CAC5C,MAAM,kBAAkB;CACxB,MAAM,0BAA0B;CAEhC,OAAO;EACN,MAAM;EACN,UAAU,IAAI;GACb,IAAI,OAAO,iBACV,OAAO;EAIT;EACA,MAAM,KAAK,IAAI;GACd,IAAI,OAAO,yBACV;GAGD,IAAI;IACH,MAAM,WAAW,MAAM,SAAS,eAAe,MAAM;IAErD,OAAO,EAAE,2BAA2B,KAAK,UAAU,QAAQ,EAAE;GAC9D,SAAS,OAAO;IACf,IAAI,iBAAiB,SAAS,UAAU,SAAS,MAAM,SAAS,UAC/D,OAAO,EAAE,2BAA2B,KAAK,UAAU,gBAAgB,EAAE;IAGtE,MAAM;GACP;EACD;EACA,gBAAgB,EAAE,MAAM,UAAU;GACjC,IAAI,SAAS,MAAM,aAAa,MAAM,IAAI;IACzC,MAAM,MAAM,OAAO,YAAY,cAAc,uBAAuB;IAEpE,IAAI,KACH,OAAO,YAAY,iBAAiB,GAAG;IAGxC,OAAO,GAAG,KAAK,EAAE,MAAM,cAAc,CAAC;GACvC;EACD;CACD;AACD"}
1
+ {"version":3,"file":"virtual-template.mjs","names":[],"sources":["../../src/vite/virtual-template.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { isAbsolute, relative, resolve } from \"node:path\";\nimport type { Plugin } from \"vite\";\nimport { js } from \"../shared/utils\";\n\nconst TEMPLATE_PATH = \"./src/template.html\";\n\nconst DEFAULT_TEMPLATE = /* html */ `\n<!DOCTYPE html>\n<html>\n <head></head>\n <body>\n <!--ssr-outlet-->\n </body>\n</html>\n`.trim();\n\nexport const virtualTemplate = (): Plugin => {\n\tconst virtualModuleId = \"virtual:yamf:template\";\n\tconst resolvedVirtualModuleId = \"\\0\" + virtualModuleId;\n\n\t// resolved against the vite root (not cwd) so running vite from another\n\t// working directory — monorepo tooling, turbo, etc — does not silently\n\t// fall back to the default template\n\tlet root = process.cwd();\n\n\treturn {\n\t\tname: \"yamf:virtual-template\",\n\t\tconfigResolved(config) {\n\t\t\troot = config.root;\n\t\t},\n\t\tresolveId(id) {\n\t\t\tif (id === virtualModuleId) {\n\t\t\t\treturn resolvedVirtualModuleId;\n\t\t\t}\n\n\t\t\treturn undefined;\n\t\t},\n\t\tasync load(id) {\n\t\t\tif (id !== resolvedVirtualModuleId) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst template = await readFile(resolve(root, TEMPLATE_PATH), \"utf8\");\n\n\t\t\t\treturn js`export const template = ${JSON.stringify(template)};`;\n\t\t\t} catch (error) {\n\t\t\t\tif (error instanceof Error && \"code\" in error && error.code === \"ENOENT\") {\n\t\t\t\t\treturn js`export const template = ${JSON.stringify(DEFAULT_TEMPLATE)};`;\n\t\t\t\t}\n\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t},\n\t\thandleHotUpdate({ file, server }) {\n\t\t\tif (!isAbsolute(file)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (relative(resolve(root, TEMPLATE_PATH), file) === \"\") {\n\t\t\t\tconst mod = server.moduleGraph.getModuleById(resolvedVirtualModuleId);\n\n\t\t\t\tif (mod) {\n\t\t\t\t\tserver.moduleGraph.invalidateModule(mod);\n\t\t\t\t}\n\n\t\t\t\tserver.ws.send({ type: \"full-reload\" });\n\t\t\t}\n\t\t},\n\t};\n};\n"],"mappings":";;;;AAKA,MAAM,gBAAgB;AAEtB,MAAM,mBAA8B;;;;;;;;EAQlC,KAAK;AAEP,MAAa,wBAAgC;CAC5C,MAAM,kBAAkB;CACxB,MAAM,0BAA0B,OAAO;CAKvC,IAAI,OAAO,QAAQ,IAAI;CAEvB,OAAO;EACN,MAAM;EACN,eAAe,QAAQ;GACtB,OAAO,OAAO;EACf;EACA,UAAU,IAAI;GACb,IAAI,OAAO,iBACV,OAAO;EAIT;EACA,MAAM,KAAK,IAAI;GACd,IAAI,OAAO,yBACV;GAGD,IAAI;IACH,MAAM,WAAW,MAAM,SAAS,QAAQ,MAAM,aAAa,GAAG,MAAM;IAEpE,OAAO,EAAE,2BAA2B,KAAK,UAAU,QAAQ,EAAE;GAC9D,SAAS,OAAO;IACf,IAAI,iBAAiB,SAAS,UAAU,SAAS,MAAM,SAAS,UAC/D,OAAO,EAAE,2BAA2B,KAAK,UAAU,gBAAgB,EAAE;IAGtE,MAAM;GACP;EACD;EACA,gBAAgB,EAAE,MAAM,UAAU;GACjC,IAAI,CAAC,WAAW,IAAI,GACnB;GAGD,IAAI,SAAS,QAAQ,MAAM,aAAa,GAAG,IAAI,MAAM,IAAI;IACxD,MAAM,MAAM,OAAO,YAAY,cAAc,uBAAuB;IAEpE,IAAI,KACH,OAAO,YAAY,iBAAiB,GAAG;IAGxC,OAAO,GAAG,KAAK,EAAE,MAAM,cAAc,CAAC;GACvC;EACD;CACD;AACD"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pajecawav/yamf",
3
3
  "type": "module",
4
- "version": "0.0.7",
4
+ "version": "0.0.9",
5
5
  "description": "Yet another meta framework",
6
6
  "license": "MIT",
7
7
  "homepage": "https://github.com/pajecawav/yamf#readme",
@@ -30,36 +30,43 @@
30
30
  "access": "public"
31
31
  },
32
32
  "peerDependencies": {
33
- "hono": "^4.12.30",
34
- "vite": "^8.1.5"
33
+ "hono": "^4.13.9",
34
+ "vite": "^8.3.0",
35
+ "wouter": "^3.11.0"
36
+ },
37
+ "peerDependenciesMeta": {
38
+ "wouter": {
39
+ "optional": true
40
+ }
35
41
  },
36
42
  "dependencies": {
37
43
  "@hono/react-compat": "^0.0.3",
38
- "devalue": "^5.8.2",
39
- "exsolve": "^1.1.0",
40
- "nitro": "3.0.260610-beta",
44
+ "@standard-schema/spec": "^1.1.0",
45
+ "devalue": "^6.0.2",
46
+ "exsolve": "^1.1.1",
47
+ "nitro": "3.0.260903-beta",
41
48
  "rolldown-string": "^0.3.1",
42
- "rou3": "^0.9.1",
49
+ "rou3": "^0.9.2",
43
50
  "ufo": "^1.6.4",
44
- "unhead": "^3.2.3",
45
- "wouter": "^3.10.0"
51
+ "unhead": "^3.4.1",
52
+ "wouter": "^3.11.0"
46
53
  },
47
54
  "devDependencies": {
48
55
  "@hono/react-compat": "^0.0.3",
49
- "@pajecawav/tools": "^0.0.7",
50
- "@playwright/test": "^1.61.1",
51
- "@types/node": "^24.13.3",
56
+ "@pajecawav/tools": "^0.0.9",
57
+ "@playwright/test": "^1.63.0",
58
+ "@types/node": "^26.6.2",
52
59
  "cross-env": "^10.1.0",
53
- "hono": "^4.12.32",
60
+ "hono": "^4.13.9",
54
61
  "husky": "^9.1.7",
55
- "npm-run-all2": "^9.0.2",
56
- "oxfmt": "^0.60.0",
57
- "oxlint": "^1.75.0",
58
- "oxlint-tsgolint": "^7.0.2001",
59
- "publint": "^0.3.22",
60
- "tsdown": "0.22.14",
62
+ "npm-run-all2": "^9.0.3",
63
+ "oxfmt": "^0.70.0",
64
+ "oxlint": "^1.85.0",
65
+ "oxlint-tsgolint": "^7.0.2002",
66
+ "publint": "^0.3.24",
67
+ "tsdown": "0.23.0",
61
68
  "typescript": "~7.0.2",
62
- "vite": "^8.1.5"
69
+ "vite": "^8.3.0"
63
70
  },
64
71
  "scripts": {
65
72
  "build": "tsdown",
@@ -1 +1,51 @@
1
- import "../island/client";
1
+ /**
2
+ * Tiny bootstrap for the island runtime.
3
+ *
4
+ * Defines the <yamf-island> custom element and dynamically imports the real
5
+ * hydration runtime (hono/jsx dom renderer, devalue, unhead client) only when
6
+ * the document actually contains an island. Pages — or whole apps — without
7
+ * islands never pay for it.
8
+ */
9
+
10
+ const pending: Set<HTMLElement> = new Set();
11
+
12
+ type Hydrate = (island: HTMLElement) => void;
13
+
14
+ let hydrate: Hydrate | null = null;
15
+ let loading: boolean = false;
16
+
17
+ const loadRuntime = (): void => {
18
+ if (loading) {
19
+ return;
20
+ }
21
+
22
+ loading = true;
23
+
24
+ void import("../island/client").then(({ hydrateIsland }) => {
25
+ hydrate = hydrateIsland;
26
+
27
+ for (const island of pending) {
28
+ pending.delete(island);
29
+ hydrate(island);
30
+ }
31
+ });
32
+ };
33
+
34
+ customElements.define(
35
+ "yamf-island",
36
+ class extends HTMLElement {
37
+ public connectedCallback() {
38
+ if (hydrate) {
39
+ hydrate(this);
40
+ return;
41
+ }
42
+
43
+ pending.add(this);
44
+ loadRuntime();
45
+ }
46
+
47
+ public disconnectedCallback() {
48
+ pending.delete(this);
49
+ }
50
+ },
51
+ );
@@ -1,7 +1,7 @@
1
- import type { ResolvableHead } from "unhead/types";
2
1
  import { useHead } from "#/hooks/useHead";
2
+ import type { YamfHead } from "#/shared/head";
3
3
 
4
- export type HeadProps = ResolvableHead;
4
+ export type HeadProps = YamfHead;
5
5
 
6
6
  export const Head = (props: HeadProps): void => {
7
7
  useHead(props);
@@ -0,0 +1,13 @@
1
+ import { useSSRContext } from "#/context/ssr";
2
+
3
+ /**
4
+ * Whether the current render runs inside Nitro's build-time prerender pass —
5
+ * the prerenderer fetching the route to write it to disk as a static file.
6
+ *
7
+ * Always `false` in dev and at request time. Prefer this over
8
+ * `import.meta.prerender`: pages are bundled by the vite ssr service, where
9
+ * that flag is never replaced and would always read as `undefined`.
10
+ */
11
+ export const isPrerendering = (): boolean => {
12
+ return useSSRContext()?.event.req.headers.has("x-nitro-prerender") ?? false;
13
+ };
@@ -1,39 +1,128 @@
1
+ import { useEffect, useRef } from "hono/jsx";
1
2
  import { useHead as _useHead, useSeoMeta as _useSeoMeta } from "unhead";
2
3
  import type { ClientUnhead } from "unhead/client";
3
4
  import { createHead } from "unhead/client";
4
- import type { ResolvableHead, UseSeoMetaInput } from "unhead/types";
5
+ import type { ActiveHeadEntry, ResolvableHead, UseSeoMetaInput } from "unhead/types";
5
6
  import { useSSRContext } from "#/context/ssr";
7
+ import type { YamfHead } from "#/shared/head";
8
+
9
+ interface StreamQueue {
10
+ _q?: unknown[][];
11
+ push?: (batch: unknown[]) => void;
12
+ }
6
13
 
7
14
  declare global {
8
15
  interface Window {
9
- __UNHEAD__?: ClientUnhead;
16
+ __unhead__?: StreamQueue;
17
+ __yamfHead__?: ClientUnhead;
10
18
  }
11
19
  }
12
20
 
13
21
  if (!import.meta.env.SSR) {
14
- window.__UNHEAD__ = createHead();
22
+ const head = createHead();
23
+
24
+ const drain = (batch: unknown[]): void => {
25
+ for (const input of batch) {
26
+ head.push(input as ResolvableHead);
27
+ }
28
+ };
29
+
30
+ // inline scripts (the handshake payload and streamed suspense patches)
31
+ // queue into window.__unhead__ while the document parses; this module is
32
+ // part of the deferred client entry and runs after parsing, so by now the
33
+ // queue holds everything the server sent — drain it into the client head
34
+ const queue = window.__unhead__?._q;
35
+
36
+ if (queue) {
37
+ for (const batch of queue) {
38
+ drain(batch);
39
+ }
40
+ }
41
+
42
+ // keep late patches (a stream that is still open) working
43
+ window.__unhead__ = { push: drain };
44
+
45
+ window.__yamfHead__ = head;
15
46
  }
16
47
 
17
- export const useHead = (input?: ResolvableHead): void => {
48
+ /**
49
+ * Binds an unhead entry to the calling component's lifecycle: the entry is
50
+ * created once on mount, patched on every re-render with the latest input,
51
+ * and disposed on unmount. Without this, every re-render pushes a new entry
52
+ * that is never removed — a leak and growing DOM render cost.
53
+ */
54
+ const useHeadEntryLifecycle = <T,>(
55
+ input: T | undefined,
56
+ createEntry: (head: ClientUnhead, input: T) => ActiveHeadEntry<T>,
57
+ ): void => {
58
+ const entryRef = useRef<ActiveHeadEntry<T> | null>(null);
59
+
60
+ useEffect(() => {
61
+ const head = window.__yamfHead__;
62
+
63
+ if (!head || input === undefined) {
64
+ return;
65
+ }
66
+
67
+ const entry = createEntry(head, input);
68
+
69
+ entryRef.current = entry;
70
+
71
+ return () => {
72
+ entry.dispose();
73
+ entryRef.current = null;
74
+ };
75
+ // mount-only: the entry is created once per component instance
76
+ // oxlint-disable-next-line eslint(exhaustive-deps)
77
+ }, []);
78
+
79
+ useEffect(() => {
80
+ if (input !== undefined) {
81
+ entryRef.current?.patch(input);
82
+ }
83
+ }, [input]);
84
+ };
85
+
86
+ export const useHead = (input?: YamfHead): void => {
18
87
  if (import.meta.env.SSR) {
88
+ if (input === undefined) {
89
+ return;
90
+ }
91
+
19
92
  const ctx = useSSRContext();
20
93
 
21
94
  if (ctx?.head) {
22
- _useHead(ctx.head, input);
95
+ // the custom `seo` key is yamf sugar for useSeoMeta — process it
96
+ // instead of passing an unknown key down to unhead
97
+ const { seo, ...rest } = input;
98
+
99
+ if (seo !== undefined) {
100
+ _useSeoMeta(ctx.head, seo);
101
+ }
102
+
103
+ _useHead(ctx.head, rest);
23
104
  }
24
- } else if (window.__UNHEAD__) {
25
- _useHead(window.__UNHEAD__, input);
105
+
106
+ return;
26
107
  }
108
+
109
+ useHeadEntryLifecycle(input, (head, resolved) => _useHead(head, resolved));
27
110
  };
28
111
 
29
112
  export const useSeoMeta = (input?: UseSeoMetaInput): void => {
30
113
  if (import.meta.env.SSR) {
114
+ if (input === undefined) {
115
+ return;
116
+ }
117
+
31
118
  const ctx = useSSRContext();
32
119
 
33
120
  if (ctx?.head) {
34
121
  _useSeoMeta(ctx.head, input);
35
122
  }
36
- } else if (window.__UNHEAD__) {
37
- _useSeoMeta(window.__UNHEAD__, input);
123
+
124
+ return;
38
125
  }
126
+
127
+ useHeadEntryLifecycle(input, (head, resolved) => _useSeoMeta(head, resolved));
39
128
  };
package/src/index.ts CHANGED
@@ -1,8 +1,17 @@
1
1
  export { definePage } from "./page";
2
- export type { PageHandler } from "./page";
2
+ export type { PageCacheOptions, PageHandler, PageRenderer } from "./page";
3
+
4
+ export type { StandardSchemaV1 } from "@standard-schema/spec";
5
+
6
+ // re-exported so apps can annotate render args without depending on unhead
7
+ export type { Unhead } from "unhead/server";
8
+
9
+ export { safeAsync } from "./safeAsync";
10
+ export type { SafeAsyncFallbackProps } from "./safeAsync";
3
11
 
4
12
  export { useSSRContext } from "./context/ssr";
5
13
  export { useEvent } from "./hooks/useEvent";
14
+ export { isPrerendering } from "./hooks/isPrerendering";
6
15
  export { useHead, useSeoMeta } from "./hooks/useHead";
7
16
 
8
17
  export type { IslandClientDirective, IslandProps } from "./island/types";