fuzor 0.5.1 → 0.5.2

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 (291) hide show
  1. package/dist/browser.d.ts +2 -0
  2. package/dist/browser.d.ts.map +1 -1
  3. package/dist/browser.js +1 -0
  4. package/dist/browser.js.map +1 -1
  5. package/dist/cli.js +10 -0
  6. package/dist/cli.js.map +1 -1
  7. package/dist/compiler/data-document.d.ts +5 -1
  8. package/dist/compiler/data-document.d.ts.map +1 -1
  9. package/dist/compiler/data-document.js +36 -7
  10. package/dist/compiler/data-document.js.map +1 -1
  11. package/dist/compiler/docs.d.ts.map +1 -1
  12. package/dist/compiler/docs.js +49 -11
  13. package/dist/compiler/docs.js.map +1 -1
  14. package/dist/compiler/index.d.ts +2 -0
  15. package/dist/compiler/index.d.ts.map +1 -1
  16. package/dist/compiler/index.js +23 -6
  17. package/dist/compiler/index.js.map +1 -1
  18. package/dist/compiler/islands.d.ts +1 -0
  19. package/dist/compiler/islands.d.ts.map +1 -1
  20. package/dist/compiler/native-authoring.d.ts +45 -0
  21. package/dist/compiler/native-authoring.d.ts.map +1 -0
  22. package/dist/compiler/native-authoring.js +576 -0
  23. package/dist/compiler/native-authoring.js.map +1 -0
  24. package/dist/compiler/native-manifest.d.ts +33 -0
  25. package/dist/compiler/native-manifest.d.ts.map +1 -0
  26. package/dist/compiler/native-manifest.js +53 -0
  27. package/dist/compiler/native-manifest.js.map +1 -0
  28. package/dist/compiler/native-ranges.d.ts +8 -0
  29. package/dist/compiler/native-ranges.d.ts.map +1 -0
  30. package/dist/compiler/native-ranges.js +92 -0
  31. package/dist/compiler/native-ranges.js.map +1 -0
  32. package/dist/compiler/native-scripts.js +2 -2
  33. package/dist/compiler/native-scripts.js.map +1 -1
  34. package/dist/compiler/page-scripts.d.ts +6 -0
  35. package/dist/compiler/page-scripts.d.ts.map +1 -1
  36. package/dist/compiler/page-scripts.js +25 -6
  37. package/dist/compiler/page-scripts.js.map +1 -1
  38. package/dist/compiler/route-policy.d.ts +1 -0
  39. package/dist/compiler/route-policy.d.ts.map +1 -1
  40. package/dist/compiler/route-policy.js +4 -1
  41. package/dist/compiler/route-policy.js.map +1 -1
  42. package/dist/compiler/server-actions.d.ts +9 -0
  43. package/dist/compiler/server-actions.d.ts.map +1 -0
  44. package/dist/compiler/server-actions.js +65 -0
  45. package/dist/compiler/server-actions.js.map +1 -0
  46. package/dist/data-bindings.d.ts +4 -0
  47. package/dist/data-bindings.d.ts.map +1 -0
  48. package/dist/data-bindings.js +16 -0
  49. package/dist/data-bindings.js.map +1 -0
  50. package/dist/dom-ownership.d.ts +5 -0
  51. package/dist/dom-ownership.d.ts.map +1 -0
  52. package/dist/dom-ownership.js +49 -0
  53. package/dist/dom-ownership.js.map +1 -0
  54. package/dist/index.d.ts +2 -0
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +1 -0
  57. package/dist/index.js.map +1 -1
  58. package/dist/internal-specifiers.d.ts +3 -0
  59. package/dist/internal-specifiers.d.ts.map +1 -0
  60. package/dist/internal-specifiers.js +22 -0
  61. package/dist/internal-specifiers.js.map +1 -0
  62. package/dist/islands/react.d.ts +0 -6
  63. package/dist/islands/react.d.ts.map +1 -1
  64. package/dist/islands/react.js +9 -26
  65. package/dist/islands/react.js.map +1 -1
  66. package/dist/json-value.d.ts +4 -0
  67. package/dist/json-value.d.ts.map +1 -0
  68. package/dist/json-value.js +10 -0
  69. package/dist/json-value.js.map +1 -0
  70. package/dist/native-expressions.d.ts +34 -0
  71. package/dist/native-expressions.d.ts.map +1 -0
  72. package/dist/native-expressions.js +118 -0
  73. package/dist/native-expressions.js.map +1 -0
  74. package/dist/native-manifest.d.ts +7 -0
  75. package/dist/native-manifest.d.ts.map +1 -0
  76. package/dist/native-manifest.js +4 -0
  77. package/dist/native-manifest.js.map +1 -0
  78. package/dist/native-ranges.d.ts +25 -0
  79. package/dist/native-ranges.d.ts.map +1 -0
  80. package/dist/native-ranges.js +51 -0
  81. package/dist/native-ranges.js.map +1 -0
  82. package/dist/native-state.d.ts +14 -0
  83. package/dist/native-state.d.ts.map +1 -0
  84. package/dist/native-state.js +27 -0
  85. package/dist/native-state.js.map +1 -0
  86. package/dist/route-path.d.ts +2 -0
  87. package/dist/route-path.d.ts.map +1 -1
  88. package/dist/route-path.js +2 -0
  89. package/dist/route-path.js.map +1 -1
  90. package/dist/runtime/binding-updates.d.ts +11 -0
  91. package/dist/runtime/binding-updates.d.ts.map +1 -0
  92. package/dist/runtime/binding-updates.js +45 -0
  93. package/dist/runtime/binding-updates.js.map +1 -0
  94. package/dist/runtime/data.d.ts +28 -1
  95. package/dist/runtime/data.d.ts.map +1 -1
  96. package/dist/runtime/data.js +101 -25
  97. package/dist/runtime/data.js.map +1 -1
  98. package/dist/runtime/docs-search.d.ts +20 -4
  99. package/dist/runtime/docs-search.d.ts.map +1 -1
  100. package/dist/runtime/docs-search.js +135 -159
  101. package/dist/runtime/docs-search.js.map +1 -1
  102. package/dist/runtime/docs-ui.d.ts +3 -1
  103. package/dist/runtime/docs-ui.d.ts.map +1 -1
  104. package/dist/runtime/docs-ui.js +19 -29
  105. package/dist/runtime/docs-ui.js.map +1 -1
  106. package/dist/runtime/islands.d.ts.map +1 -1
  107. package/dist/runtime/islands.js +2 -1
  108. package/dist/runtime/islands.js.map +1 -1
  109. package/dist/runtime/native-graph.d.ts +15 -0
  110. package/dist/runtime/native-graph.d.ts.map +1 -0
  111. package/dist/runtime/native-graph.js +103 -0
  112. package/dist/runtime/native-graph.js.map +1 -0
  113. package/dist/runtime/native-manifest.d.ts +17 -0
  114. package/dist/runtime/native-manifest.d.ts.map +1 -0
  115. package/dist/runtime/native-manifest.js +111 -0
  116. package/dist/runtime/native-manifest.js.map +1 -0
  117. package/dist/runtime/native-ranges.d.ts +8 -0
  118. package/dist/runtime/native-ranges.d.ts.map +1 -0
  119. package/dist/runtime/native-ranges.js +100 -0
  120. package/dist/runtime/native-ranges.js.map +1 -0
  121. package/dist/runtime/native-stream-reader.d.ts +7 -0
  122. package/dist/runtime/native-stream-reader.d.ts.map +1 -0
  123. package/dist/runtime/native-stream-reader.js +79 -0
  124. package/dist/runtime/native-stream-reader.js.map +1 -0
  125. package/dist/runtime/native-stream.d.ts +5 -0
  126. package/dist/runtime/native-stream.d.ts.map +1 -0
  127. package/dist/runtime/native-stream.js +14 -0
  128. package/dist/runtime/native-stream.js.map +1 -0
  129. package/dist/runtime/native.d.ts +41 -0
  130. package/dist/runtime/native.d.ts.map +1 -1
  131. package/dist/runtime/native.js +176 -55
  132. package/dist/runtime/native.js.map +1 -1
  133. package/dist/runtime/page-scripts.d.ts.map +1 -1
  134. package/dist/runtime/page-scripts.js +8 -3
  135. package/dist/runtime/page-scripts.js.map +1 -1
  136. package/dist/runtime/router.d.ts.map +1 -1
  137. package/dist/runtime/router.js +6 -1
  138. package/dist/runtime/router.js.map +1 -1
  139. package/dist/runtime/server-actions.d.ts +5 -0
  140. package/dist/runtime/server-actions.d.ts.map +1 -0
  141. package/dist/runtime/server-actions.js +79 -0
  142. package/dist/runtime/server-actions.js.map +1 -0
  143. package/dist/server-actions.d.ts +18 -0
  144. package/dist/server-actions.d.ts.map +1 -0
  145. package/dist/server-actions.js +138 -0
  146. package/dist/server-actions.js.map +1 -0
  147. package/dist/server-components/closures.d.ts +1 -12
  148. package/dist/server-components/closures.d.ts.map +1 -1
  149. package/dist/server-components/closures.js +0 -75
  150. package/dist/server-components/closures.js.map +1 -1
  151. package/dist/server-components/continuation.d.ts +7 -0
  152. package/dist/server-components/continuation.d.ts.map +1 -0
  153. package/dist/server-components/continuation.js +17 -0
  154. package/dist/server-components/continuation.js.map +1 -0
  155. package/dist/server-components/environment.js +1 -1
  156. package/dist/server-components/environment.js.map +1 -1
  157. package/dist/server-components/protocol.d.ts +5 -1
  158. package/dist/server-components/protocol.d.ts.map +1 -1
  159. package/dist/server-components/protocol.js +9 -1
  160. package/dist/server-components/protocol.js.map +1 -1
  161. package/dist/server-components/server.d.ts +4 -7
  162. package/dist/server-components/server.d.ts.map +1 -1
  163. package/dist/server-components/server.js +2 -185
  164. package/dist/server-components/server.js.map +1 -1
  165. package/dist/server-components/stream-server.d.ts +6 -0
  166. package/dist/server-components/stream-server.d.ts.map +1 -0
  167. package/dist/server-components/stream-server.js +197 -0
  168. package/dist/server-components/stream-server.js.map +1 -0
  169. package/dist/server-components/stream.d.ts +16 -3
  170. package/dist/server-components/stream.d.ts.map +1 -1
  171. package/dist/server-components/stream.js +31 -4
  172. package/dist/server-components/stream.js.map +1 -1
  173. package/dist/server-components/transport.d.ts +20 -0
  174. package/dist/server-components/transport.d.ts.map +1 -0
  175. package/dist/server-components/transport.js +2 -0
  176. package/dist/server-components/transport.js.map +1 -0
  177. package/dist/server-components/updates.d.ts +56 -0
  178. package/dist/server-components/updates.d.ts.map +1 -0
  179. package/dist/server-components/updates.js +319 -0
  180. package/dist/server-components/updates.js.map +1 -0
  181. package/dist/server-data-protocol.d.ts +14 -0
  182. package/dist/server-data-protocol.d.ts.map +1 -0
  183. package/dist/server-data-protocol.js +15 -0
  184. package/dist/server-data-protocol.js.map +1 -0
  185. package/dist/server-data.d.ts +18 -0
  186. package/dist/server-data.d.ts.map +1 -1
  187. package/dist/server-data.js.map +1 -1
  188. package/dist/server-native-stream.d.ts +5 -0
  189. package/dist/server-native-stream.d.ts.map +1 -0
  190. package/dist/server-native-stream.js +39 -0
  191. package/dist/server-native-stream.js.map +1 -0
  192. package/dist/server-native.d.ts +24 -0
  193. package/dist/server-native.d.ts.map +1 -0
  194. package/dist/server-native.js +67 -0
  195. package/dist/server-native.js.map +1 -0
  196. package/dist/server-reactivity.d.ts +13 -0
  197. package/dist/server-reactivity.d.ts.map +1 -0
  198. package/dist/server-reactivity.js +49 -0
  199. package/dist/server-reactivity.js.map +1 -0
  200. package/dist/server.d.ts +8 -4
  201. package/dist/server.d.ts.map +1 -1
  202. package/dist/server.js +55 -16
  203. package/dist/server.js.map +1 -1
  204. package/dist/ui/behaviors/accordion.d.ts.map +1 -1
  205. package/dist/ui/behaviors/accordion.js +4 -3
  206. package/dist/ui/behaviors/accordion.js.map +1 -1
  207. package/dist/ui/behaviors/collapsible.d.ts.map +1 -1
  208. package/dist/ui/behaviors/collapsible.js +2 -1
  209. package/dist/ui/behaviors/collapsible.js.map +1 -1
  210. package/dist/ui/behaviors/dialog.d.ts.map +1 -1
  211. package/dist/ui/behaviors/dialog.js +5 -4
  212. package/dist/ui/behaviors/dialog.js.map +1 -1
  213. package/dist/ui/behaviors/menu.d.ts.map +1 -1
  214. package/dist/ui/behaviors/menu.js +4 -3
  215. package/dist/ui/behaviors/menu.js.map +1 -1
  216. package/dist/ui/behaviors/select.d.ts +23 -1
  217. package/dist/ui/behaviors/select.d.ts.map +1 -1
  218. package/dist/ui/behaviors/select.js +81 -11
  219. package/dist/ui/behaviors/select.js.map +1 -1
  220. package/dist/ui/behaviors/switch.d.ts.map +1 -1
  221. package/dist/ui/behaviors/switch.js +2 -1
  222. package/dist/ui/behaviors/switch.js.map +1 -1
  223. package/dist/ui/behaviors/toolbar.d.ts.map +1 -1
  224. package/dist/ui/behaviors/toolbar.js +2 -1
  225. package/dist/ui/behaviors/toolbar.js.map +1 -1
  226. package/dist/ui/catalogue.d.ts +1 -1
  227. package/dist/ui/components/accordion/accordion.d.ts.map +1 -1
  228. package/dist/ui/components/accordion/accordion.js +3 -2
  229. package/dist/ui/components/accordion/accordion.js.map +1 -1
  230. package/dist/ui/components/collapsible/collapsible.d.ts.map +1 -1
  231. package/dist/ui/components/collapsible/collapsible.js +2 -1
  232. package/dist/ui/components/collapsible/collapsible.js.map +1 -1
  233. package/dist/ui/components/dialog/dialog.d.ts.map +1 -1
  234. package/dist/ui/components/dialog/dialog.js +4 -3
  235. package/dist/ui/components/dialog/dialog.js.map +1 -1
  236. package/dist/ui/components/navigation/breadcrumbs.d.ts.map +1 -1
  237. package/dist/ui/components/navigation/breadcrumbs.js +2 -1
  238. package/dist/ui/components/navigation/breadcrumbs.js.map +1 -1
  239. package/dist/ui/components/select/select.d.ts +20 -0
  240. package/dist/ui/components/select/select.d.ts.map +1 -1
  241. package/dist/ui/components/select/select.js +20 -0
  242. package/dist/ui/components/select/select.js.map +1 -1
  243. package/dist/ui/components/tabs/tabs.d.ts.map +1 -1
  244. package/dist/ui/components/tabs/tabs.js +6 -5
  245. package/dist/ui/components/tabs/tabs.js.map +1 -1
  246. package/dist/ui/core/brand.d.ts +33 -0
  247. package/dist/ui/core/brand.d.ts.map +1 -0
  248. package/dist/ui/core/brand.js +96 -0
  249. package/dist/ui/core/brand.js.map +1 -0
  250. package/dist/ui/core/component-styles.d.ts.map +1 -1
  251. package/dist/ui/core/component-styles.js +14 -8
  252. package/dist/ui/core/component-styles.js.map +1 -1
  253. package/dist/ui/core/element.d.ts.map +1 -1
  254. package/dist/ui/core/element.js +2 -3
  255. package/dist/ui/core/element.js.map +1 -1
  256. package/dist/ui/core/index.d.ts +1 -0
  257. package/dist/ui/core/index.d.ts.map +1 -1
  258. package/dist/ui/core/index.js +1 -0
  259. package/dist/ui/core/index.js.map +1 -1
  260. package/dist/ui/core/layout-styles.d.ts.map +1 -1
  261. package/dist/ui/core/layout-styles.js +12 -6
  262. package/dist/ui/core/layout-styles.js.map +1 -1
  263. package/dist/ui/core/parts.d.ts.map +1 -1
  264. package/dist/ui/core/parts.js +5 -4
  265. package/dist/ui/core/parts.js.map +1 -1
  266. package/dist/ui/core/portal.d.ts.map +1 -1
  267. package/dist/ui/core/portal.js +17 -1
  268. package/dist/ui/core/portal.js.map +1 -1
  269. package/dist/ui/machine-state.d.ts +11 -11
  270. package/dist/ui/state.d.ts +1 -1
  271. package/dist/vite.d.ts +5 -10
  272. package/dist/vite.d.ts.map +1 -1
  273. package/dist/vite.js +112 -114
  274. package/dist/vite.js.map +1 -1
  275. package/package.json +2 -12
  276. package/dist/server-components/browser.d.ts +0 -75
  277. package/dist/server-components/browser.d.ts.map +0 -1
  278. package/dist/server-components/browser.js +0 -558
  279. package/dist/server-components/browser.js.map +0 -1
  280. package/dist/server-components/compiler.d.ts +0 -6
  281. package/dist/server-components/compiler.d.ts.map +0 -1
  282. package/dist/server-components/compiler.js +0 -118
  283. package/dist/server-components/compiler.js.map +0 -1
  284. package/dist/server-components/navigation.d.ts +0 -10
  285. package/dist/server-components/navigation.d.ts.map +0 -1
  286. package/dist/server-components/navigation.js +0 -105
  287. package/dist/server-components/navigation.js.map +0 -1
  288. package/dist/server-components/references.d.ts +0 -22
  289. package/dist/server-components/references.d.ts.map +0 -1
  290. package/dist/server-components/references.js +0 -94
  291. package/dist/server-components/references.js.map +0 -1
