Skip to content

Commit 579e2eb

Browse files
committed
Get rid of the global variable __aleph
1 parent bb5df4d commit 579e2eb

File tree

13 files changed

+131
-160
lines changed

13 files changed

+131
-160
lines changed

framework/core/redirect.ts

+11-11
Original file line numberDiff line numberDiff line change
@@ -50,26 +50,26 @@ export function redirect(href: string, replace?: boolean) {
5050
}
5151
}
5252

53-
// prefetch module using `<link rel="modulepreload" href="...">`
53+
const prefetched = new Set<string>();
54+
55+
/** prefetch module using `<link rel="modulepreload" href="...">` */
5456
export const prefetchModule = (url: URL) => {
57+
if (prefetched.has(url.href)) {
58+
return;
59+
}
60+
prefetched.add(url.href);
5561
if (!router) {
5662
throw new Error("router is not ready.");
5763
}
58-
const { getRouteModule } = Reflect.get(window, "__aleph");
5964
const deploymentId = window.document.body.getAttribute("data-deployment-id");
65+
const q = deploymentId ? `?v=${deploymentId}` : "";
6066
const matches = matchRoutes(url, router);
6167
matches.map(([_, meta]) => {
62-
const { filename } = meta;
63-
try {
64-
getRouteModule(filename);
65-
} catch (_e) {
68+
if (!document.querySelector(`link[data-module-id="${meta.filename}"]`)) {
6669
const link = document.createElement("link");
67-
let href = meta.filename.slice(1);
68-
if (deploymentId) {
69-
href += `?v=${deploymentId}`;
70-
}
7170
link.setAttribute("rel", "modulepreload");
72-
link.setAttribute("href", href);
71+
link.setAttribute("href", meta.filename.slice(1) + q);
72+
link.setAttribute("data-module-id", meta.filename);
7373
document.head.appendChild(link);
7474
}
7575
});

framework/core/router.ts

+60-33
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ export type Router = {
3939
_app?: Route;
4040
};
4141

42+
export type CSRContext = {
43+
readonly router: Router;
44+
readonly modules: RouteModule[];
45+
};
46+
4247
export type RouteRegExp = {
4348
prefix: string;
4449
test(filename: string): boolean;
@@ -148,48 +153,71 @@ export function loadRouterFromTag(): Router {
148153
return { routes: [], prefix: "" };
149154
}
150155

151-
export function loadSSRModulesFromTag(): RouteModule[] {
152-
const { getRouteModule } = Reflect.get(window, "__aleph");
156+
export function importModule(filename: string): Promise<Record<string, unknown>> {
157+
const v = document.body.getAttribute("data-deployment-id");
158+
return import(filename.slice(1) + (v ? "?v=" + v : ""));
159+
}
160+
161+
export async function createCSRContext(): Promise<CSRContext> {
162+
const router = loadRouterFromTag();
153163
const el = window.document.getElementById("ssr-data");
154164
if (el) {
155165
try {
156166
const data = JSON.parse(el.innerText);
157-
if (Array.isArray(data)) {
158-
let deferedData: Record<string, unknown> | null | undefined = undefined;
159-
return data.map(({ url, filename, dataDefered, ...rest }) => {
160-
const mod = getRouteModule(filename);
161-
if (dataDefered) {
162-
if (deferedData === undefined) {
163-
const el = window.document?.getElementById("defered-data");
164-
if (el) {
165-
deferedData = JSON.parse(el.innerText);
166-
} else {
167-
deferedData = null;
168-
}
169-
}
170-
if (deferedData) {
171-
rest.data = deferedData[url];
167+
if (!Array.isArray(data)) {
168+
throw new Error("invalid SSR data");
169+
}
170+
let deferedData: Record<string, unknown> | null | undefined = undefined;
171+
const modules = await Promise.all(data.map(async ({ url, filename, dataDefered, ...rest }) => {
172+
const mod = await importModule(filename);
173+
if (dataDefered) {
174+
if (deferedData === undefined) {
175+
const el = window.document?.getElementById("defered-data");
176+
if (el) {
177+
deferedData = JSON.parse(el.innerText);
172178
} else {
173-
rest.data = Promise.resolve(null);
179+
deferedData = null;
174180
}
175181
}
176-
if (rest.error) {
177-
rest.data = new FetchError(500, rest.error.message, { stack: rest.error.stack });
178-
rest.error = undefined;
182+
if (deferedData) {
183+
rest.data = deferedData[url];
184+
} else {
185+
rest.data = Promise.resolve(null);
179186
}
180-
return <RouteModule> {
181-
url: new URL(url, location.href),
182-
filename,
183-
exports: mod,
184-
...rest,
185-
};
186-
});
187-
}
187+
}
188+
if (rest.error) {
189+
rest.data = new FetchError(500, rest.error.message, { stack: rest.error.stack });
190+
rest.error = undefined;
191+
}
192+
return <RouteModule> {
193+
url: new URL(url, location.href),
194+
filename,
195+
exports: mod,
196+
...rest,
197+
};
198+
}));
199+
return {
200+
router,
201+
modules,
202+
};
188203
} catch (e) {
189-
throw new Error(`loadSSRModulesFromTag: ${e.message}`);
204+
throw new Error(`createCSRContext: ${e.message}`);
190205
}
191206
}
192-
return [];
207+
return {
208+
router,
209+
modules: await Promise.all(
210+
matchRoutes(new URL(location.href), router).map(async ([ret, meta]) => {
211+
const mod = await importModule(meta.filename);
212+
return {
213+
url: new URL(ret.pathname.input + location.search, location.href),
214+
params: ret.pathname.groups,
215+
filename: meta.filename,
216+
exports: mod,
217+
};
218+
}),
219+
),
220+
};
193221
}
194222

195223
export async function fetchRouteData(dataCache: Map<string, RouteData>, dataUrl: string, defer?: boolean) {
@@ -254,7 +282,6 @@ export function watchRouter(
254282
dataCache: Map<string, RouteData>,
255283
onRedirect: (url: URL, modules: RouteModule[]) => void,
256284
): () => void {
257-
const { importRouteModule } = Reflect.get(window, "__aleph");
258285
const router = loadRouterFromTag();
259286

260287
// `popstate` event handler
@@ -267,7 +294,7 @@ export function watchRouter(
267294
url: new URL(ret.pathname.input + url.search, url.href),
268295
params: ret.pathname.groups,
269296
filename,
270-
exports: await importRouteModule(filename),
297+
exports: await importModule(filename),
271298
};
272299
const dataUrl = rmod.url.pathname + rmod.url.search;
273300
const dataConfig = rmod.exports.data as Record<string, unknown> | true | undefined;

framework/react/client.ts

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
import { createElement } from "react";
22
import { createRoot, hydrateRoot } from "react-dom/client";
33
import { App, RouterProps } from "./mod.ts";
4+
import { createCSRContext } from "../core/router.ts";
45

56
export type RenderOptions = {
67
root?: string | HTMLElement | null;
78
createPortal?: RouterProps["createPortal"];
8-
hydrate?: boolean;
99
};
1010

11-
export function bootstrap(options: RenderOptions = {}) {
12-
const { root = "#root", createPortal, hydrate = !!document.head.querySelector("script#ssr-data") } = options;
11+
export async function bootstrap(options: RenderOptions = {}) {
12+
const { root = "#root", createPortal } = options;
1313
const rootEl = typeof root === "string" ? document.querySelector(root) : root;
1414
if (!rootEl) {
1515
throw new Error(`No element found for selector "${root}"`);
1616
}
17-
const el = createElement(App, { createPortal });
18-
if (hydrate) {
17+
const csrContext = await createCSRContext();
18+
const el = createElement(App, { csrContext, createPortal });
19+
if (document.head.querySelector("script#ssr-data")) {
1920
hydrateRoot(rootEl, el);
2021
} else {
2122
createRoot(rootEl).render(el);

framework/react/link.ts

+2-5
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ import { cleanPath, isFilledString, isLikelyHttpURL, splitBy, trimSuffix } from
44
import { prefetchModule, redirect } from "../core/redirect.ts";
55
import { useRouter } from "./router.ts";
66

7-
const prefetched = new Set<string>();
8-
97
export type LinkProps = PropsWithChildren<
108
{
119
to: string;
@@ -59,9 +57,8 @@ export function Link(props: LinkProps) {
5957
return undefined;
6058
}, [href, propAriaCurrent]);
6159
const prefetch = useCallback(() => {
62-
if (!isLikelyHttpURL(href) && !prefetched.has(href)) {
60+
if (!isLikelyHttpURL(href)) {
6361
prefetchModule(new URL(href, location.href));
64-
prefetched.add(href);
6562
}
6663
}, [href]);
6764
const timerRef = useRef<number | null>(null);
@@ -72,7 +69,7 @@ export function Link(props: LinkProps) {
7269
if (e.defaultPrevented) {
7370
return;
7471
}
75-
if (!timerRef.current && !prefetched.has(href)) {
72+
if (!timerRef.current) {
7673
timerRef.current = setTimeout(() => {
7774
timerRef.current = null;
7875
prefetch();

framework/react/router.ts

+11-11
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,23 @@ import type { FC, ReactNode } from "react";
22
import { createElement, isValidElement, StrictMode, Suspense, useContext, useEffect, useMemo, useState } from "react";
33
import type { SSRContext } from "../../server/types.ts";
44
import { redirect } from "../core/redirect.ts";
5-
import type { RouteModule } from "../core/router.ts";
6-
import { fetchRouteData, loadSSRModulesFromTag, watchRouter } from "../core/router.ts";
5+
import type { CSRContext, RouteModule } from "../core/router.ts";
6+
import { fetchRouteData, watchRouter } from "../core/router.ts";
77
import { ForwardPropsContext, RouterContext, type RouterContextProps } from "./context.ts";
88
import { DataProvider, type RouteData } from "./data.ts";
99
import { Err, ErrorBoundary } from "./error.ts";
1010

1111
export type RouterProps = {
12+
readonly csrContext?: CSRContext;
1213
readonly ssrContext?: SSRContext;
1314
readonly createPortal?: RouterContextProps["createPortal"];
1415
};
1516

1617
/** The `Router` component for react. */
1718
export const Router: FC<RouterProps> = (props) => {
18-
const { ssrContext, createPortal } = props;
19-
const [url, setUrl] = useState(() => ssrContext?.url || new URL(window.location?.href));
20-
const [modules, setModules] = useState(() => ssrContext?.modules || loadSSRModulesFromTag());
19+
const { csrContext, ssrContext, createPortal } = props;
20+
const [url, setUrl] = useState(() => ssrContext?.url ?? new URL(window.location?.href));
21+
const [modules, setModules] = useState(() => ssrContext?.modules ?? csrContext?.modules ?? []);
2122
const dataCache = useMemo(() => {
2223
const cache = new Map<string, RouteData>();
2324
modules.forEach(({ url, data, dataCacheTtl }) => {
@@ -28,7 +29,7 @@ export const Router: FC<RouterProps> = (props) => {
2829
cache.set(dataUrl, {
2930
data,
3031
dataCacheTtl,
31-
dataExpires: Date.now() + (dataCacheTtl || 1) * 1000,
32+
dataExpires: Date.now() + (dataCacheTtl ?? 0) * 1000,
3233
});
3334
}
3435
});
@@ -66,18 +67,17 @@ export const Router: FC<RouterProps> = (props) => {
6667
{ value },
6768
createElement(
6869
RouteRoot,
69-
{ modules, dataCache, ssrContext },
70+
{ modules, dataCache },
7071
),
7172
);
7273
};
7374

7475
type RouteRootProps = {
7576
modules: RouteModule[];
7677
dataCache: Map<string, RouteData>;
77-
ssrContext?: SSRContext;
7878
};
7979

80-
const RouteRoot: FC<RouteRootProps> = ({ modules, dataCache, ssrContext }) => {
80+
const RouteRoot: FC<RouteRootProps> = ({ modules, dataCache }) => {
8181
const { url, exports, withData } = modules[0];
8282
const dataUrl = url.pathname + url.search;
8383
let el: ReactNode;
@@ -88,7 +88,7 @@ const RouteRoot: FC<RouteRootProps> = ({ modules, dataCache, ssrContext }) => {
8888
null,
8989
modules.length > 1 && createElement(
9090
RouteRoot,
91-
{ modules: modules.slice(1), dataCache, ssrContext },
91+
{ modules: modules.slice(1), dataCache },
9292
),
9393
);
9494
if (withData) {
@@ -123,7 +123,7 @@ const RouteRoot: FC<RouteRootProps> = ({ modules, dataCache, ssrContext }) => {
123123
};
124124

125125
/** The `App` component alias to the `Router` in `StrictMode` mode. */
126-
export const App: FC<Omit<RouterProps, "strictMode">> = (props) => {
126+
export const App: FC<RouterProps> = (props) => {
127127
return createElement(StrictMode, null, createElement(Router, { ...props }));
128128
};
129129

framework/vue/link.ts

+2-5
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ import { useRouter } from "./router.ts";
33
import { cleanPath, isFilledString, isLikelyHttpURL, splitBy } from "../../shared/util.ts";
44
import { prefetchModule, redirect } from "../core/redirect.ts";
55

6-
const prefetched = new Set<string>();
7-
86
export const Link = defineComponent({
97
name: "Link",
108
props: {
@@ -46,9 +44,8 @@ export const Link = defineComponent({
4644
};
4745

4846
const prefetch = () => {
49-
if (!isLikelyHttpURL(href.value) && !prefetched.has(href.value)) {
47+
if (!isLikelyHttpURL(href.value)) {
5048
prefetchModule(new URL(href.value, location.href));
51-
prefetched.add(href.value);
5249
}
5350
};
5451

@@ -58,7 +55,7 @@ export const Link = defineComponent({
5855
if (e.defaultPrevented) {
5956
return;
6057
}
61-
if (!timer && !prefetched.has(href.value)) {
58+
if (!timer) {
6259
timer = setTimeout(() => {
6360
timer = null;
6461
prefetch();

framework/vue/mod.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import { createApp } from "./router.ts";
2+
import { createCSRContext } from "../core/router.ts";
23

3-
export function bootstrap(options?: { root?: string | HTMLElement; hydrate?: boolean }) {
4-
const { root = "#root", hydrate = !!document.head.querySelector("script#ssr-data") } = options ?? {};
5-
createApp().mount(root, hydrate);
4+
export async function bootstrap(options?: { root?: string | HTMLElement }) {
5+
const { root = "#root" } = options ?? {};
6+
const hydrate = !!document.head.querySelector("script#ssr-data");
7+
const csrContext = await createCSRContext();
8+
createApp({ csrContext }).mount(root, hydrate);
69
}
710

811
export { useData } from "./data.ts";

framework/vue/router.ts

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { Component, Ref, ShallowRef } from "vue";
22
import { createSSRApp, defineComponent, h, ref, shallowRef, watch } from "vue";
3-
import { RouteModule, watchRouter } from "../core/router.ts";
4-
import { loadSSRModulesFromTag } from "../core/router.ts";
3+
import type { CSRContext, RouteModule } from "../core/router.ts";
4+
import { watchRouter } from "../core/router.ts";
55
import type { SSRContext } from "../../server/types.ts";
66
import { RouterContext } from "./context.ts";
77
import { Link } from "./link.ts";
@@ -15,6 +15,7 @@ export type RouteData = {
1515
};
1616

1717
type RootProps = {
18+
csrContext?: CSRContext;
1819
ssrContext?: SSRContext;
1920
};
2021

@@ -108,14 +109,14 @@ const createRouterRoot = (props: RouterRootProps) => {
108109
};
109110

110111
const createApp = (props?: RootProps) => {
111-
const { ssrContext } = props || {};
112-
const modules = shallowRef(ssrContext?.modules || loadSSRModulesFromTag());
112+
const { csrContext, ssrContext } = props ?? {};
113+
const modules = shallowRef(ssrContext?.modules ?? csrContext?.modules ?? []);
113114

114115
if (modules.value.length === 0) {
115116
return createSSRApp(Err, { status: 404, message: "page not found" });
116117
}
117118

118-
const url = ref(ssrContext?.url || new URL(window.location?.href));
119+
const url = ref(ssrContext?.url ?? new URL(window.location?.href));
119120
const dataCache = new Map<string, RouteData>();
120121
const dataUrl = ref(url.value.pathname + url.value.search);
121122

server/build.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export async function build(
5858
for (const [_, { pattern }] of routes) {
5959
const { pathname } = pattern;
6060
if (pathname.includes("/:")) {
61-
const url = new URL("http://localhost/__aleph.getStaticPaths");
61+
const url = new URL("http://localhost/-/getStaticPaths");
6262
url.searchParams.set("pattern", pathname);
6363
const res = await request(url);
6464
if (res.status === 200 && res.headers.get("content-type")?.startsWith("application/json")) {

0 commit comments

Comments
 (0)