Skip to content

Commit 035f47b

Browse files
committed
perf: preload discovered client entries
1 parent ba9e512 commit 035f47b

10 files changed

Lines changed: 453 additions & 158 deletions

File tree

app/controllers/blog/controller.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ describe("Blog route", () => {
2525
);
2626
expect(html).toContain("Featured Articles");
2727
expect(html).toContain('action="/_actions/newsletter"');
28+
expect(html).toContain(
29+
'<link rel="modulepreload" href="/assets/app/assets/newsletter-subscribe.tsx"',
30+
);
2831

2932
let mainNavigation = html.match(/<nav aria-label="Main".*?<\/nav>/s)?.[0];
3033
if (!mainNavigation) throw new Error("Missing main navigation");

app/controllers/home/controller.test.ts

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -71,27 +71,21 @@ describe("home route", () => {
7171
expect(html).not.toContain('rel="stylesheet" href="/styles/app.css"');
7272
expect(html).not.toContain("fonts.googleapis.com");
7373
expect(html).not.toContain("fonts.gstatic.com");
74-
expect(html).toContain(
75-
'<link rel="modulepreload" href="/assets/app/assets/document-head-sync.tsx"',
76-
);
77-
expect(html).toContain(
78-
'<link rel="modulepreload" href="/assets/app/assets/remix-landing/landing-enhancements.tsx"',
79-
);
80-
expect(html).toContain(
81-
'<link rel="modulepreload" href="/assets/app/assets/remix-landing/components/feature-section.tsx"',
82-
);
83-
expect(html).toContain(
84-
'<link rel="modulepreload" href="/assets/app/assets/remix-landing/components/particle-canvas.tsx"',
85-
);
86-
expect(html).not.toContain(
87-
'<link rel="modulepreload" href="/assets/app/assets/fps-counter.tsx"',
74+
let modulePreloads = [
75+
...html.matchAll(/<link rel="modulepreload" href="([^"]+)"/g),
76+
].map((match) => match[1]);
77+
let bootstrapSrc = html.match(
78+
/<script type="module" async src="([^"]+)"/,
79+
)?.[1];
80+
81+
expect(modulePreloads).toContain(
82+
"/assets/app/assets/remix-landing/landing-enhancements.tsx",
8883
);
89-
expect(
90-
html.indexOf("/assets/app/assets/remix-landing/landing-enhancements.tsx"),
91-
).toBeLessThan(html.indexOf("/assets/app/assets/fathom.ts"));
92-
expect(html).toContain(
93-
'<script type="module" async src="/assets/app/assets/entry.ts"',
84+
expect(modulePreloads).toContain(
85+
"/assets/app/assets/fps-counter-toggle.tsx",
9486
);
87+
expect(modulePreloads).not.toContain("/assets/app/assets/fps-counter.tsx");
88+
expect(bootstrapSrc).toBe("/assets/app/assets/entry.ts");
9589

9690
expect(html).toMatch(/class="loading-screen-overlay\b/);
9791
expect(html).not.toContain("@keyframes loading-screen-dismiss");

app/controllers/home/controller.tsx

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
11
import { routes } from "../../routes.ts";
22
import { assetPaths } from "../../utils/asset-paths.ts";
33
import { CACHE_CONTROL } from "../../utils/cache-control.ts";
4-
import { preloadAssetEntries } from "../../middleware/asset-entry.ts";
54
import type { AppContext } from "../../middleware/render.ts";
6-
import { HomePage, homeBrowserEntries } from "./page.tsx";
7-
8-
export async function homeHandler(context: AppContext) {
9-
let { render, request } = context;
10-
await preloadAssetEntries(context.assetEntry, homeBrowserEntries);
5+
import { HomePage } from "./page.tsx";
116

7+
export async function homeHandler({ render, request }: AppContext) {
128
let requestUrl = new URL(request.url);
139
let pageUrl = `${requestUrl.origin}${routes.home.href()}`;
1410
let previewImage = `${requestUrl.origin}${assetPaths.marketing.defaultOgImage}`;

app/controllers/home/page.tsx

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { css, type Handle } from "remix/ui";
22
import { FpsCounterToggle } from "../../assets/fps-counter-toggle.tsx";
33
import { RemixLandingEnhancements } from "../../assets/remix-landing/landing-enhancements.tsx";
4-
import { LandingNewsletterSubscribeForm } from "../../assets/remix-landing/components/feature-section.tsx";
54
import { LoadingScreen } from "../../assets/remix-landing/components/loading-screen.tsx";
65
import { RUNNER_AVIF_SRC } from "../../assets/remix-landing/runner-media.ts";
76
import { colors } from "../../assets/remix-landing/styles/tokens.ts";
@@ -18,14 +17,6 @@ const HOME_TITLE = "Remix - A Web Framework for Building Anything";
1817
const HOME_DESCRIPTION =
1918
"Remix is a batteries-included, ultra-productive, zero dependencies and bundler-free framework, ready to develop with in a agent-first world.";
2019

21-
// Preload the hydration roots needed for the initial experience and let the
22-
// asset server follow their imports. The FPS counter stays out of this graph so
23-
// its event-triggered dynamic import remains lazy.
24-
export let homeBrowserEntries = [
25-
RemixLandingEnhancements.$entryId,
26-
LandingNewsletterSubscribeForm.$entryId,
27-
];
28-
2920
export function HomePage(handle: Handle<HomePageProps>) {
3021
return () => (
3122
<Document

app/middleware/asset-entry.ts

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,11 @@ import {
44
type Middleware,
55
type RequestContext,
66
} from "remix/router";
7-
import { DocumentHeadSync } from "../assets/document-head-sync.tsx";
87
import { assetServer } from "../utils/assets.server.ts";
98

109
export interface AssetEntry {
11-
sourceEntries: string[];
10+
source: string;
1211
src: string;
13-
preloads: string[];
1412
}
1513

1614
export let assetEntryContext = createContextKey<AssetEntry>();
@@ -26,38 +24,20 @@ export function loadAssetEntry(
2624
entry = defaultEntry,
2725
): Middleware<AssetEntryContextEntry> {
2826
return async (context, next) => {
29-
let entries = [entry, DocumentHeadSync.$entryId];
30-
let [src, preloads] = await Promise.all([
31-
assetServer.getHref(entry),
32-
assetServer.getPreloads(entries),
33-
]);
27+
let src = await assetServer.getHref(entry);
3428

3529
context.set(
3630
assetEntryContext,
3731
{
38-
sourceEntries: entries,
32+
source: entry,
3933
src,
40-
preloads: preloads.filter((href) => href !== src),
4134
},
4235
{ property: "assetEntry" },
4336
);
4437
return next();
4538
};
4639
}
4740

48-
export async function preloadAssetEntries(
49-
assetEntry: AssetEntry,
50-
entries: readonly string[],
51-
) {
52-
// Resolve one graph so Remix can keep every root ahead of deeper imports.
53-
assetEntry.sourceEntries = [
54-
...new Set([...assetEntry.sourceEntries, ...entries]),
55-
];
56-
assetEntry.preloads = (
57-
await assetServer.getPreloads(assetEntry.sourceEntries)
58-
).filter((href) => href !== assetEntry.src);
59-
}
60-
6141
export function setAssetEntry(context: RequestContext, assetEntry: AssetEntry) {
6242
context.set(assetEntryContext, assetEntry, { property: "assetEntry" });
6343
}

0 commit comments

Comments
 (0)