@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
@@ -5,12 +5,36 @@ import { Visitor } from "vite";
5
5
 
6
6
  const ISLAND_REGEX = /\.island\.(j|t)sx?$/;
7
7
 
8
+ // inits that can plausibly evaluate to a component; anything else (object,
9
+ // array, literal, class…) is left as a plain export with a warning
10
+ const WRAPPABLE_INIT_TYPES = new Set([
11
+ "ArrowFunctionExpression",
12
+ "FunctionExpression",
13
+ "CallExpression",
14
+ "OptionalCallExpression",
15
+ "Identifier",
16
+ "ConditionalExpression",
17
+ "SequenceExpression",
18
+ "TSAsExpression",
19
+ "NonNullExpression",
20
+ ]);
21
+
22
+ const isWrappableInit = (init: { type: string }): boolean => {
23
+ return WRAPPABLE_INIT_TYPES.has(init.type);
24
+ };
25
+
8
26
  type Lang = NonNullable<ParserOptions["lang"]>;
9
27
 
10
28
  function langFromId(id: string): Lang {
11
- if (id.endsWith(".tsx")) return "tsx";
12
- if (id.endsWith(".ts")) return "ts";
13
- if (id.endsWith(".jsx")) return "jsx";
29
+ if (id.endsWith(".tsx")) {
30
+ return "tsx";
31
+ }
32
+ if (id.endsWith(".ts")) {
33
+ return "ts";
34
+ }
35
+ if (id.endsWith(".jsx")) {
36
+ return "jsx";
37
+ }
14
38
  return "js";
15
39
  }
16
40
 
