fuzor 0.5.0 → 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 +12 -10
  272. package/dist/vite.d.ts.map +1 -1
  273. package/dist/vite.js +115 -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}`);
@@ -821,7 +807,9 @@ export function fuzorPlugin(options = {}) {
821
807
  include.push("vue");
822
808
  return {
823
809
  resolve: { dedupe: ["react", "react-dom", "solid-js", "vue"] },
824
- optimizeDeps: { include, ...runtimeOptimizer },
810
+ // A `*.fragment.html` is a compiled fragment, not a page: Vite's dependency scan would load it as an entry and
811
+ // fail on the virtual module it resolves to. Pages keep Vite's own default of every `.html` file.
812
+ optimizeDeps: { include, ...(config.optimizeDeps?.entries === undefined ? { entries: ["**/*.html", "!**/*.fragment.html"] } : {}), ...runtimeOptimizer },
825
813
  // Vite 6.4 accepts named environments in the config hook. This is
826
814
  // emitted only for explicit opt-in; Vite 6/7 legacy users are untouched.
827
815
  ...(options.experimental?.environments ? {
@@ -848,11 +836,14 @@ export function fuzorPlugin(options = {}) {
848
836
  }
849
837
  },
850
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;
851
845
  if (!resolveOptions.ssr)
852
846
  assertBrowserViewImport(id, importer);
853
- if (id === "fuzor/server-components/server" && !resolveOptions.ssr) {
854
- throw new Error("The server-component evaluator is server-only. Import browser runtime and protocol separately.");
855
- }
856
847
  if (id.startsWith("virtual:fuzor/route-client/"))
857
848
  return `\0${id}`;
858
849
  if (id.startsWith("virtual:fuzor/page-script/"))
@@ -943,6 +934,8 @@ export function fuzorPlugin(options = {}) {
943
934
  }
944
935
  },
945
936
  async load(id, loadOptions) {
937
+ if (id.includes("?fuzor-server-script=") || nativeServerModules.has(id))
938
+ return loadHtmlServer(id, nativeServerModules, file => this.addWatchFile(file));
946
939
  if (id.startsWith("\0virtual:fuzor/route-client/")) {
947
940
  await loadProject();
948
941
  const route = scriptRoutes.find(route => `\0${routeScriptId(route)}` === id);
@@ -957,6 +950,8 @@ export function fuzorPlugin(options = {}) {
957
950
  throw new Error("Unknown Fuzor page script");
958
951
  this.addWatchFile(script.source);
959
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");
960
955
  const native = compileNativeScript(source, script);
961
956
  if (native) {
962
957
  for (const handler of native.handlers)
@@ -972,31 +967,6 @@ export function fuzorPlugin(options = {}) {
972
967
  throw new Error("Unknown native handler");
973
968
  return (await import("vite")).transformWithEsbuild(handler.code, handler.source, { loader: handler.language, target: "es2022" });
974
969
  }
975
- if (id.startsWith("\0fuzor:closure:")) {
976
- const refs = await references();
977
- const closure = refs.closures.find(ref => id === `\0fuzor:closure:${ref.id}.ts`);
978
- if (!closure)
979
- throw new Error("Unknown resumable closure reference");
980
- return (await import("vite")).transformWithEsbuild(closure.code, id, { loader: "ts", target: "es2022" }).then(result => result.code);
981
- }
982
- if (options.serverComponents?.references) {
983
- const refs = await references();
984
- if (id === "\0virtual:fuzor/views") {
985
- if (loadOptions?.ssr)
986
- 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)])};`;
987
- return browserReferencesCode(refs, viewBuildId, file => "/" + file);
988
- }
989
- if (loadOptions?.ssr) {
990
- const stub = referenceStub(id.split("?")[0], refs);
991
- if (stub)
992
- return stub;
993
- }
994
- }
995
- if (id.startsWith("\0fuzor:fragment:")) {
996
- const file = id.slice("\0fuzor:fragment:".length, -3);
997
- this.addWatchFile(file);
998
- return readFile(file, "utf8").then(html => `export default ${JSON.stringify(compileFragment(html))};`);
999
- }
1000
970
  if (Object.hasOwn(rendererModules, id.slice(1)))
1001
971
  return rendererModules[id.slice(1)];
1002
972
  const runtimeModule = runtimeModules.get(id);
