Skip to content

Commit

Permalink
feat(clerk-js): Render @clerk/ui (#4114)
Browse files Browse the repository at this point in the history
Co-authored-by: Alex Carpenter <[email protected]>
Co-authored-by: Dylan Staley <[email protected]>
Co-authored-by: Nikos Douvlis <[email protected]>
Co-authored-by: panteliselef <[email protected]>
Co-authored-by: Jacek Radko <[email protected]>
  • Loading branch information
6 people authored Oct 17, 2024
1 parent 45f3ae5 commit 3b50b67
Show file tree
Hide file tree
Showing 45 changed files with 606 additions and 107 deletions.
5 changes: 5 additions & 0 deletions .changeset/late-kiwis-warn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@clerk/elements": patch
---

Remove @clerk/clerk-react as a dev depedency. Move @clerk/shared to depedencies (previously devDepedencies).
9 changes: 9 additions & 0 deletions .changeset/nervous-guests-guess.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@clerk/clerk-js": minor
"@clerk/elements": minor
"@clerk/nextjs": minor
"@clerk/shared": minor
"@clerk/types": minor
---

Add experimental support for new UI components
5 changes: 5 additions & 0 deletions .changeset/unlucky-steaks-protect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@clerk/types": patch
---

Fix `SignInProps`/`SignUpProps` `__experimental` type to allow for arbitrary properties
17 changes: 14 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions packages/clerk-js/bundlewatch.config.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"files": [
{ "path": "./dist/clerk.browser.js", "maxSize": "65.5kB" },
{ "path": "./dist/clerk.headless.js", "maxSize": "43kB" },
{ "path": "./dist/clerk.browser.js", "maxSize": "68kB" },
{ "path": "./dist/clerk.headless.js", "maxSize": "44kB" },
{ "path": "./dist/ui-common*.js", "maxSize": "86KB" },
{ "path": "./dist/vendors*.js", "maxSize": "70KB" },
{ "path": "./dist/coinbase*.js", "maxSize": "58KB" },
Expand All @@ -15,6 +15,6 @@
{ "path": "./dist/userbutton*.js", "maxSize": "5KB" },
{ "path": "./dist/userprofile*.js", "maxSize": "15KB" },
{ "path": "./dist/userverification*.js", "maxSize": "5KB" },
{ "path": "./dist/onetap*.js", "maxSize": "1KB" }
{ "path": "./dist/onetap*.js", "maxSize": "2KB" }
]
}
4 changes: 0 additions & 4 deletions packages/clerk-js/global.d.ts

This file was deleted.