package/dist/vite.js CHANGED
@@ -3,12 +3,13 @@ import { compileDataDocument } from './compiler/data-document.js';
3
3
  import { parseRoutePolicy } from './compiler/route-policy.js';
4
4
  import { extractPageScripts, compilePageScript } from "./compiler/page-scripts.js";
5
5
  import { compileNativeScript } from "./compiler/native-scripts.js";
6
+ import { renderNativeBinding } from "./native-ranges.js";
7
+ import { compileAuthoredNativeScript, prepareNativeScripts, htmlServerPlugin, assertNoServerScripts, resolveHtmlServer, loadHtmlServer } from "./compiler/native-authoring.js";
8
+ import { emitNativeManifests } from "./compiler/native-manifest.js";
6
9
  import { compileWorkflows, workflowPlugin } from "./workflow/compiler.js";
10
+ import { resolveInternalSpecifier } from "./internal-specifiers.js";
7
11
  import { assertBrowserViewImport } from "./server-components/environment.js";
8
- import { replaceClosures } from "./server-components/closures.js";
9
- import { discoverViewReferences, browserReferencesCode } from "./server-components/references.js";
10
12
  import { randomUUID } from "node:crypto";
11
- import { compileFragment, compiledFragmentsPlugin } from "./server-components/compiler.js";
12
13
  import { makeRouteMatches } from "./runtime/route-matches.js";