@@ -36,6 +60,7 @@ export const islands = (): Plugin[] => {
36
60
  s.prepend(`import __assets from "${id}?assets=client";\n`);
37
61
 
38
62
  const seenExports = new Set<string>();
63
+ const warnings: string[] = [];
39
64
 
40
65
  new Visitor({
41
66
  ExportDefaultDeclaration(node) {
@@ -45,6 +70,9 @@ export const islands = (): Plugin[] => {
45
70
  declaration.type !== "FunctionExpression" &&
46
71
  declaration.type !== "ArrowFunctionExpression"
47
72
  ) {
73
+ warnings.push(
74
+ `${id}: export default <${declaration.type === "Identifier" ? "identifier" : declaration.type}> is not wrapped as an island — only function components are supported`,
75
+ );
48
76
  return;
49
77
  }
50
78
 
@@ -58,8 +86,20 @@ export const islands = (): Plugin[] => {
58
86
  },
59
87
  ExportNamedDeclaration(node) {
60
88
  const declaration = node.declaration;
61
- if (declaration?.type === "VariableDeclaration") {
62
- const declarator = declaration.declarations[0];
89
+
90
+ if (!declaration) {
91
+ if (node.exportKind !== "type") {
92
+ warnings.push(
93
+ `${id}: export { … } statements are not wrapped as islands — export the component with \`export const\` or \`export function\``,
94
+ );
95
+ }
96
+ return;
97
+ }
98
+
99
+ if (declaration.type === "VariableDeclaration") {
100
+ const declarators = declaration.declarations;
101
+ const declarator = declarators[0];
102
+
63
103
  if (
64
104
  !declarator ||
65
105
  declarator.id.type !== "Identifier" ||
@@ -68,7 +108,22 @@ export const islands = (): Plugin[] => {
68
108
  ) {
69
109
  return;
70
110
  }
111
+
71
112
  const exportName = declarator.id.name;
113
+
114
+ if (!isWrappableInit(declarator.init)) {
115
+ warnings.push(
116
+ `${id}: export "${exportName}" does not look like a component — left as a plain export, not an island`,
117
+ );
118
+ return;
119
+ }
120
+
121
+ if (declarators.length > 1) {
122
+ warnings.push(
123
+ `${id}: only the first declarator of a multi-declarator export is wrapped as an island ("${exportName}") — split the declaration`,
124
+ );
125
+ }
126
+
72
127
  seenExports.add(exportName);
73
128
 
74
129
  // export const <name> = <init> ->
@@ -82,7 +137,7 @@ export const islands = (): Plugin[] => {
82
137
  declarator.init.end,
83
138
  `; export const ${exportName} = __runtime.createIsland(__wrap_${exportName}, "${exportName}", __assets)`,
84
139
  );
85
- } else if (declaration?.type === "FunctionDeclaration") {
140
+ } else if (declaration.type === "FunctionDeclaration") {
86
141
  const fnId = declaration.id;
87
142
  if (
88
143
  !fnId ||
@@ -105,6 +160,10 @@ export const islands = (): Plugin[] => {
105
160
  }
106
161
  },
107
162
  }).visit(program);
163
+
164
+ for (const warning of warnings) {
165
+ this.warn(warning);
166
+ }
108
167
  }),
109
168
  },
110
169
  },
@@ -113,8 +172,9 @@ export const islands = (): Plugin[] => {
113
172
  transform: {
114
173
  order: "post",
115
174
  handler: withMagicString(function (s) {
116
- if (s.original.includes("__island_raw_import__")) {
117
- s.replaceAll("__island_raw_import__", "import");
175
+ // the token is namespaced to avoid colliding with user code
176
+ if (s.original.includes("__yamf_raw_import__")) {
177
+ s.replaceAll("__yamf_raw_import__", "import");
118
178
  }
119
179
  }),
120
180
  },
@@ -0,0 +1,27 @@
1
+ import type { NitroModule } from "nitro/types";
2
+
3
+ // Nitro's prerenderer is a standalone rolldown build (`nitro-prerender` preset)
4
+ // that cannot run yamf's vite plugins, so `virtual:yamf:*` modules, the islands
5
+ // transform, and the vite asset manifests do not exist there. Its auto-detected
6
+ // serverEntry (src/server.tsx) would be bundled by rolldown and crash the
7
+ // worker at init (`Received protocol 'virtual:'`). Disabling it lets prerender
8
+ // requests fall through to the inherited renderer — nitro's vite ssr-renderer —
9
+ // which dispatches them into the vite-built ssr service bundle: the exact code
10
+ // path the production server uses.
11
+ export const prerenderModule: NitroModule = {
12
+ name: "yamf:prerender",
13
+ setup(nitro) {
14
+ nitro.hooks.hook("prerender:config", config => {
15
+ config.serverEntry = false;
16
+ });
17
+
18
+ nitro.hooks.hook("prerender:generate", route => {
19
+ // static hosts (github-pages & friends) expect the not-found document
20
+ // at exactly /404.html; nitro would write /404 as 404/index.html
21
+ // (autoSubfolderIndex)
22
+ if (route.route === "/404" && !route.error && !route.skip) {
23
+ route.fileName = "404.html";
24
+ }
25
+ });
26
+ },
27
+ };
@@ -1,3 +1,4 @@
1
+ import { resolve } from "node:path";
1
2
  import { resolveModulePath } from "exsolve";
2
3
  import type { Plugin } from "vite";
3
4
  import { js } from "../shared/utils";
@@ -8,8 +9,15 @@ export const virtualRoot = (): Plugin => {
8
9
  const virtualModuleId = "virtual:yamf:root";
9
10
  const resolvedVirtualModuleId = "\0" + virtualModuleId;
10
11
 
12
+ // resolved against the vite root (not cwd) so running vite from another
13
+ // working directory does not silently skip the root layout
14
+ let root = process.cwd();
15
+
11
16
  return {
12
17
  name: "yamf:virtual-root",
18
+ configResolved(config) {
19
+ root = config.root;
20
+ },
13
21
  resolveId(id) {
14
22
  if (id === virtualModuleId) {
15
23
  return resolvedVirtualModuleId;
@@ -22,13 +30,15 @@ export const virtualRoot = (): Plugin => {
22
30
  return;
23
31
  }
24
32
 
25
- const root = resolveModulePath(ROOT_PATH, {
33
+ const rootPath = resolve(root, ROOT_PATH);
34
+
35
+ const rootModule = resolveModulePath(rootPath, {
26
36
  try: true,
27
37
  suffixes: ["", "/index"],
28
38
  extensions: [".tsx", ".jsx", ".ts", ".js", ".mjs", ".cjs", ".mts", ".cts"],
29
39
  });
30
40
 
31
- if (!root) {
41
+ if (!rootModule) {
32
42
  return js`
33
43
  const Root = null;
34
44
  const rootAssets = null;
@@ -38,8 +48,8 @@ export { Root, rootAssets };
38
48
  }
39
49
 
40
50
  return js`
41
- import Root from "${ROOT_PATH}";
42
- import rootAssets from "${ROOT_PATH}?assets=ssr";
51
+ import Root from "${rootModule}";
52
+ import rootAssets from "${rootModule}?assets=ssr";
43
53
 
44
54
  export { Root, rootAssets };
45
55
  `.trim();
@@ -1,5 +1,5 @@
1
1
  import { readFile } from "node:fs/promises";
2
- import { relative } from "node:path";
2
+ import { isAbsolute, relative, resolve } from "node:path";
3
3
  import type { Plugin } from "vite";
4
4
  import { js } from "../shared/utils";
5
5
 
@@ -19,8 +19,16 @@ export const virtualTemplate = (): Plugin => {
19
19
  const virtualModuleId = "virtual:yamf:template";
20
20
  const resolvedVirtualModuleId = "\0" + virtualModuleId;
21
21
 
22
+ // resolved against the vite root (not cwd) so running vite from another
23
+ // working directory — monorepo tooling, turbo, etc — does not silently
24
+ // fall back to the default template
25
+ let root = process.cwd();
26
+
22
27
  return {
23
28
  name: "yamf:virtual-template",
29
+ configResolved(config) {
30
+ root = config.root;
31
+ },
24
32
  resolveId(id) {
25
33
  if (id === virtualModuleId) {
26
34
  return resolvedVirtualModuleId;
@@ -34,7 +42,7 @@ export const virtualTemplate = (): Plugin => {
34
42
  }
35
43
 
36
44
  try {
37
- const template = await readFile(TEMPLATE_PATH, "utf8");
45
+ const template = await readFile(resolve(root, TEMPLATE_PATH), "utf8");
38
46
 
39
47
  return js`export const template = ${JSON.stringify(template)};`;
40
48
  } catch (error) {
@@ -46,7 +54,11 @@ export const virtualTemplate = (): Plugin => {
46
54
  }
47
55
  },
48
56
  handleHotUpdate({ file, server }) {
49
- if (relative(file, TEMPLATE_PATH) === "") {
57
+ if (!isAbsolute(file)) {
58
+ return;
59
+ }
60
+
61
+ if (relative(resolve(root, TEMPLATE_PATH), file) === "") {
50
62
  const mod = server.moduleGraph.getModuleById(resolvedVirtualModuleId);
51
63
 
52
64
  if (mod) {