Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions packages/host/app/routes/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ export default class RenderRoute extends Route<Model> {
(globalThis as any).__waitForRenderLoadStability = undefined;
window.removeEventListener('boxel-render-error', this.handleRenderError);
this.#detachWindowErrorListeners();
this.#removePrerenderBaseHref();
this.lastStoreResetKey = undefined;
this.renderBaseParams = undefined;
this.lastRenderErrorSignature = undefined;
Expand Down Expand Up @@ -214,6 +215,9 @@ export default class RenderRoute extends Route<Model> {
let parsedOptions = parseRenderRouteOptions(options);
let canonicalOptions = serializeRenderRouteOptions(parsedOptions);
this.#setupTransitionHelper(id, nonce, canonicalOptions);
// Without this, relative `<img src>` in a card template resolves against
// the /render/... synthetic URL and 404s. CS-11146.
this.#installPrerenderBaseHref(id);
// Stamp the "consuming realm" — the realm that owns the card being
// rendered — onto a global the store-service's federated-search
// wrapper reads. The realm-server's job-scoped search cache pairs
Expand Down Expand Up @@ -936,6 +940,10 @@ export default class RenderRoute extends Route<Model> {
(globalThis as any).__renderModel = undefined;
(globalThis as any).__docsInFlight = undefined;
(globalThis as any).__waitForRenderLoadStability = undefined;
// Same reason as the globals above: in tests the owner can be
// destroyed without deactivate firing, which would leak the
// injected <base> into the next route/test.
this.#removePrerenderBaseHref();
});
}

Expand Down Expand Up @@ -1509,6 +1517,46 @@ export default class RenderRoute extends Route<Model> {
return id;
}

#installPrerenderBaseHref(id: string): void {
if (typeof document === 'undefined') {
return;
}
let baseHref: string;
try {
baseHref = new URL('./', this.#normalizeCardId(id)).href;
} catch {
return;
}
let head = document.head;
if (!head) {
return;
}
let existing = head.querySelector(
'base[data-prerender-base]',
) as HTMLBaseElement | null;
if (existing) {
if (existing.href !== baseHref) {
existing.href = baseHref;
}
return;
}
let baseEl = document.createElement('base');
baseEl.setAttribute('data-prerender-base', '');
baseEl.href = baseHref;
if (head.firstChild) {
head.insertBefore(baseEl, head.firstChild);
} else {
head.appendChild(baseEl);
}
}

#removePrerenderBaseHref(): void {
if (typeof document === 'undefined') {
return;
}
document.head?.querySelector('base[data-prerender-base]')?.remove();
}
Comment on lines +1558 to +1563

#fallbackDepsFromIds(ids: (string | undefined)[]): string[] {
// Seed dependency ids in every shape we might see in index/module rows:
// original id, normalized card id, and `.json` variants. This keeps error
Expand Down
15 changes: 15 additions & 0 deletions packages/host/tests/acceptance/prerender-html-test.gts
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,21 @@ module('Acceptance | prerender | html', function (hooks) {
.containsText('Paper', 'isolated format is rendered');
});

test('prerender installs a <base href> pointing at the card directory', async function (assert) {
let url = `${testRealmURL}Cat/paper.json`;
await visit(renderPath(url, '/html/isolated/0'));
let baseEl = document.head.querySelector(
'base[data-prerender-base]',
) as HTMLBaseElement | null;
assert.ok(baseEl, '<base data-prerender-base> is present in <head>');
let expectedDir = new URL('./', url.replace(/\.json$/, '')).href;
assert.strictEqual(
baseEl?.href,
expectedDir,
'<base href> resolves to the card containing directory',
);
});

test('prerender embedded html', async function (assert) {
let url = `${testRealmURL}Cat/paper.json`;
await visit(renderPath(url, '/html/embedded/0'));
Expand Down
Loading