Skip to content

Commit d7841c4

Browse files
committed
refactor clerk/ui, clerk/js and clerk/react to use the shared ClerkUi interface
- also updated Components so that new versions of clerk and environment propagate automatically
1 parent 3b6cdf2 commit d7841c4

File tree

14 files changed

+371
-394
lines changed

14 files changed

+371
-394
lines changed

packages/clerk-js/src/core/clerk.ts

Lines changed: 267 additions & 256 deletions
Large diffs are not rendered by default.

packages/clerk-js/src/global.d.ts

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,20 @@ declare module '*.svg' {
33
export default value;
44
}
55

6-
declare const __PKG_NAME__: string;
7-
declare const __PKG_VERSION__: string;
8-
declare const __DEV__: boolean;
9-
10-
/**
11-
* Build time feature flags.
12-
*/
13-
declare const __BUILD_DISABLE_RHC__: string;
14-
declare const __BUILD_VARIANT_CHANNEL__: boolean;
15-
declare const __BUILD_VARIANT_CHIPS__: boolean;
16-
17-
interface Window {
18-
__unstable__onBeforeSetActive: (intent?: 'sign-out') => Promise<void> | void;
19-
__unstable__onAfterSetActive: () => Promise<void> | void;
6+
declare global {
7+
const __PKG_NAME__: string;
8+
const __PKG_VERSION__: string;
9+
const __DEV__: boolean;
2010

2111
/**
22-
* TODO @nikos: implment type
23-
* UI components loaded from @clerk/ui browser bundle.
24-
* This is injected by the @clerk/ui package and consumed by clerk-js.
12+
* Build time feature flags.
2513
*/
26-
__unstable_ClerkUi?: {
27-
mountComponentRenderer: any;
28-
version: string;
29-
};
14+
const __BUILD_DISABLE_RHC__: string;
15+
const __BUILD_VARIANT_CHANNEL__: boolean;
16+
const __BUILD_VARIANT_CHIPS__: boolean;
17+
18+
interface Window {
19+
__unstable__onBeforeSetActive: (intent?: 'sign-out') => Promise<void> | void;
20+
__unstable__onAfterSetActive: () => Promise<void> | void;
21+
}
3022
}

packages/clerk-js/src/index.browser.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,6 @@ import './utils/setWebpackChunkPublicPath';
55

66
import { Clerk } from './core/clerk';
77

8-
// Load UI components from window (injected by @clerk/ui browser bundle)
9-
if (window.__unstable_ClerkUi) {
10-
Clerk.mountComponentRenderer = window.__unstable_ClerkUi.mountComponentRenderer;
11-
}
12-
138
const publishableKey =
149
document.querySelector('script[data-clerk-publishable-key]')?.getAttribute('data-clerk-publishable-key') ||
1510
window.__clerk_publishable_key ||
@@ -27,7 +22,6 @@ const domain =
2722
if (!window.Clerk) {
2823
window.Clerk = new Clerk(publishableKey, {
2924
proxyUrl,
30-
// @ts-expect-error - domain is not typed
3125
domain,
3226
});
3327
}

packages/clerk-js/src/index.legacy.browser.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,6 @@ import 'regenerator-runtime/runtime';
77

88
import { Clerk } from './core/clerk';
99

10-
// Load UI components from window (injected by @clerk/ui browser bundle)
11-
if (window.__unstable_ClerkUi) {
12-
Clerk.mountComponentRenderer = window.__unstable_ClerkUi.mountComponentRenderer;
13-
}
14-
1510
const publishableKey =
1611
document.querySelector('script[data-clerk-publishable-key]')?.getAttribute('data-clerk-publishable-key') ||
1712
window.__clerk_publishable_key ||
@@ -29,7 +24,6 @@ const domain =
2924
if (!window.Clerk) {
3025
window.Clerk = new Clerk(publishableKey, {
3126
proxyUrl,
32-
// @ts-expect-error
3327
domain,
3428
});
3529
}

packages/react/src/isomorphicClerk.ts

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ import type {
5454
WaitlistResource,
5555
Without,
5656
} from '@clerk/shared/types';
57-
import type { MountComponentRenderer } from '@clerk/shared/ui';
57+
import type { ClerkUiConstructor } from '@clerk/shared/ui';
5858
import { handleValueOrFn } from '@clerk/shared/utils';
5959

6060
import { errorThrower } from './errors/errorThrower';
@@ -82,10 +82,7 @@ const SDK_METADATA = {
8282

8383
export interface Global {
8484
Clerk?: HeadlessBrowserClerk | BrowserClerk;
85-
__unstable_ClerkUi?: {
86-
mountComponentRenderer: MountComponentRenderer;
87-
version: string;
88-
};
85+
__unstable_ClerkUiCtor?: ClerkUiConstructor;
8986
}
9087

9188
declare const global: Global;
@@ -453,19 +450,18 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk {
453450
}
454451

455452
try {
456-
console.log('yo');
457-
458453
const clerkPromise = this.loadClerkJsEntryChunk();
459-
const clerkUiEntry = { resolve: () => this.loadClerkUiEntryChunk().then(a => a.mountComponentRenderer) };
454+
const clerkUiCtor = this.loadClerkUiEntryChunk();
460455
await clerkPromise;
461456

462457
if (!global.Clerk) {
463458
// TODO @nikos: somehow throw if clerk ui failed to load but it was not headless
464459
throw new Error('Failed to download latest ClerkJS. Contact support@clerk.com.');
465460
}
461+
466462
if (!global.Clerk.loaded) {
467463
this.beforeLoad(global.Clerk);
468-
await global.Clerk.load({ ...this.options, clerkUiEntry });
464+
await global.Clerk.load({ ...this.options, clerkUiCtor });
469465
}
470466
if (global.Clerk.loaded) {
471467
this.replayInterceptedInvocations(global.Clerk);
@@ -508,10 +504,10 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk {
508504
domain: this.domain,
509505
nonce: this.options.nonce,
510506
});
511-
if (!global.__unstable_ClerkUi) {
507+
if (!global.__unstable_ClerkUiCtor) {
512508
throw new Error('Failed to download latest Clerk UI. Contact support@clerk.com.');
513509
}
514-
return global.__unstable_ClerkUi;
510+
return global.__unstable_ClerkUiCtor;
515511
}
516512

517513
public on: Clerk['on'] = (...args) => {

packages/react/src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@ import type {
1212
TasksRedirectOptions,
1313
Without,
1414
} from '@clerk/shared/types';
15+
import type { ClerkUiConstructor } from '@clerk/shared/ui';
1516
import type React from 'react';
1617

1718
declare global {
1819
interface Window {
1920
__clerk_publishable_key?: string;
2021
__clerk_proxy_url?: Clerk['proxyUrl'];
2122
__clerk_domain?: Clerk['domain'];
23+
__unstable_ClerkUiCtor?: ClerkUiConstructor;
2224
}
2325
}
2426

packages/shared/src/loadClerkJsScript.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,17 @@ export type LoadClerkUiScriptOptions = {
4242
*
4343
* @returns `true` if window.Clerk exists and has the expected structure with a load method.
4444
*/
45-
function isClerkGlobalProperlyLoaded(prop: 'Clerk' | '__unstable_ClerkUi'): boolean {
45+
function isClerkGlobalProperlyLoaded(prop: 'Clerk' | '__unstable_ClerkUiCtor'): boolean {
4646
if (typeof window === 'undefined' || !(window as any)[prop]) {
4747
return false;
4848
}
4949

5050
// Basic validation that window.Clerk has the expected structure
5151
const val = (window as any)[prop];
52-
return typeof val === 'object' && (val.load ? typeof val.load === 'function' : true);
52+
return !!val;
5353
}
5454
const isClerkProperlyLoaded = () => isClerkGlobalProperlyLoaded('Clerk');
55-
const isClerkUiProperlyLoaded = () => isClerkGlobalProperlyLoaded('__unstable_ClerkUi');
55+
const isClerkUiProperlyLoaded = () => isClerkGlobalProperlyLoaded('__unstable_ClerkUiCtor');
5656

5757
/**
5858
* Hotloads the Clerk JS script with robust failure detection.

packages/shared/src/types/clerk.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { ClerkUiEntry } from '../ui/types';
1+
import type { ClerkUiConstructor } from '../ui/types';
22
import type { APIKeysNamespace } from './apiKeys';
33
import type {
44
APIKeysTheme,
@@ -1037,7 +1037,7 @@ export type ClerkOptions = ClerkOptionsNavigation &
10371037
/**
10381038
* Clerk UI entrypoint.
10391039
*/
1040-
clerkUiEntry?: ClerkUiEntry;
1040+
clerkUiCtor?: Promise<ClerkUiConstructor>;
10411041
/**
10421042
* Optional object to style your components. Will only affect [Clerk Components](https://clerk.com/docs/reference/components/overview) and not [Account Portal](https://clerk.com/docs/guides/customizing-clerk/account-portal) pages.
10431043
*/

packages/shared/src/ui/types.ts

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,39 @@
1-
/**
2-
* UI-related types and utilities
3-
*/
1+
import type { Appearance, Clerk, ClerkOptions, EnvironmentResource } from '../types';
42

5-
// Placeholder - Add UI-specific types and utilities here
63
export type UIVersion = string;
74

8-
// TODO: replace with a proper interface
9-
export type MountComponentRenderer = (
10-
clerk: any,
11-
environment: any,
12-
options: any,
13-
) => {
14-
ensureMounted: (options?: { preloadHint?: string }) => Promise<{
15-
mountImpersonationFab: () => void;
16-
updateProps: (props: any) => void;
17-
openModal: (name: string, props: any) => void;
18-
closeModal: (name: string) => void;
19-
openDrawer: (name: string, props: any) => void;
20-
closeDrawer: (name: string) => void;
21-
mountComponent: (config: any) => void;
22-
unmountComponent: (config: any) => void;
23-
prefetch: (name: string) => void;
24-
}>;
5+
export type ComponentControls = {
6+
mountComponent: (params: { appearanceKey: string; name: string; node: HTMLDivElement; props?: any }) => void;
7+
unmountComponent: (params: { node: HTMLDivElement }) => void;
8+
updateProps: (params: {
9+
appearance?: Appearance | undefined;
10+
options?: ClerkOptions | undefined;
11+
node?: HTMLDivElement;
12+
props?: unknown;
13+
}) => void;
14+
openModal: (modal: string, props?: any) => void;
15+
closeModal: (modal: string, options?: { notify?: boolean }) => void;
16+
openDrawer: (drawer: string, props?: any) => void;
17+
closeDrawer: (drawer: string, options?: { notify?: boolean }) => void;
18+
prefetch: (component: 'organizationSwitcher') => void;
19+
mountImpersonationFab: () => void;
2520
};
2621

27-
export interface ClerkUiEntry {
28-
resolve: () => Promise<MountComponentRenderer>;
22+
// Instance shape that the class will implement
23+
export interface ClerkUiInstance {
24+
version: string;
25+
ensureMounted: (opts?: { preloadHint?: string }) => Promise<ComponentControls>;
2926
}
27+
28+
// Constructor type
29+
export interface ClerkUiConstructor {
30+
new (
31+
getClerk: () => Clerk,
32+
getEnvironment: () => EnvironmentResource | null | undefined,
33+
options: ClerkOptions,
34+
importModule: (module: string) => Promise<any>,
35+
): ClerkUiInstance;
36+
version: string;
37+
}
38+
39+
export type ClerkUi = ClerkUiInstance;

packages/ui/src/ClerkUi.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import type { Clerk, ClerkOptions, EnvironmentResource } from '@clerk/shared/types';
2+
import type { ClerkUiInstance, ComponentControls as SharedComponentControls } from '@clerk/shared/ui';
3+
4+
import { type MountComponentRenderer, mountComponentRenderer } from './Components';
5+
6+
export class ClerkUi implements ClerkUiInstance {
7+
static version = __PKG_VERSION__;
8+
version = __PKG_VERSION__;
9+
10+
#componentRenderer: ReturnType<MountComponentRenderer>;
11+
12+
constructor(
13+
getClerk: () => Clerk,
14+
getEnvironment: () => EnvironmentResource | null | undefined,
15+
options: ClerkOptions,
16+
_importModule: (module: string) => Promise<any>,
17+
) {
18+
this.#componentRenderer = mountComponentRenderer(getClerk, getEnvironment, options);
19+
}
20+
21+
ensureMounted(opts?: { preloadHint?: string }): Promise<SharedComponentControls> {
22+
return this.#componentRenderer.ensureMounted(opts as unknown as any) as Promise<SharedComponentControls>;
23+
}
24+
}

0 commit comments

Comments
 (0)