@@ -1005,7 +975,7 @@ export function fuzorPlugin(options = {}) {
1005
975
  if (!loadOptions?.ssr && !nested && vite) {
1006
976
  const file = id.split("?")[0];
1007
977
  const relative = path.relative(vite.root, file).split(path.sep).join("/");
1008
- 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)) {
1009
979
  throw new Error(`${relative} is server-only and cannot be imported by browser code. Read its data with <fuzor-data> or routeData() instead.`);
1010
980
  }
1011
981
  }
@@ -1013,7 +983,32 @@ export function fuzorPlugin(options = {}) {
1013
983
  return undefined;
1014
984
  await loadProject();
1015
985
  const mixedClient = profile.mixed && project?.routes.some(route => route.policy?.navigation === "spa");
1016
- 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(",")}} };` : "";
1017
1012
  const registry = islands.map((island) => {
1018
1013
  const load = island.file.endsWith(".vue")
1019
1014
  ? `Promise.all([import(${JSON.stringify(island.file)}), import("fuzor/islands/vue")]).then(([component, adapter]) => adapter.vueIsland(component.default)).catch(reloadOnStaleDeployment)`
@@ -1021,16 +1016,24 @@ export function fuzorPlugin(options = {}) {
1021
1016
  return `${JSON.stringify(island.name)}: () => ${load}`;
1022
1017
  }).join(",\n");
1023
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(() => {})";
1024
1024
  const hasDocs = (project?.routes ?? []).some(route => route.path === "/docs/" || route.path.startsWith("/docs/"));
1025
1025
  const routerModule = profile.electron || options.navigation === "embedded" ? "fuzor/runtime/router/embedded" : options.navigation === "navigation-api" ? "fuzor/runtime/router/navigation-api" : "fuzor/runtime";
1026
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";']),
1027
1030
  (target === "spa" || mixedClient)
1028
1031
  ? `import { createRouter } from "${routerModule}"; import { reloadOnStaleDeployment } from "fuzor/runtime/islands";`
1029
- : 'import { mountIslands, islandErrorHandler, reloadOnStaleDeployment } from "fuzor/runtime/islands";',
1032
+ : `import { ${withIslands ? "mountIslands, islandErrorHandler, " : ""}reloadOnStaleDeployment } from "fuzor/runtime/islands";`,
1030
1033
  ...(target === "mpa" ? ['import { mountPageScripts } from "fuzor/runtime/page-scripts"; import { stripBase } from "fuzor/runtime";'] : []),
1031
- ...(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";'] : []),
1032
- ...(mixedClient ? ['import { mountIslands, islandErrorHandler } from "fuzor/runtime/islands";'] : []),
1033
- ...(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";'] : []),
1034
1037
  ...(hasDocs ? ['import { mountDocsUi } from "fuzor/runtime/docs-ui";'] : [])
1035
1038
  ];
1036
1039
  const docsInit = hasDocs ? `let stopDocsUi;
@@ -1040,17 +1043,16 @@ const disposeDocs = () => {
1040
1043
  if (docsDisposed) return;
1041
1044
  docsDisposed = true;
1042
1045
  window.removeEventListener("pagehide", hideDocs);
1043
- stopDocsUi?.();
1046
+ void stopDocsUi?.().catch(error => console.error(error));
1044
1047
  };
1045
1048
  void mountDocsUi({ document, base: import.meta.env.BASE_URL }).then(stop => {
1046
1049
  stopDocsUi = stop;
1047
- if (docsDisposed) stop();
1050
+ if (docsDisposed) void stop().catch(error => console.error(error));
1048
1051
  }, error => console.error(error));
1049
1052
  window.addEventListener("pagehide", hideDocs);
1050
1053
  if (import.meta.hot) import.meta.hot.dispose(disposeDocs);
1051
1054
  ` : "";
1052
- const effectiveViewOptions = `...viewOptions, manifest: { ...viewOptions.manifest, buildId: ${JSON.stringify(viewBuildId)} }`;
1053
- 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;
1054
1056
  export const ready = router ? router.start() : Promise.resolve(undefined);
1055
1057
  void ready.catch(error => console.error(error));
1056
1058
  if (import.meta.hot) import.meta.hot.dispose(() => { void router?.dispose(); });`;
@@ -1059,13 +1061,11 @@ if (import.meta.hot) import.meta.hot.dispose(() => { void router?.dispose(); });
1059
1061
  const lifetime = new AbortController();
1060
1062
  let cleanup;