1 change: 1 addition & 0 deletions packages/clerk-js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"@clerk/localizations": "3.3.0",
"@clerk/shared": "2.9.2",
"@clerk/types": "4.26.0",
"@clerk/ui": "0.1.9",
"@coinbase/wallet-sdk": "4.0.4",
"@emotion/cache": "11.11.0",
"@emotion/react": "11.11.1",
Expand Down
59 changes: 41 additions & 18 deletions packages/clerk-js/src/core/clerk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import type {
HandleOAuthCallbackParams,
InstanceType,
ListenerCallback,
LoadedClerk,
NavigateOptions,
OrganizationListProps,
OrganizationProfileProps,
Expand Down Expand Up @@ -64,6 +65,7 @@ import type {
} from '@clerk/types';

import type { MountComponentRenderer } from '../ui/Components';
import { UI } from '../ui/new';
import {
ALLOWED_PROTOCOLS,
buildURL,
Expand Down Expand Up @@ -145,7 +147,12 @@ const defaultOptions: ClerkOptions = {
signUpForceRedirectUrl: undefined,
};

function clerkIsLoaded(clerk: ClerkInterface): clerk is LoadedClerk {
return !!clerk.client;
}

export class Clerk implements ClerkInterface {
public __experimental_ui?: UI;
public static mountComponentRenderer?: MountComponentRenderer;

public static version: string = __PKG_VERSION__;
Expand Down Expand Up @@ -317,6 +324,14 @@ export class Clerk implements ClerkInterface {
} else {
this.#loaded = await this.#loadInNonStandardBrowser();
}

if (clerkIsLoaded(this)) {
this.__experimental_ui = new UI({
router: this.#options.__experimental_router,
clerk: this,
options: this.#options,
});
}
};

public signOut: SignOut = async (callbackOrOptions?: SignOutCallback | SignOutOptions, options?: SignOutOptions) => {
Expand Down Expand Up @@ -495,15 +510,19 @@ export class Clerk implements ClerkInterface {
};

public mountSignIn = (node: HTMLDivElement, props?: SignInProps): void => {
this.assertComponentsReady(this.#componentControls);
void this.#componentControls.ensureMounted({ preloadHint: 'SignIn' }).then(controls =>
controls.mountComponent({
name: 'SignIn',
appearanceKey: 'signIn',
node,
props,
}),
);
if (props && props.__experimental?.newComponents && this.__experimental_ui) {
this.__experimental_ui.mount('SignIn', node, props);
} else {
this.assertComponentsReady(this.#componentControls);
void this.#componentControls.ensureMounted({ preloadHint: 'SignIn' }).then(controls =>
controls.mountComponent({
name: 'SignIn',
appearanceKey: 'signIn',
node,
props,
}),
);
}
this.telemetry?.record(eventPrebuiltComponentMounted('SignIn', props));
};

Expand All @@ -517,15 +536,19 @@ export class Clerk implements ClerkInterface {
};

public mountSignUp = (node: HTMLDivElement, props?: SignUpProps): void => {
this.assertComponentsReady(this.#componentControls);
void this.#componentControls.ensureMounted({ preloadHint: 'SignUp' }).then(controls =>
controls.mountComponent({
name: 'SignUp',
appearanceKey: 'signUp',
node,
props,
}),
);
if (props && props.__experimental?.newComponents && this.__experimental_ui) {
this.__experimental_ui.mount('SignUp', node, props);
} else {
this.assertComponentsReady(this.#componentControls);
void this.#componentControls.ensureMounted({ preloadHint: 'SignUp' }).then(controls =>
controls.mountComponent({
name: 'SignUp',
appearanceKey: 'signUp',
node,
props,
}),
);
}
this.telemetry?.record(eventPrebuiltComponentMounted('SignUp', props));
};

Expand Down
4 changes: 4 additions & 0 deletions packages/clerk-js/src/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
declare module '@clerk/ui/styles.css' {
const content: string;
export default content;
}
98 changes: 98 additions & 0 deletions packages/clerk-js/src/ui/new/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { createDeferredPromise } from '@clerk/shared';
import type { ClerkHostRouter } from '@clerk/shared/router';
import type { ClerkOptions, LoadedClerk } from '@clerk/types';

import type { init } from './renderer';
import type { ClerkNewComponents, ComponentDefinition } from './types';

function assertRouter(router: ClerkHostRouter | undefined): asserts router is ClerkHostRouter {
if (!router) {
throw new Error(`Clerk: Attempted to use functionality that requires the "router" option to be provided to Clerk.`);
}
}

export class UI {
router?: ClerkHostRouter;
clerk: LoadedClerk;
options: ClerkOptions;
componentRegistry = new Map<string, ComponentDefinition>();

#rendererPromise?: ReturnType<typeof createDeferredPromise>;
#renderer?: ReturnType<typeof init>;

constructor({
router,
clerk,
options,
}: {
router: ClerkHostRouter | undefined;
clerk: LoadedClerk;
options: ClerkOptions;
}) {
this.router = router;
this.clerk = clerk;
this.options = options;

// register components
this.register('SignIn', {
type: 'component',
load: () =>
import(/* webpackChunkName: "rebuild--sign-in" */ '@clerk/ui/sign-in').then(({ SignIn }) => ({
default: SignIn,
})),
});
this.register('SignUp', {
type: 'component',
load: () =>
import(/* webpackChunkName: "rebuild--sign-up" */ '@clerk/ui/sign-up').then(({ SignUp }) => ({
default: SignUp,
})),
});
}

// Mount a component from the registry
mount<C extends keyof ClerkNewComponents>(componentName: C, node: HTMLElement, props: ClerkNewComponents[C]): void {
const component = this.componentRegistry.get(componentName);
if (!component) {
throw new Error(`clerk/ui: Unable to find component definition for ${componentName}`);
}

// immediately start loading the component
component.load();

this.renderer()
.then(() => {
this.#renderer?.mount(this.#renderer.createElementFromComponentDefinition(component), props, node);
})
.catch(err => {
console.error(`clerk/ui: Error mounting component ${componentName}:`, err);
});
}

unmount(node: HTMLElement) {
this.#renderer?.unmount(node);
}

// Registers a component for rendering later
register(componentName: string, componentDefinition: ComponentDefinition) {
this.componentRegistry.set(componentName, componentDefinition);
}

renderer() {
if (this.#rendererPromise) {
return this.#rendererPromise.promise;
}

this.#rendererPromise = createDeferredPromise();

import('./renderer').then(({ init, wrapperInit }) => {
assertRouter(this.router);
this.#renderer = init({
wrapper: wrapperInit({ clerk: this.clerk, options: this.options, router: this.router }),
});
this.#rendererPromise?.resolve();
});

return this.#rendererPromise.promise;
}
}
Loading

0 comments on commit 3b50b67

Please sign in to comment.