Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(astro): Add support for View Transitions #4354

Merged
merged 15 commits into from
Oct 18, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions .changeset/late-pigs-study.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@clerk/astro": minor
---

Add support for Astro View Transitions
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
import { ViewTransitions } from 'astro:transitions';

interface Props {
title: string;
}

const { title } = Astro.props;
---

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="description" content="Astro description" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="generator" content={Astro.generator} />
<title>{title}</title>
<ViewTransitions />
</head>
<body>
<main>
<slot />
</main>
</body>
</html>
15 changes: 15 additions & 0 deletions integration/templates/astro-node/src/pages/transitions/index.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
import { SignedIn, SignedOut, UserButton } from "@clerk/astro/components";
import Layout from "../../layouts/ViewTransitionsLayout.astro";
---

<Layout title="Sign in">
<div class="w-full flex justify-center">
<SignedOut>
<a href="/transitions/sign-in">Sign in</a>
</SignedOut>
<SignedIn>
<UserButton />
</SignedIn>
</div>
</Layout>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
import { SignIn } from "@clerk/astro/components";
import Layout from "../../layouts/ViewTransitionsLayout.astro";
---

<Layout title="Sign in">
<div class="w-full flex justify-center">
<SignIn forceRedirectUrl="/transitions" />
</div>
</Layout>
30 changes: 30 additions & 0 deletions integration/tests/astro/components.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -451,4 +451,34 @@ testAgainstRunningApps({ withPattern: ['astro.node.withCustomRoles'] })('basic f
await u.po.expect.toBeSignedIn();
await expect(u.page.getByText('Not a member')).toBeVisible();
});

test('renders components and keep internal routing behavior when view transitions is enabled', async ({
page,
context,
}) => {
const u = createTestUtils({ app, page, context });
await u.page.goToRelative('/transitions');
// Navigate to sign-in page using link to simulate transition
await u.page.getByRole('link', { name: /Sign in/i }).click();

// Components should be mounted on the new document
// when navigating through links
await u.page.waitForURL(`${app.serverUrl}/transitions/sign-in`);
await u.po.signIn.waitForMounted();

await u.po.signIn.setIdentifier(fakeAdmin.email);
await u.po.signIn.continue();
await u.page.waitForURL(`${app.serverUrl}/transitions/sign-in#/factor-one`);

await u.po.signIn.setPassword(fakeAdmin.password);
await u.po.signIn.continue();

await u.po.expect.toBeSignedIn();

// Internal Clerk routing should still work
await u.page.waitForURL(`${app.serverUrl}/transitions`);

// Components should be rendered on hard reload
await u.po.userButton.waitForMounted();
});
});
27 changes: 25 additions & 2 deletions packages/astro/src/integration/create-integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,31 @@ function createIntegration<Params extends HotloadAstroClerkIntegrationParams>()
'page',
`
${command === 'dev' ? `console.log("${packageName}","Initialize Clerk: page")` : ''}
import { runInjectionScript } from "${buildImportPath}";
await runInjectionScript(${JSON.stringify(internalParams)});`,
import { runInjectionScript, swapDocument } from "${buildImportPath}";
import { navigate, transitionEnabledOnThisPage } from "astro:transitions/client";

