fibrae 0.2.3 → 0.3.1

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 (138) hide show
  1. package/dist/atom-utils.d.ts +52 -0
  2. package/dist/atom-utils.js +64 -0
  3. package/dist/atom-utils.js.map +1 -0
  4. package/dist/cli/build.d.ts +34 -0
  5. package/dist/cli/build.js +92 -0
  6. package/dist/cli/build.js.map +1 -0
  7. package/dist/cli/cli.d.ts +10 -0
  8. package/dist/cli/cli.js +43 -0
  9. package/dist/cli/cli.js.map +1 -0
  10. package/dist/cli/config.d.ts +19 -0
  11. package/dist/cli/config.js +5 -0
  12. package/dist/cli/config.js.map +1 -0
  13. package/dist/cli/html.d.ts +19 -0
  14. package/dist/cli/html.js +95 -0
  15. package/dist/cli/html.js.map +1 -0
  16. package/dist/cli/index.d.ts +6 -0
  17. package/dist/cli/index.js +4 -0
  18. package/dist/cli/index.js.map +1 -0
  19. package/dist/cli/vite-plugin.d.ts +9 -0
  20. package/dist/cli/vite-plugin.js +143 -0
  21. package/dist/cli/vite-plugin.js.map +1 -0
  22. package/dist/components.d.ts +28 -30
  23. package/dist/components.js +35 -53
  24. package/dist/components.js.map +1 -1
  25. package/dist/core.js +7 -10
  26. package/dist/core.js.map +1 -1
  27. package/dist/dom.d.ts +25 -6
  28. package/dist/dom.js +161 -27
  29. package/dist/dom.js.map +1 -1
  30. package/dist/fiber-boundary.d.ts +39 -0
  31. package/dist/fiber-boundary.js +151 -0
  32. package/dist/fiber-boundary.js.map +1 -0
  33. package/dist/fiber-commit.d.ts +27 -0
  34. package/dist/fiber-commit.js +247 -0
  35. package/dist/fiber-commit.js.map +1 -0
  36. package/dist/fiber-render.d.ts +9 -9
  37. package/dist/fiber-render.js +165 -958
  38. package/dist/fiber-render.js.map +1 -1
  39. package/dist/fiber-tree.d.ts +77 -0
  40. package/dist/fiber-tree.js +152 -0
  41. package/dist/fiber-tree.js.map +1 -0
  42. package/dist/fiber-update.d.ts +46 -0
  43. package/dist/fiber-update.js +521 -0
  44. package/dist/fiber-update.js.map +1 -0
  45. package/dist/h.js.map +1 -1
  46. package/dist/index.d.ts +3 -2
  47. package/dist/index.js +4 -2
  48. package/dist/index.js.map +1 -1
  49. package/dist/jsx-runtime/index.d.ts +368 -2
  50. package/dist/live/atom.d.ts +31 -0
  51. package/dist/live/atom.js +33 -0
  52. package/dist/live/atom.js.map +1 -0
  53. package/dist/live/client.d.ts +50 -0
  54. package/dist/live/client.js +90 -0
  55. package/dist/live/client.js.map +1 -0
  56. package/dist/live/codec.d.ts +39 -0
  57. package/dist/live/codec.js +41 -0
  58. package/dist/live/codec.js.map +1 -0
  59. package/dist/live/config.d.ts +13 -0
  60. package/dist/live/config.js +11 -0
  61. package/dist/live/config.js.map +1 -0
  62. package/dist/live/index.d.ts +25 -0
  63. package/dist/live/index.js +19 -0
  64. package/dist/live/index.js.map +1 -0
  65. package/dist/live/server.d.ts +83 -0
  66. package/dist/live/server.js +106 -0
  67. package/dist/live/server.js.map +1 -0
  68. package/dist/live/sse-stream.d.ts +14 -0
  69. package/dist/live/sse-stream.js +30 -0
  70. package/dist/live/sse-stream.js.map +1 -0
  71. package/dist/live/types.d.ts +40 -0
  72. package/dist/live/types.js +20 -0
  73. package/dist/live/types.js.map +1 -0
  74. package/dist/mdx/index.d.ts +125 -0
  75. package/dist/mdx/index.js +137 -0
  76. package/dist/mdx/index.js.map +1 -0
  77. package/dist/mdx/parse.d.ts +42 -0
  78. package/dist/mdx/parse.js +147 -0
  79. package/dist/mdx/parse.js.map +1 -0
  80. package/dist/mdx/render.d.ts +23 -0
  81. package/dist/mdx/render.js +263 -0
  82. package/dist/mdx/render.js.map +1 -0
  83. package/dist/router/Form.d.ts +90 -0
  84. package/dist/router/Form.js +166 -0
  85. package/dist/router/Form.js.map +1 -0
  86. package/dist/router/History.d.ts +4 -9
  87. package/dist/router/History.js +0 -8
  88. package/dist/router/History.js.map +1 -1
  89. package/dist/router/Link.d.ts +27 -28
  90. package/dist/router/Link.js +50 -119
  91. package/dist/router/Link.js.map +1 -1
  92. package/dist/router/Navigator.d.ts +25 -33
  93. package/dist/router/Navigator.js +41 -149
  94. package/dist/router/Navigator.js.map +1 -1
  95. package/dist/router/Route.d.ts +24 -7
  96. package/dist/router/Route.js +42 -27
  97. package/dist/router/Route.js.map +1 -1
  98. package/dist/router/Router.d.ts +27 -19
  99. package/dist/router/Router.js +112 -120
  100. package/dist/router/Router.js.map +1 -1
  101. package/dist/router/RouterBuilder.d.ts +171 -36
  102. package/dist/router/RouterBuilder.js +101 -39
  103. package/dist/router/RouterBuilder.js.map +1 -1
  104. package/dist/router/RouterOutlet.d.ts +1 -18
  105. package/dist/router/RouterOutlet.js +60 -48
  106. package/dist/router/RouterOutlet.js.map +1 -1
  107. package/dist/router/RouterState.d.ts +1 -1
  108. package/dist/router/index.d.ts +8 -5
  109. package/dist/router/index.js +6 -3
  110. package/dist/router/index.js.map +1 -1
  111. package/dist/router/register.d.ts +37 -0
  112. package/dist/router/register.js +18 -0
  113. package/dist/router/register.js.map +1 -0
  114. package/dist/router/utils.d.ts +36 -0
  115. package/dist/router/utils.js +48 -0
  116. package/dist/router/utils.js.map +1 -0
  117. package/dist/runtime.d.ts +11 -8
  118. package/dist/runtime.js +20 -2
  119. package/dist/runtime.js.map +1 -1
  120. package/dist/server.d.ts +2 -2
  121. package/dist/server.js +15 -29
  122. package/dist/server.js.map +1 -1
  123. package/dist/shared.d.ts +61 -62
  124. package/dist/shared.js +51 -13
  125. package/dist/shared.js.map +1 -1
  126. package/dist/tracking.d.ts +4 -3
  127. package/dist/tracking.js +6 -1
  128. package/dist/tracking.js.map +1 -1
  129. package/package.json +45 -7
  130. package/dist/hydration.d.ts +0 -30
  131. package/dist/hydration.js +0 -355
  132. package/dist/hydration.js.map +0 -1
  133. package/dist/render.d.ts +0 -19
  134. package/dist/render.js +0 -285
  135. package/dist/render.js.map +0 -1
  136. package/dist/scope-utils.d.ts +0 -14
  137. package/dist/scope-utils.js +0 -29
  138. package/dist/scope-utils.js.map +0 -1
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Component-scoped atom utilities.
3
+ *
4
+ * Thin wrappers that tie atom subscriptions to the component lifecycle
5
+ * via ComponentScope, so cleanup happens automatically on unmount.
6
+ */
7
+ import * as Effect from "effect/Effect";
8
+ import * as Scope from "effect/Scope";
9
+ import { Atom, Registry as AtomRegistry } from "@effect-atom/atom";
10
+ import { ComponentScope } from "./shared.js";
11
+ /**
12
+ * Subscribe to an atom for the lifetime of the current component.
13
+ * The subscription is cleaned up automatically when the component unmounts.
14
+ *
15
+ * @example
16
+ * ```tsx
17
+ * const Counter = () =>
18
+ * Effect.gen(function* () {
19
+ * yield* subscribeAtom(countAtom, (value) => {
20
+ * console.log("count changed:", value);
21
+ * });
22
+ * const count = yield* Atom.get(countAtom);
23
+ * return <div>Count: {count}</div>;
24
+ * });
25
+ * ```
26
+ */
27
+ export declare const subscribeAtom: <A>(atom: Atom.Atom<A>, callback: (value: A) => void) => Effect.Effect<void, never, AtomRegistry.AtomRegistry | ComponentScope>;
28
+ /**
29
+ * Run an Effect after the component mounts, scoped to the component lifetime.
30
+ * The forked fiber is interrupted when the component unmounts.
31
+ *
32
+ * Useful for imperative setup (DOM manipulation, external library init)
33
+ * that needs to wait for the DOM to be ready.
34
+ *
35
+ * @example
36
+ * ```tsx
37
+ * const Editor = () =>
38
+ * Effect.gen(function* () {
39
+ * const ref = { current: null as HTMLDivElement | null };
40
+ *
41
+ * yield* mountAtom(
42
+ * Effect.gen(function* () {
43
+ * const editor = monaco.create(ref.current!);
44
+ * yield* Effect.addFinalizer(() => Effect.sync(() => editor.dispose()));
45
+ * }),
46
+ * );
47
+ *
48
+ * return <div ref={el => ref.current = el} />;
49
+ * });
50
+ * ```
51
+ */
52
+ export declare const mountAtom: <A, E>(effect: Effect.Effect<A, E, Scope.Scope>) => Effect.Effect<void, never, ComponentScope>;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Component-scoped atom utilities.
3
+ *
4
+ * Thin wrappers that tie atom subscriptions to the component lifecycle
5
+ * via ComponentScope, so cleanup happens automatically on unmount.
6
+ */
7
+ import * as Effect from "effect/Effect";
8
+ import * as Scope from "effect/Scope";
9
+ import { Atom, Registry as AtomRegistry } from "@effect-atom/atom";
10
+ import { ComponentScope } from "./shared.js";
11
+ /**
12
+ * Subscribe to an atom for the lifetime of the current component.
13
+ * The subscription is cleaned up automatically when the component unmounts.
14
+ *
15
+ * @example
16
+ * ```tsx
17
+ * const Counter = () =>
18
+ * Effect.gen(function* () {
19
+ * yield* subscribeAtom(countAtom, (value) => {
20
+ * console.log("count changed:", value);
21
+ * });
22
+ * const count = yield* Atom.get(countAtom);
23
+ * return <div>Count: {count}</div>;
24
+ * });
25
+ * ```
26
+ */
27
+ export const subscribeAtom = (atom, callback) => Effect.gen(function* () {
28
+ const { scope } = yield* ComponentScope;
29
+ const registry = yield* AtomRegistry.AtomRegistry;
30
+ const unsubscribe = registry.subscribe(atom, callback);
31
+ yield* Scope.addFinalizer(scope, Effect.sync(unsubscribe));
32
+ });
33
+ /**
34
+ * Run an Effect after the component mounts, scoped to the component lifetime.
35
+ * The forked fiber is interrupted when the component unmounts.
36
+ *
37
+ * Useful for imperative setup (DOM manipulation, external library init)
38
+ * that needs to wait for the DOM to be ready.
39
+ *
40
+ * @example
41
+ * ```tsx
42
+ * const Editor = () =>
43
+ * Effect.gen(function* () {
44
+ * const ref = { current: null as HTMLDivElement | null };
45
+ *
46
+ * yield* mountAtom(
47
+ * Effect.gen(function* () {
48
+ * const editor = monaco.create(ref.current!);
49
+ * yield* Effect.addFinalizer(() => Effect.sync(() => editor.dispose()));
50
+ * }),
51
+ * );
52
+ *
53
+ * return <div ref={el => ref.current = el} />;
54
+ * });
55
+ * ```
56
+ */
57
+ export const mountAtom = (effect) => Effect.gen(function* () {
58
+ const { scope, mounted } = yield* ComponentScope;
59
+ yield* Effect.gen(function* () {
60
+ yield* mounted;
61
+ yield* effect;
62
+ }).pipe(Effect.forkScoped, Scope.extend(scope));
63
+ });
64
+ //# sourceMappingURL=atom-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atom-utils.js","sourceRoot":"","sources":["../src/atom-utils.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,IAAI,EAAE,QAAQ,IAAI,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE7C;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAC3B,IAAkB,EAClB,QAA4B,EAC4C,EAAE,CAC1E,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,CAAC,CAAC,cAAc,CAAC;IACxC,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,YAAY,CAAC,YAAY,CAAC;IAClD,MAAM,WAAW,GAAG,QAAQ,CAAC,SAAS,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IACvD,KAAK,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;AAC7D,CAAC,CAAC,CAAC;AAEL;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CACvB,MAAwC,EACI,EAAE,CAC9C,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,KAAK,CAAC,CAAC,cAAc,CAAC;IACjD,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QACzB,KAAK,CAAC,CAAC,OAAO,CAAC;QACf,KAAK,CAAC,CAAC,MAAM,CAAC;IAChB,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;AAClD,CAAC,CAAC,CAAC"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * SSG build pipeline.
3
+ *
4
+ * Orchestrates: Vite client build → route discovery → pre-render → write HTML.
5
+ */
6
+ import * as Effect from "effect/Effect";
7
+ import * as Layer from "effect/Layer";
8
+ import { FileSystem } from "@effect/platform";
9
+ import { Path } from "@effect/platform";
10
+ import { Router, RouterHandlers } from "../router/index.js";
11
+ import type { HeadData } from "../router/index.js";
12
+ import type { VElement } from "../shared.js";
13
+ export interface BuildOptions {
14
+ /** The fibrae router instance */
15
+ readonly router: Router.Router;
16
+ /** Layer providing RouterHandlers */
17
+ readonly handlersLayer: Layer.Layer<RouterHandlers>;
18
+ /** Wraps each route's rendered element in the app shell */
19
+ readonly appShell: (element: VElement) => VElement;
20
+ /** Output directory */
21
+ readonly outDir: string;
22
+ /** Base path prefix for routes (e.g. "/app") */
23
+ readonly basePath?: string;
24
+ /** Path to client JS bundle (relative to site root) */
25
+ readonly clientScript?: string;
26
+ /** Default page title */
27
+ readonly title?: string;
28
+ /** Global head tags injected into every page */
29
+ readonly headTags?: HeadData;
30
+ }
31
+ /**
32
+ * Pre-render all routes marked with `prerender: true` to static HTML files.
33
+ */
34
+ export declare const build: (options: BuildOptions) => Effect.Effect<void, unknown, FileSystem.FileSystem | Path.Path>;
@@ -0,0 +1,92 @@
1
+ /**
2
+ * SSG build pipeline.
3
+ *
4
+ * Orchestrates: Vite client build → route discovery → pre-render → write HTML.
5
+ */
6
+ import * as Effect from "effect/Effect";
7
+ import * as Layer from "effect/Layer";
8
+ import { FileSystem } from "@effect/platform";
9
+ import { Path } from "@effect/platform";
10
+ import { renderToStringWith, SSRAtomRegistryLayer } from "../server.js";
11
+ import { Router, RouterHandlers, getPrerenderRoutes } from "../router/index.js";
12
+ import { buildPage } from "./html.js";
13
+ /**
14
+ * Render a single route to an HTML string.
15
+ */
16
+ const renderRoute = (config) => Effect.gen(function* () {
17
+ const serverLayer = Router.serverLayer({
18
+ router: config.router,
19
+ pathname: config.pathname,
20
+ search: "",
21
+ basePath: config.basePath,
22
+ });
23
+ const fullLayer = Layer.provideMerge(serverLayer, Layer.merge(config.handlersLayer, SSRAtomRegistryLayer));
24
+ const { html, dehydratedState, head } = yield* Effect.gen(function* () {
25
+ const { element, head: routeHead } = yield* Router.CurrentRouteElement;
26
+ const renderResult = yield* renderToStringWith(config.appShell(element));
27
+ return { ...renderResult, head: routeHead };
28
+ }).pipe(Effect.provide(fullLayer));
29
+ return yield* buildPage({
30
+ html,
31
+ dehydratedState: dehydratedState,
32
+ clientScript: config.clientScript,
33
+ title: config.title,
34
+ head,
35
+ headTags: config.headTags,
36
+ });
37
+ });
38
+ /**
39
+ * Compute all (pathname, handler) pairs from prerender routes.
40
+ */
41
+ const expandRoutes = (prerenderRoutes, basePath) => Effect.all(prerenderRoutes.flatMap(({ handler, paramSets }) => paramSets.map((params) => handler.route
42
+ .interpolate(params)
43
+ .pipe(Effect.map((pathname) => ({ pathname: basePath + pathname, handler }))))));
44
+ /**
45
+ * Write an HTML string to the appropriate file path under outDir.
46
+ * Creates directories as needed.
47
+ *
48
+ * / → outDir/index.html
49
+ * /about → outDir/about/index.html
50
+ * /posts/1 → outDir/posts/1/index.html
51
+ */
52
+ const writePageFile = (outDir, pathname, html) => Effect.gen(function* () {
53
+ const fs = yield* FileSystem.FileSystem;
54
+ const path = yield* Path.Path;
55
+ const filePath = pathname === "/" || pathname === ""
56
+ ? path.join(outDir, "index.html")
57
+ : path.join(outDir, pathname, "index.html");
58
+ yield* fs.makeDirectory(path.dirname(filePath), { recursive: true });
59
+ yield* fs.writeFileString(filePath, html);
60
+ });
61
+ /**
62
+ * Pre-render all routes marked with `prerender: true` to static HTML files.
63
+ */
64
+ export const build = (options) => Effect.gen(function* () {
65
+ const { router, handlersLayer, appShell, outDir, basePath = "", clientScript, title, headTags, } = options;
66
+ // Resolve handlers to get prerender routes
67
+ const handlers = yield* Effect.provide(Effect.flatMap(RouterHandlers, (h) => getPrerenderRoutes(h)), handlersLayer);
68
+ const routes = yield* expandRoutes(handlers, basePath);
69
+ if (routes.length === 0) {
70
+ yield* Effect.log("No prerender routes found.");
71
+ return;
72
+ }
73
+ yield* Effect.log(`Pre-rendering ${routes.length} page(s)...`);
74
+ // Render all routes
75
+ const pages = yield* Effect.all(routes.map(({ pathname }) => renderRoute({
76
+ router,
77
+ handlersLayer,
78
+ appShell,
79
+ pathname,
80
+ basePath,
81
+ clientScript,
82
+ title,
83
+ headTags,
84
+ }).pipe(Effect.map((html) => ({ pathname, html })))));
85
+ // Write all pages to disk
86
+ yield* Effect.forEach(pages, ({ pathname, html }) => Effect.gen(function* () {
87
+ yield* writePageFile(outDir, pathname, html);
88
+ yield* Effect.log(` ${pathname} → ${outDir}${pathname === "/" ? "/index.html" : `${pathname}/index.html`}`);
89
+ }), { discard: true });
90
+ yield* Effect.log(`Done. ${pages.length} page(s) written to ${outDir}/`);
91
+ });
92
+ //# sourceMappingURL=build.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.js","sourceRoot":"","sources":["../../src/cli/build.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * fibrae CLI — static site generation for fibrae apps.
4
+ *
5
+ * Commands:
6
+ * fibrae build Pre-render routes and build client bundle
7
+ * fibrae dev Start Vite dev server with on-demand SSR
8
+ * fibrae preview Serve the built output
9
+ */
10
+ export {};
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * fibrae CLI — static site generation for fibrae apps.
4
+ *
5
+ * Commands:
6
+ * fibrae build Pre-render routes and build client bundle
7
+ * fibrae dev Start Vite dev server with on-demand SSR
8
+ * fibrae preview Serve the built output
9
+ */
10
+ const [command] = process.argv.slice(2);
11
+ const commands = {
12
+ async build() {
13
+ const { build } = await import("vite");
14
+ await build();
15
+ },
16
+ async dev() {
17
+ const { createServer } = await import("vite");
18
+ const server = await createServer({ server: { open: true } });
19
+ await server.listen();
20
+ server.printUrls();
21
+ },
22
+ async preview() {
23
+ const { preview } = await import("vite");
24
+ const server = await preview();
25
+ server.printUrls();
26
+ },
27
+ };
28
+ const run = commands[command ?? ""];
29
+ if (!run) {
30
+ console.log(`Usage: fibrae <command>
31
+
32
+ Commands:
33
+ build Pre-render routes and build client bundle
34
+ dev Start Vite dev server with on-demand SSR
35
+ preview Serve the built output`);
36
+ process.exit(command ? 1 : 0);
37
+ }
38
+ run().catch((err) => {
39
+ console.error(err);
40
+ process.exit(1);
41
+ });
42
+ export {};
43
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli/cli.ts"],"names":[],"mappings":";AACA;;;;;;;GAOG;AAEH,MAAM,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAExC,MAAM,QAAQ,GAAwC;IACpD,KAAK,CAAC,KAAK;QACT,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,CAAC;QACvC,MAAM,KAAK,EAAE,CAAC;IAChB,CAAC;IAED,KAAK,CAAC,GAAG;QACP,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,CAAC;QAC9C,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;QAC9D,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC;QACtB,MAAM,CAAC,SAAS,EAAE,CAAC;IACrB,CAAC;IAED,KAAK,CAAC,OAAO;QACX,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,CAAC;QACzC,MAAM,MAAM,GAAG,MAAM,OAAO,EAAE,CAAC;QAC/B,MAAM,CAAC,SAAS,EAAE,CAAC;IACrB,CAAC;CACF,CAAC;AAEF,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;AAEpC,IAAI,CAAC,GAAG,EAAE,CAAC;IACT,OAAO,CAAC,GAAG,CAAC;;;;;kCAKoB,CAAC,CAAC;IAClC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAChC,CAAC;AAED,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IAClB,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACnB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Configuration types for fibrae-cli.
3
+ */
4
+ import type { HeadData } from "../router/RouterBuilder.js";
5
+ export interface FibraeConfig {
6
+ /** Module path that exports { router, handlers, App } */
7
+ readonly entry: string;
8
+ /** Client hydration entry point */
9
+ readonly client: string;
10
+ /** Output directory (default: "dist") */
11
+ readonly outDir?: string;
12
+ /** Base path prefix for routes */
13
+ readonly basePath?: string;
14
+ /** Default page title */
15
+ readonly title?: string;
16
+ /** Global head tags injected into every page (fonts, analytics, meta, etc.) */
17
+ readonly headTags?: HeadData;
18
+ }
19
+ export declare const defineConfig: (config: FibraeConfig) => FibraeConfig;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Configuration types for fibrae-cli.
3
+ */
4
+ export const defineConfig = (config) => config;
5
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/cli/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAmBH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,MAAoB,EAAgB,EAAE,CAAC,MAAM,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * HTML page template for static site generation.
3
+ *
4
+ * Uses fibrae JSX to generate full HTML documents wrapping
5
+ * pre-rendered content with dehydrated state and client scripts.
6
+ */
7
+ import type { HeadData } from "../router/RouterBuilder.js";
8
+ import * as Effect from "effect/Effect";
9
+ import * as Option from "effect/Option";
10
+ export interface PageOptions {
11
+ readonly html: string;
12
+ readonly dehydratedState: ReadonlyArray<unknown>;
13
+ readonly clientScript?: string;
14
+ readonly title?: string;
15
+ readonly head: Option.Option<HeadData>;
16
+ /** Global head tags from config, merged with per-route head */
17
+ readonly headTags?: HeadData;
18
+ }
19
+ export declare const buildPage: (options: PageOptions) => Effect.Effect<string, unknown, never>;
@@ -0,0 +1,95 @@
1
+ /**
2
+ * HTML page template for static site generation.
3
+ *
4
+ * Uses fibrae JSX to generate full HTML documents wrapping
5
+ * pre-rendered content with dehydrated state and client scripts.
6
+ */
7
+ // eslint-disable-next-line no-unused-vars -- jsx is used by the JSX transform (jsxFactory)
8
+ import { jsx } from "../jsx-runtime/index.js";
9
+ import { h } from "../h.js";
10
+ import { renderToString } from "../server.js";
11
+ import * as Effect from "effect/Effect";
12
+ import * as Option from "effect/Option";
13
+ import * as Array from "effect/Array";
14
+ const metaToElement = (meta) => {
15
+ if ("title" in meta)
16
+ return Option.none();
17
+ if ("charset" in meta)
18
+ return Option.some(jsx("meta", { charset: meta.charset }));
19
+ if ("script:ld+json" in meta)
20
+ return Option.some(jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: JSON.stringify(meta["script:ld+json"]) }));
21
+ if ("name" in meta)
22
+ return Option.some(jsx("meta", { name: meta.name, content: meta.content }));
23
+ if ("property" in meta)
24
+ return Option.some(jsx("meta", { property: meta.property, content: meta.content }));
25
+ if ("httpEquiv" in meta)
26
+ return Option.some(jsx("meta", { "http-equiv": meta.httpEquiv, content: meta.content }));
27
+ if ("tagName" in meta) {
28
+ const { tagName, ...attrs } = meta;
29
+ // Dynamic tag name -- JSX requires literal tags, so use h() directly
30
+ return Option.some(h(tagName, attrs));
31
+ }
32
+ return Option.none();
33
+ };
34
+ /**
35
+ * Get the dedup key for a meta descriptor.
36
+ * Per TanStack Router pattern: meta tags with the same name/property are
37
+ * deduplicated, with per-route tags winning over global tags.
38
+ */
39
+ const metaKey = (meta) => {
40
+ if ("name" in meta)
41
+ return `name:${meta.name}`;
42
+ if ("property" in meta)
43
+ return `property:${meta.property}`;
44
+ if ("httpEquiv" in meta)
45
+ return `httpEquiv:${meta.httpEquiv}`;
46
+ if ("charset" in meta)
47
+ return "charset";
48
+ return undefined;
49
+ };
50
+ /**
51
+ * Merge meta arrays with deduplication. Per-route entries override globals
52
+ * when they share the same name/property/httpEquiv key.
53
+ */
54
+ const dedupMeta = (global, perRoute) => {
55
+ const routeKeys = new Set(perRoute.map(metaKey).filter(Boolean));
56
+ const filtered = global.filter((m) => {
57
+ const key = metaKey(m);
58
+ return key === undefined || !routeKeys.has(key);
59
+ });
60
+ return [...filtered, ...perRoute];
61
+ };
62
+ const buildHeadChildren = (title, head, headTags) => {
63
+ const headData = Option.getOrUndefined(head);
64
+ // Per-route title wins, then global headTags title, then config title
65
+ const pageTitle = headData?.title ?? headTags?.title ?? title;
66
+ // Merge global headTags with per-route head.
67
+ // Meta tags are deduplicated by name/property (per-route wins).
68
+ // Links and scripts are concatenated (global first, then per-route).
69
+ const allMeta = dedupMeta(headTags?.meta ?? [], headData?.meta ?? []);
70
+ const allLinks = [...(headTags?.links ?? []), ...(headData?.links ?? [])];
71
+ const allScripts = [...(headTags?.scripts ?? []), ...(headData?.scripts ?? [])];
72
+ return [
73
+ jsx("meta", { charset: "UTF-8" }),
74
+ jsx("meta", { name: "viewport", content: "width=device-width, initial-scale=1.0" }),
75
+ ...Option.match(Option.fromNullable(pageTitle), {
76
+ onNone: () => [],
77
+ onSome: (t) => [jsx("title", null, t)],
78
+ }),
79
+ ...Array.filterMap(allMeta, metaToElement),
80
+ ...allLinks.map((attrs) => jsx("link", { ...attrs })),
81
+ ...allScripts.flatMap((script) => script.src
82
+ ? [jsx("script", { type: script.type, src: script.src })]
83
+ : script.content
84
+ ? [jsx("script", { type: script.type, dangerouslySetInnerHTML: script.content })]
85
+ : []),
86
+ ];
87
+ };
88
+ const PageShell = (props) => (jsx("html", { lang: "en" },
89
+ jsx("head", null, buildHeadChildren(props.title, props.head, props.headTags)),
90
+ jsx("body", null,
91
+ jsx("div", { id: "root", dangerouslySetInnerHTML: props.html }),
92
+ jsx("script", { type: "application/json", id: "__fibrae-state__", dangerouslySetInnerHTML: JSON.stringify(props.dehydratedState) }),
93
+ props.clientScript ? jsx("script", { type: "module", src: props.clientScript }) : null)));
94
+ export const buildPage = (options) => renderToString(PageShell(options)).pipe(Effect.map(({ html }) => `<!DOCTYPE html>\n${html}`));
95
+ //# sourceMappingURL=html.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html.js","sourceRoot":"","sources":["../../src/cli/html.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export { buildPage } from "./html.js";
2
+ export type { PageOptions } from "./html.js";
3
+ export { build } from "./build.js";
4
+ export type { BuildOptions } from "./build.js";
5
+ export { defineConfig } from "./config.js";
6
+ export type { FibraeConfig } from "./config.js";
@@ -0,0 +1,4 @@
1
+ export { buildPage } from "./html.js";
2
+ export { build } from "./build.js";
3
+ export { defineConfig } from "./config.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Vite plugin for fibrae static site generation.
3
+ *
4
+ * Hooks into Vite's build pipeline to pre-render routes after the client build.
5
+ * In dev mode, provides on-demand SSR middleware.
6
+ */
7
+ import type { Plugin } from "vite";
8
+ import type { FibraeConfig } from "./config.js";
9
+ export declare const fibrae: (config: FibraeConfig) => Plugin<any>;
@@ -0,0 +1,143 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Option from "effect/Option";
3
+ import * as Layer from "effect/Layer";
4
+ import { NodeContext } from "@effect/platform-node";
5
+ import { Router, RouterHandlers } from "../router/index.js";
6
+ import { renderToStringWith, SSRAtomRegistryLayer } from "../server.js";
7
+ import { buildPage } from "./html.js";
8
+ /**
9
+ * Render a single page for the dev server.
10
+ */
11
+ const renderDevPage = (opts) => Effect.gen(function* () {
12
+ const serverLayer = Router.serverLayer({
13
+ router: opts.router,
14
+ pathname: opts.pathname,
15
+ search: "",
16
+ basePath: opts.basePath,
17
+ });
18
+ const fullLayer = Layer.provideMerge(serverLayer, Layer.merge(opts.handlersLayer, SSRAtomRegistryLayer));
19
+ const { html, dehydratedState, head } = yield* Effect.gen(function* () {
20
+ const { head: routeHead } = yield* Router.CurrentRouteElement;
21
+ const renderResult = opts.App
22
+ ? yield* renderToStringWith(opts.App())
23
+ : yield* Effect.gen(function* () {
24
+ const { element } = yield* Router.CurrentRouteElement;
25
+ const app = opts.appShell ? opts.appShell(element) : element;
26
+ return yield* renderToStringWith(app);
27
+ });
28
+ return { ...renderResult, head: routeHead };
29
+ }).pipe(Effect.provide(fullLayer));
30
+ return yield* buildPage({
31
+ html,
32
+ dehydratedState: dehydratedState,
33
+ clientScript: opts.clientScript,
34
+ title: opts.title,
35
+ head,
36
+ headTags: opts.headTags,
37
+ });
38
+ });
39
+ /**
40
+ * Walk the Vite module graph from an entry and collect CSS module URLs.
41
+ * Injects these as <style> tags in the SSR HTML to prevent FOUC.
42
+ */
43
+ const collectCss = async (server, entryUrl) => {
44
+ // Warm the module graph so CSS deps are discovered
45
+ await server.transformRequest(entryUrl);
46
+ const seen = new Set();
47
+ const cssUrls = [];
48
+ const walk = (mod) => {
49
+ if (!mod?.id || seen.has(mod.id))
50
+ return;
51
+ seen.add(mod.id);
52
+ if (mod.id.endsWith(".css")) {
53
+ cssUrls.push(mod.url);
54
+ }
55
+ for (const dep of mod.importedModules) {
56
+ walk(dep);
57
+ }
58
+ };
59
+ walk(await server.moduleGraph.getModuleByUrl(entryUrl));
60
+ return cssUrls;
61
+ };
62
+ export const fibrae = (config) => {
63
+ let _resolvedConfig;
64
+ return {
65
+ name: "fibrae-ssg",
66
+ configResolved(resolved) {
67
+ _resolvedConfig = resolved;
68
+ },
69
+ configureServer(server) {
70
+ server.middlewares.use(async (req, res, next) => {
71
+ const url = req.url;
72
+ if (!url || url.startsWith("/@") || url.includes(".")) {
73
+ return next();
74
+ }
75
+ try {
76
+ const appModule = await server.ssrLoadModule(config.entry);
77
+ const { router, handlersLayer, appShell, App } = appModule;
78
+ if (!router || !handlersLayer) {
79
+ return next();
80
+ }
81
+ // Only SSR routes the router knows about; pass everything else through
82
+ if (Option.isNone(router.matchRoute(url))) {
83
+ return next();
84
+ }
85
+ const rawHtml = await Effect.runPromise(renderDevPage({
86
+ router,
87
+ handlersLayer,
88
+ App,
89
+ appShell,
90
+ pathname: url,
91
+ basePath: config.basePath ?? "",
92
+ clientScript: config.client,
93
+ title: config.title,
94
+ headTags: config.headTags,
95
+ }));
96
+ // Collect CSS from client entry's module graph and inject into <head>
97
+ const cssUrls = await collectCss(server, config.client);
98
+ const cssTags = cssUrls.map((u) => `<link rel="stylesheet" href="${u}">`).join("\n");
99
+ const withCss = cssTags ? rawHtml.replace("</head>", `${cssTags}\n</head>`) : rawHtml;
100
+ // Let Vite inject HMR client
101
+ const result = await server.transformIndexHtml(url, withCss);
102
+ res.setHeader("Content-Type", "text/html");
103
+ res.end(result);
104
+ }
105
+ catch {
106
+ next();
107
+ }
108
+ });
109
+ },
110
+ async closeBundle() {
111
+ if (_resolvedConfig.command !== "build")
112
+ return;
113
+ const outDir = config.outDir ?? _resolvedConfig.build.outDir ?? "dist";
114
+ try {
115
+ const entryPath = new URL(config.entry, `file://${process.cwd()}/`).pathname;
116
+ const appModule = await import(entryPath);
117
+ const { router, handlersLayer, appShell } = appModule;
118
+ if (!router || !handlersLayer) {
119
+ console.warn("[fibrae-ssg] Entry module missing router or handlersLayer exports. Skipping SSG.");
120
+ return;
121
+ }
122
+ const { build: ssgBuild } = await import("./build.js");
123
+ const clientScript = config.client
124
+ ? `/${config.client.replace(/\.tsx?$/, ".js")}`
125
+ : undefined;
126
+ await Effect.runPromise(ssgBuild({
127
+ router,
128
+ handlersLayer,
129
+ appShell: appShell ?? ((el) => el),
130
+ outDir,
131
+ basePath: config.basePath ?? "",
132
+ clientScript,
133
+ title: config.title,
134
+ headTags: config.headTags,
135
+ }).pipe(Effect.provide(NodeContext.layer)));
136
+ }
137
+ catch (e) {
138
+ console.error("[fibrae-ssg] Pre-render failed:", e);
139
+ }
140
+ },
141
+ };
142
+ };
143
+ //# sourceMappingURL=vite-plugin.js.map