Skip to content

Commit 3b50b67

Browse files
brkalowalexcarpenterdstaleynikosdouvlispanteliselef
authored
feat(clerk-js): Render @clerk/ui (#4114)
Co-authored-by: Alex Carpenter <im.alexcarpenter@gmail.com> Co-authored-by: Dylan Staley <88163+dstaley@users.noreply.github.com> Co-authored-by: Nikos Douvlis <nikosdouvlis@gmail.com> Co-authored-by: panteliselef <panteliselef@outlook.com> Co-authored-by: Jacek Radko <jacekradko@gmail.com>
1 parent 45f3ae5 commit 3b50b67

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+606
-107
lines changed

.changeset/late-kiwis-warn.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@clerk/elements": patch
3+
---
4+
5+
Remove @clerk/clerk-react as a dev depedency. Move @clerk/shared to depedencies (previously devDepedencies).

.changeset/nervous-guests-guess.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"@clerk/clerk-js": minor
3+
"@clerk/elements": minor
4+
"@clerk/nextjs": minor
5+
"@clerk/shared": minor
6+
"@clerk/types": minor
7+
---
8+
9+
Add experimental support for new UI components
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@clerk/types": patch
3+
---
4+
5+
Fix `SignInProps`/`SignUpProps` `__experimental` type to allow for arbitrary properties

package-lock.json

Lines changed: 14 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/clerk-js/bundlewatch.config.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"files": [
3-
{ "path": "./dist/clerk.browser.js", "maxSize": "65.5kB" },
4-
{ "path": "./dist/clerk.headless.js", "maxSize": "43kB" },
3+
{ "path": "./dist/clerk.browser.js", "maxSize": "68kB" },
4+
{ "path": "./dist/clerk.headless.js", "maxSize": "44kB" },
55
{ "path": "./dist/ui-common*.js", "maxSize": "86KB" },
66
{ "path": "./dist/vendors*.js", "maxSize": "70KB" },
77
{ "path": "./dist/coinbase*.js", "maxSize": "58KB" },
@@ -15,6 +15,6 @@
1515
{ "path": "./dist/userbutton*.js", "maxSize": "5KB" },
1616
{ "path": "./dist/userprofile*.js", "maxSize": "15KB" },
1717
{ "path": "./dist/userverification*.js", "maxSize": "5KB" },
18-
{ "path": "./dist/onetap*.js", "maxSize": "1KB" }
18+
{ "path": "./dist/onetap*.js", "maxSize": "2KB" }
1919
]
2020
}

packages/clerk-js/global.d.ts

Lines changed: 0 additions & 4 deletions
This file was deleted.