1061
1063
  const dispose = () => { lifetime.abort(); void cleanup?.(); };
1062
- ${options.serverComponents ? `const viewRenderer = document.querySelector("script[data-fuzor-snapshot]") ? createViewRenderer(outlet, { ${effectiveViewOptions} }) : undefined;
1063
- const mountComponents = viewRenderer ? viewRenderer.resume().then(() => () => viewRenderer.dispose()) : mountIslands(outlet, islands, islandErrorHandler, { signal: lifetime.signal });` : `const mountComponents = mountIslands(outlet, islands, islandErrorHandler, { signal: lifetime.signal });`}
1064
- ${options.serverComponents ? "const viewRendererActive = !!viewRenderer;" : "const viewRendererActive = false;"}
1064
+ const mountComponents = ${mountIslandsCode};
1065
1065
  const pathname = stripBase(location.pathname, import.meta.env.BASE_URL) ?? location.pathname;
1066
1066
  const routePath = pathname.endsWith("/") ? pathname : pathname + "/";
1067
1067
  const scriptRoute = Object.hasOwn(scripts, routePath) ? routePath : outlet.getAttribute("data-fuzor-pattern") ?? "";
1068
- 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; } })();
1069
1069
  void mount.then(stop => {
1070
1070
  cleanup = stop;
1071
1071
  if (lifetime.signal.aborted) void stop();
@@ -1073,7 +1073,10 @@ void mount.then(stop => {
1073
1073
  window.addEventListener("pagehide", event => { if (!event.persisted) dispose(); });
1074
1074
  if (import.meta.hot) import.meta.hot.dispose(dispose);`;
1075
1075
  return `${imports.join("\n")}
1076
+ ${options.ui?.brand === undefined ? "" : `defineBrand(${JSON.stringify(options.ui.brand)});`}
1076
1077
  const islands = { ${registry} };
1078
+ ${nativeLoaders}
1079
+ ${nativeData ? `enableNativeStreams(${JSON.stringify(viewBuildId)});` : ""}
1077
1080
  const scripts = { ${scriptRegistry} };
1078
1081
  const outlet = document.querySelector("fuzor-router");
1079
1082
  if (!outlet) throw new Error("Fuzor outlet is missing");
@@ -1090,6 +1093,7 @@ ${docsInit}${mixedClient ? spaStart + "\nif (!router) {\n" + mpaStart + "\n}" :
1090
1093
  const pathname = stripBase(requested, pathBase) ?? requested;
1091
1094
  const currentPath = findRoute(compiled, pathname) ? pathname : "/";
1092
1095
  parseRoutePolicy(raw, path.join(vite.root, "index.html"), false);
1096
+ assertNoServerScripts(raw, path.join(vite.root, "index.html"));
1093
1097
  const shell = compilePlatformBlocks(raw, profile.platform, path.join(vite.root, "index.html"));
1094
1098
  shellHead = readDocumentHead(shell);
1095
1099
  let html = applyDocumentMetadata(replaceOutlet(shell, outletFor(compiled, target, currentPath, fallbacks(), profile.mixed)), pageHead(shellHead, findRoute(compiled, currentPath)?.metadata ?? {}));
@@ -1161,10 +1165,7 @@ ${docsInit}${mixedClient ? spaStart + "\nif (!router) {\n" + mpaStart + "\n}" :
1161
1165
  const compiled = await loadProject();
1162
1166
  const manifest = serverManifest(compiled, target, template, pathBase, fallbacks(), withStaticModuleImports, options.clientRoutes, `/${vite.build.assetsDir}/`, profile.mixed);
1163
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;
1164
- const views = options.serverComponents ? await generatedViews((await createEnvironmentModuleRuntime(server, moduleEnvironment, environmentApiEnabled).loadModule("/" + options.serverComponents.server)).default) : undefined;
1165
- if (views && JSON.stringify(Object.keys(views.routes).sort()) !== JSON.stringify([...options.serverComponents.routes].sort()))
1166
- throw new Error("Server view routes must match serverComponents.routes.");
1167
- 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 }));
1168
1169
  }
1169
1170
  await writeWebResponse(response, result, { method: request.method ?? "GET", signal: controller.signal });
1170
1171
  }
@@ -1205,7 +1206,7 @@ ${docsInit}${mixedClient ? spaStart + "\nif (!router) {\n" + mpaStart + "\n}" :
1205
1206
  }
1206
1207
  });
