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

Commit 3e12a2a

Browse files
author
Je
committed
refactor: improve async import component
1 parent 82449b7 commit 3e12a2a

File tree

9 files changed

+77
-109
lines changed

9 files changed

+77
-109
lines changed

app.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { E404Page, E501App, E501Page } from './error.ts'
44
import events from './events.ts'
55
import { createRouter } from './router.ts'
66
import type { AppManifest, Module, RouterURL } from './types.ts'
7-
import util, { hashShort } from './util.ts'
7+
import util, { hashShort, reModuleExt } from './util.ts'
88

99
export function ALEPH({ initial }: {
1010
initial: {
@@ -51,6 +51,9 @@ export function ALEPH({ initial }: {
5151
if (url.pagePath && url.pagePath in pageModules) {
5252
const mod = pageModules[url.pagePath]!
5353
const { default: Component } = await import(getModuleImportUrl(baseUrl, mod))
54+
await Promise.all(mod.asyncDeps?.map(dep => {
55+
return import(util.cleanPath(`${baseUrl}/_aleph/${dep.url.replace(reModuleExt, '')}.${dep.hash.slice(0, hashShort)}.js`))
56+
}) || [])
5457
if (util.isLikelyReactComponent(Component)) {
5558
setPage({ url, Component })
5659
} else {
@@ -193,5 +196,5 @@ export async function redirect(url: string, replace: boolean) {
193196
}
194197

195198
export function getModuleImportUrl(baseUrl: string, mod: Module) {
196-
return util.cleanPath(baseUrl + '/_aleph/' + mod.id.replace(/\.js$/, `.${mod.hash.slice(0, hashShort)}.js`))
199+
return util.cleanPath(baseUrl + '/_aleph/' + util.trimSuffix(mod.id, '.js') + `.${mod.hash.slice(0, hashShort)}.js`)
197200
}

bootstrap.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { ALEPH, getModuleImportUrl } from './app.ts'
44
import { ErrorBoundary } from './error.ts'
55
import { createRouter } from './router.ts'
66
import type { AppManifest, Module, RouterURL } from './types.ts'
7-
import util from './util.ts'
7+
import util, { hashShort, reModuleExt } from './util.ts'
88

99
export default async function bootstrap({
1010
baseUrl,
@@ -39,7 +39,11 @@ export default async function bootstrap({
3939
)
4040
}
4141

42-
const pageModule = pageModules[url.pagePath]!
42+
const pageModule = pageModules[url.pagePath]
43+
if (!pageModule) {
44+
throw new Error('page module not found')
45+
}
46+
4347
const [
4448
{ default: data },
4549
{ default: App },
@@ -68,6 +72,17 @@ export default async function bootstrap({
6872
)
6973
)
7074

75+
// import async sytle dependencies
76+
const asyncDeps: { url: string, hash: string }[] = []
77+
for (const key in coreModules) {
78+
const mod = coreModules[key]
79+
mod.asyncDeps?.forEach(deps => asyncDeps.push(deps))
80+
}
81+
pageModule.asyncDeps?.forEach(deps => asyncDeps.push(deps))
82+
await Promise.all(asyncDeps.map(dep => {
83+
return import(util.cleanPath(`${baseUrl}/_aleph/${dep.url.replace(reModuleExt, '')}.${dep.hash.slice(0, hashShort)}.js`))
84+
}))
85+
7186
if (dataEl) {
7287
hydrate(el, mainEl)
7388
// remove ssr head elements, set a timmer to avoid tab title flash

head.ts

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import React, { Children, createElement, isValidElement, PropsWithChildren, ReactElement, ReactNode, useEffect } from 'https://esm.sh/react'
2-
import { importAll } from './importor.ts'
3-
import util from './util.ts'
2+
import util, { hashShort } from './util.ts'
43

54
const serverHeadElements: Array<{ type: string, props: Record<string, any> }> = []
65
const serverStyles: Map<string, { css: string, asLink: boolean }> = new Map()
76

8-
export async function renderHead(styleModules?: string[]) {
7+
export async function renderHead(styleModules?: { url: string, hash: string, async?: boolean }[]) {
8+
const { appDir, buildID } = (window as any).ALEPH_ENV as { appDir: string, buildID: string }
99
const tags: string[] = []
1010
serverHeadElements.forEach(({ type, props }) => {
1111
if (type === 'title') {
@@ -28,13 +28,17 @@ export async function renderHead(styleModules?: string[]) {
2828
}
2929
}
3030
})
31-
await importAll()
32-
styleModules?.filter(id => serverStyles.has(id)).forEach(id => {
33-
const { css, asLink } = serverStyles.get(id)!
34-
if (asLink) {
35-
tags.push(`<link rel="stylesheet" href="${css}" data-module-id=${JSON.stringify(id)} />`)
36-
} else {
37-
tags.push(`<style type="text/css" data-module-id=${JSON.stringify(id)}>${css}</style>`)
31+
await Promise.all(styleModules?.filter(({ async }) => !!async).map(({ url, hash }) => {
32+
return import('file://' + util.cleanPath(`${appDir}/.aleph/build-${buildID}/${url}.${hash.slice(0, hashShort)}.js`))
33+
}) || [])
34+
styleModules?.forEach(({ url }) => {
35+
if (serverStyles.has(url)) {
36+
const { css, asLink } = serverStyles.get(url)!
37+
if (asLink) {
38+
tags.push(`<link rel="stylesheet" href="${css}" data-module-id=${JSON.stringify(url)} />`)
39+
} else {
40+
tags.push(`<style type="text/css" data-module-id=${JSON.stringify(url)}>${css}</style>`)
41+
}
3842
}
3943
})
4044
serverHeadElements.splice(0, serverHeadElements.length)

importor.ts

Lines changed: 0 additions & 43 deletions
This file was deleted.

mod.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,5 @@ export * from './context.ts'
33
export { ErrorPage } from './error.ts'
44
export { default as Head, SEO, Viewport } from './head.ts'
55
export * from './hooks.ts'
6-
export { Import } from './importor.ts'
76
export { default as Link } from './link.ts'
8-
7+
export const Import = (_: { from: string }) => null

project.ts

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,17 @@ import util, { existsDirSync, existsFileSync, hashShort, reHashJs, reHttp, reMod
1010
import { cleanCSS, Document, less } from './vendor/mod.ts'
1111
import { version } from './version.ts'
1212

13+
interface Dep {
14+
url: string
15+
hash: string
16+
async?: boolean
17+
}
18+
1319
interface Module {
1420
id: string
1521
url: string
1622
isRemote: boolean
17-
deps: { url: string, hash: string }[]
23+
deps: Dep[]
1824
sourceFilePath: string
1925
sourceType: string
2026
sourceHash: string
@@ -647,10 +653,7 @@ export default class Project {
647653
}
648654
if (this.#modules.has('/404.js')) {
649655
const { url, hash } = this.#modules.get('/404.js')!
650-
config.coreModules['404'] = {
651-
id: '/404.js',
652-
hash
653-
}
656+
config.coreModules['404'] = { id: '/404.js', hash }
654657
deps.push({ url, hash })
655658
}
656659
this.#pageModules.forEach(({ moduleID }, pagePath) => {
@@ -659,6 +662,16 @@ export default class Project {
659662
deps.push({ url, hash })
660663
})
661664

665+
for (const key in config.coreModules) {
666+
const m = config.coreModules[key]
667+
m.asyncDeps = this._lookupStyleDeps(m.id).filter(({ async }) => !!async).map(({ url, hash }) => ({ url, hash }))
668+
}
669+
670+
for (const key in config.pageModules) {
671+
const m = config.pageModules[key]
672+
m.asyncDeps = this._lookupStyleDeps(m.id).filter(({ async }) => !!async).map(({ url, hash }) => ({ url, hash }))
673+
}
674+
662675
module.jsContent = [
663676
this.isDev && 'import "./-/deno.land/x/aleph/hmr.js";',
664677
'import "./-/deno.land/x/aleph/tsc/tslib.js";',
@@ -864,7 +877,7 @@ export default class Project {
864877
target: this.config.buildTarget,
865878
mode: this.mode,
866879
reactRefresh: this.isDev && !mod.isRemote && (mod.id === '/404.js' || mod.id === '/app.js' || mod.id.startsWith('/pages/') || mod.id.startsWith('/components/')),
867-
rewriteImportPath: (path: string) => this._rewriteImportPath(mod, path),
880+
rewriteImportPath: (path: string, async?: boolean) => this._rewriteImportPath(mod, path, async),
868881
}
869882
const { diagnostics, outputText, sourceMapText } = compile(mod.sourceFilePath, sourceContent, compileOptions)
870883
if (diagnostics && diagnostics.length > 0) {
@@ -994,7 +1007,7 @@ export default class Project {
9941007
})
9951008
}
9961009

997-
private _rewriteImportPath(mod: Module, importPath: string): string {
1010+
private _rewriteImportPath(mod: Module, importPath: string, async?: boolean): string {
9981011
const { importMap } = this.config
9991012
let rewrittenPath: string
10001013
if (importPath in importMap.imports) {
@@ -1029,17 +1042,17 @@ export default class Project {
10291042
}
10301043
}
10311044
if (reHttp.test(importPath)) {
1032-
mod.deps.push({ url: importPath, hash: '' })
1045+
mod.deps.push({ url: importPath, hash: '', async })
10331046
} else {
10341047
if (mod.isRemote) {
10351048
const sourceUrl = new URL(mod.url)
10361049
let pathname = importPath
10371050
if (!pathname.startsWith('/')) {
10381051
pathname = path.join(path.dirname(sourceUrl.pathname), importPath)
10391052
}
1040-
mod.deps.push({ url: sourceUrl.protocol + '//' + sourceUrl.host + pathname, hash: '' })
1053+
mod.deps.push({ url: sourceUrl.protocol + '//' + sourceUrl.host + pathname, hash: '', async })
10411054
} else {
1042-
mod.deps.push({ url: path.resolve('/', path.dirname(mod.url), importPath), hash: '' })
1055+
mod.deps.push({ url: path.resolve('/', path.dirname(mod.url), importPath), hash: '', async })
10431056
}
10441057
}
10451058

@@ -1086,8 +1099,8 @@ export default class Project {
10861099
const data = await this.getData()
10871100
const html = renderPage(data, url, appModule ? App : undefined, Page)
10881101
const head = await renderHead([
1089-
appModule ? this._lookupStyles(appModule) : [],
1090-
this._lookupStyles(pageModule),
1102+
appModule ? this._lookupStyleDeps(appModule.id) : [],
1103+
this._lookupStyleDeps(pageModule.id),
10911104
].flat())
10921105
ret.code = 200
10931106
ret.head = head
@@ -1103,19 +1116,19 @@ export default class Project {
11031116
return ret
11041117
}
11051118

1106-
private _lookupStyles(mod: Module, a: string[] = [], s: Set<string> = new Set()): string[] {
1107-
if (s.has(mod.id)) {
1119+
private _lookupStyleDeps(moduleID: string, a: Dep[] = [], s: Set<string> = new Set()) {
1120+
const mod = this.getModule(moduleID)
1121+
if (!mod) {
11081122
return a
11091123
}
1110-
s.add(mod.id)
1111-
a.push(...mod.deps.map(({ url }) => url).filter(url => reStyleModuleExt.test(url)))
1124+
if (s.has(moduleID)) {
1125+
return a
1126+
}
1127+
s.add(moduleID)
1128+
a.push(...mod.deps.filter(({ url }) => reStyleModuleExt.test(url)))
11121129
mod.deps.forEach(({ url }) => {
11131130
if (reModuleExt.test(url) && !reHttp.test(url)) {
1114-
const id = url.replace(reModuleExt, '.js')
1115-
const smod = this.getModule(id)
1116-
if (smod) {
1117-
this._lookupStyles(smod, a, s)
1118-
}
1131+
this._lookupStyleDeps(url.replace(reModuleExt, '.js'), a, s)
11191132
}
11201133
})
11211134
return a

tsc/compile.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export interface CompileOptions {
88
target: string
99
mode: 'development' | 'production'
1010
reactRefresh: boolean
11-
rewriteImportPath: (importPath: string) => string
11+
rewriteImportPath: (importPath: string, async?: boolean) => string
1212
}
1313

1414
export function createSourceFile(fileName: string, source: string) {

tsc/transform-react-jsx.ts

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,16 @@
11
import ts from 'https://esm.sh/typescript'
2-
import { path } from '../std.ts'
32

4-
export default function transformReactJsx(sf: ts.SourceFile, node: ts.Node, options: { mode: 'development' | 'production', rewriteImportPath: (importPath: string) => string }): ts.VisitResult<ts.Node> {
3+
export default function transformReactJsx(sf: ts.SourceFile, node: ts.Node, options: { mode: 'development' | 'production', rewriteImportPath: (importPath: string, async: boolean) => string }): ts.VisitResult<ts.Node> {
54
if (ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) {
65
let props = Array.from(node.attributes.properties)
76

87
if (node.tagName.getText() === 'Import') {
9-
props = []
10-
let rawPath = ''
118
for (let i = 0; i < node.attributes.properties.length; i++) {
129
const prop = node.attributes.properties[i]
1310
if (ts.isJsxAttribute(prop) && prop.name.text === 'from' && prop.initializer && ts.isStringLiteral(prop.initializer)) {
14-
rawPath = prop.initializer.text
15-
} else {
16-
props.push(prop)
11+
options.rewriteImportPath(prop.initializer.text, true)
1712
}
1813
}
19-
if (rawPath) {
20-
// ensure 'from' prop is the first one of all props
21-
props.unshift(
22-
ts.createJsxAttribute(
23-
ts.createIdentifier('from'),
24-
ts.createJsxExpression(undefined, ts.createStringLiteral(options.rewriteImportPath(rawPath)))
25-
)
26-
)
27-
props.push(
28-
ts.createJsxAttribute(
29-
ts.createIdentifier('rawPath'),
30-
ts.createJsxExpression(undefined, ts.createStringLiteral(rawPath))
31-
),
32-
ts.createJsxAttribute(
33-
ts.createIdentifier('resolveDir'),
34-
ts.createJsxExpression(undefined, ts.createStringLiteral(path.dirname(sf.fileName)))
35-
)
36-
)
37-
}
3814
}
3915

4016
if (options.mode === 'development') {

types.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export interface APIHandle {
6262
}
6363

6464
export interface Module {
65-
readonly id: string,
66-
readonly hash: string,
65+
readonly id: string
66+
readonly hash: string
67+
readonly asyncDeps?: { url: string, hash: string }[]
6768
}

0 commit comments

Comments
 (0)