Skip to content
This repository was archived by the owner on Jul 6, 2025. It is now read-only.

Commit 679abbb

Browse files
author
Je
committed
feat: new routing system
refactor: rename 'app.ts' to 'aleph.ts' refactor: remove server dir
1 parent 34f6f30 commit 679abbb

20 files changed

+769
-598
lines changed

aleph.ts

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
import React, { ComponentType, useCallback, useEffect, useRef, useState } from 'https://esm.sh/react'
2+
import { DataContext, RouterContext } from './context.ts'
3+
import { E404Page, E501App, E501Page, ErrorBoundary } from './error.ts'
4+
import events from './events.ts'
5+
import type { Routing } from './router.ts'
6+
import type { PageProps, RouteModule, RouterURL } from './types.ts'
7+
import util, { hashShort, reModuleExt } from './util.ts'
8+
9+
export function ALEPH({ initial }: {
10+
initial: {
11+
routing: Routing
12+
url: RouterURL
13+
staticData: Record<string, any>
14+
components: Record<string, ComponentType<any>>
15+
pageProps: PageProps
16+
}
17+
}) {
18+
const ref = useRef({ routing: initial.routing })
19+
const [staticData, setStaticData] = useState(() => initial.staticData)
20+
const [e404, setE404] = useState(() => {
21+
const { E404 } = initial.components
22+
return {
23+
Component: E404 && util.isLikelyReactComponent(E404) ? E404 : E404Page
24+
}
25+
})
26+
const [app, setApp] = useState(() => {
27+
const { App } = initial.components
28+
return {
29+
Component: App ? (util.isLikelyReactComponent(App) ? App : E501App) : null
30+
}
31+
})
32+
const [page, setPage] = useState<{ url: RouterURL, pageProps: PageProps | null }>(() => {
33+
const { url, pageProps } = initial
34+
return { pageProps, url }
35+
})
36+
const onpopstate = useCallback(async () => {
37+
const { routing } = ref.current
38+
const { baseUrl } = routing
39+
const [url, pageModuleTree] = routing.createRouter()
40+
if (url.pagePath !== '') {
41+
const ctree: { id: string, Component?: ComponentType<any> }[] = pageModuleTree.map(({ id }) => ({ id }))
42+
const imports = pageModuleTree.map(async mod => {
43+
const { default: C } = await import(getModuleImportUrl(baseUrl, mod))
44+
if (mod.asyncDeps) {
45+
// import async dependencies
46+
for (const dep of mod.asyncDeps) {
47+
await import(getModuleImportUrl(baseUrl, { id: dep.url.replace(reModuleExt, '.js'), hash: dep.hash }))
48+
}
49+
}
50+
const pc = ctree.find(pc => pc.id === mod.id)
51+
if (pc) {
52+
if (util.isLikelyReactComponent(C)) {
53+
pc.Component = C
54+
} else {
55+
pc.Component = E501Page
56+
}
57+
}
58+
})
59+
await Promise.all(imports)
60+
const pageProps: PageProps = {
61+
Page: ctree[0].Component || (() => null),
62+
pageProps: {}
63+
}
64+
ctree.slice(1).reduce((p, m) => {
65+
const c: PageProps = {
66+
Page: m.Component || (() => null),
67+
pageProps: {}
68+
}
69+
p.pageProps = c
70+
return c
71+
}, pageProps)
72+
setPage({ url, pageProps })
73+
} else {
74+
setPage({ url, pageProps: null })
75+
}
76+
}, [ref])
77+
78+
useEffect(() => {
79+
window.addEventListener('popstate', onpopstate)
80+
events.on('popstate', onpopstate)
81+
82+
return () => {
83+
window.removeEventListener('popstate', onpopstate)
84+
events.off('popstate', onpopstate)
85+
}
86+
}, [onpopstate])
87+
88+
useEffect(() => {
89+
const { routing: { baseUrl } } = ref.current
90+
const onUpdateData = (data: any) => {
91+
console.log('[DATA]', data)
92+
setStaticData(data)
93+
}
94+
const onAddModule = async (mod: RouteModule) => {
95+
switch (mod.id) {
96+
case '/404.js': {
97+
const { default: Component } = await import(getModuleImportUrl(baseUrl, mod, true))
98+
if (util.isLikelyReactComponent(Component)) {
99+
setE404({ Component })
100+
} else {
101+
setE404({ Component: E404Page })
102+
}
103+
break
104+
}
105+
case '/app.js': {
106+
const { default: Component } = await import(getModuleImportUrl(baseUrl, mod, true))
107+
if (util.isLikelyReactComponent(Component)) {
108+
setApp({ Component })
109+
} else {
110+
setApp({ Component: E501App })
111+
}
112+
break
113+
}
114+
case '/data.js': {
115+
const { default: data } = await import(getModuleImportUrl(baseUrl, mod, true))
116+
console.log('[DATA]', data)
117+
setStaticData(data)
118+
break
119+
}
120+
default: {
121+
if (mod.id.startsWith('/pages/')) {
122+
const { routing } = ref.current
123+
routing.update(mod)
124+
}
125+
break
126+
}
127+
}
128+
}
129+
const onRemoveModule = (moduleId: string) => {
130+
switch (moduleId) {
131+
case '/404.js':
132+
setE404({ Component: E404Page })
133+
break
134+
case '/app.js':
135+
setApp({ Component: null })
136+
break
137+
case '/data.js':
138+
console.log('[DATA]', {})
139+
setStaticData({})
140+
break
141+
default:
142+
if (moduleId.startsWith('/pages/')) {
143+
const { routing } = ref.current
144+
routing.removeRoute(moduleId)
145+
}
146+
break
147+
}
148+
}
149+
150+
events.on('update-data', onUpdateData)
151+
events.on('add-module', onAddModule)
152+
events.on('remove-module', onRemoveModule)
153+
154+
return () => {
155+
events.off('update-data', onUpdateData)
156+
events.off('add-module', onAddModule)
157+
events.off('remove-module', onRemoveModule)
158+
}
159+
}, [ref])
160+
161+
return (
162+
React.createElement(
163+
ErrorBoundary,
164+
null,
165+
React.createElement(
166+
DataContext.Provider,
167+
{ value: staticData },
168+
React.createElement(
169+
RouterContext.Provider,
170+
{ value: page.url },
171+
...[
172+
(page.pageProps && app.Component) && React.createElement(app.Component, page.pageProps),
173+
(page.pageProps && !app.Component) && React.createElement(page.pageProps.Page, page.pageProps.pageProps),
174+
!page.pageProps && React.createElement(e404.Component)
175+
].filter(Boolean),
176+
)
177+
)
178+
)
179+
)
180+
}
181+
182+
export async function redirect(url: string, replace?: boolean) {
183+
const { location, history } = window as any
184+
185+
if (util.isHttpUrl(url)) {
186+
location.href = url
187+
return
188+
}
189+
190+
url = util.cleanPath(url)
191+
if (replace) {
192+
history.replaceState(null, '', url)
193+
} else {
194+
history.pushState(null, '', url)
195+
}
196+
events.emit('popstate', { type: 'popstate' })
197+
}
198+
199+
export function getModuleImportUrl(baseUrl: string, mod: RouteModule, forceFetch = false) {
200+
return util.cleanPath(baseUrl + '/_aleph/' + util.trimSuffix(mod.id, '.js') + `.${mod.hash.slice(0, hashShort)}.js` + (forceFetch ? `?t=${Date.now()}` : ''))
201+
}

server/api.ts renamed to api.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import type { ServerRequest } from '../std.ts'
2-
import type { APIRequest, APIResponse } from '../types.ts'
1+
import type { ServerRequest } from './std.ts'
2+
import type { APIRequest, APIResponse } from './types.ts'
33

4-
export class PostAPIRequest implements APIRequest {
4+
export class AlephAPIRequest implements APIRequest {
55
#req: ServerRequest
66

77
cookies: ReadonlyMap<string, string>
@@ -48,7 +48,7 @@ export class PostAPIRequest implements APIRequest {
4848
}
4949
}
5050

51-
export class PostAPIResponse implements APIResponse {
51+
export class AlephAPIResponse implements APIResponse {
5252
#req: ServerRequest
5353
#headers: Headers
5454
#status: number

0 commit comments

Comments
 (0)