packages/clerk-js/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"@clerk/localizations": "3.3.0",
5454
"@clerk/shared": "2.9.2",
5555
"@clerk/types": "4.26.0",
56+
"@clerk/ui": "0.1.9",
5657
"@coinbase/wallet-sdk": "4.0.4",
5758
"@emotion/cache": "11.11.0",
5859
"@emotion/react": "11.11.1",

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

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import type {
3737
HandleOAuthCallbackParams,
3838
InstanceType,
3939
ListenerCallback,
40+
LoadedClerk,
4041
NavigateOptions,
4142
OrganizationListProps,
4243
OrganizationProfileProps,
@@ -64,6 +65,7 @@ import type {
6465
} from '@clerk/types';
6566

6667
import type { MountComponentRenderer } from '../ui/Components';
68+
import { UI } from '../ui/new';
6769
import {
6870
ALLOWED_PROTOCOLS,
6971
buildURL,
@@ -145,7 +147,12 @@ const defaultOptions: ClerkOptions = {
145147
signUpForceRedirectUrl: undefined,
146148
};
147149

150+
function clerkIsLoaded(clerk: ClerkInterface): clerk is LoadedClerk {
151+
return !!clerk.client;
152+
}
153+
148154
export class Clerk implements ClerkInterface {
155+
public __experimental_ui?: UI;
149156
public static mountComponentRenderer?: MountComponentRenderer;
150157

151158
public static version: string = __PKG_VERSION__;
@@ -317,6 +324,14 @@ export class Clerk implements ClerkInterface {
317324
} else {
318325
this.#loaded = await this.#loadInNonStandardBrowser();
319326
}
327+
328+
if (clerkIsLoaded(this)) {
329+
this.__experimental_ui = new UI({
330+
router: this.#options.__experimental_router,
331+
clerk: this,
332+
options: this.#options,
333+
});
334+
}
320335
};
321336

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

497512
public mountSignIn = (node: HTMLDivElement, props?: SignInProps): void => {
498-
this.assertComponentsReady(this.#componentControls);
499-
void this.#componentControls.ensureMounted({ preloadHint: 'SignIn' }).then(controls =>
500-
controls.mountComponent({
501-
name: 'SignIn',
502-
appearanceKey: 'signIn',
503-
node,
504-
props,
505-
}),
506-
);
513+
if (props && props.__experimental?.newComponents && this.__experimental_ui) {
514+
this.__experimental_ui.mount('SignIn', node, props);
515+
} else {
516+
this.assertComponentsReady(this.#componentControls);
517+
void this.#componentControls.ensureMounted({ preloadHint: 'SignIn' }).then(controls =>
518+
controls.mountComponent({
519+
name: 'SignIn',
520+
appearanceKey: 'signIn',
521+
node,
522+
props,
523+
}),
524+
);
525+
}
507526
this.telemetry?.record(eventPrebuiltComponentMounted('SignIn', props));
508527
};
509528

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

519538
public mountSignUp = (node: HTMLDivElement, props?: SignUpProps): void => {
520-
this.assertComponentsReady(this.#componentControls);
521-
void this.#componentControls.ensureMounted({ preloadHint: 'SignUp' }).then(controls =>
522-
controls.mountComponent({
523-
name: 'SignUp',
524-
appearanceKey: 'signUp',
525-
node,
526-
props,
527-
}),
528-
);
539+
if (props && props.__experimental?.newComponents && this.__experimental_ui) {
540+
this.__experimental_ui.mount('SignUp', node, props);
541+
} else {
542+
this.assertComponentsReady(this.#componentControls);
543+
void this.#componentControls.ensureMounted({ preloadHint: 'SignUp' }).then(controls =>
544+
controls.mountComponent({
545+
name: 'SignUp',
546+
appearanceKey: 'signUp',
547+
node,
548+
props,
549+
}),
550+
);
551+
}
529552
this.telemetry?.record(eventPrebuiltComponentMounted('SignUp', props));
530553
};
531554

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
declare module '@clerk/ui/styles.css' {
2+
const content: string;
3+
export default content;
4+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { createDeferredPromise } from '@clerk/shared';
2+
import type { ClerkHostRouter } from '@clerk/shared/router';
3+
import type { ClerkOptions, LoadedClerk } from '@clerk/types';
4+
5+
import type { init } from './renderer';
6+
import type { ClerkNewComponents, ComponentDefinition } from './types';
7+
8+
function assertRouter(router: ClerkHostRouter | undefined): asserts router is ClerkHostRouter {
9+
if (!router) {
10+
throw new Error(`Clerk: Attempted to use functionality that requires the "router" option to be provided to Clerk.`);
11+
}
12+
}
13+
14+
export class UI {
15+
router?: ClerkHostRouter;
16+
clerk: LoadedClerk;
17+
options: ClerkOptions;
18+
componentRegistry = new Map<string, ComponentDefinition>();
19+
20+
#rendererPromise?: ReturnType<typeof createDeferredPromise>;
21+
#renderer?: ReturnType<typeof init>;
22+
23+
constructor({
24+
router,
25+
clerk,
26+
options,
27+
}: {
28+
router: ClerkHostRouter | undefined;
29+
clerk: LoadedClerk;
30+
options: ClerkOptions;
31+
}) {
32+
this.router = router;
33+
this.clerk = clerk;
34+
this.options = options;
35+
36+
// register components
37+
this.register('SignIn', {
38+
type: 'component',
39+
load: () =>
40+
import(/* webpackChunkName: "rebuild--sign-in" */ '@clerk/ui/sign-in').then(({ SignIn }) => ({
41+
default: SignIn,
42+
})),
43+
});
44+
this.register('SignUp', {
45+
type: 'component',
46+
load: () =>
47+
import(/* webpackChunkName: "rebuild--sign-up" */ '@clerk/ui/sign-up').then(({ SignUp }) => ({
48+
default: SignUp,
49+
})),
50+
});
51+
}
52+
53+
// Mount a component from the registry
54+
mount<C extends keyof ClerkNewComponents>(componentName: C, node: HTMLElement, props: ClerkNewComponents[C]): void {
55+
const component = this.componentRegistry.get(componentName);
56+
if (!component) {
57+
throw new Error(`clerk/ui: Unable to find component definition for ${componentName}`);
58+
}
59+
60+
// immediately start loading the component
61+
component.load();
62+
63+
this.renderer()
64+
.then(() => {
65+
this.#renderer?.mount(this.#renderer.createElementFromComponentDefinition(component), props, node);
66+
})
67+
.catch(err => {
68+
console.error(`clerk/ui: Error mounting component ${componentName}:`, err);
69+
});
70+
}
71+
72+
unmount(node: HTMLElement) {
73+
this.#renderer?.unmount(node);
74+
}
75+
76+
// Registers a component for rendering later
77+
register(componentName: string, componentDefinition: ComponentDefinition) {
78+
this.componentRegistry.set(componentName, componentDefinition);
79+
}
80+
81+
renderer() {
82+
if (this.#rendererPromise) {
83+
return this.#rendererPromise.promise;
84+
}
85+
86+
this.#rendererPromise = createDeferredPromise();
87+
88+
import('./renderer').then(({ init, wrapperInit }) => {
89+
assertRouter(this.router);
90+
this.#renderer = init({
91+
wrapper: wrapperInit({ clerk: this.clerk, options: this.options, router: this.router }),
92+
});
93+
this.#rendererPromise?.resolve();
94+
});
95+
96+
return this.#rendererPromise.promise;
97+
}
98+
}

0 commit comments

Comments
 (0)