From c5ff0d1ca3b167038f261778cbd20264c628cbba Mon Sep 17 00:00:00 2001 From: Jacek Date: Fri, 23 Jan 2026 12:29:10 -0600 Subject: [PATCH 01/21] feat(react,vue,astro): Add ui prop for version metadata Adds `ui` prop to ClerkProvider for specifying UI metadata. Each SDK decides whether to use `ui.ctor` based on support level. - `@clerk/ui` exports only `{ ui }` with version and ctor - Chrome extension uses `ui.ctor` (verified to work) - React, Vue, Astro use CDN loading (not verified for bundling yet) - Omit `clerkUiCtor` from public ClerkProviderProps type --- .../astro/src/internal/create-clerk-instance.ts | 7 +------ packages/astro/src/types.ts | 5 +++++ .../chrome-extension/src/react/ClerkProvider.tsx | 7 ++++--- packages/react/src/contexts/ClerkProvider.tsx | 1 + packages/react/src/types.ts | 2 +- packages/shared/src/types/clerk.ts | 10 +++++++++- packages/ui/src/index.ts | 16 ++++++++++++++-- packages/ui/src/internal/index.ts | 7 +++++++ packages/vue/src/plugin.ts | 16 +++++++--------- 9 files changed, 49 insertions(+), 22 deletions(-) diff --git a/packages/astro/src/internal/create-clerk-instance.ts b/packages/astro/src/internal/create-clerk-instance.ts index e1cbd520144..ab56c0d7826 100644 --- a/packages/astro/src/internal/create-clerk-instance.ts +++ b/packages/astro/src/internal/create-clerk-instance.ts @@ -109,16 +109,11 @@ async function getClerkJsEntryChunk(options?: AstroClerkCre } /** - * Gets the ClerkUI constructor, either from options or by loading the script. - * Returns early if window.__internal_ClerkUiCtor already exists. + * Gets the ClerkUI constructor by loading from CDN. */ async function getClerkUiEntryChunk( options?: AstroClerkCreateInstanceParams, ): Promise { - if (options?.clerkUiCtor) { - return options.clerkUiCtor; - } - await loadClerkUiScript(options); if (!window.__internal_ClerkUiCtor) { diff --git a/packages/astro/src/types.ts b/packages/astro/src/types.ts index 7f0613e5968..658daadcca2 100644 --- a/packages/astro/src/types.ts +++ b/packages/astro/src/types.ts @@ -36,6 +36,11 @@ type AstroClerkIntegrationParams = Without< * The URL that `@clerk/ui` should be hot-loaded from. */ clerkUiUrl?: string; + /** + * The Clerk UI bundle to use. When provided with a bundled UI via + * `ui.ctor`, it will be used instead of loading from CDN. + */ + ui?: TUi; }; type AstroClerkCreateInstanceParams = AstroClerkIntegrationParams & { diff --git a/packages/chrome-extension/src/react/ClerkProvider.tsx b/packages/chrome-extension/src/react/ClerkProvider.tsx index 45237484496..9e9ce74206d 100644 --- a/packages/chrome-extension/src/react/ClerkProvider.tsx +++ b/packages/chrome-extension/src/react/ClerkProvider.tsx @@ -2,7 +2,7 @@ import type { Clerk } from '@clerk/clerk-js/no-rhc'; import type { ClerkProviderProps as ClerkReactProviderProps } from '@clerk/react'; import { ClerkProvider as ClerkReactProvider } from '@clerk/react'; import type { Ui } from '@clerk/react/internal'; -import { ClerkUi } from '@clerk/ui/entry'; +import { ui } from '@clerk/ui'; import React from 'react'; import { createClerkClient } from '../internal/clerk'; @@ -32,11 +32,12 @@ export function ClerkProvider(props: ChromeExtensionClerkPr return null; } + // Chrome extension uses bundled UI via ui.ctor return ( {children} diff --git a/packages/react/src/contexts/ClerkProvider.tsx b/packages/react/src/contexts/ClerkProvider.tsx index 351ee6f2b5f..b07a0b9fabf 100644 --- a/packages/react/src/contexts/ClerkProvider.tsx +++ b/packages/react/src/contexts/ClerkProvider.tsx @@ -9,6 +9,7 @@ import { ClerkContextProvider } from './ClerkContextProvider'; function ClerkProviderBase(props: ClerkProviderProps) { const { initialState, children, ...restIsomorphicClerkOptions } = props; + const isomorphicClerkOptions = restIsomorphicClerkOptions as unknown as IsomorphicClerkOptions; return ( diff --git a/packages/react/src/types.ts b/packages/react/src/types.ts index c769d58afac..f78b9d8eed9 100644 --- a/packages/react/src/types.ts +++ b/packages/react/src/types.ts @@ -36,7 +36,7 @@ declare global { /** * @interface */ -export type ClerkProviderProps = Omit & { +export type ClerkProviderProps = Omit & { children: React.ReactNode; /** * Provide an initial state of the Clerk client during server-side rendering. You don't need to set this value yourself unless you're [developing an SDK](https://clerk.com/docs/guides/development/sdk-development/overview). diff --git a/packages/shared/src/types/clerk.ts b/packages/shared/src/types/clerk.ts index 314d186793b..db232523600 100644 --- a/packages/shared/src/types/clerk.ts +++ b/packages/shared/src/types/clerk.ts @@ -2436,7 +2436,15 @@ export type IsomorphicClerkOptions = Without & { * This is a structural-only type for the `ui` object that can be passed * to Clerk.load() and ClerkProvider */ - ui?: { version: string; url?: string }; + ui?: { + version: string; + url?: string; + /** + * The Clerk UI constructor. When provided, this will be used instead of + * loading the UI from CDN. This is useful for bundling the UI with your app. + */ + ctor?: ClerkUiConstructor | Promise; + }; } & MultiDomainAndOrProxy; export interface LoadedClerk extends Clerk { diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index cc3aa52b41b..358e3fb32e8 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -1,12 +1,24 @@ import type { Ui } from './internal'; import type { Appearance } from './internal/appearance'; +import { ClerkUi } from './ClerkUi'; + declare const PACKAGE_VERSION: string; /** - * Default ui object for Clerk UI components - * Tagged with the internal Appearance type for type-safe appearance prop inference + * UI object for Clerk UI components. + * Pass this to ClerkProvider to use the bundled UI. + * + * @example + * ```tsx + * import { ui } from '@clerk/ui'; + * + * + * ... + * + * ``` */ export const ui = { version: PACKAGE_VERSION, + ctor: ClerkUi, } as Ui; diff --git a/packages/ui/src/internal/index.ts b/packages/ui/src/internal/index.ts index 2a9e39b207e..b53f5135087 100644 --- a/packages/ui/src/internal/index.ts +++ b/packages/ui/src/internal/index.ts @@ -1,3 +1,5 @@ +import type { ClerkUiConstructor } from '@clerk/shared/ui'; + import type { Appearance } from './appearance'; export type { ComponentControls, MountComponentRenderer } from '../Components'; @@ -26,6 +28,11 @@ export type Ui = Tagged< { version: string; url?: string; + /** + * The Clerk UI constructor. When provided, this will be used instead of + * loading the UI from CDN. This is useful for bundling the UI with your app. + */ + ctor?: ClerkUiConstructor | Promise; /** * Phantom property for type-level appearance inference * This property never exists at runtime diff --git a/packages/vue/src/plugin.ts b/packages/vue/src/plugin.ts index 91a89ffe91d..06ee7253307 100644 --- a/packages/vue/src/plugin.ts +++ b/packages/vue/src/plugin.ts @@ -78,15 +78,13 @@ export const clerkPlugin: Plugin<[PluginOptions]> = { void (async () => { try { const clerkPromise = loadClerkJsScript(options); - const clerkUiCtorPromise = pluginOptions.clerkUiCtor - ? Promise.resolve(pluginOptions.clerkUiCtor) - : (async () => { - await loadClerkUiScript(options); - if (!window.__internal_ClerkUiCtor) { - throw new Error('Failed to download latest Clerk UI. Contact support@clerk.com.'); - } - return window.__internal_ClerkUiCtor; - })(); + const clerkUiCtorPromise = (async () => { + await loadClerkUiScript(options); + if (!window.__internal_ClerkUiCtor) { + throw new Error('Failed to download latest Clerk UI. Contact support@clerk.com.'); + } + return window.__internal_ClerkUiCtor; + })(); await clerkPromise; From d8370e0efb64d15c6010baa47dfb73234352bf55 Mon Sep 17 00:00:00 2001 From: Jacek Date: Fri, 23 Jan 2026 14:09:04 -0600 Subject: [PATCH 02/21] chore: add changeset for ui prop --- .changeset/shiny-owls-dance.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .changeset/shiny-owls-dance.md diff --git a/.changeset/shiny-owls-dance.md b/.changeset/shiny-owls-dance.md new file mode 100644 index 00000000000..c459eb70b10 --- /dev/null +++ b/.changeset/shiny-owls-dance.md @@ -0,0 +1,23 @@ +--- +'@clerk/ui': minor +'@clerk/react': minor +'@clerk/vue': minor +'@clerk/astro': minor +'@clerk/chrome-extension': minor +'@clerk/shared': minor +--- + +Add `ui` prop to ClerkProvider for UI version metadata + +The `ui` object from `@clerk/ui` contains version info and the bundled constructor. Each SDK decides whether to use `ui.ctor` based on its support level: +- Chrome Extension: Uses bundled UI via `ui.ctor` +- React/Next.js, Vue, Astro: Uses CDN loading + +Usage: +```tsx +import { ui } from '@clerk/ui'; + + + ... + +``` From 67fdc89ab69cd8d717b4c53fac66ffa234706142 Mon Sep 17 00:00:00 2001 From: Jacek Date: Fri, 23 Jan 2026 14:30:49 -0600 Subject: [PATCH 03/21] refactor: use ui prop instead of clerkUiCtor in chrome-extension --- .changeset/shiny-owls-dance.md | 4 +--- packages/chrome-extension/src/react/ClerkProvider.tsx | 4 ++-- packages/react/src/isomorphicClerk.ts | 4 ++++ 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.changeset/shiny-owls-dance.md b/.changeset/shiny-owls-dance.md index c459eb70b10..33efe62f669 100644 --- a/.changeset/shiny-owls-dance.md +++ b/.changeset/shiny-owls-dance.md @@ -9,9 +9,7 @@ Add `ui` prop to ClerkProvider for UI version metadata -The `ui` object from `@clerk/ui` contains version info and the bundled constructor. Each SDK decides whether to use `ui.ctor` based on its support level: -- Chrome Extension: Uses bundled UI via `ui.ctor` -- React/Next.js, Vue, Astro: Uses CDN loading +The `ui` object from `@clerk/ui` is passed to ClerkProvider. When `ui.ctor` is available, it will be used for bundled UI; otherwise, the UI is loaded from CDN. Usage: ```tsx diff --git a/packages/chrome-extension/src/react/ClerkProvider.tsx b/packages/chrome-extension/src/react/ClerkProvider.tsx index 9e9ce74206d..6f6929e38a8 100644 --- a/packages/chrome-extension/src/react/ClerkProvider.tsx +++ b/packages/chrome-extension/src/react/ClerkProvider.tsx @@ -32,12 +32,12 @@ export function ClerkProvider(props: ChromeExtensionClerkPr return null; } - // Chrome extension uses bundled UI via ui.ctor + // Chrome extension uses bundled UI via ui prop return ( {children} diff --git a/packages/react/src/isomorphicClerk.ts b/packages/react/src/isomorphicClerk.ts index b39bf352b8c..ba7e91b6cec 100644 --- a/packages/react/src/isomorphicClerk.ts +++ b/packages/react/src/isomorphicClerk.ts @@ -509,6 +509,10 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk { } private async getClerkUiEntryChunk(): Promise { + // Use bundled UI constructor if provided via ui.ctor or clerkUiCtor + if (this.options.ui?.ctor) { + return this.options.ui.ctor; + } if (this.options.clerkUiCtor) { return this.options.clerkUiCtor; } From 2399eff5a60694131f45d257c16e7af79a3d5cac Mon Sep 17 00:00:00 2001 From: Jacek Date: Fri, 23 Jan 2026 15:01:47 -0600 Subject: [PATCH 04/21] refactor: remove clerkUiCtor from public API, add __internal_forceBundledUI escape hatch --- .../templates/express-vite/src/client/main.ts | 6 ++++-- .../astro/src/internal/create-clerk-instance.ts | 13 +++++++++++-- .../chrome-extension/src/react/ClerkProvider.tsx | 6 ++++-- packages/react/src/isomorphicClerk.ts | 8 +++----- packages/react/src/types.ts | 2 +- packages/shared/src/types/clerk.ts | 11 +++++++++-- packages/ui/src/internal/index.ts | 6 ++++++ packages/vue/src/plugin.ts | 9 +++++++-- 8 files changed, 45 insertions(+), 16 deletions(-) diff --git a/integration/templates/express-vite/src/client/main.ts b/integration/templates/express-vite/src/client/main.ts index bf19f46d7b7..1d652826a68 100644 --- a/integration/templates/express-vite/src/client/main.ts +++ b/integration/templates/express-vite/src/client/main.ts @@ -1,13 +1,15 @@ import { Clerk } from '@clerk/clerk-js'; -import { ClerkUi } from '@clerk/ui/entry'; +import { ui } from '@clerk/ui'; const publishableKey = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY; document.addEventListener('DOMContentLoaded', async function () { const clerk = new Clerk(publishableKey); + // Use bundled UI via ui.ctor await clerk.load({ - clerkUiCtor: ClerkUi, + ui, + clerkUiCtor: ui.ctor, }); if (clerk.isSignedIn) { diff --git a/packages/astro/src/internal/create-clerk-instance.ts b/packages/astro/src/internal/create-clerk-instance.ts index ab56c0d7826..9d4ba2e7bb9 100644 --- a/packages/astro/src/internal/create-clerk-instance.ts +++ b/packages/astro/src/internal/create-clerk-instance.ts @@ -109,12 +109,21 @@ async function getClerkJsEntryChunk(options?: AstroClerkCre } /** - * Gets the ClerkUI constructor by loading from CDN. + * Gets the ClerkUI constructor by loading from CDN with version pinning. */ async function getClerkUiEntryChunk( options?: AstroClerkCreateInstanceParams, ): Promise { - await loadClerkUiScript(options); + // Load UI from CDN with version pinning from ui.version + await loadClerkUiScript( + options + ? { + ...options, + clerkUiVersion: options.ui?.version, + clerkUiUrl: options.ui?.url, + } + : undefined, + ); if (!window.__internal_ClerkUiCtor) { throw new Error('Failed to download latest Clerk UI. Contact support@clerk.com.'); diff --git a/packages/chrome-extension/src/react/ClerkProvider.tsx b/packages/chrome-extension/src/react/ClerkProvider.tsx index 6f6929e38a8..6b013a2e0d0 100644 --- a/packages/chrome-extension/src/react/ClerkProvider.tsx +++ b/packages/chrome-extension/src/react/ClerkProvider.tsx @@ -32,12 +32,14 @@ export function ClerkProvider(props: ChromeExtensionClerkPr return null; } - // Chrome extension uses bundled UI via ui prop + // Chrome extension must use bundled UI (no CDN access in extensions) + const bundledUi = { ...ui, __internal_forceBundledUI: true } as typeof ui; + return ( {children} diff --git a/packages/react/src/isomorphicClerk.ts b/packages/react/src/isomorphicClerk.ts index ba7e91b6cec..7e77a5d425b 100644 --- a/packages/react/src/isomorphicClerk.ts +++ b/packages/react/src/isomorphicClerk.ts @@ -509,14 +509,12 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk { } private async getClerkUiEntryChunk(): Promise { - // Use bundled UI constructor if provided via ui.ctor or clerkUiCtor - if (this.options.ui?.ctor) { + // Use bundled UI constructor if forced (e.g., Chrome Extension) and ui.ctor is available + if (this.options.ui?.__internal_forceBundledUI && this.options.ui.ctor) { return this.options.ui.ctor; } - if (this.options.clerkUiCtor) { - return this.options.clerkUiCtor; - } + // Default: load from CDN, using ui.version if available for version pinning await loadClerkUiScript({ ...this.options, clerkUiVersion: this.options.ui?.version, diff --git a/packages/react/src/types.ts b/packages/react/src/types.ts index f78b9d8eed9..c769d58afac 100644 --- a/packages/react/src/types.ts +++ b/packages/react/src/types.ts @@ -36,7 +36,7 @@ declare global { /** * @interface */ -export type ClerkProviderProps = Omit & { +export type ClerkProviderProps = Omit & { children: React.ReactNode; /** * Provide an initial state of the Clerk client during server-side rendering. You don't need to set this value yourself unless you're [developing an SDK](https://clerk.com/docs/guides/development/sdk-development/overview). diff --git a/packages/shared/src/types/clerk.ts b/packages/shared/src/types/clerk.ts index db232523600..27c4e364c15 100644 --- a/packages/shared/src/types/clerk.ts +++ b/packages/shared/src/types/clerk.ts @@ -1083,7 +1083,8 @@ export type ClerkOptions = ClerkOptionsNavigation & AfterMultiSessionSingleSignOutUrl & ClerkUnsafeOptions & { /** - * Clerk UI entrypoint. + * @internal + * Clerk UI entrypoint. Used internally by SDKs to pass the UI constructor to clerk-js. */ clerkUiCtor?: ClerkUiConstructor | Promise; /** @@ -2405,7 +2406,7 @@ export type ClerkProp = | undefined | null; -export type IsomorphicClerkOptions = Without & { +export type IsomorphicClerkOptions = Without & { Clerk?: ClerkProp; /** * The URL that `@clerk/clerk-js` should be hot-loaded from. @@ -2444,6 +2445,12 @@ export type IsomorphicClerkOptions = Without & { * loading the UI from CDN. This is useful for bundling the UI with your app. */ ctor?: ClerkUiConstructor | Promise; + /** + * @internal + * Force using the bundled UI constructor instead of loading from CDN. + * Used internally by SDKs that must bundle the UI (e.g., Chrome Extension). + */ + __internal_forceBundledUI?: boolean; }; } & MultiDomainAndOrProxy; diff --git a/packages/ui/src/internal/index.ts b/packages/ui/src/internal/index.ts index b53f5135087..e8247a86654 100644 --- a/packages/ui/src/internal/index.ts +++ b/packages/ui/src/internal/index.ts @@ -33,6 +33,12 @@ export type Ui = Tagged< * loading the UI from CDN. This is useful for bundling the UI with your app. */ ctor?: ClerkUiConstructor | Promise; + /** + * @internal + * Force using the bundled UI constructor instead of loading from CDN. + * Used internally by SDKs that must bundle the UI (e.g., Chrome Extension). + */ + __internal_forceBundledUI?: boolean; /** * Phantom property for type-level appearance inference * This property never exists at runtime diff --git a/packages/vue/src/plugin.ts b/packages/vue/src/plugin.ts index 06ee7253307..0ada513115a 100644 --- a/packages/vue/src/plugin.ts +++ b/packages/vue/src/plugin.ts @@ -78,8 +78,13 @@ export const clerkPlugin: Plugin<[PluginOptions]> = { void (async () => { try { const clerkPromise = loadClerkJsScript(options); + // Load UI from CDN with version pinning from ui.version const clerkUiCtorPromise = (async () => { - await loadClerkUiScript(options); + await loadClerkUiScript({ + ...options, + clerkUiVersion: pluginOptions.ui?.version, + clerkUiUrl: pluginOptions.ui?.url, + }); if (!window.__internal_ClerkUiCtor) { throw new Error('Failed to download latest Clerk UI. Contact support@clerk.com.'); } @@ -93,7 +98,7 @@ export const clerkPlugin: Plugin<[PluginOptions]> = { } clerk.value = window.Clerk; - const loadOptions = { ...options, clerkUiCtor: clerkUiCtorPromise } as unknown as ClerkOptions; + const loadOptions = { ...options, clerkUiCtor: clerkUiCtorPromise } as ClerkOptions; await window.Clerk.load(loadOptions); loaded.value = true; From 89bb1ff653fc0662be09c23e0a213a7a12f25cf0 Mon Sep 17 00:00:00 2001 From: Jacek Date: Fri, 23 Jan 2026 15:02:11 -0600 Subject: [PATCH 05/21] chore: update changeset description --- .changeset/shiny-owls-dance.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.changeset/shiny-owls-dance.md b/.changeset/shiny-owls-dance.md index 33efe62f669..5fc9b26c323 100644 --- a/.changeset/shiny-owls-dance.md +++ b/.changeset/shiny-owls-dance.md @@ -7,9 +7,15 @@ '@clerk/shared': minor --- -Add `ui` prop to ClerkProvider for UI version metadata +Add `ui` prop to ClerkProvider for UI version pinning -The `ui` object from `@clerk/ui` is passed to ClerkProvider. When `ui.ctor` is available, it will be used for bundled UI; otherwise, the UI is loaded from CDN. +The `ui` object from `@clerk/ui` is passed to ClerkProvider to pin the UI version loaded from CDN. Each SDK decides internally whether to use the bundled constructor (`ui.ctor`) or load from CDN. + +**Breaking Change (internal):** `clerkUiCtor` is no longer exposed in `IsomorphicClerkOptions`. SDKs should use `ui` prop instead. + +**SDK Behavior:** +- Chrome Extension: Uses bundled UI (`__internal_forceBundledUI: true`) +- React/Next.js, Vue, Astro: Load from CDN with version pinning via `ui.version` Usage: ```tsx From ccac6399586068a867be512f59a205e66f83fec7 Mon Sep 17 00:00:00 2001 From: Jacek Date: Fri, 23 Jan 2026 16:20:57 -0600 Subject: [PATCH 06/21] refactor: rename clerkUiCtor to ClerkUI and ClerkUiConstructor to ClerkUIConstructor --- .changeset/shiny-owls-dance.md | 2 +- integration/templates/express-vite/src/client/main.ts | 2 +- packages/astro/src/internal/create-clerk-instance.ts | 8 ++++---- packages/astro/src/types.ts | 4 ++-- packages/clerk-js/sandbox/app.ts | 2 +- packages/clerk-js/src/core/clerk.ts | 4 ++-- packages/clerk-js/src/global.d.ts | 2 +- packages/react/src/isomorphicClerk.ts | 10 +++++----- packages/react/src/types.ts | 4 ++-- packages/shared/src/types/clerk.ts | 8 ++++---- packages/shared/src/ui/types.ts | 2 +- packages/ui/src/global.d.ts | 4 ++-- packages/ui/src/internal/index.ts | 4 ++-- .../warnAboutCustomizationWithoutPinning.test.ts | 6 +++--- .../src/utils/warnAboutCustomizationWithoutPinning.ts | 2 +- packages/vue/src/plugin.ts | 8 ++++---- 16 files changed, 36 insertions(+), 36 deletions(-) diff --git a/.changeset/shiny-owls-dance.md b/.changeset/shiny-owls-dance.md index 5fc9b26c323..7078e4174e9 100644 --- a/.changeset/shiny-owls-dance.md +++ b/.changeset/shiny-owls-dance.md @@ -11,7 +11,7 @@ Add `ui` prop to ClerkProvider for UI version pinning The `ui` object from `@clerk/ui` is passed to ClerkProvider to pin the UI version loaded from CDN. Each SDK decides internally whether to use the bundled constructor (`ui.ctor`) or load from CDN. -**Breaking Change (internal):** `clerkUiCtor` is no longer exposed in `IsomorphicClerkOptions`. SDKs should use `ui` prop instead. +**Breaking Change (internal):** `clerkUiCtor` renamed to `ClerkUI` and is no longer exposed in `IsomorphicClerkOptions`. SDKs should use `ui` prop instead. **SDK Behavior:** - Chrome Extension: Uses bundled UI (`__internal_forceBundledUI: true`) diff --git a/integration/templates/express-vite/src/client/main.ts b/integration/templates/express-vite/src/client/main.ts index 1d652826a68..1e393ec5c95 100644 --- a/integration/templates/express-vite/src/client/main.ts +++ b/integration/templates/express-vite/src/client/main.ts @@ -9,7 +9,7 @@ document.addEventListener('DOMContentLoaded', async function () { // Use bundled UI via ui.ctor await clerk.load({ ui, - clerkUiCtor: ui.ctor, + ClerkUI: ui.ctor, }); if (clerk.isSignedIn) { diff --git a/packages/astro/src/internal/create-clerk-instance.ts b/packages/astro/src/internal/create-clerk-instance.ts index 9d4ba2e7bb9..4630bb9b296 100644 --- a/packages/astro/src/internal/create-clerk-instance.ts +++ b/packages/astro/src/internal/create-clerk-instance.ts @@ -4,7 +4,7 @@ import { setClerkJsLoadingErrorPackageName, } from '@clerk/shared/loadClerkJsScript'; import type { ClerkOptions } from '@clerk/shared/types'; -import type { ClerkUiConstructor } from '@clerk/shared/ui'; +import type { ClerkUIConstructor } from '@clerk/shared/ui'; import type { Ui } from '@clerk/ui/internal'; import { $clerkStore } from '../stores/external'; @@ -40,7 +40,7 @@ async function createClerkInstanceInternal(options?: AstroC // Both functions return early if the scripts are already loaded // (e.g., via middleware-injected script tags in the HTML head). const clerkJsChunk = getClerkJsEntryChunk(options); - const clerkUiCtor = getClerkUiEntryChunk(options); + const ClerkUI = getClerkUiEntryChunk(options); await clerkJsChunk; @@ -59,7 +59,7 @@ async function createClerkInstanceInternal(options?: AstroC routerReplace: createNavigationHandler(window.history.replaceState.bind(window.history)), ...options, // Pass the clerk-ui constructor promise to clerk.load() - clerkUiCtor, + ClerkUI, } as unknown as ClerkOptions; initOptions = clerkOptions; @@ -113,7 +113,7 @@ async function getClerkJsEntryChunk(options?: AstroClerkCre */ async function getClerkUiEntryChunk( options?: AstroClerkCreateInstanceParams, -): Promise { +): Promise { // Load UI from CDN with version pinning from ui.version await loadClerkUiScript( options diff --git a/packages/astro/src/types.ts b/packages/astro/src/types.ts index 658daadcca2..5862a935c34 100644 --- a/packages/astro/src/types.ts +++ b/packages/astro/src/types.ts @@ -7,7 +7,7 @@ import type { ShowProps, Without, } from '@clerk/shared/types'; -import type { ClerkUiConstructor } from '@clerk/shared/ui'; +import type { ClerkUIConstructor } from '@clerk/shared/ui'; import type { Appearance, Ui } from '@clerk/ui/internal'; type AstroClerkUpdateOptions = Pick & { @@ -64,7 +64,7 @@ declare global { __astro_clerk_component_props: Map>>; __astro_clerk_function_props: Map>>; Clerk: BrowserClerk; - __internal_ClerkUiCtor?: ClerkUiConstructor; + __internal_ClerkUiCtor?: ClerkUIConstructor; } } diff --git a/packages/clerk-js/sandbox/app.ts b/packages/clerk-js/sandbox/app.ts index 315e0822f31..5d48ac03d31 100644 --- a/packages/clerk-js/sandbox/app.ts +++ b/packages/clerk-js/sandbox/app.ts @@ -446,7 +446,7 @@ void (async () => { ...(componentControls.clerk.getProps() ?? {}), signInUrl: '/sign-in', signUpUrl: '/sign-up', - clerkUiCtor: window.__internal_ClerkUiCtor, + ClerkUI: window.__internal_ClerkUiCtor, }); renderCurrentRoute(); updateVariables(); diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index 38a8e411478..2f7fab15e94 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -485,8 +485,8 @@ export class Clerk implements ClerkInterface { this.#options = this.#initOptions(options); // Initialize ClerkUi if it was provided - if (this.#options.clerkUiCtor) { - this.#clerkUi = Promise.resolve(this.#options.clerkUiCtor).then( + if (this.#options.ClerkUI) { + this.#clerkUi = Promise.resolve(this.#options.ClerkUI).then( ClerkUI => new ClerkUI( () => this, diff --git a/packages/clerk-js/src/global.d.ts b/packages/clerk-js/src/global.d.ts index 52a15e8dee6..d00bd29334c 100644 --- a/packages/clerk-js/src/global.d.ts +++ b/packages/clerk-js/src/global.d.ts @@ -18,7 +18,7 @@ interface Window { __internal_onBeforeSetActive: (intent?: 'sign-out') => Promise | void; __internal_onAfterSetActive: () => Promise | void; // eslint-disable-next-line @typescript-eslint/consistent-type-imports - __internal_ClerkUiCtor?: import('@clerk/shared/types').ClerkUiConstructor; + __internal_ClerkUiCtor?: import('@clerk/shared/types').ClerkUIConstructor; /** * Promise used for coordination between standalone getToken() from @clerk/shared and clerk-js. * When getToken() is called before Clerk loads, it creates this promise with __resolve/__reject callbacks. diff --git a/packages/react/src/isomorphicClerk.ts b/packages/react/src/isomorphicClerk.ts index 7e77a5d425b..6cad4025a75 100644 --- a/packages/react/src/isomorphicClerk.ts +++ b/packages/react/src/isomorphicClerk.ts @@ -59,7 +59,7 @@ import type { WaitlistResource, Without, } from '@clerk/shared/types'; -import type { ClerkUiConstructor } from '@clerk/shared/ui'; +import type { ClerkUIConstructor } from '@clerk/shared/ui'; import { handleValueOrFn } from '@clerk/shared/utils'; import { errorThrower } from './errors/errorThrower'; @@ -87,7 +87,7 @@ const SDK_METADATA = { export interface Global { Clerk?: HeadlessBrowserClerk | BrowserClerk; - __internal_ClerkUiCtor?: ClerkUiConstructor; + __internal_ClerkUiCtor?: ClerkUIConstructor; } declare const global: Global; @@ -461,12 +461,12 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk { } try { - const clerkUiCtor = this.getClerkUiEntryChunk(); + const ClerkUI = this.getClerkUiEntryChunk(); const clerk = await this.getClerkJsEntryChunk(); if (!clerk.loaded) { this.beforeLoad(clerk); - await clerk.load({ ...this.options, clerkUiCtor }); + await clerk.load({ ...this.options, ClerkUI }); } if (clerk.loaded) { this.replayInterceptedInvocations(clerk); @@ -508,7 +508,7 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk { return global.Clerk; } - private async getClerkUiEntryChunk(): Promise { + private async getClerkUiEntryChunk(): Promise { // Use bundled UI constructor if forced (e.g., Chrome Extension) and ui.ctor is available if (this.options.ui?.__internal_forceBundledUI && this.options.ui.ctor) { return this.options.ui.ctor; diff --git a/packages/react/src/types.ts b/packages/react/src/types.ts index c769d58afac..577a8ebe2b3 100644 --- a/packages/react/src/types.ts +++ b/packages/react/src/types.ts @@ -8,7 +8,7 @@ import type { SignUpRedirectOptions, TasksRedirectOptions, } from '@clerk/shared/types'; -import type { ClerkUiConstructor } from '@clerk/shared/ui'; +import type { ClerkUIConstructor } from '@clerk/shared/ui'; import type { Appearance, ExtractAppearanceType, Ui } from '@clerk/ui/internal'; import type React from 'react'; @@ -29,7 +29,7 @@ declare global { __clerk_publishable_key?: string; __clerk_proxy_url?: Clerk['proxyUrl']; __clerk_domain?: Clerk['domain']; - __internal_ClerkUiCtor?: ClerkUiConstructor; + __internal_ClerkUiCtor?: ClerkUIConstructor; } } diff --git a/packages/shared/src/types/clerk.ts b/packages/shared/src/types/clerk.ts index 27c4e364c15..0e68a14c4fc 100644 --- a/packages/shared/src/types/clerk.ts +++ b/packages/shared/src/types/clerk.ts @@ -1,6 +1,6 @@ import type { ClerkGlobalHookError } from '@/errors/globalHookError'; -import type { ClerkUiConstructor } from '../ui/types'; +import type { ClerkUIConstructor } from '../ui/types'; import type { APIKeysNamespace } from './apiKeys'; import type { BillingCheckoutResource, @@ -1086,7 +1086,7 @@ export type ClerkOptions = ClerkOptionsNavigation & * @internal * Clerk UI entrypoint. Used internally by SDKs to pass the UI constructor to clerk-js. */ - clerkUiCtor?: ClerkUiConstructor | Promise; + ClerkUI?: ClerkUIConstructor | Promise; /** * 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/account-portal/overview) pages. */ @@ -2406,7 +2406,7 @@ export type ClerkProp = | undefined | null; -export type IsomorphicClerkOptions = Without & { +export type IsomorphicClerkOptions = Without & { Clerk?: ClerkProp; /** * The URL that `@clerk/clerk-js` should be hot-loaded from. @@ -2444,7 +2444,7 @@ export type IsomorphicClerkOptions = Without; + ctor?: ClerkUIConstructor | Promise; /** * @internal * Force using the bundled UI constructor instead of loading from CDN. diff --git a/packages/shared/src/ui/types.ts b/packages/shared/src/ui/types.ts index 8b7e5ec4d73..6a624048842 100644 --- a/packages/shared/src/ui/types.ts +++ b/packages/shared/src/ui/types.ts @@ -30,7 +30,7 @@ export interface ClerkUiInstance { } // Constructor type -export interface ClerkUiConstructor { +export interface ClerkUIConstructor { new ( getClerk: () => Clerk, getEnvironment: () => EnvironmentResource | null | undefined, diff --git a/packages/ui/src/global.d.ts b/packages/ui/src/global.d.ts index faed7431895..4f762843224 100644 --- a/packages/ui/src/global.d.ts +++ b/packages/ui/src/global.d.ts @@ -1,5 +1,5 @@ import type { Clerk } from '@clerk/shared/types'; -import type { ClerkUiConstructor } from '@clerk/shared/ui'; +import type { ClerkUIConstructor } from '@clerk/shared/ui'; declare module '*.svg' { const value: React.FC>; @@ -20,6 +20,6 @@ declare global { * Unstable API for accessing UI components separately from clerk-js. * This is injected by the @clerk/ui browser bundle. */ - __internal_ClerkUiCtor?: ClerkUiConstructor; + __internal_ClerkUiCtor?: ClerkUIConstructor; } } diff --git a/packages/ui/src/internal/index.ts b/packages/ui/src/internal/index.ts index e8247a86654..5c401b75255 100644 --- a/packages/ui/src/internal/index.ts +++ b/packages/ui/src/internal/index.ts @@ -1,4 +1,4 @@ -import type { ClerkUiConstructor } from '@clerk/shared/ui'; +import type { ClerkUIConstructor } from '@clerk/shared/ui'; import type { Appearance } from './appearance'; @@ -32,7 +32,7 @@ export type Ui = Tagged< * The Clerk UI constructor. When provided, this will be used instead of * loading the UI from CDN. This is useful for bundling the UI with your app. */ - ctor?: ClerkUiConstructor | Promise; + ctor?: ClerkUIConstructor | Promise; /** * @internal * Force using the bundled UI constructor instead of loading from CDN. diff --git a/packages/ui/src/utils/__tests__/warnAboutCustomizationWithoutPinning.test.ts b/packages/ui/src/utils/__tests__/warnAboutCustomizationWithoutPinning.test.ts index 63f01603f13..92a63c7b681 100644 --- a/packages/ui/src/utils/__tests__/warnAboutCustomizationWithoutPinning.test.ts +++ b/packages/ui/src/utils/__tests__/warnAboutCustomizationWithoutPinning.test.ts @@ -52,11 +52,11 @@ describe('warnAboutCustomizationWithoutPinning', () => { expect(message).toContain('elements.card "& > div"'); }); - test('still warns when clerkUiCtor is provided without ui (CDN scenario)', () => { - // clerkUiCtor is always set when loading from CDN, but ui is only set + test('still warns when ClerkUI is provided without ui (CDN scenario)', () => { + // ClerkUI is always set when loading from CDN, but ui is only set // when the user explicitly imports @clerk/ui warnAboutCustomizationWithoutPinning({ - clerkUiCtor: class MockClerkUi {} as any, + ClerkUI: class MockClerkUi {} as any, appearance: { elements: { card: { '& > div': { color: 'red' } } }, }, diff --git a/packages/ui/src/utils/warnAboutCustomizationWithoutPinning.ts b/packages/ui/src/utils/warnAboutCustomizationWithoutPinning.ts index 8ed7c9cf9b7..dbab1ee8d08 100644 --- a/packages/ui/src/utils/warnAboutCustomizationWithoutPinning.ts +++ b/packages/ui/src/utils/warnAboutCustomizationWithoutPinning.ts @@ -129,7 +129,7 @@ function collectElementPatterns(elements: Record): string[] { * If the user has explicitly imported @clerk/ui and passed it via the `ui` option, * they have "pinned" their version and no warning is shown. * - * Note: We check `options.ui` (not `options.clerkUiCtor`) because clerkUiCtor is + * Note: We check `options.ui` (not `options.ClerkUI`) because ClerkUI is * always set when loading from CDN via window.__internal_ClerkUiCtor. */ export function warnAboutCustomizationWithoutPinning(options?: ClerkOptions): void { diff --git a/packages/vue/src/plugin.ts b/packages/vue/src/plugin.ts index 0ada513115a..34f6baf1261 100644 --- a/packages/vue/src/plugin.ts +++ b/packages/vue/src/plugin.ts @@ -11,7 +11,7 @@ import type { Resources, Without, } from '@clerk/shared/types'; -import type { ClerkUiConstructor } from '@clerk/shared/ui'; +import type { ClerkUIConstructor } from '@clerk/shared/ui'; import type { Appearance, Ui } from '@clerk/ui/internal'; import type { Plugin } from 'vue'; import { computed, ref, shallowRef, triggerRef } from 'vue'; @@ -19,7 +19,7 @@ import { computed, ref, shallowRef, triggerRef } from 'vue'; import { ClerkInjectionKey } from './keys'; declare global { interface Window { - __internal_ClerkUiCtor?: ClerkUiConstructor; + __internal_ClerkUiCtor?: ClerkUIConstructor; } } @@ -79,7 +79,7 @@ export const clerkPlugin: Plugin<[PluginOptions]> = { try { const clerkPromise = loadClerkJsScript(options); // Load UI from CDN with version pinning from ui.version - const clerkUiCtorPromise = (async () => { + const ClerkUIPromise = (async () => { await loadClerkUiScript({ ...options, clerkUiVersion: pluginOptions.ui?.version, @@ -98,7 +98,7 @@ export const clerkPlugin: Plugin<[PluginOptions]> = { } clerk.value = window.Clerk; - const loadOptions = { ...options, clerkUiCtor: clerkUiCtorPromise } as ClerkOptions; + const loadOptions = { ...options, ClerkUI: ClerkUIPromise } as ClerkOptions; await window.Clerk.load(loadOptions); loaded.value = true; From cf2a3547f2d238965cd83187267557ac1bed53d6 Mon Sep 17 00:00:00 2001 From: Jacek Date: Fri, 23 Jan 2026 16:28:33 -0600 Subject: [PATCH 07/21] refactor: rename __internal_ClerkUiCtor to __internal_ClerkUICtor --- .../src/internal/create-clerk-instance.ts | 4 ++-- packages/astro/src/types.ts | 2 +- packages/clerk-js/sandbox/app.ts | 2 +- packages/clerk-js/sandbox/template.html | 2 +- packages/clerk-js/src/global.d.ts | 2 +- .../src/__tests__/isomorphicClerk.test.ts | 4 ++-- packages/react/src/isomorphicClerk.ts | 6 +++--- packages/react/src/types.ts | 2 +- .../src/__tests__/loadClerkJsScript.spec.ts | 18 +++++++++--------- packages/shared/src/loadClerkJsScript.ts | 4 ++-- packages/ui/src/global.d.ts | 2 +- packages/ui/src/index.browser.ts | 4 ++-- packages/ui/src/index.legacy.browser.ts | 4 ++-- .../warnAboutCustomizationWithoutPinning.ts | 2 +- packages/vue/src/plugin.ts | 6 +++--- 15 files changed, 32 insertions(+), 32 deletions(-) diff --git a/packages/astro/src/internal/create-clerk-instance.ts b/packages/astro/src/internal/create-clerk-instance.ts index 4630bb9b296..9791c68c8d7 100644 --- a/packages/astro/src/internal/create-clerk-instance.ts +++ b/packages/astro/src/internal/create-clerk-instance.ts @@ -125,11 +125,11 @@ async function getClerkUiEntryChunk( : undefined, ); - if (!window.__internal_ClerkUiCtor) { + if (!window.__internal_ClerkUICtor) { throw new Error('Failed to download latest Clerk UI. Contact support@clerk.com.'); } - return window.__internal_ClerkUiCtor; + return window.__internal_ClerkUICtor; } export { createClerkInstance, updateClerkOptions }; diff --git a/packages/astro/src/types.ts b/packages/astro/src/types.ts index 5862a935c34..65aceea3981 100644 --- a/packages/astro/src/types.ts +++ b/packages/astro/src/types.ts @@ -64,7 +64,7 @@ declare global { __astro_clerk_component_props: Map>>; __astro_clerk_function_props: Map>>; Clerk: BrowserClerk; - __internal_ClerkUiCtor?: ClerkUIConstructor; + __internal_ClerkUICtor?: ClerkUIConstructor; } } diff --git a/packages/clerk-js/sandbox/app.ts b/packages/clerk-js/sandbox/app.ts index 5d48ac03d31..dcc7c75c016 100644 --- a/packages/clerk-js/sandbox/app.ts +++ b/packages/clerk-js/sandbox/app.ts @@ -446,7 +446,7 @@ void (async () => { ...(componentControls.clerk.getProps() ?? {}), signInUrl: '/sign-in', signUpUrl: '/sign-up', - ClerkUI: window.__internal_ClerkUiCtor, + ClerkUI: window.__internal_ClerkUICtor, }); renderCurrentRoute(); updateVariables(); diff --git a/packages/clerk-js/sandbox/template.html b/packages/clerk-js/sandbox/template.html index 924e159fcd2..4983019bdf9 100644 --- a/packages/clerk-js/sandbox/template.html +++ b/packages/clerk-js/sandbox/template.html @@ -355,7 +355,7 @@ - +