feat(vue): Introduce Vue SDK - #4461
Conversation
🦋 Changeset detectedLatest commit: b443c40 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
c47245f to
bbb3a0d
Compare
There was a problem hiding this comment.
This file contains all Clerk Control components that is a 1:1 equivalent of the React control components. They can be used like this
<script setup>
import { SignedIn } from '@clerk/vue'
</script>
<template>
<SignedIn>You are signed in</SignedIn>
</template>There was a problem hiding this comment.
This file contains all Clerk UI components that is a 1:1 equivalent of the React UI components. They can be used like this:
<script setup>
import { UserProfile } from '@clerk/vue'
</script>
<template>
<UserProfile />
</template>| autoPropsPlugin({ | ||
| include: ['**/*.ts'], | ||
| }) as EsbuildPlugin, |
There was a problem hiding this comment.
Instead of redeclaring all props for all of available components, this plugin will generate a runtime prop using the type prop.
So this component:
will turn into this when built:
So without this plugin, we will have to manually add runtime props to each component, and make sure they will always match.
|
|
||
| import type { VueClerkInjectionKeyType } from './types'; | ||
|
|
||
| export const ClerkInjectionKey = Symbol('clerk') as InjectionKey<VueClerkInjectionKeyType>; |
7a415dd to
edb5a4e
Compare
chore(vue): add initial setup, components and composables chore(vue): Update build config to add runtime props chore(vue): Add license and initial README chore(vue): Fix incorrect mounting chore(vue): Make sure Clerk is loaded for useAuth functions chore(vue): Fix imports chore(vue): add basic JSDoc chore(vue): add JSDoc to composables chore(vue): Add unstyled buttons chore(vue): Add reusable button handler for unstyled components chore(vue): Prevent hydration errors on UI component mount chore(vue): Improve clerk ref reactivity chore(vue): Add changeset test(vue): Add unstyled components unit tests chore(vue): Move auto prop generator to dev deps test(vue): Test sign out button props test(vue): Assert single child for unstyled components chore(vue): Use built-in error thrower for errors chore(vue): Add AuthenticateWithMetamaskButton unstyled component chore(vue): Add autoprops plugin comment chore(vue): Simplify autoprops comment chore(vue): Simplify autoprops comment chore(vue): use named export for user-event chore(vue): sync dependencies chore(vue): make unstyled component reusable functions close to react test(vue): Switch to vitest for unit tests chore(vue): consistent render functions ci(vue): Add vue test in workflow fix(vue): Try and fix rspack error fix(vue): Export conditions chore(vue): Drop cjs variant chore(vue): Use built-in in-browser function chore(vue): Throw an error on clerk-js load fail chore(vue): Move injection key to a separate file chore(vue): Reinstall deps chore(vue): Use Symbol as injection key chore(vue): type fixes chore(vue): Format composable JSDocs
53879bc to
fead36b
Compare
| h(Portal, { | ||
| mount: clerk.value?.mountUserProfile, | ||
| unmount: clerk.value?.unmountUserProfile, | ||
| updateProps: (clerk.value as any)?.__unstable__updateProps, |
There was a problem hiding this comment.
❓ Why are we using as any here?
There was a problem hiding this comment.
__unstable__updateProps is an internal unstable property and we didn't add types yet, just like what we have in the React SDK
| const hasActiveSessions = clientCtx.value?.activeSessions && clientCtx.value.activeSessions.length > 0; | ||
|
|
||
| if (sessionCtx.value === null && hasActiveSessions) { | ||
| void clerk.value.redirectToAfterSignOut(); | ||
| } else { | ||
| void clerk.value.redirectToSignIn(props); | ||
| } |
There was a problem hiding this comment.
this seems like a good candidate to abstract into clerk-js 👀
There was a problem hiding this comment.
Added a ticket that will address shared types and this 🫡
| watchEffect(() => { | ||
| if (!clerk.value) { | ||
| return; | ||
| } |
There was a problem hiding this comment.
Seeing lots of this, potentially something we could abstract. Not sure if this is allowed in vue land:
watchEffectWithClerk(clerk => {
void clerk.value.redirectToSignUp(props);
});There was a problem hiding this comment.
Good catch. That should be valid in Vue land and should be simpler to write and less boilerplate.
I was also thinking of having something like withClerk HOC:
export const RedirectToSignUp = withClerk(
defineComponent(({ clerk, ...props }: WithClerkProp<SignUpRedirectOptions>) => {
onMounted(() => {
void props.clerk.redirectToSignUp(props);
});
return () => null;
}),
)this gives the same mental model as what we have in the React SDK but requires slightly more setup code 🤔
There was a problem hiding this comment.
Yeah, watchEffectWithClerk is growing on me. Let's do it, less boilerplate 🫡
function onClerkLoaded(callback) {
const clerk = useClerk();
watchEffect(() => {
if (!clerk.value?.loaded) {
return
}
callback(clerk.value)
})
}| export type ProtectProps = | ||
| | { | ||
| condition?: never; | ||
| role: OrganizationCustomRoleKey; | ||
| permission?: never; | ||
| } | ||
| | { | ||
| condition?: never; | ||
| role?: never; | ||
| permission: OrganizationCustomPermissionKey; | ||
| } | ||
| | { | ||
| condition: (has: CheckAuthorizationWithCustomPermissions) => boolean; | ||
| role?: never; | ||
| permission?: never; | ||
| } | ||
| | { | ||
| condition?: never; | ||
| role?: never; | ||
| permission?: never; | ||
| }; |
There was a problem hiding this comment.
Do we hae a shared type for this? Can we make it shared?
There was a problem hiding this comment.
Replied here. Astro will benefit from it too.
| }; | ||
| } | ||
|
|
||
| type UseAuthReturn = |
There was a problem hiding this comment.
This should be moved to a shared type so they don't drift
| * </div> | ||
| * </template> | ||
| */ | ||
| export const useAuth: UseAuth = () => { |
There was a problem hiding this comment.
more candidates for shared helpers here to avoid drift 👀
| import type { ToComputedRefs } from './utils'; | ||
| import { toComputedRefs } from './utils'; | ||
|
|
||
| type UseOrganizationReturn = |
There was a problem hiding this comment.
I think it's important we move these hook return types to some shared spot so we ensure consistent interfaces across our SDKs. It doesn't necessarily need to be done as part of this project, but something we should try and tackle soon 👍
There was a problem hiding this comment.
Regarding the type suggestions, I agree and let's do it in a separate ticket. I can help with this after completing the Vue project 🫡. The Astro project will also benefit from these shared types.
Co-authored-by: Bryce Kalow <bryce@clerk.dev>
Co-authored-by: Bryce Kalow <bryce@clerk.dev>
| export const useClerkLoaded = (callback: (clerk: LoadedClerk) => void) => { | ||
| const clerk = useClerk(); | ||
|
|
||
| watch( | ||
| clerk, | ||
| unwrappedClerk => { | ||
| if (!unwrappedClerk?.loaded) { | ||
| return; | ||
| } | ||
|
|
||
| callback(unwrappedClerk as LoadedClerk); | ||
| }, | ||
| { immediate: true }, | ||
| ); | ||
| }; |
There was a problem hiding this comment.
This composable is used inside the control components where we want to execute a Clerk method when Clerk is loaded.
We're using a watch here instead of watchEffect to react to a specific state, instead of watching every reactivate state inside the callback
| import { useClerkContext } from '../composables/useClerkContext'; | ||
| import { useClerkLoaded } from '../utils/useClerkLoaded'; | ||
|
|
||
| export const SignedIn = defineComponent((_, { slots }) => { |
There was a problem hiding this comment.
Would users benefit from us adding JSDoc comments to these exports or wouldn't it be shown in ones IDE?
There was a problem hiding this comment.
Why would it only show up on the import? I guess typically you'd expect this to exist during usage
There was a problem hiding this comment.
Let's tackle this in a separate ticket 🫡
| * low-level access to Clerk's functionality, enabling custom authentication UI and flows. | ||
| * | ||
| * @example | ||
| * A simple example: |
There was a problem hiding this comment.
We can remove the "A simple example" from the @example blocks (also in other files of this PR) because the tag will already mark it as such + the word "simple" should probably be avoided as for some folks it might indeed not be simple (and thus discouraging)
|
|
||
| if (!ctx) { | ||
| throw new Error( | ||
| 'This component/composable can only be used when the Vue plugin is installed. Learn more: https://clerk.com/docs/components/clerk-provider', |
There was a problem hiding this comment.
Shouldn't this link to the installation docs of the Clerk Vue SDK instead?
There was a problem hiding this comment.
Updated to add a quickstart link to Vue SDK (docs to follow)
Co-authored-by: Bryce Kalow <bryce@clerk.dev>




Description
This PR introduces an experimental version of Vue SDK! It ports the original code of
vue-clerkinside this monorepo as a new package called@clerk/vue.These are the adjustments that happened while porting the original code:
IsomorphicClerkas it's hard to keep in sync with@clerk/clerk-react'sIsomorphicClerkand there's really no benefit of it in Vue.Things to keep in mind when reviewing this PR:
.vuefile for each component and keep all related components in the same file.@clerk/clerk-reacthave an equivalent in@clerk/vue. They all use the same name.We will introduce E2E tests and quickstart repo in a separate PR.
Resolves ECO-222
Checklist
npm testruns as expected.npm run buildruns as expected.Type of change