13
14
  import { makeClientRouteMatcher } from "./runtime/client-routes.js";
14
15
  import { errorHtml, notFoundHtml } from "./boundaries.js";
@@ -35,6 +36,7 @@ import { toWebRequest, writeWebResponse } from "./server/node.js";
35
36
  import { routeTypes } from "./runtime/href.js";
36
37
  import { writeElectronScaffold } from "./electron.js";
37
38
  import { createEnvironmentModuleRuntime, detectViteEnvironmentApi, invalidateEnvironmentModule, normalizeEnvironmentConfiguration } from "./vite-environments.js";
39
+ import { validateBrandPrefix } from "./ui/core/brand.js";
38
40
  const virtualId = "virtual:fuzor/client";
39
41
  const rendererModules = {
40
42
  "virtual:fuzor/compiler/react": 'export { createElement } from "react"; export { renderToString, renderToStaticMarkup } from "react-dom/server";',
@@ -287,13 +289,6 @@ function replaceOutlet(document, content) {
287
289
  throw new Error(`index.html must contain ${outletStart} and ${outletEnd}.`);
288
290
  return document.slice(0, start + outletStart.length) + content + document.slice(end);
289
291
  }
290
- function serverViewDocument(html) {
291
- let found = false;
292
- const document = html.replace(/(<fuzor-router\b[^>]*>)[\s\S]*?<\/fuzor-router>/, (_match, open) => { found = true; return open + "<!--fuzor-view--></fuzor-router>"; });
293
- if (!found || !document.includes("</body>"))
294
- throw new Error("Server views require a compiled outlet and a document body.");
295
- return document.replace("</body>", "<!--fuzor-snapshot--></body>");
296
- }
297
292
  /**
298
293
  * The plugin creates private Vite servers to load island and `paths` modules. Their config file loads
299
294
  * this plugin again; those nested instances only provide virtual modules and never compile the project.
@@ -304,17 +299,19 @@ const nestedServer = new AsyncLocalStorage();
304
299
  * open an HMR WebSocket (two would compete for its port, and a failed one never closes) and must not share the
305
300
  * dev server's dependency cache (each would re-optimize and invalidate the other's pre-bundled files).
306
301
  */
307
- export function nestedServerConfig(config) {
302
+ export function nestedServerConfig(config, nativeModules) {
308
303
  return {
309
304
  root: config.root,
310
305
  configFile: config.configFile ?? false,
311
- plugins: [workflowPlugin(), {
306
+ plugins: [{ name: "fuzor:internal-specifiers", enforce: "pre", resolveId: id => resolveInternalSpecifier(id) }, htmlServerPlugin(nativeModules), workflowPlugin(), {
312
307
  name: "fuzor:nested-renderers",
313
308
  resolveId(id) { if (Object.hasOwn(rendererModules, id))
314
309
  return `\0${id}`; },
315
310
  load(id) { if (id.startsWith("\0") && Object.hasOwn(rendererModules, id.slice(1)))
316
311
  return rendererModules[id.slice(1)]; }
317
312
  }],
313
+ // Fuzor's server runtime is one module graph: compiler-owned modules and `fuzor/server` must not load twice.
314
+ ssr: { noExternal: ["fuzor"] },
318
315
  server: { middlewareMode: true, hmr: false, ws: false },
319
316
  cacheDir: path.join(config.cacheDir, "fuzor-nested"),
320
317
  optimizeDeps: { noDiscovery: true, include: [] },
@@ -380,20 +377,6 @@ export function resolveFuzorProfile(options = {}, env = process.env, serverModul
380
377
  const electron = options.electron ?? envElectron ?? requestedTarget === "electron";
381
378
  const target = (requestedTarget === "electron" ? "spa" : requestedTarget);
382
379
  const serverReasons = serverModules.map(file => file.startsWith("api/") ? `${file} defines an API endpoint` : file.endsWith(".html") ? `${file} declares request rendering` : `${file} defines server data`);
383
- if (options.serverComponents) {
384
- makeClientRouteMatcher(Object.fromEntries(options.serverComponents.routes.map(route => [route, "/"])));
385
- for (const entry of [options.serverComponents.server, options.serverComponents.client, options.serverComponents.references].filter((value) => value !== undefined)) {
386
- if (!entry || path.isAbsolute(entry) || entry.split(/[\\/]/).includes(".."))
387
- throw new Error("Server component entries must be paths inside the project.");
388
- }
389
- if (!options.serverComponents.client && !options.serverComponents.references)
390
- throw new Error("Server components require a client entry or generated reference directory.");
391
- if (!options.serverComponents.routes.length)
392
- throw new Error("Server components need at least one route.");
393
- if (options.navigation === "embedded")
394
- throw new Error("Server components require HTTP navigation.");
395
- serverReasons.push(`${options.serverComponents.server} defines server components`);
396
- }
397
380
  const inline = electron || packaged ? true : options.inline ?? envInline ?? false;
398
381
  if (inline && target !== "spa")
399
382
  throw new Error("Inline output is only supported for the SPA target.");
@@ -418,27 +401,11 @@ export function resolveFuzorProfile(options = {}, env = process.env, serverModul
418
401
  return { target, mixed, backendOutDir: ".fuzor/dist-backend", platform, server, inline, outDir: electron ? ".fuzor/dist-desktop" : platform !== "web" ? `.fuzor/dist-${platform}` : mixed ? ".fuzor/web/dist" : `.fuzor/web/dist-${target}`, serverReasons, adapter, ...(electron ? { electron: true } : {}) };
419
402
  }
420
403
  export function fuzorPlugin(options = {}) {
404
+ if (options.ui?.brand !== undefined)
405
+ validateBrandPrefix(options.ui.brand);
421
406
  const nested = nestedServer.getStore() === true;
407
+ const nativeServerModules = new Map();
422
408
  let viewBuildId = randomUUID();
423
- let referencesPromise;
424
- const references = () => referencesPromise ??= discoverViewReferences(vite.root, options.serverComponents.references);
425
- const referenceStub = (file, refs) => {
426
- const relative = path.relative(vite.root, file).split(path.sep).join("/");
427
- const client = refs.clients.find(ref => ref.file === relative);
428
- if (client)
429
- return `export default ${JSON.stringify({ id: client.id })};`;
430
- const closures = refs.closures.filter(ref => ref.file === relative);
431
- if (closures.length)
432
- return readFile(file, "utf8").then(code => replaceClosures(code, closures));
433
- const handlers = refs.handlers.filter(ref => ref.file === relative);
434
- return handlers.length ? handlers.map(ref => `export const ${ref.name}=${JSON.stringify({ id: ref.id })};`).join("\n") : undefined;
435
- };
436
- const generatedViews = async (views) => {
437
- if (!options.serverComponents?.references)
438
- return views;
439
- const refs = await references();
440
- return { ...views, manifest: { buildId: viewBuildId, templates: refs.templates }, components: refs.clients.map(ref => ref.id), handlers: [...refs.handlers.map(ref => ref.id), ...refs.closures.map(ref => ref.id)] };
441
- };
442
409
  const configuredEnvironments = normalizeEnvironmentConfiguration(options.experimental?.environments);
443
410
  const environmentNames = {
444
411
  client: configuredEnvironments.client ?? "client",
@@ -481,6 +448,8 @@ export function fuzorPlugin(options = {}) {
481
448
  let project;
482
449
  let pageScripts = [];
483
450
  const nativeHandlers = new Map();
451
+ const authoredNative = new Map();
452
+ let nativeAdmissions = new Map();
484
453
  let inlineModuleFiles = [];
485
454
  let scriptRoutes = [];
486
455
  const routeScriptId = (route) => `virtual:fuzor/route-client/${encodeURIComponent(route.path)}/${route.path === "/" ? "index" : route.path.split("/").filter(Boolean).join("-").replace(/[^a-zA-Z0-9_-]/g, "_")}-client.js`;
@@ -503,7 +472,7 @@ export function fuzorPlugin(options = {}) {
503
472
  let solidHydrationScript = "";
504
473
  const pending = (async () => {
505
474
  let owned;
506
- const createModuleServer = () => nestedServer.run(true, () => createServer(nestedServerConfig(vite)));
475
+ const createModuleServer = () => nestedServer.run(true, () => createServer(nestedServerConfig(vite, nativeServerModules)));
507
476
  const moduleServer = () => devServer ? Promise.resolve(devServer) : owned ??= createModuleServer();
508
477
  // client:none analysis reads client transforms without HMR code, which the dev server would add. It has its
509
478
  // own server: client transforms wait for pending requests to settle, and preview renders on the module
@@ -672,6 +641,8 @@ export function fuzorPlugin(options = {}) {
672
641
  const compileRaw = async (signal, loadModule) => {
673
642
  let compiled = await compileProject(vite.root, { ...(options.markdown ? { markdown: options.markdown } : {}), ...(options.redirects ? { redirects: options.redirects } : {}), platform: profile.platform, signal, loadModule, cache: compileCache })
674
643
  .catch(async (error) => { throw await withViteLocation(error); });
644
+ nativeServerModules.clear();
645
+ compiled = await prepareNativeScripts(compiled, vite.root, loadModule, nativeServerModules);
675
646
  const entry = options.entry ?? process.env.FUZOR_ENTRY;
676
647
  if (entry) {
677
648
  if (!inline || target !== "spa")
@@ -688,15 +659,20 @@ export function fuzorPlugin(options = {}) {
688
659
  if (!compiled.routes.some(route => route.path === "/"))
689
660
  throw new Error("Application entry must have a static root template; request-rendered routes require an HTTP rendering deployment.");
690
661
  }
662
+ const inferredServer = compiled.routes.filter(route => route.serverData || route.policy?.rendering === "request");
663
+ if (inferredServer.length) {
664
+ profile = resolveFuzorProfile(options, process.env, [...new Set([...serverModuleFiles, ...inferredServer.map(route => path.relative(vite.root, route.source).split(path.sep).join("/"))])]);
665
+ withServer = profile.server;
666
+ }
691
667
  const deployment = options.deployment ?? process.env.FUZOR_DEPLOYMENT;
692
668
  const requestRoutes = compiled.routes.filter(route => route.policy?.rendering === "request");
693
669
  if (deployment && !["static", "github-pages", "node", "cloudflare"].includes(deployment))
694
670
  throw new Error("Unknown Fuzor deployment target: " + deployment);
695
- if ((deployment === "static" || deployment === "github-pages") && (requestRoutes.length || options.serverComponents?.routes.length))
696
- throw new Error(`Route ${(requestRoutes[0]?.path ?? options.serverComponents.routes[0])} requires request-time server rendering. ${deployment} supports static files only. Pre-render this route, regenerate and republish its files, or choose node/cloudflare rendering. Remote API calls from static SPAs are allowed.`);
671
+ if ((deployment === "static" || deployment === "github-pages") && requestRoutes.length)
672
+ throw new Error(`Route ${requestRoutes[0].path} requires request-time server rendering. ${deployment} supports static files only. Pre-render this route, regenerate and republish its files, or choose node/cloudflare rendering. Remote API calls from static SPAs are allowed.`);
697
673
  for (const route of requestRoutes)
698
- if (route.requestPattern && route.policy?.navigation === "spa" && !options.serverComponents?.routes.some(pattern => makeClientRouteMatcher({ [pattern]: "/" })(route.path)))
699
- throw new Error(`${route.source}: unenumerated request routes inside an SPA require a server-component renderer. Use navigation=document or register serverComponents.`);
674
+ if (route.requestPattern && route.policy?.navigation === "spa" && !route.serverData?.startsWith("virtual:fuzor/native-data/"))
675
+ throw new Error(`${route.source}: unenumerated request routes inside an SPA need an HTML server component. Add one or use navigation=document.`);
700
676
  if (options.routeTree) {
701
677
  if (target !== "spa" && !profile.mixed)
702
678
  throw new Error("routeTree requires a SPA target.");
@@ -716,8 +692,8 @@ export function fuzorPlugin(options = {}) {
716
692
  check(options.routeTree);
717
693
  }
718
694
  if (options.clientRoutes && Object.keys(options.clientRoutes).length) {
719
- if (target !== "spa" || !options.serverComponents && (withServer || apiFiles.length > 0 || serverModuleFiles.length > 0))
720
- throw new Error("clientRoutes requires a static SPA target.");
695
+ if (target !== "spa")
696
+ throw new Error("clientRoutes requires an SPA target.");
721
697
  makeClientRouteMatcher(options.clientRoutes);
722
698
  for (const target of Object.values(options.clientRoutes)) {
723
699
  const route = compiled.routes.find(route => route.path === routeKey(target));
@@ -735,7 +711,18 @@ export function fuzorPlugin(options = {}) {
735
711
  const extracted = extractPageScripts(compiled, vite.root);
736
712
  pageScripts = extracted.scripts;
737
713
  scriptRoutes = extracted.routes;
738
- return extracted.project;
714
+ authoredNative.clear();
715
+ nativeHandlers.clear();
716
+ for (const script of pageScripts)
717
+ if (script.native) {
718
+ const compiled = compileAuthoredNativeScript(script.code ?? await readFile(script.source, "utf8"), script, viewBuildId);
719
+ authoredNative.set(script.id, compiled);
720
+ for (const handler of compiled.handlers)
721
+ nativeHandlers.set(handler.id, handler);
722
+ }
723
+ const emitted = emitNativeManifests(extracted.project, scriptRoutes, pageScripts, authoredNative, viewBuildId);
724
+ nativeAdmissions = emitted.admissions;
725
+ return emitted.project;
739
726
  };
740
727
  const scanIslands = async () => {
741
728
  return Effect.runPromise(scanIslandModulesEffect(vite.root, Object.keys(options.runtimes ?? {})));
@@ -743,7 +730,6 @@ export function fuzorPlugin(options = {}) {
743
730
  const refresh = async (server) => {
744
731
  const version = ++revision;
745
732
  viewBuildId = randomUUID();
746
- referencesPromise = undefined;
747
733
  compilation?.abort();
748
734
  project = undefined;
749
735
  projectPromise = undefined;
@@ -810,8 +796,8 @@ export function fuzorPlugin(options = {}) {
810
796
  profile = resolveFuzorProfile(options, process.env, serverModuleFiles);
811
797
  ({ target, inline } = profile);
812
798
  withServer = profile.server;
813
- if (options.clientRoutes && Object.keys(options.clientRoutes).length && (target !== "spa" || withServer && !options.serverComponents))
814
- throw new Error("clientRoutes requires a static SPA target.");
799
+ if (options.clientRoutes && Object.keys(options.clientRoutes).length && target !== "spa")
800
+ throw new Error("clientRoutes requires an SPA target.");
815
801
  const packageJson = await readFile(path.join(path.resolve(config.root ?? process.cwd()), "package.json"), "utf8").then(text => JSON.parse(text), () => ({}));
816
802
  const dependencies = { ...packageJson.dependencies, ...packageJson.devDependencies };
817
803
  const include = ["Cause", "Context", "Deferred", "Effect", "Exit", "Fiber", "Layer", "Schema", "Scope", "reactivity/Atom", "reactivity/AtomRegistry"].map(module => `fuzor > effect/${module}`);
@@ -850,11 +836,14 @@ export function fuzorPlugin(options = {}) {
850
836
  }
851
837
  },
852
838
  async resolveId(id, importer, resolveOptions) {
839
+ const internalFile = resolveInternalSpecifier(id);
840
+ if (internalFile)
841
+ return internalFile;
842
+ const serverModule = resolveHtmlServer(id, resolveOptions.ssr, nativeServerModules);
843
+ if (serverModule)
844
+ return serverModule;
853
845
  if (!resolveOptions.ssr)
854
846
  assertBrowserViewImport(id, importer);
855
- if (id === "fuzor/server-components/server" && !resolveOptions.ssr) {
856
- throw new Error("The server-component evaluator is server-only. Import browser runtime and protocol separately.");
857
- }
858
847
  if (id.startsWith("virtual:fuzor/route-client/"))
859
848
  return `\0${id}`;
860
849
  if (id.startsWith("virtual:fuzor/page-script/"))
@@ -945,6 +934,8 @@ export function fuzorPlugin(options = {}) {
945
934
  }
946
935
  },
947
936
  async load(id, loadOptions) {
937
+ if (id.includes("?fuzor-server-script=") || nativeServerModules.has(id))
938
+ return loadHtmlServer(id, nativeServerModules, file => this.addWatchFile(file));
948
939
  if (id.startsWith("\0virtual:fuzor/route-client/")) {
949
940
  await loadProject();
950
941
  const route = scriptRoutes.find(route => `\0${routeScriptId(route)}` === id);
@@ -959,6 +950,8 @@ export function fuzorPlugin(options = {}) {
959
950
  throw new Error("Unknown Fuzor page script");
960
951
  this.addWatchFile(script.source);
961
952
  const source = script.code ?? await readFile(script.source, "utf8");
953
+ if (script.native)
954
+ throw new Error("Authored native declarations are adopted from HTML, not executable descriptor modules");
962
955
  const native = compileNativeScript(source, script);
963
956
  if (native) {
964
957
  for (const handler of native.handlers)
@@ -974,31 +967,6 @@ export function fuzorPlugin(options = {}) {
974
967
  throw new Error("Unknown native handler");
975
968
  return (await import("vite")).transformWithEsbuild(handler.code, handler.source, { loader: handler.language, target: "es2022" });
976
969
  }
977
- if (id.startsWith("\0fuzor:closure:")) {
978
- const refs = await references();
979
- const closure = refs.closures.find(ref => id === `\0fuzor:closure:${ref.id}.ts`);
980
- if (!closure)
981
- throw new Error("Unknown resumable closure reference");
982
- return (await import("vite")).transformWithEsbuild(closure.code, id, { loader: "ts", target: "es2022" }).then(result => result.code);
983
- }
984
- if (options.serverComponents?.references) {
985
- const refs = await references();
986
- if (id === "\0virtual:fuzor/views") {
987
- if (loadOptions?.ssr)
988
- return `export const manifest=${JSON.stringify({ buildId: viewBuildId, templates: refs.templates })}; export const components=${JSON.stringify(refs.clients.map(ref => ref.id))}; export const handlers=${JSON.stringify([...refs.handlers.map(ref => ref.id), ...refs.closures.map(ref => ref.id)])};`;
989
- return browserReferencesCode(refs, viewBuildId, file => "/" + file);
990
- }
991
- if (loadOptions?.ssr) {
992
- const stub = referenceStub(id.split("?")[0], refs);
993
- if (stub)
994
- return stub;
995
- }
996
- }
997
- if (id.startsWith("\0fuzor:fragment:")) {
998
- const file = id.slice("\0fuzor:fragment:".length, -3);
999
- this.addWatchFile(file);
1000
- return readFile(file, "utf8").then(html => `export default ${JSON.stringify(compileFragment(html))};`);
1001
- }
1002
970
  if (Object.hasOwn(rendererModules, id.slice(1)))
1003
971
  return rendererModules[id.slice(1)];
1004
972
  const runtimeModule = runtimeModules.get(id);
@@ -1007,7 +975,7 @@ export function fuzorPlugin(options = {}) {
1007
975
  if (!loadOptions?.ssr && !nested && vite) {
1008
976
  const file = id.split("?")[0];
1009
977
  const relative = path.relative(vite.root, file).split(path.sep).join("/");
1010
- if (options.serverComponents && path.resolve(vite.root, options.serverComponents.server) === file || serverModuleFiles.includes(relative) || relative.startsWith("api/") || /\.server\.[cm]?[jt]sx?$/.test(relative)) {
978
+ if (serverModuleFiles.includes(relative) || relative.startsWith("api/") || /\.server\.[cm]?[jt]sx?$/.test(relative)) {
1011
979
  throw new Error(`${relative} is server-only and cannot be imported by browser code. Read its data with <fuzor-data> or routeData() instead.`);
1012
980
  }
1013
981
  }
@@ -1015,7 +983,32 @@ export function fuzorPlugin(options = {}) {
1015
983
  return undefined;
1016
984
  await loadProject();
1017
985
  const mixedClient = profile.mixed && project?.routes.some(route => route.policy?.navigation === "spa");
1018
- const scriptRegistry = scriptRoutes.map(route => `${JSON.stringify(route.path)}: () => import(${JSON.stringify(routeScriptId(route))}).catch(reloadOnStaleDeployment)`).join(",\n");
986
+ const scriptRegistry = scriptRoutes.map(route => nativeAdmissions.has(route.path)
987
+ ? `${JSON.stringify(route.path)}: async () => readNativeManifest(outlet, ${JSON.stringify(route.path)}, ${JSON.stringify(viewBuildId)}, ${JSON.stringify(nativeAdmissions.get(route.path))}, nativeLoaders)`
988
+ : `${JSON.stringify(route.path)}: () => import(${JSON.stringify(routeScriptId(route))}).catch(reloadOnStaleDeployment)`).join(",\n");
989
+ const handlerLoaders = new Map();
990
+ const graphLoaders = new Map();
991
+ const cssImports = new Set();
992
+ for (const script of pageScripts) {
993
+ const compiled = authoredNative.get(script.id);
994
+ if (!compiled)
995
+ continue;
996
+ this.addWatchFile(script.source);
997
+ for (const css of compiled.css) {
998
+ const resolved = await this.resolve(css, script.source);
999
+ if (!resolved)
1000
+ throw new Error(`${script.source}: cannot resolve native stylesheet ${css}`);
1001
+ cssImports.add(`import ${JSON.stringify(resolved.id)};`);
1002
+ }
1003
+ for (const [name, id] of compiled.actionIds)
1004
+ if ((script.native.events.some(event => event.action === name) || script.native.effects?.some(effect => effect.action === name)))
1005
+ handlerLoaders.set(id, `${JSON.stringify(id)}: {server: ${Object.hasOwn(script.native.serverActions ?? {}, name)}, load: () => import(${JSON.stringify(`virtual:fuzor/native-handler/${id}.js`)}).catch(reloadOnStaleDeployment)}`);
1006
+ if (compiled.graphId)
1007
+ graphLoaders.set(compiled.graphId, `${JSON.stringify(compiled.graphId)}: (definition, data) => import("fuzor/runtime/native-graph").then(module => module.nativeGraphModule(definition, data, ${JSON.stringify(script.native.computed ?? {})}, ${JSON.stringify(script.native.ranges ?? [])}, [${(script.native.effects ?? []).map(effect => `{dependencies:${JSON.stringify(effect.dependencies)},load:()=>import(${JSON.stringify(`virtual:fuzor/native-handler/${compiled.actionIds.get(effect.action)}.js`)})}`).join(",")}]))`);
1008
+ }
1009
+ const manifestScripts = new Set(scriptRoutes.filter(route => nativeAdmissions.has(route.path)).flatMap(route => route.bindings.filter(binding => !authoredNative.has(binding.id)).map(binding => binding.id)));
1010
+ const nativeData = pageScripts.some(script => script.native?.request);
1011
+ const nativeLoaders = nativeAdmissions.size ? `const nativeLoaders = { ${nativeData ? "data: routeData," : ""} handlers: {${[...handlerLoaders.values()].join(",")}}, graphs: {${[...graphLoaders.values()].join(",")}}, scripts: {${[...manifestScripts].map(id => `${JSON.stringify(id)}: () => import(${JSON.stringify(pageScriptId(pageScripts.find(script => script.id === id)))}).catch(reloadOnStaleDeployment)`).join(",")}} };` : "";
1019
1012
  const registry = islands.map((island) => {
1020
1013
  const load = island.file.endsWith(".vue")
1021
1014
  ? `Promise.all([import(${JSON.stringify(island.file)}), import("fuzor/islands/vue")]).then(([component, adapter]) => adapter.vueIsland(component.default)).catch(reloadOnStaleDeployment)`
@@ -1023,16 +1016,24 @@ export function fuzorPlugin(options = {}) {
1023
1016
  return `${JSON.stringify(island.name)}: () => ${load}`;
1024
1017
  }).join(",\n");
1025
1018
  const withData = target === "mpa" && !!project?.routes.some(route => route.serverData);
1019
+ // Keep every discovered module, including ones introduced by page scripts. An empty static
1020
+ // registry cannot activate an island; retain diagnostics for authored hosts and open server/dev paths.
1021
+ const withIslands = islands.length > 0 || withServer || vite.command === "serve"
1022
+ || [...(project?.routes ?? []), ...(project?.boundaries ?? [])].some(route => /<fuzor-island\b/i.test(route.html));
1023
+ const mountIslandsCode = withIslands ? "mountIslands(outlet, islands, islandErrorHandler, { signal: lifetime.signal })" : "Promise.resolve(() => {})";
1026
1024
  const hasDocs = (project?.routes ?? []).some(route => route.path === "/docs/" || route.path.startsWith("/docs/"));
1027
1025
  const routerModule = profile.electron || options.navigation === "embedded" ? "fuzor/runtime/router/embedded" : options.navigation === "navigation-api" ? "fuzor/runtime/router/navigation-api" : "fuzor/runtime";
1028
1026
  const imports = [
1027
+ ...cssImports,
1028
+ ...(nativeAdmissions.size ? ['import { readNativeManifest } from "fuzor/runtime/native-manifest";'] : []),
1029
+ ...(options.ui?.brand === undefined ? [] : ['import { defineBrand } from "fuzor/ui/core";']),
1029
1030
  (target === "spa" || mixedClient)
1030
1031
  ? `import { createRouter } from "${routerModule}"; import { reloadOnStaleDeployment } from "fuzor/runtime/islands";`
1031
- : 'import { mountIslands, islandErrorHandler, reloadOnStaleDeployment } from "fuzor/runtime/islands";',
1032
+ : `import { ${withIslands ? "mountIslands, islandErrorHandler, " : ""}reloadOnStaleDeployment } from "fuzor/runtime/islands";`,
1032
1033
  ...(target === "mpa" ? ['import { mountPageScripts } from "fuzor/runtime/page-scripts"; import { stripBase } from "fuzor/runtime";'] : []),
1033
- ...(options.serverComponents ? [options.serverComponents.references ? 'import generatedViewOptions from "virtual:fuzor/views";' : "", options.serverComponents.client ? `import suppliedViewOptions from ${JSON.stringify("/" + options.serverComponents.client)};` : "", `const viewOptions = { ${options.serverComponents.references ? "...generatedViewOptions" : ""}${options.serverComponents.references && options.serverComponents.client ? "," : ""}${options.serverComponents.client ? "...suppliedViewOptions" : ""} };`, (target === "spa" || mixedClient) ? 'import { makeClientRouteMatcher } from "fuzor/runtime"; import { serverViewRoutes } from "fuzor/server-components/navigation"; import { createViewRenderer } from "fuzor/server-components/browser";' : 'import { createViewRenderer } from "fuzor/server-components/browser";'] : []),
1034
- ...(mixedClient ? ['import { mountIslands, islandErrorHandler } from "fuzor/runtime/islands";'] : []),
1035
- ...(withData ? ['import { loadRouteData } from "fuzor/runtime/data";'] : []),
1034
+ ...(mixedClient && withIslands ? ['import { mountIslands, islandErrorHandler } from "fuzor/runtime/islands";'] : []),
1035
+ ...(withData || nativeData ? [`import { ${[withData ? "loadRouteData" : "", nativeData ? "routeData" : ""].filter(Boolean).join(",")} } from "fuzor/runtime/data";`] : []),
1036
+ ...(nativeData ? ['import { enableNativeStreams } from "fuzor/runtime/native-stream";'] : []),
1036
1037
  ...(hasDocs ? ['import { mountDocsUi } from "fuzor/runtime/docs-ui";'] : [])
1037
1038
  ];
1038
1039
  const docsInit = hasDocs ? `let stopDocsUi;
@@ -1042,17 +1043,16 @@ const disposeDocs = () => {
1042
1043
  if (docsDisposed) return;
1043
1044
  docsDisposed = true;
1044
1045
  window.removeEventListener("pagehide", hideDocs);
1045
- stopDocsUi?.();
1046
+ void stopDocsUi?.().catch(error => console.error(error));
1046
1047
  };
1047
1048
  void mountDocsUi({ document, base: import.meta.env.BASE_URL }).then(stop => {
1048
1049
  stopDocsUi = stop;
1049
- if (docsDisposed) stop();
1050
+ if (docsDisposed) void stop().catch(error => console.error(error));
1050
1051
  }, error => console.error(error));
1051
1052
  window.addEventListener("pagehide", hideDocs);
1052
1053
  if (import.meta.hot) import.meta.hot.dispose(disposeDocs);
1053
1054
  ` : "";
1054
- const effectiveViewOptions = `...viewOptions, manifest: { ...viewOptions.manifest, buildId: ${JSON.stringify(viewBuildId)} }`;
1055
- const spaStart = `export const router = (${mixedClient ? 'outlet.getAttribute("data-fuzor-navigation") === "spa" && ' : ""}!outlet.hasAttribute("data-fuzor-not-found") && !outlet.hasAttribute("data-fuzor-error")) ? createRouter(outlet, { islands, scripts, base: import.meta.env.BASE_URL${options.serverComponents ? `, renderer: serverViewRoutes({ ${effectiveViewOptions}, routes: ${JSON.stringify(options.serverComponents.routes)}, acceptsRoute: path => !${mixedClient} || [...document.querySelectorAll("template[data-fuzor-route]")].some(t => t.getAttribute("data-fuzor-route") === path || t.hasAttribute("data-fuzor-request-pattern") && makeClientRouteMatcher({[t.getAttribute("data-fuzor-request-pattern")]: "/"})(path)), base: import.meta.env.BASE_URL })` : ""}${options.clientRoutes ? `, clientRoutes: ${JSON.stringify(options.clientRoutes)}` : ""}${options.routeTree ? `, routeTree: ${JSON.stringify(options.routeTree)}` : ""}${options.viewTransitions ? ", viewTransitions: true" : ""}${options.prefetch === "intent" ? ', prefetch: "intent"' : ""} }) : undefined;
1055
+ const spaStart = `export const router = (${mixedClient ? 'outlet.getAttribute("data-fuzor-navigation") === "spa" && ' : ""}!outlet.hasAttribute("data-fuzor-not-found") && !outlet.hasAttribute("data-fuzor-error")) ? createRouter(outlet, { islands, scripts, base: import.meta.env.BASE_URL${options.clientRoutes ? `, clientRoutes: ${JSON.stringify(options.clientRoutes)}` : ""}${options.routeTree ? `, routeTree: ${JSON.stringify(options.routeTree)}` : ""}${options.viewTransitions ? ", viewTransitions: true" : ""}${options.prefetch === "intent" ? ', prefetch: "intent"' : ""} }) : undefined;
1056
1056
  export const ready = router ? router.start() : Promise.resolve(undefined);
1057
1057
  void ready.catch(error => console.error(error));
1058
1058
  if (import.meta.hot) import.meta.hot.dispose(() => { void router?.dispose(); });`;
@@ -1061,13 +1061,11 @@ if (import.meta.hot) import.meta.hot.dispose(() => { void router?.dispose(); });
1061
1061
  const lifetime = new AbortController();
1062
1062
  let cleanup;
1063
1063
  const dispose = () => { lifetime.abort(); void cleanup?.(); };
1064
- ${options.serverComponents ? `const viewRenderer = document.querySelector("script[data-fuzor-snapshot]") ? createViewRenderer(outlet, { ${effectiveViewOptions} }) : undefined;
1065
- const mountComponents = viewRenderer ? viewRenderer.resume().then(() => () => viewRenderer.dispose()) : mountIslands(outlet, islands, islandErrorHandler, { signal: lifetime.signal });` : `const mountComponents = mountIslands(outlet, islands, islandErrorHandler, { signal: lifetime.signal });`}
1066
- ${options.serverComponents ? "const viewRendererActive = !!viewRenderer;" : "const viewRendererActive = false;"}
1064
+ const mountComponents = ${mountIslandsCode};
1067
1065
  const pathname = stripBase(location.pathname, import.meta.env.BASE_URL) ?? location.pathname;
1068
1066
  const routePath = pathname.endsWith("/") ? pathname : pathname + "/";
1069
1067
  const scriptRoute = Object.hasOwn(scripts, routePath) ? routePath : outlet.getAttribute("data-fuzor-pattern") ?? "";
1070
- const mount = (async () => { const stopComponents = await mountComponents; try { const stopScripts = await mountPageScripts(outlet, viewRendererActive ? {} : scripts, { signal: lifetime.signal, route: scriptRoute }); return async () => { await stopScripts(); await stopComponents(); }; } catch (error) { await stopComponents(); throw error; } })();
1068
+ const mount = (async () => { const stopComponents = await mountComponents; try { const stopScripts = await mountPageScripts(outlet, scripts, { signal: lifetime.signal, route: scriptRoute }); return async () => { await stopScripts(); await stopComponents(); }; } catch (error) { await stopComponents(); throw error; } })();
1071
1069
  void mount.then(stop => {
1072
1070
  cleanup = stop;
1073
1071
  if (lifetime.signal.aborted) void stop();
@@ -1075,7 +1073,10 @@ void mount.then(stop => {
1075
1073
  window.addEventListener("pagehide", event => { if (!event.persisted) dispose(); });
1076
1074
  if (import.meta.hot) import.meta.hot.dispose(dispose);`;
1077
1075
  return `${imports.join("\n")}
1076
+ ${options.ui?.brand === undefined ? "" : `defineBrand(${JSON.stringify(options.ui.brand)});`}
1078
1077
  const islands = { ${registry} };
1078
+ ${nativeLoaders}
1079
+ ${nativeData ? `enableNativeStreams(${JSON.stringify(viewBuildId)});` : ""}
1079
1080
  const scripts = { ${scriptRegistry} };
1080
1081
  const outlet = document.querySelector("fuzor-router");
1081
1082
  if (!outlet) throw new Error("Fuzor outlet is missing");
@@ -1092,6 +1093,7 @@ ${docsInit}${mixedClient ? spaStart + "\nif (!router) {\n" + mpaStart + "\n}" :
1092
1093
  const pathname = stripBase(requested, pathBase) ?? requested;
1093
1094
  const currentPath = findRoute(compiled, pathname) ? pathname : "/";
1094
1095
  parseRoutePolicy(raw, path.join(vite.root, "index.html"), false);
1096
+ assertNoServerScripts(raw, path.join(vite.root, "index.html"));
1095
1097
  const shell = compilePlatformBlocks(raw, profile.platform, path.join(vite.root, "index.html"));
1096
1098
  shellHead = readDocumentHead(shell);
1097
1099
  let html = applyDocumentMetadata(replaceOutlet(shell, outletFor(compiled, target, currentPath, fallbacks(), profile.mixed)), pageHead(shellHead, findRoute(compiled, currentPath)?.metadata ?? {}));
@@ -1163,10 +1165,7 @@ ${docsInit}${mixedClient ? spaStart + "\nif (!router) {\n" + mpaStart + "\n}" :
1163
1165
  const compiled = await loadProject();
1164
1166
  const manifest = serverManifest(compiled, target, template, pathBase, fallbacks(), withStaticModuleImports, options.clientRoutes, `/${vite.build.assetsDir}/`, profile.mixed);
1165
1167
  const data = dataRequest || compiled.routes.some(route => route.policy?.rendering === "request") ? await loadRouteDataModules(compiled, file => createEnvironmentModuleRuntime(server, moduleEnvironment, environmentApiEnabled).loadModule(file), vite.root) : undefined;
1166
- const views = options.serverComponents ? await generatedViews((await createEnvironmentModuleRuntime(server, moduleEnvironment, environmentApiEnabled).loadModule("/" + options.serverComponents.server)).default) : undefined;
1167
- if (views && JSON.stringify(Object.keys(views.routes).sort()) !== JSON.stringify([...options.serverComponents.routes].sort()))
1168
- throw new Error("Server view routes must match serverComponents.routes.");
1169
- result = await createServerHandler(manifest, { ...(data ? { data } : {}), ...(views ? { views: { ...views, manifest: { ...views.manifest, buildId: viewBuildId }, documentPatterns: manifest.requestPatterns ?? {}, document: serverViewDocument(template), documents: Object.fromEntries(Object.entries("documents" in manifest ? manifest.documents : manifest.routeDocuments ?? {}).map(([route, html]) => [route, serverViewDocument(html)])) } } : {}), onError })(toWebRequest(request, { url, signal: controller.signal }));
1168
+ result = await createServerHandler({ ...manifest, buildId: viewBuildId }, { renderBinding: renderNativeBinding, ...(data ? { data } : {}), onError })(toWebRequest(request, { url, signal: controller.signal }));
1170
1169
  }
1171
1170
  await writeWebResponse(response, result, { method: request.method ?? "GET", signal: controller.signal });
1172
1171
  }
@@ -1207,7 +1206,7 @@ ${docsInit}${mixedClient ? spaStart + "\nif (!router) {\n" + mpaStart + "\n}" :
1207
1206
  }
1208
1207
  });
1209
1208
  server.watcher.on("add", (file) => {
1210
- if (pageScripts.some(script => script.source === file) || options.serverComponents && (file.endsWith(".fragment.html") || options.serverComponents.references && file.startsWith(path.resolve(vite.root, options.serverComponents.references) + path.sep) || file === path.resolve(vite.root, options.serverComponents.server) || options.serverComponents.client && file === path.resolve(vite.root, options.serverComponents.client)) || ["/pages/", "/components/", "/layouts/", "/islands/", "/api/"].some((part) => file.replaceAll("\\", "/").includes(part))) {
1209
+ if (pageScripts.some(script => script.source === file) || ["/pages/", "/components/", "/layouts/", "/islands/", "/api/"].some((part) => file.replaceAll("\\", "/").includes(part))) {
1211
1210
  void refresh(server).catch((error) => server.config.logger.error(String(error)));
1212
1211
  }
1213
1212
  });
@@ -1320,8 +1319,7 @@ ${docsInit}${mixedClient ? spaStart + "\nif (!router) {\n" + mpaStart + "\n}" :
1320
1319
  await mkdir(serverDir, { recursive: true });
1321
1320
  await writeFile(path.join(serverDir, "fuzor.manifest.json"), JSON.stringify({ ...artifact, clientDirectory: path.relative(serverDir, outDir).split(path.sep).join("/") }, null, 2) + "\n");
1322
1321
  const dataRoutes = compiled.routes.filter(route => route.serverData);
1323
- const refs = options.serverComponents?.references ? await references() : undefined;
1324
- if (!dataRoutes.length && !apiFiles.length && !options.serverComponents && !options.serverExternal) {
1322
+ if (!dataRoutes.length && !apiFiles.length && !options.serverExternal) {
1325
1323
  await writeFile(path.join(serverDir, "entry.mjs"), `import { createServerHandler } from "fuzor/server";\n` +
1326
1324
  `export const manifest = ${JSON.stringify(manifest)};\n` +
1327
1325
  `export const handler = createServerHandler(manifest);\n`, "utf8");
@@ -1331,8 +1329,7 @@ ${docsInit}${mixedClient ? spaStart + "\nif (!router) {\n" + mpaStart + "\n}" :
1331
1329
  const files = [...new Set(dataRoutes.map(route => route.serverData))];
1332
1330
  const entryId = "virtual:fuzor/server-entry";
1333
1331
  const code = [
1334
- `import { createServerHandler, isServerData } from "fuzor/server";`,
1335
- ...(options.serverComponents ? [`import suppliedViews from ${JSON.stringify(path.resolve(vite.root, options.serverComponents.server))};`, `const views = { ...suppliedViews${refs ? `, manifest: ${JSON.stringify({ buildId: viewBuildId, templates: refs.templates })}, components: ${JSON.stringify(refs.clients.map(ref => ref.id))}, handlers: ${JSON.stringify([...refs.handlers.map(ref => ref.id), ...refs.closures.map(ref => ref.id)])}` : ""} };`, `if (JSON.stringify(Object.keys(views.routes).sort()) !== ${JSON.stringify(JSON.stringify([...options.serverComponents.routes].sort()))}) throw new Error("Server view routes must match serverComponents.routes.");`] : []),
1332
+ `import { createServerHandler, isServerData${Object.values(manifest.dataBindings ?? {}).some(bindings => bindings.some(binding => binding.kind === "range")) ? ", renderNativeBinding" : ""} } from "fuzor/server";`,
1336
1333
  ...files.map((file, index) => `import data${index} from ${JSON.stringify(file)};`),
1337
1334
  ...apiFiles.map((route, index) => `import * as api${index} from ${JSON.stringify(route.file)};`),
1338
1335
  `const modules = [${files.map((_, index) => `data${index}`).join(", ")}];`,
@@ -1341,14 +1338,15 @@ ${docsInit}${mixedClient ? spaStart + "\nif (!router) {\n" + mpaStart + "\n}" :
1341
1338
  `export const manifest = ${JSON.stringify(manifest)};`,
1342
1339
  `const routes = ${JSON.stringify(dataRoutes.map(route => [route.path, files.indexOf(route.serverData), route.params ?? null]))};`,
1343
1340
  `const api = [${apiFiles.map((route, index) => `{ pattern: ${JSON.stringify(route.pattern)}, segments: ${JSON.stringify(route.segments)}, source: ${JSON.stringify(route.relative)}, module: api${index} }`).join(", ")}];`,
1344
- `export const handler = createServerHandler(manifest, { data: Object.fromEntries(routes.map(([route, index, params]) => [route, params ? { data: modules[index], params } : { data: modules[index] }])), api${options.serverComponents ? `, views: { ...views, manifest: { ...views.manifest, buildId: ${JSON.stringify(viewBuildId)} }, documentPatterns:${JSON.stringify(manifest.requestPatterns ?? {})}, document: ${JSON.stringify(serverViewDocument(rootHtml))}, documents:${JSON.stringify(Object.fromEntries(Object.entries("documents" in manifest ? manifest.documents : manifest.routeDocuments ?? {}).map(([route, html]) => [route, serverViewDocument(html)])))} }` : ""} });`
1341
+ `export const handler = createServerHandler(manifest, { data: Object.fromEntries(routes.map(([route, index, params]) => [route, params ? { data: modules[index], params } : { data: modules[index] }])), api${Object.values(manifest.dataBindings ?? {}).some(bindings => bindings.some(binding => binding.kind === "range")) ? ", renderBinding: renderNativeBinding" : ""} });`
1345
1342
  ].join("\n");
1346
1343
  await nestedServer.run(true, () => viteBuild({
1347
1344
  root: vite.root, mode: vite.mode, configFile: vite.configFile ?? false, logLevel: "error", publicDir: false,
1348
- // One runtime copy: the entry and server modules share fuzor/server and effect, either both imported from
1349
- // node_modules (the default) or, for what `serverExternal` does not name, bundled once into the output.
1350
- ssr: options.serverExternal ? { external: [...options.serverExternal], noExternal: true } : { external: ["fuzor", "effect"] },
1351
- plugins: [compiledFragmentsPlugin(), workflowPlugin(), ...(refs ? [{ name: "fuzor:server-references", load: (id) => referenceStub(id, refs) }] : []), { name: "fuzor:server-entry", resolveId: id => id === entryId ? `\0${entryId}` : undefined, load: id => id === `\0${entryId}` ? code : undefined }],
1345
+ // One runtime copy: Fuzor's own server runtime is always bundled into the entry (generated code reaches modules
1346
+ // that are not package entries), so the entry and server modules share one copy of it. `effect` is imported from
1347
+ // node_modules by default; with `serverExternal`, everything it does not name is bundled once.
1348
+ ssr: options.serverExternal ? { external: options.serverExternal.filter(name => name !== "fuzor"), noExternal: true } : { external: ["effect"], noExternal: ["fuzor"] },
1349
+ plugins: [{ name: "fuzor:internal-specifiers", enforce: "pre", resolveId: id => resolveInternalSpecifier(id) }, htmlServerPlugin(nativeServerModules), workflowPlugin(), { name: "fuzor:server-entry", resolveId: id => id === entryId ? `\0${entryId}` : undefined, load: id => id === `\0${entryId}` ? code : undefined }],
1352
1350
  build: { ssr: true, outDir: serverDir, emptyOutDir: false, copyPublicDir: false, minify: false,
1353
1351
  rollupOptions: { input: entryId, output: { entryFileNames: "entry.mjs", chunkFileNames: "chunks/[name]-[hash].mjs", format: "es" } } }
1354
1352
  }));
@@ -1356,7 +1354,7 @@ ${docsInit}${mixedClient ? spaStart + "\nif (!router) {\n" + mpaStart + "\n}" :
1356
1354
  // The same finished documents are also written to client/ below, so it can be served by a CDN or
1357
1355
  // static host with only /api and server data requests reaching the server.
1358
1356
  if (profile.adapter === "cloudflare")
1359
- await writeCloudflareOutput(serverDir, vite.root, target, pathBase, outDir, [...compiled.routes.filter(route => route.policy?.rendering === "request").map(route => route.requestPattern ? route.requestPattern.slice(0, route.requestPattern.indexOf(":")) + "*" : route.path), ...(options.serverComponents?.routes ?? []).map(pattern => pattern.includes(":") ? pattern.slice(0, pattern.indexOf(":")) + "*" : pattern)], path.relative(path.resolve(vite.root, profile.backendOutDir), process.env.FUZOR_STAGE_DIR && outDir.startsWith(path.resolve(process.env.FUZOR_STAGE_DIR) + path.sep) ? path.resolve(vite.root, path.relative(process.env.FUZOR_STAGE_DIR, outDir)) : outDir).split(path.sep).join("/"));
1357
+ await writeCloudflareOutput(serverDir, vite.root, target, pathBase, outDir, [...compiled.routes.filter(route => route.policy?.rendering === "request").map(route => route.requestPattern ? route.requestPattern.slice(0, route.requestPattern.indexOf(":")) + "*" : route.path)], path.relative(path.resolve(vite.root, profile.backendOutDir), process.env.FUZOR_STAGE_DIR && outDir.startsWith(path.resolve(process.env.FUZOR_STAGE_DIR) + path.sep) ? path.resolve(vite.root, path.relative(process.env.FUZOR_STAGE_DIR, outDir)) : outDir).split(path.sep).join("/"));
1360
1358
  }
1361
1359
  const index = searchIndex(compiled, options.searchIndex);
1362
1360
  if (index)
@@ -1406,7 +1404,7 @@ ${docsInit}${mixedClient ? spaStart + "\nif (!router) {\n" + mpaStart + "\n}" :
1406
1404
  if (nested)
1407
1405
  return;
1408
1406
  const file = context.file.replaceAll("\\", "/");
1409
- if (pageScripts.some(script => script.source === file) || options.serverComponents && (file.endsWith(".fragment.html") || options.serverComponents.references && file.startsWith(path.resolve(vite.root, options.serverComponents.references) + path.sep) || file === path.resolve(vite.root, options.serverComponents.server) || options.serverComponents.client && file === path.resolve(vite.root, options.serverComponents.client)) || ["/pages/", "/components/", "/layouts/", "/islands/", "/api/"].some((part) => file.includes(part))) {
1407
+ if (pageScripts.some(script => script.source === file) || ["/pages/", "/components/", "/layouts/", "/islands/", "/api/"].some((part) => file.includes(part))) {
1410
1408
  const environment = context.environment?.name;
1411
1409
  if (environment)
1412
1410
  invalidateEnvironmentModule(context.server, resolvedVirtualId, environment, environmentApiEnabled);