flamefront 0.1.0-alpha.0 → 0.1.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.
package/src/lifecycle.ts CHANGED
@@ -23,9 +23,10 @@ import {
23
23
  staticRouteFragmentDataFile,
24
24
  } from "./static-fragment-artifacts.ts"
25
25
  import {
26
- staticFragmentProtocol,
27
- type StaticFragmentArtifact,
26
+ routeFragmentProtocol,
27
+ type RouteFragmentArtifact,
28
28
  } from "./fragment-client.ts"
29
+ import { setGlobRoot } from "./glob.ts"
29
30
 
30
31
  export {
31
32
  staticRouteFile,
@@ -65,6 +66,7 @@ export async function loadProject(
65
66
  const url = pathToFileURL(routesFile)
66
67
 
67
68
  url.searchParams.set("ff", String(Date.now()))
69
+ setGlobRoot(root)
68
70
  const module = (await import(url.href)) as AppModule
69
71
  const app = module.app ?? module.default
70
72
 
@@ -234,7 +236,6 @@ export async function buildProject(root = process.cwd()): Promise<void> {
234
236
  configFile: resolve(root, "vite.config.ts"),
235
237
  build: {
236
238
  outDir: clientDirectory,
237
- manifest: true,
238
239
  },
239
240
  })
240
241
  await build({
@@ -320,7 +321,7 @@ export async function prerenderStaticRoutes(
320
321
  render: (request: Request) => Promise<RenderDocumentResult>,
321
322
  loadData?: (request: Request) => Promise<unknown>,
322
323
  routing: Pick<NormalizedRoutingOptions, "basename"> = { basename: "/" },
323
- renderFragment?: (request: Request) => Promise<StaticFragmentArtifact>,
324
+ renderFragment?: (request: Request) => Promise<RouteFragmentArtifact>,
324
325
  ): Promise<void> {
325
326
  for (const route of routes) {
326
327
  const outputFile = staticRouteFile(clientDirectory, route)
@@ -342,14 +343,14 @@ export async function prerenderStaticRoutes(
342
343
  const fragment = renderFragment
343
344
  ? await renderFragment(request)
344
345
  : ({
345
- protocol: staticFragmentProtocol,
346
+ protocol: routeFragmentProtocol,
346
347
  route: route.path,
347
348
  boundary: route.entry,
348
349
  html: rendered.html,
349
350
  routeData: data,
350
351
  boundaries: [],
351
352
  status: rendered.status,
352
- } satisfies StaticFragmentArtifact)
353
+ } satisfies RouteFragmentArtifact)
353
354
 
354
355
  await mkdir(dirname(outputFile), { recursive: true })
355
356
  await writeFile(outputFile, rendered.html)
@@ -1,5 +1,6 @@
1
1
  import type { AppDefinition, RouteDefinition } from "./index.ts"
2
- import type { RouterDocument, RouterDocumentProps } from "./octane.ts"
2
+ import type { RouterDocument, RouterDocumentProps } from "./octane.tsx"
3
+ import { shellIdentifierPrefix } from "./identifier-prefix.ts"
3
4
 
4
5
  export type OctaneClientApp<Route extends RouteDefinition = RouteDefinition> =
5
6
  Pick<AppDefinition<Route>, "match" | "prefetch">
@@ -48,11 +49,13 @@ export interface OctaneClientRuntime<
48
49
  container: Container,
49
50
  component: RouterDocument,
50
51
  props: RouterDocumentProps,
52
+ options?: { readonly identifierPrefix?: string },
51
53
  ): Root
52
54
  hydrateRoot(
53
55
  container: Container,
54
56
  component: RouterDocument,
55
57
  props: RouterDocumentProps,
58
+ options?: { readonly identifierPrefix?: string },
56
59
  ): Root
57
60
  }
58
61
 
@@ -127,8 +130,12 @@ export async function startOctaneClientWithRuntime<
127
130
  const routerDocument = options.routerDocument ?? runtime.routerDocument
128
131
  const props: RouterDocumentProps = { router, context: undefined }
129
132
  const clientRoot = shouldHydrate
130
- ? runtime.hydrateRoot(root, routerDocument, props)
131
- : runtime.renderRoot(root, routerDocument, props)
133
+ ? runtime.hydrateRoot(root, routerDocument, props, {
134
+ identifierPrefix: shellIdentifierPrefix,
135
+ })
136
+ : runtime.renderRoot(root, routerDocument, props, {
137
+ identifierPrefix: shellIdentifierPrefix,
138
+ })
132
139
 
133
140
  return { router, root: clientRoot }
134
141
  }
@@ -1,6 +1,7 @@
1
1
  import type { DataRouter } from "@octanejs/remix-router"
2
- import { createRoot, hydrateRoot, type Root } from "octane"
2
+ import { createRoot, hydrateRoot, type ComponentBody, type Root } from "octane"
3
3
  import type { RouteDefinition } from "./index.ts"
4
+ import type { RouterDocumentProps } from "./octane.tsx"
4
5
  import {
5
6
  startOctaneClientWithRuntime,
6
7
  type StartOctaneClientOptions,
@@ -30,13 +31,18 @@ export function startOctaneClient<Route extends RouteDefinition>(
30
31
  consumeHydrationData: consumeStaticRouterHydrationData,
31
32
  createRoutePrefetcher,
32
33
  createClientRouter,
33
- renderRoot(root, component, props) {
34
- const clientRoot = createRoot(root)
34
+ renderRoot(root, component, props, options) {
35
+ const clientRoot = createRoot(root, options)
35
36
 
36
- clientRoot.render(component as never, props)
37
+ clientRoot.render(component as ComponentBody<RouterDocumentProps>, props)
37
38
  return clientRoot
38
39
  },
39
- hydrateRoot: (root, component, props) =>
40
- hydrateRoot(root, component as never, props),
40
+ hydrateRoot: (root, component, props, options) =>
41
+ hydrateRoot(
42
+ root,
43
+ component as ComponentBody<RouterDocumentProps>,
44
+ props,
45
+ options,
46
+ ),
41
47
  })
42
48
  }
@@ -0,0 +1,19 @@
1
+ declare module "octane/compiler" {
2
+ export interface CompileOptions {
3
+ readonly dev?: boolean
4
+ readonly hmr?: boolean | "vite" | "webpack"
5
+ readonly mode?: "client" | "server"
6
+ }
7
+
8
+ export interface CompileResult {
9
+ readonly code: string
10
+ readonly map: object | null
11
+ readonly diagnostics: readonly unknown[]
12
+ }
13
+
14
+ export function compile(
15
+ source: string,
16
+ filename: string,
17
+ options?: CompileOptions,
18
+ ): CompileResult
19
+ }
@@ -0,0 +1,124 @@
1
+ import {
2
+ StaticRouterProvider,
3
+ type RouteObject,
4
+ createStaticRouter,
5
+ type DataRouter,
6
+ type StaticHandlerContext,
7
+ } from "@octanejs/remix-router"
8
+ import { renderToString } from "octane/server"
9
+ import { routeFragmentBoundaryTarget } from "./fragment.tsx"
10
+ import { RouterDocument } from "./octane-router-document.ts"
11
+ import type { OctaneRenderer } from "./octane.tsx"
12
+
13
+ function findRoute(
14
+ routes: readonly RouteObject[],
15
+ id: string,
16
+ ): RouteObject | undefined {
17
+ for (const route of routes) {
18
+ if (route.id === id) {
19
+ return route
20
+ }
21
+
22
+ const match = route.children ? findRoute(route.children, id) : undefined
23
+
24
+ if (match) {
25
+ return match
26
+ }
27
+ }
28
+
29
+ return undefined
30
+ }
31
+
32
+ function errorsForBoundary(
33
+ context: StaticHandlerContext,
34
+ matches: readonly { readonly route: { readonly id: string } }[],
35
+ ): StaticHandlerContext["errors"] {
36
+ if (!context.errors) {
37
+ return null
38
+ }
39
+
40
+ const ids = new Set(matches.map((match) => match.route.id))
41
+ const errors: Record<string, unknown> = {}
42
+ let inheritedError: unknown = undefined
43
+
44
+ for (const [id, error] of Object.entries(context.errors)) {
45
+ if (ids.has(id)) {
46
+ errors[id] = error
47
+ } else if (inheritedError === undefined) {
48
+ inheritedError = error
49
+ }
50
+ }
51
+
52
+ if (inheritedError !== undefined && matches[0]) {
53
+ errors[matches[0].route.id] ??= inheritedError
54
+ }
55
+
56
+ return Object.keys(errors).length > 0 ? errors : null
57
+ }
58
+
59
+ export const defaultOctaneRenderer: OctaneRenderer = {
60
+ createStaticRouter: (routes, context) =>
61
+ createStaticRouter(
62
+ routes as RouteObject[],
63
+ context as StaticHandlerContext,
64
+ ),
65
+ renderToString: (component, props, options) =>
66
+ renderToString(
67
+ component as Parameters<typeof renderToString>[0],
68
+ props,
69
+ options,
70
+ ),
71
+ renderRouteFragment: (router, context, boundary, options) => {
72
+ const dataRouter = router as DataRouter
73
+ const staticContext = context as StaticHandlerContext
74
+ const state = dataRouter.state
75
+ const matchIndex = state.matches.findIndex(
76
+ (match) => match.route?.id === boundary,
77
+ )
78
+
79
+ if (matchIndex < 0) {
80
+ throw new Error(
81
+ `No server router match exists for fragment boundary ${JSON.stringify(boundary)}.`,
82
+ )
83
+ }
84
+
85
+ const outletRoute = findRoute(dataRouter.routes, boundary)
86
+
87
+ if (!outletRoute) {
88
+ throw new Error(
89
+ `No server route exists for fragment boundary ${JSON.stringify(boundary)}.`,
90
+ )
91
+ }
92
+
93
+ const matches = staticContext.matches.slice(matchIndex)
94
+ const outletContext = {
95
+ ...staticContext,
96
+ matches,
97
+ errors: errorsForBoundary(staticContext, matches),
98
+ }
99
+ const outletRouter = createStaticRouter([outletRoute], outletContext)
100
+ const FragmentBoundaryProvider = routeFragmentBoundaryTarget.Provider
101
+ const FragmentRoot = () => (
102
+ <FragmentBoundaryProvider
103
+ value={options?.includeBoundary ? null : boundary}
104
+ >
105
+ <StaticRouterProvider
106
+ router={outletRouter}
107
+ context={outletContext}
108
+ hydrate={false}
109
+ />
110
+ </FragmentBoundaryProvider>
111
+ )
112
+
113
+ return renderToString(
114
+ FragmentRoot,
115
+ {},
116
+ {
117
+ ...(options?.identifierPrefix
118
+ ? { identifierPrefix: options.identifierPrefix }
119
+ : {}),
120
+ },
121
+ )
122
+ },
123
+ defaultRouterDocument: RouterDocument,
124
+ }
@@ -1,5 +1,22 @@
1
1
  import { RouterProvider } from "@octanejs/remix-router/dom"
2
- import type { RouterDocument as RouterDocumentComponent } from "./octane.ts"
2
+ import { createElement } from "octane"
3
+ import { routeOutletHtmlContext } from "./fragment.tsx"
4
+ import type {
5
+ RouterDocument as RouterDocumentComponent,
6
+ RouterDocumentProps,
7
+ } from "./octane.tsx"
3
8
 
4
9
  /** Canonical router root shared by Flamefront's Octane server and client. */
5
- export const RouterDocument = RouterProvider as RouterDocumentComponent
10
+ export const RouterDocument: RouterDocumentComponent = (
11
+ props: RouterDocumentProps,
12
+ ) => {
13
+ const Router = RouterProvider as unknown as (props: {
14
+ readonly router: unknown
15
+ }) => unknown
16
+
17
+ return createElement(
18
+ routeOutletHtmlContext.Provider,
19
+ { value: props.outletHtml ?? null },
20
+ createElement(Router, { router: props.router }),
21
+ )
22
+ }