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

Commit 0e971a5

Browse files
author
Je
committed
refactor: improve exports
1 parent 0f68861 commit 0e971a5

File tree

5 files changed

+95
-100
lines changed

5 files changed

+95
-100
lines changed

head.ts

Lines changed: 75 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -2,81 +2,7 @@ import React, { Children, createElement, isValidElement, PropsWithChildren, Reac
22
import type { AlephRuntime } from './types.ts'
33
import util, { hashShort } from './util.ts'
44

5-
const serverHeadElements: Map<string, { type: string, props: Record<string, any> }> = new Map()
6-
const serverStyles: Map<string, { css: string, asLink: boolean }> = new Map()
7-
8-
export async function renderHead(styles?: { url: string, hash: string, async?: boolean }[]) {
9-
const { __appRoot, __buildMode, __buildTarget } = (window as any).ALEPH as AlephRuntime
10-
const tags: string[] = []
11-
serverHeadElements.forEach(({ type, props }) => {
12-
if (type === 'title') {
13-
if (util.isNEString(props.children)) {
14-
tags.push(`<title ssr>${props.children}</title>`)
15-
} else if (util.isNEArray(props.children)) {
16-
tags.push(`<title ssr>${props.children.join('')}</title>`)
17-
}
18-
} else {
19-
const attrs = Object.keys(props)
20-
.filter(key => key !== 'children')
21-
.map(key => ` ${key}=${JSON.stringify(props[key])}`)
22-
.join('')
23-
if (util.isNEString(props.children)) {
24-
tags.push(`<${type}${attrs} ssr>${props.children}</${type}>`)
25-
} else if (util.isNEArray(props.children)) {
26-
tags.push(`<${type}${attrs} ssr>${props.children.join('')}</${type}>`)
27-
} else {
28-
tags.push(`<${type}${attrs} ssr />`)
29-
}
30-
}
31-
})
32-
await Promise.all(styles?.filter(({ async }) => !!async).map(({ url, hash }) => {
33-
return import('file://' + util.cleanPath(`${__appRoot}/.aleph/${__buildMode}.${__buildTarget}/${url}.${hash.slice(0, hashShort)}.js`))
34-
}) || [])
35-
styles?.forEach(({ url }) => {
36-
if (serverStyles.has(url)) {
37-
const { css, asLink } = serverStyles.get(url)!
38-
if (asLink) {
39-
tags.push(`<link rel="stylesheet" href="${css}" data-module-id=${JSON.stringify(url)} />`)
40-
} else {
41-
tags.push(`<style type="text/css" data-module-id=${JSON.stringify(url)}>${css}</style>`)
42-
}
43-
}
44-
})
45-
serverHeadElements.clear()
46-
return tags
47-
}
48-
49-
export function applyCSS(id: string, css: string, asLink: boolean = false) {
50-
if (window.Deno) {
51-
serverStyles.set(id, { css, asLink })
52-
} else {
53-
const { document } = (window as any)
54-
const styleEl = document.createElement(asLink ? 'link' : 'style')
55-
const prevStyleEls = Array.from(document.head.children).filter((el: any) => el.getAttribute('data-module-id') === id)
56-
if (asLink) {
57-
styleEl.rel = 'stylesheet'
58-
styleEl.href = css
59-
} else {
60-
styleEl.type = 'text/css'
61-
styleEl.appendChild(document.createTextNode(css))
62-
}
63-
styleEl.setAttribute('data-module-id', id)
64-
document.head.appendChild(styleEl)
65-
if (prevStyleEls.length > 0) {
66-
if (asLink) {
67-
styleEl.addEventListener('load', () => {
68-
prevStyleEls.forEach(el => document.head.removeChild(el))
69-
})
70-
} else {
71-
setTimeout(() => {
72-
prevStyleEls.forEach(el => document.head.removeChild(el))
73-
}, 0)
74-
}
75-
}
76-
}
77-
}
78-
79-
export function Head({ children }: PropsWithChildren<{}>) {
5+
export default function Head({ children }: PropsWithChildren<{}>) {
806
if (window.Deno) {
817
parse(children).forEach(({ type, props }, key) => serverHeadElements.set(key, { type, props }))
828
}
@@ -182,6 +108,80 @@ export function Viewport(props: ViewportProps) {
182108
)
183109
}
184110

111+
const serverHeadElements: Map<string, { type: string, props: Record<string, any> }> = new Map()
112+
const serverStyles: Map<string, { css: string, asLink: boolean }> = new Map()
113+
114+
export async function renderHead(styles?: { url: string, hash: string, async?: boolean }[]) {
115+
const { __appRoot, __buildMode, __buildTarget } = (window as any).ALEPH as AlephRuntime
116+
const tags: string[] = []
117+
serverHeadElements.forEach(({ type, props }) => {
118+
if (type === 'title') {
119+
if (util.isNEString(props.children)) {
120+
tags.push(`<title ssr>${props.children}</title>`)
121+
} else if (util.isNEArray(props.children)) {
122+
tags.push(`<title ssr>${props.children.join('')}</title>`)
123+
}
124+
} else {
125+
const attrs = Object.keys(props)
126+
.filter(key => key !== 'children')
127+
.map(key => ` ${key}=${JSON.stringify(props[key])}`)
128+
.join('')
129+
if (util.isNEString(props.children)) {
130+
tags.push(`<${type}${attrs} ssr>${props.children}</${type}>`)
131+
} else if (util.isNEArray(props.children)) {
132+
tags.push(`<${type}${attrs} ssr>${props.children.join('')}</${type}>`)
133+
} else {
134+
tags.push(`<${type}${attrs} ssr />`)
135+
}
136+
}
137+
})
138+
await Promise.all(styles?.filter(({ async }) => !!async).map(({ url, hash }) => {
139+
return import('file://' + util.cleanPath(`${__appRoot}/.aleph/${__buildMode}.${__buildTarget}/${url}.${hash.slice(0, hashShort)}.js`))
140+
}) || [])
141+
styles?.forEach(({ url }) => {
142+
if (serverStyles.has(url)) {
143+
const { css, asLink } = serverStyles.get(url)!
144+
if (asLink) {
145+
tags.push(`<link rel="stylesheet" href="${css}" data-module-id=${JSON.stringify(url)} />`)
146+
} else {
147+
tags.push(`<style type="text/css" data-module-id=${JSON.stringify(url)}>${css}</style>`)
148+
}
149+
}
150+
})
151+
serverHeadElements.clear()
152+
return tags
153+
}
154+
155+
export function applyCSS(id: string, css: string, asLink: boolean = false) {
156+
if (window.Deno) {
157+
serverStyles.set(id, { css, asLink })
158+
} else {
159+
const { document } = (window as any)
160+
const styleEl = document.createElement(asLink ? 'link' : 'style')
161+
const prevStyleEls = Array.from(document.head.children).filter((el: any) => el.getAttribute('data-module-id') === id)
162+
if (asLink) {
163+
styleEl.rel = 'stylesheet'
164+
styleEl.href = css
165+
} else {
166+
styleEl.type = 'text/css'
167+
styleEl.appendChild(document.createTextNode(css))
168+
}
169+
styleEl.setAttribute('data-module-id', id)
170+
document.head.appendChild(styleEl)
171+
if (prevStyleEls.length > 0) {
172+
if (asLink) {
173+
styleEl.addEventListener('load', () => {
174+
prevStyleEls.forEach(el => document.head.removeChild(el))
175+
})
176+
} else {
177+
setTimeout(() => {
178+
prevStyleEls.forEach(el => document.head.removeChild(el))
179+
}, 0)
180+
}
181+
}
182+
}
183+
}
184+
185185
function parse(node: ReactNode, els: Map<string, { type: string, props: Record<string, any> }> = new Map()) {
186186
Children.forEach(node, child => {
187187
if (!isValidElement(child)) {

link.ts

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ interface LinkProps {
1414

1515
const fetchedPageModules = new Set<string>()
1616

17-
export function Link({
17+
export default function Link({
1818
to,
1919
replace = false,
2020
prefetch: prefetchImmediately = false,
@@ -143,8 +143,8 @@ export function NavLink({
143143

144144
interface ImportProps {
145145
from: string
146+
name?: string // default is 'default'
146147
props?: Record<string, any>
147-
placeholder?: ReactElement
148148
fallback?: ReactElement
149149
}
150150

@@ -157,9 +157,14 @@ export function Import(props: ImportProps) {
157157
if (reModuleExt.test(__sourceFile)) {
158158
const p = util.splitPath(__importer)
159159
p.pop()
160-
import(util.cleanPath("/_aleph/" + p.join('/') + '/' + props.from))
161-
.then(({ default: Component }) => {
162-
setMod({ Component })
160+
import(util.cleanPath('/_aleph/' + p.join('/') + '/' + props.from))
161+
.then(mod => {
162+
const Component = mod[props.name || 'default']
163+
if (util.isLikelyReactComponent(Component)) {
164+
setMod({ Component })
165+
} else {
166+
setError(`component${props.name ? ` '${props.name}'` : ''} not found`)
167+
}
163168
})
164169
.catch((err: Error) => {
165170
setError(err.message)
@@ -168,21 +173,15 @@ export function Import(props: ImportProps) {
168173
}, [__importer, __sourceFile])
169174

170175
if (error) {
171-
if (props.fallback) {
172-
return props.fallback
173-
}
174176
return React.createElement('div', { style: { color: 'red' } }, error)
175177
}
176178

177179
if (mod.Component) {
178180
return React.createElement(mod.Component, props.props)
179181
}
180182

181-
if (reModuleExt.test(__sourceFile)) {
182-
if (props.placeholder) {
183-
return props.placeholder
184-
}
185-
return React.createElement('div', { style: { color: 'gray' } }, 'Loading...')
183+
if (reModuleExt.test(__sourceFile) && props.fallback) {
184+
return props.fallback
186185
}
187186

188187
return null

mod.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export { redirect } from './aleph.ts'
22
export * from './context.ts'
3-
export { Head, SEO, Viewport } from './head.ts'
3+
export { default as Head, SEO, Viewport } from './head.ts'
44
export * from './hooks.ts'
5-
export { Import, Link, NavLink } from './link.ts'
6-
5+
export { default as Import } from './import.ts'
6+
export { default as Link, NavLink } from './link.ts'

project.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ export class Project {
251251
{ src: path.join(baseUrl, `/_aleph/main.${mainModule.hash.slice(0, hashShort)}.js`), type: 'module' },
252252
{ src: path.join(baseUrl, `/_aleph/-/deno.land/x/aleph/nomodule.js${this.isDev ? '?dev' : ''}`), nomodule: true },
253253
],
254-
body: `<main><p><em>Loading...</em></p></main>`, // todo: custom `loading` page
254+
body: `<main></main>`, // todo: custom `loading` page
255255
minify: !this.isDev
256256
})
257257
return html

renderer.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, { ComponentType, ReactElement } from 'https://esm.sh/react'
22
import { renderToString } from 'https://esm.sh/react-dom/server'
33
import { RouterContext } from './context.ts'
4-
import { AsyncUseDenoError, E400MissingDefaultExportAsComponent, E404Page, ErrorBoundary } from './error.ts'
4+
import { AsyncUseDenoError, E400MissingDefaultExportAsComponent, E404Page } from './error.ts'
55
import events from './events.ts'
66
import { createPageProps } from './routing.ts'
77
import type { RouterURL } from './types.ts'
@@ -72,13 +72,9 @@ export async function renderPage(
7272
}
7373
html = renderToString(
7474
React.createElement(
75-
ErrorBoundary,
76-
null,
77-
React.createElement(
78-
RouterContext.Provider,
79-
{ value: url },
80-
el
81-
)
75+
RouterContext.Provider,
76+
{ value: url },
77+
el
8278
)
8379
)
8480
break

0 commit comments

Comments
 (0)