1207
1208
  server.watcher.on("add", (file) => {
1208
- 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))) {
1209
1210
  void refresh(server).catch((error) => server.config.logger.error(String(error)));
1210
1211
  }
1211
1212
  });
@@ -1318,8 +1319,7 @@ ${docsInit}${mixedClient ? spaStart + "\nif (!router) {\n" + mpaStart + "\n}" :
1318
1319
  await mkdir(serverDir, { recursive: true });
1319
1320
  await writeFile(path.join(serverDir, "fuzor.manifest.json"), JSON.stringify({ ...artifact, clientDirectory: path.relative(serverDir, outDir).split(path.sep).join("/") }, null, 2) + "\n");
1320
1321
  const dataRoutes = compiled.routes.filter(route => route.serverData);
1321
- const refs = options.serverComponents?.references ? await references() : undefined;
1322
- if (!dataRoutes.length && !apiFiles.length && !options.serverComponents) {
1322
+ if (!dataRoutes.length && !apiFiles.length && !options.serverExternal) {
1323
1323
  await writeFile(path.join(serverDir, "entry.mjs"), `import { createServerHandler } from "fuzor/server";\n` +
1324
1324
  `export const manifest = ${JSON.stringify(manifest)};\n` +
1325
1325
  `export const handler = createServerHandler(manifest);\n`, "utf8");
@@ -1329,8 +1329,7 @@ ${docsInit}${mixedClient ? spaStart + "\nif (!router) {\n" + mpaStart + "\n}" :
1329
1329
  const files = [...new Set(dataRoutes.map(route => route.serverData))];
1330
1330
  const entryId = "virtual:fuzor/server-entry";
1331
1331
  const code = [
1332
- `import { createServerHandler, isServerData } from "fuzor/server";`,
1333
- ...(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";`,
1334
1333
  ...files.map((file, index) => `import data${index} from ${JSON.stringify(file)};`),
1335
1334
  ...apiFiles.map((route, index) => `import * as api${index} from ${JSON.stringify(route.file)};`),
1336
1335
  `const modules = [${files.map((_, index) => `data${index}`).join(", ")}];`,
@@ -1339,13 +1338,15 @@ ${docsInit}${mixedClient ? spaStart + "\nif (!router) {\n" + mpaStart + "\n}" :
1339
1338
  `export const manifest = ${JSON.stringify(manifest)};`,
1340
1339
  `const routes = ${JSON.stringify(dataRoutes.map(route => [route.path, files.indexOf(route.serverData), route.params ?? null]))};`,
1341
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(", ")}];`,
1342
- `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" : ""} });`
1343
1342
  ].join("\n");
1344
1343
  await nestedServer.run(true, () => viteBuild({
1345
1344
  root: vite.root, mode: vite.mode, configFile: vite.configFile ?? false, logLevel: "error", publicDir: false,
1346
- // One runtime copy: the entry and server modules must share fuzor/server and effect from node_modules.
1347
- ssr: { external: ["fuzor", "effect"] },
1348
- 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 }],
1349
1350
  build: { ssr: true, outDir: serverDir, emptyOutDir: false, copyPublicDir: false, minify: false,
1350
1351
  rollupOptions: { input: entryId, output: { entryFileNames: "entry.mjs", chunkFileNames: "chunks/[name]-[hash].mjs", format: "es" } } }
1351
1352
  }));
@@ -1353,7 +1354,7 @@ ${docsInit}${mixedClient ? spaStart + "\nif (!router) {\n" + mpaStart + "\n}" :
1353
1354
  // The same finished documents are also written to client/ below, so it can be served by a CDN or
1354
1355
  // static host with only /api and server data requests reaching the server.
1355
1356
  if (profile.adapter === "cloudflare")
1356
- 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("/"));
1357
1358
  }
1358
1359
  const index = searchIndex(compiled, options.searchIndex);
1359
1360
  if (index)
@@ -1403,7 +1404,7 @@ ${docsInit}${mixedClient ? spaStart + "\nif (!router) {\n" + mpaStart + "\n}" :
1403
1404
  if (nested)
1404
1405
  return;
1405
1406
  const file = context.file.replaceAll("\\", "/");
1406
- 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))) {
1407
1408
  const environment = context.environment?.name;
1408
1409
  if (environment)
1409
1410
  invalidateEnvironmentModule(context.server, resolvedVirtualId, environment, environmentApiEnabled);