if (transitionEnabledOnThisPage()) {
document.addEventListener('astro:before-swap', (e) => {
const clerkComponents = document.querySelector('#clerk-components');
// Keep the div element added by Clerk
if (clerkComponents) {
const clonedEl = clerkComponents.cloneNode(true);
e.newDocument.body.appendChild(clonedEl);
}

e.swap = () => swapDocument(e.newDocument);
});

document.addEventListener('astro:page-load', async (e) => {
await runInjectionScript({
...${JSON.stringify(internalParams)},
routerPush: navigate,
routerReplace: (url) => navigate(url, { history: 'replace' }),
});
})
} else {
await runInjectionScript(${JSON.stringify(internalParams)});
}`,
);
},
},
Expand Down
2 changes: 2 additions & 0 deletions packages/astro/src/integration/vite-plugin-astro-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ export function vitePluginAstroConfig(astroConfig: AstroConfig): VitePlugin {
// This ensures @clerk/astro/client is properly processed and bundled,
// resolving runtime import issues in these components.
config.optimizeDeps?.include?.push('@clerk/astro/client');
// Let astro vite plugin handle this.
config.optimizeDeps?.exclude?.push('astro:transitions/client');
},
load(id) {
if (id === resolvedVirtualModuleId) {
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/internal/create-clerk-instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ async function createClerkInstanceInternal(options?: AstroClerkCreateInstancePar
}

initOptions = {
...options,
routerPush: createNavigationHandler(window.history.pushState.bind(window.history)),
routerReplace: createNavigationHandler(window.history.replaceState.bind(window.history)),
...options,
};

return clerkJSInstance
Expand Down
1 change: 1 addition & 0 deletions packages/astro/src/internal/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ const runInjectionScript = createInjectionScriptRunner(createClerkInstance);
export { runInjectionScript };

export { generateSafeId } from './utils/generateSafeId';
export { swapDocument } from './swap-document';
61 changes: 61 additions & 0 deletions packages/astro/src/internal/swap-document.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// eslint-disable-next-line import/no-unresolved
import { swapFunctions } from 'astro:transitions/client';
Copy link
Member

@panteliselef panteliselef Oct 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Is this always bundled with Astro ? Is there a case where the module will not exists causing our integration to break ?

Copy link
Member Author

@wobsoriano wobsoriano Oct 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is indeed bundled with Astro (astro/virtual-modules/transitions-swap-functions.js), but is not available in Astro v3.

Also we let the Astro Vite plugin handle it by excluding from vite.optimizeDeps:

config.optimizeDeps?.exclude?.push('astro:transitions/client');


const PERSIST_ATTR = 'data-astro-transition-persist';
const EMOTION_ATTR = 'data-emotion';

/**
* @internal
* Custom swap function to make mounting and styling
* of Clerk components work with View Transitions in Astro.
*
* See https://docs.astro.build/en/guides/view-transitions/#building-a-custom-swap-function
*/
export function swapDocument(doc: Document) {
swapFunctions.deselectScripts(doc);
swapFunctions.swapRootAttributes(doc);

// Keep the elements created by `@emotion/cache`
const emotionElements = document.querySelectorAll(`style[${EMOTION_ATTR}]`);
swapHeadElements(doc, Array.from(emotionElements));
Comment on lines +19 to +20
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to keep the <style> element added by @emotion/cache to keep the styling when switching between pages.

Screenshot 2024-10-17 at 1 34 41 PM

The default Astro swap function will remove this one


const restoreFocusFunction = swapFunctions.saveFocus();
swapFunctions.swapBodyElement(doc.body, document.body);
restoreFocusFunction();
}

/**
* This function is a copy of the original `swapHeadElements` function from `astro:transitions/client`.
* The difference is that you can pass a list of elements that should not be removed
* in the new document.
*
* See https://github.com/withastro/astro/blob/d6f17044d3873df77cfbc73230cb3194b5a7d82a/packages/astro/src/transitions/swap-functions.ts#L51
*/
function swapHeadElements(doc: Document, ignoredElements: Element[]) {
for (const el of Array.from(document.head.children)) {
const newEl = persistedHeadElement(el, doc);

if (newEl) {
newEl.remove();
} else {
if (!ignoredElements.includes(el)) {
el.remove();
}
}
}

document.head.append(...doc.head.children);
}

function persistedHeadElement(el: Element, newDoc: Document) {
const id = el.getAttribute(PERSIST_ATTR);
const newEl = id && newDoc.head.querySelector(`[${PERSIST_ATTR}="${id}"]`);
if (newEl) {
return newEl;
}
if (el.matches('link[rel=stylesheet]')) {
const href = el.getAttribute('href');
return newDoc.head.querySelector(`link[rel=stylesheet][href="${href}"]`);
}
return null;
}
2 changes: 1 addition & 1 deletion packages/astro/tsup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ export default defineConfig(() => {
bundle: true,
sourcemap: true,
format: ['esm'],
external: ['astro', 'react', 'react-dom', 'node:async_hooks', '#async-local-storage'],
external: ['astro', 'react', 'react-dom', 'node:async_hooks', '#async-local-storage', 'astro:transitions/client'],
};
});
Loading