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

Commit ad881a6

Browse files
author
Wenjie Xia
committed
refactor: improve framework loading
1 parent 10c9615 commit ad881a6

File tree

15 files changed

+291
-288
lines changed

15 files changed

+291
-288
lines changed

framework/core/hmr.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,10 @@ socket.addEventListener('close', () => {
6565
socket.addEventListener('message', ({ data: rawData }: { data?: string }) => {
6666
if (rawData) {
6767
try {
68-
const { type, url, hash, updateUrl } = JSON.parse(rawData)
68+
const { type, url, hash, asyncDeps, updateUrl } = JSON.parse(rawData)
6969
switch (type) {
7070
case 'add':
71-
events.emit('add-module', { url, hash })
71+
events.emit('add-module', { url, hash, asyncDeps })
7272
break
7373
case 'update':
7474
const mod = modules.get(url)

framework/core/routing.ts

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
import { pageModuleExts } from '../../shared/constants.ts'
22
import util from '../../shared/util.ts'
3-
import type { RouterURL } from '../../types.ts'
3+
import type { DependencyDescriptor, RouterURL } from '../../types.ts'
44
import events from './events.ts'
55

6-
export interface Route {
6+
const ghostRoute: Route = { path: '', module: { url: '', hash: '' } }
7+
8+
export type Route = {
79
path: string
810
module: RouteModule
911
children?: Route[]
1012
}
1113

12-
export interface RouteModule {
14+
export type RouteModule = {
1315
readonly url: string
1416
readonly hash: string
15-
readonly asyncDeps?: { data?: boolean, style?: boolean }
17+
readonly asyncDeps?: DependencyDescriptor[]
1618
}
1719

18-
const ghostRoute: Route = { path: '', module: { url: '', hash: '' } }
19-
2020
export class Routing {
2121
private _routes: Route[]
2222
private _baseUrl: string
@@ -113,7 +113,7 @@ export class Routing {
113113
let pathname = util.cleanPath(util.trimPrefix(loc.pathname, this._baseUrl))
114114
let pagePath = ''
115115
let params: Record<string, string> = {}
116-
let tree: RouteModule[] = []
116+
let chain: RouteModule[] = []
117117

118118
if (pathname !== '/' && this._locales.length > 0) {
119119
const a = pathname.split('/')
@@ -128,18 +128,18 @@ export class Routing {
128128
const path = routePath.map(r => r.path).join('')
129129
const [p, ok] = matchPath(path, pathname)
130130
if (ok) {
131-
tree = routePath.map(r => r.module)
131+
chain = routePath.map(r => r.module)
132132
const c = routePath[routePath.length - 1].children?.find(c => c.path === '/')
133133
if (c) {
134-
tree.push(c.module)
134+
chain.push(c.module)
135135
}
136136
pagePath = path
137137
params = p
138138
return false
139139
}
140140
}, true)
141141

142-
return [{ locale, pathname, pagePath, params, query }, tree]
142+
return [{ locale, pathname, pagePath, params, query }, chain]
143143
}
144144

145145
lookup(callback: (path: Route[]) => Boolean | void) {
@@ -231,6 +231,15 @@ export function isHttpUrl(url: string) {
231231
}
232232
}
233233

234+
export function isPageModule(url: string) {
235+
for (const ext of pageModuleExts) {
236+
if (url.endsWith('.' + ext)) {
237+
return true
238+
}
239+
}
240+
return false
241+
}
242+
234243
export function getPagePathname(url: string): string {
235244
const pathname = trimPageModuleExt(url).replace(/^\/pages\//, '/').replace(/\/?index$/, '/')
236245
return pathname

framework/react/bootstrap.ts

Lines changed: 24 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ import type { ComponentType } from 'https://esm.sh/react'
22
import { createElement } from 'https://esm.sh/react'
33
import { hydrate, render } from 'https://esm.sh/react-dom'
44
import { Route, RouteModule, Routing, trimPageModuleExt } from '../core/routing.ts'
5+
import type { PageRoute } from './pageprops.ts'
6+
import { createPageProps } from './pageprops.ts'
57
import Router from './router.ts'
6-
import { importModule } from './util.ts'
8+
import { importModule, loadPageDataFromTag } from './util.ts'
79

8-
type BootstrapOptions = {
10+
type Options = {
911
baseUrl: string
1012
defaultLocale: string
1113
locales: string[]
@@ -14,15 +16,11 @@ type BootstrapOptions = {
1416
renderMode: 'ssr' | 'spa'
1517
}
1618

17-
export default async function bootstrap({ baseUrl, defaultLocale, locales, routes, sharedModules, renderMode }: BootstrapOptions) {
19+
export default async function bootstrap(options: Options) {
20+
const { baseUrl, defaultLocale, locales, routes, sharedModules, renderMode } = options
1821
const { document } = window as any
19-
const ssrDataEl = document.querySelector('#ssr-data')
20-
const routing = new Routing(routes, baseUrl, defaultLocale, locales)
21-
const [url, pageModuleTree] = routing.createRouter()
22-
const pageComponentTree: { url: string, Component?: ComponentType }[] = pageModuleTree.map(({ url }) => ({ url }))
2322
const customComponents: Record<string, ComponentType> = {}
24-
25-
await Promise.all([...sharedModules, ...pageModuleTree].map(async mod => {
23+
await Promise.all(sharedModules.map(async mod => {
2624
const { default: C } = await importModule(baseUrl, mod)
2725
switch (trimPageModuleExt(mod.url)) {
2826
case '/404':
@@ -31,36 +29,29 @@ export default async function bootstrap({ baseUrl, defaultLocale, locales, route
3129
case '/app':
3230
customComponents['App'] = C
3331
break
34-
default:
35-
const pc = pageComponentTree.find(pc => pc.url === mod.url)
36-
if (pc) {
37-
pc.Component = C
38-
}
39-
break
4032
}
4133
}))
42-
43-
if (ssrDataEl) {
44-
const ssrData = JSON.parse(ssrDataEl.innerText)
45-
for (const key in ssrData) {
46-
Object.assign(window, { [`data://${url.pathname}#${key}`]: ssrData[key] })
47-
}
48-
}
49-
50-
const rootEl = createElement(
51-
Router,
52-
{
53-
url,
54-
routing,
55-
customComponents,
56-
pageComponentTree
34+
const routing = new Routing(routes, baseUrl, defaultLocale, locales)
35+
const [url, pageModuleChain] = routing.createRouter()
36+
const imports = await Promise.all(pageModuleChain.map(async mod => {
37+
const [{ default: Component }] = await Promise.all([
38+
importModule(baseUrl, mod),
39+
mod.asyncDeps?.filter(({ isData }) => !!isData).length ? loadPageDataFromTag(url) : Promise.resolve(),
40+
mod.asyncDeps?.filter(({ isStyle }) => !!isStyle).map(dep => importModule(baseUrl, dep)) || Promise.resolve()
41+
].flat())
42+
return {
43+
url: mod.url,
44+
Component,
5745
}
58-
)
46+
}))
47+
const pageRoute: PageRoute = { ...createPageProps(imports), url }
48+
const routerEl = createElement(Router, { customComponents, pageRoute, routing })
5949
const mountPoint = document.getElementById('__aleph')
50+
6051
if (renderMode === 'ssr') {
61-
hydrate(rootEl, mountPoint)
52+
hydrate(routerEl, mountPoint)
6253
} else {
63-
render(rootEl, mountPoint)
54+
render(routerEl, mountPoint)
6455
}
6556

6657
// remove ssr head elements, set a timmer to avoid the tab title flash

framework/react/context.ts

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,27 @@
11
import { createContext } from 'https://esm.sh/react'
22
import type { RouterURL } from '../../types.ts'
3+
import { createNamedContext } from './util.ts'
34

4-
export const RouterContext = createContext<RouterURL>({
5+
export const RouterContext = createNamedContext<RouterURL>({
56
locale: 'en',
67
pagePath: '/',
78
pathname: '/',
89
params: {},
910
query: new URLSearchParams(),
10-
})
11-
RouterContext.displayName = 'RouterContext'
11+
}, 'RouterContext')
12+
13+
type SuspenseContextProps = {
14+
loading: boolean
15+
}
16+
export const SuspenseContext = createNamedContext<SuspenseContextProps>({
17+
loading: false
18+
}, 'SuspenseContext')
1219

13-
interface RenderStorage {
20+
type RendererContextProps = {
1421
headElements: Map<string, { type: string, props: Record<string, any> }>
1522
scriptsElements: Map<string, { type: string, props: Record<string, any> }>
1623
}
17-
18-
export const RendererContext = createContext<{ storage: RenderStorage }>({
19-
storage: {
20-
headElements: new Map(),
21-
scriptsElements: new Map()
22-
}
24+
export const RendererContext = createContext<RendererContextProps>({
25+
headElements: new Map(),
26+
scriptsElements: new Map()
2327
})

framework/react/head.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import Script from './script.ts'
77
export default function Head(props: PropsWithChildren<{}>) {
88
const renderer = useContext(RendererContext)
99

10-
if (window.Deno) {
11-
parse(props.children).forEach(({ type, props }, key) => renderer.storage.headElements.set(key, { type, props }))
10+
if (util.inDeno()) {
11+
parse(props.children).forEach(({ type, props }, key) => renderer.headElements.set(key, { type, props }))
1212
}
1313

1414
useEffect(() => {

framework/react/hooks.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { ComponentType } from 'https://esm.sh/react'
22
import { createElement, useContext, useMemo } from 'https://esm.sh/react'
3+
import util from '../../shared/util.ts'
34
import type { RouterURL } from '../../types.ts'
45
import events from '../core/events.ts'
56
import { RouterContext } from './context.ts'
@@ -58,32 +59,32 @@ export function useDeno<T = any>(callback: () => (T | Promise<T>), revalidate?:
5859
const global = window as any
5960
const dataUrl = 'data://' + pathname
6061
const eventName = 'useDeno-' + dataUrl
61-
const { ['rendering-' + dataUrl]: renderingData } = global
6262
const key = dataUrl + '#' + id
6363
const expires = revalidate ? Date.now() + revalidate * 1000 : 0
64-
if (renderingData && key in renderingData) {
65-
return renderingData[key]
66-
} else if (typeof Deno !== 'undefined' && Deno.version.deno) {
64+
const renderingDataCache = global['rendering-' + dataUrl]
65+
if (renderingDataCache && key in renderingDataCache) {
66+
return renderingDataCache[key] // 2+ pass
67+
} else if (util.inDeno()) {
6768
const v = callback()
6869
if (v instanceof Promise) {
6970
events.emit(eventName, id, v.then(value => {
70-
if (renderingData) {
71-
renderingData[key] = value
71+
if (renderingDataCache) {
72+
renderingDataCache[key] = value
7273
}
7374
events.emit(eventName, id, { value, expires })
7475
}))
7576
// thow an `AsyncUseDenoError` to break current rendering, then re-render
7677
throw new AsyncUseDenoError()
7778
} else {
78-
if (renderingData) {
79-
renderingData[key] = v
79+
if (renderingDataCache) {
80+
renderingDataCache[key] = v
8081
}
8182
events.emit(eventName, id, { value: v, expires })
8283
return v
8384
}
8485
}
8586
return global[key].value || null
86-
}, [pathname])
87+
}, [id, pathname])
8788
}
8889

8990
/**

framework/react/pageprops.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import type { ComponentType } from 'https://esm.sh/react'
2+
import { RouterURL } from '../../types.ts'
3+
import { getPagePathname } from '../core/routing.ts'
4+
import { E400MissingComponent } from './error.ts'
5+
import { isLikelyReactComponent } from './util.ts'
6+
7+
export type PageProps = {
8+
Page: ComponentType<any> | null
9+
pageProps: (Partial<PageProps> & { name?: string }) | null
10+
}
11+
12+
export type PageRoute = PageProps & {
13+
url: RouterURL
14+
}
15+
16+
export function createPageProps(componentChain: { url: string, Component?: ComponentType<any> }[]): PageProps {
17+
const pageProps: PageProps = {
18+
Page: null,
19+
pageProps: null
20+
}
21+
if (componentChain.length > 0) {
22+
Object.assign(pageProps, createPagePropsSegment(componentChain[0]))
23+
}
24+
if (componentChain.length > 1) {
25+
componentChain.slice(1).reduce((p, seg) => {
26+
const c = createPagePropsSegment(seg)
27+
p.pageProps = c
28+
return c
29+
}, pageProps)
30+
}
31+
return pageProps
32+
}
33+
34+
function createPagePropsSegment(seg: { url: string, Component?: ComponentType<any> }): PageProps {
35+
const pageProps: PageProps = {
36+
Page: null,
37+
pageProps: null
38+
}
39+
if (seg.Component) {
40+
if (isLikelyReactComponent(seg.Component)) {
41+
pageProps.Page = seg.Component
42+
} else {
43+
pageProps.Page = E400MissingComponent
44+
pageProps.pageProps = { name: 'Page: ' + getPagePathname(seg.url) }
45+
}
46+
}
47+
return pageProps
48+
}

0 commit comments

Comments
 (0)