feat(vue): Introduce Vue SDK - #4461

Merged
wobsoriano merged 20 commits into
mainfrom
rob/eco-222-port-vue-clerk-to-clerkvue
Nov 8, 2024
Merged

feat(vue): Introduce Vue SDK#4461
wobsoriano merged 20 commits into
mainfrom
rob/eco-222-port-vue-clerk-to-clerkvue

Conversation

@wobsoriano

@wobsorianowobsoriano commented Nov 2, 2024

Copy link
Copy Markdown
Member

Description

This PR introduces an experimental version of Vue SDK! It ports the original code of vue-clerk inside this monorepo as a new package called @clerk/vue.

These are the adjustments that happened while porting the original code:

  • Removed IsomorphicClerk as it's hard to keep in sync with @clerk/clerk-react's IsomorphicClerk and there's really no benefit of it in Vue.
  • Custom pages and menu items have been intentionally excluded from this PR to focus on the main components and functions. These features will be implemented in a separate PR to follow.
  • Added unit tests to unstyled components using Vitest
  • Dropped CJS support (nobody's using cjs in Vue since Vue 3)

Things to keep in mind when reviewing this PR:

  • All components (UI, Control, Unstyled) are using Render Functions - this allow us to use pure JavaScript to create Vue components. Another plus is that we dont have to create a separate .vue file for each component and keep all related components in the same file.
  • When you see the term "composables", it's like "hooks" in React for Vue.
  • All components and hooks from @clerk/clerk-react have 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 test runs as expected.
  • npm run build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

@changeset-bot

changeset-botBot commented Nov 2, 2024

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: b443c40

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
NameType
@clerk/vuePatch

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

@wobsoriano
wobsorianoforce-pushed the rob/eco-222-port-vue-clerk-to-clerkvue branch from c47245f to bbb3a0dCompareNovember 2, 2024 02:19

@wobsorianowobsorianoNov 4, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

@wobsorianowobsorianoNov 4, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

Comment on lines +21 to +23
autoPropsPlugin({
include: ['**/*.ts'],
}) as EsbuildPlugin,

@wobsorianowobsorianoNov 4, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of redeclaring all props for all of available components, this plugin will generate a runtime prop using the type prop.

So this component:

Screenshot 2024-11-04 at 2 20 32 PM

will turn into this when built:

Screenshot 2024-11-04 at 9 59 17 AM

So without this plugin, we will have to manually add runtime props to each component, and make sure they will always match.

@wobsoriano
wobsoriano marked this pull request as ready for review November 4, 2024 22:23

import type { VueClerkInjectionKeyType } from './types';

export const ClerkInjectionKey = Symbol('clerk') as InjectionKey<VueClerkInjectionKeyType>;

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read more here regarding Symbols as key

Comment threadpackages/vue/src/components/uiComponents.ts
@wobsoriano
wobsorianoforce-pushed the rob/eco-222-port-vue-clerk-to-clerkvue branch 4 times, most recently from 7a415dd to edb5a4eCompareNovember 5, 2024 15:31
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
@wobsoriano
wobsorianoforce-pushed the rob/eco-222-port-vue-clerk-to-clerkvue branch from 53879bc to fead36bCompareNovember 5, 2024 20:08
h(Portal, {
mount: clerk.value?.mountUserProfile,
unmount: clerk.value?.unmountUserProfile,
updateProps: (clerk.value as any)?.__unstable__updateProps,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Why are we using as any here?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

__unstable__updateProps is an internal unstable property and we didn't add types yet, just like what we have in the React SDK

updateProps={(clerkasany).__unstable__updateProps}

Comment on lines +47 to +53
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);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems like a good candidate to abstract into clerk-js 👀

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a ticket that will address shared types and this 🫡

Comment on lines +62 to +65
watchEffect(() => {
if (!clerk.value) {
return;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seeing lots of this, potentially something we could abstract. Not sure if this is allowed in vue land:

watchEffectWithClerk(clerk=>{voidclerk.value.redirectToSignUp(props);});

@wobsorianowobsorianoNov 6, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

exportconstRedirectToSignUp=withClerk(defineComponent(({ clerk, ...props}: WithClerkProp<SignUpRedirectOptions>)=>{onMounted(()=>{voidprops.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 🤔

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, watchEffectWithClerk is growing on me. Let's do it, less boilerplate 🫡

functiononClerkLoaded(callback){constclerk=useClerk();watchEffect(()=>{if(!clerk.value?.loaded){return}callback(clerk.value)})}

Comment on lines +140 to +160
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;
};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we hae a shared type for this? Can we make it shared?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replied here. Astro will benefit from it too.

};
}

type UseAuthReturn =

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be moved to a shared type so they don't drift

* </div>
* </template>
*/
export const useAuth: UseAuth = () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

more candidates for shared helpers here to avoid drift 👀

import type { ToComputedRefs } from './utils';
import { toComputedRefs } from './utils';

type UseOrganizationReturn =

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 👍

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment threadpackages/vue/src/errors/messages.ts Outdated
Comment threadpackages/vue/src/global.d.ts Outdated
wobsorianoand others added 2 commits November 6, 2024 10:16
Co-authored-by: Bryce Kalow <bryce@clerk.dev>
Co-authored-by: Bryce Kalow <bryce@clerk.dev>
Comment on lines +17 to +31
export const useClerkLoaded = (callback: (clerk: LoadedClerk) => void) => {
const clerk = useClerk();

watch(
clerk,
unwrappedClerk => {
if (!unwrappedClerk?.loaded) {
return;
}

callback(unwrappedClerk as LoadedClerk);
},
{ immediate: true },
);
};

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 }) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would users benefit from us adding JSDoc comments to these exports or wouldn't it be shown in ones IDE?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question! So the doc shows up in the import statement:

Screenshot 2024-11-07 at 7 51 59 AM

but not when used inside template:

Screenshot 2024-11-07 at 7 52 10 AM

I skipped it because of that but we can definitely add it 🤔

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would it only show up on the import? I guess typically you'd expect this to exist during usage

@wobsorianowobsorianoNov 8, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, looks like it is a limitation when using render functions. When doing normal Vue SFC, it appears when used inside template:

Screenshot 2024-11-08 at 7 33 53 AMScreenshot 2024-11-08 at 7 34 09 AM

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated! thank you


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',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this link to the installation docs of the Clerk Vue SDK instead?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to add a quickstart link to Vue SDK (docs to follow)

@anagstefanagstef left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Amazing work! 🔥

Comment threadpackages/vue/tsconfig.json Outdated
@wobsoriano
wobsoriano merged commit fd30c21 into mainNov 8, 2024
@wobsoriano
wobsoriano deleted the rob/eco-222-port-vue-clerk-to-clerkvue branch November 8, 2024 17:15
wobsoriano added a commit that referenced this pull request Feb 8, 2025
Co-authored-by: Bryce Kalow <bryce@clerk.dev>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@wobsoriano@jacekradko@brkalow@octoper@anagstef@LekoArts@clerk-cookie
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(vue): Introduce Vue SDK - #4461

Merged
wobsoriano merged 20 commits into
mainfrom
rob/eco-222-port-vue-clerk-to-clerkvue
Nov 8, 2024
Merged

feat(vue): Introduce Vue SDK#4461
wobsoriano merged 20 commits into
mainfrom
rob/eco-222-port-vue-clerk-to-clerkvue

Conversation

@wobsoriano

@wobsorianowobsoriano commented Nov 2, 2024

Copy link
Copy Markdown
Member

Description

This PR introduces an experimental version of Vue SDK! It ports the original code of vue-clerk inside this monorepo as a new package called @clerk/vue.

These are the adjustments that happened while porting the original code:

  • Removed IsomorphicClerk as it's hard to keep in sync with @clerk/clerk-react's IsomorphicClerk and there's really no benefit of it in Vue.
  • Custom pages and menu items have been intentionally excluded from this PR to focus on the main components and functions. These features will be implemented in a separate PR to follow.
  • Added unit tests to unstyled components using Vitest
  • Dropped CJS support (nobody's using cjs in Vue since Vue 3)

Things to keep in mind when reviewing this PR:

  • All components (UI, Control, Unstyled) are using Render Functions - this allow us to use pure JavaScript to create Vue components. Another plus is that we dont have to create a separate .vue file for each component and keep all related components in the same file.
  • When you see the term "composables", it's like "hooks" in React for Vue.
  • All components and hooks from @clerk/clerk-react have 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 test runs as expected.
  • npm run build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

@changeset-bot

changeset-botBot commented Nov 2, 2024

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: b443c40

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
NameType
@clerk/vuePatch

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

@wobsoriano
wobsorianoforce-pushed the rob/eco-222-port-vue-clerk-to-clerkvue branch from c47245f to bbb3a0dCompareNovember 2, 2024 02:19

@wobsorianowobsorianoNov 4, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

@wobsorianowobsorianoNov 4, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

Comment on lines +21 to +23
autoPropsPlugin({
include: ['**/*.ts'],
}) as EsbuildPlugin,

@wobsorianowobsorianoNov 4, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of redeclaring all props for all of available components, this plugin will generate a runtime prop using the type prop.

So this component:

Screenshot 2024-11-04 at 2 20 32 PM

will turn into this when built:

Screenshot 2024-11-04 at 9 59 17 AM

So without this plugin, we will have to manually add runtime props to each component, and make sure they will always match.

@wobsoriano
wobsoriano marked this pull request as ready for review November 4, 2024 22:23

import type { VueClerkInjectionKeyType } from './types';

export const ClerkInjectionKey = Symbol('clerk') as InjectionKey<VueClerkInjectionKeyType>;

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read more here regarding Symbols as key

Comment threadpackages/vue/src/components/uiComponents.ts
@wobsoriano
wobsorianoforce-pushed the rob/eco-222-port-vue-clerk-to-clerkvue branch 4 times, most recently from 7a415dd to edb5a4eCompareNovember 5, 2024 15:31
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
@wobsoriano
wobsorianoforce-pushed the rob/eco-222-port-vue-clerk-to-clerkvue branch from 53879bc to fead36bCompareNovember 5, 2024 20:08
h(Portal, {
mount: clerk.value?.mountUserProfile,
unmount: clerk.value?.unmountUserProfile,
updateProps: (clerk.value as any)?.__unstable__updateProps,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Why are we using as any here?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

__unstable__updateProps is an internal unstable property and we didn't add types yet, just like what we have in the React SDK

updateProps={(clerkasany).__unstable__updateProps}

Comment on lines +47 to +53
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);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems like a good candidate to abstract into clerk-js 👀

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a ticket that will address shared types and this 🫡

Comment on lines +62 to +65
watchEffect(() => {
if (!clerk.value) {
return;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seeing lots of this, potentially something we could abstract. Not sure if this is allowed in vue land:

watchEffectWithClerk(clerk=>{voidclerk.value.redirectToSignUp(props);});

@wobsorianowobsorianoNov 6, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

exportconstRedirectToSignUp=withClerk(defineComponent(({ clerk, ...props}: WithClerkProp<SignUpRedirectOptions>)=>{onMounted(()=>{voidprops.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 🤔

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, watchEffectWithClerk is growing on me. Let's do it, less boilerplate 🫡

functiononClerkLoaded(callback){constclerk=useClerk();watchEffect(()=>{if(!clerk.value?.loaded){return}callback(clerk.value)})}

Comment on lines +140 to +160
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;
};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we hae a shared type for this? Can we make it shared?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replied here. Astro will benefit from it too.

};
}

type UseAuthReturn =

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be moved to a shared type so they don't drift

* </div>
* </template>
*/
export const useAuth: UseAuth = () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

more candidates for shared helpers here to avoid drift 👀

import type { ToComputedRefs } from './utils';
import { toComputedRefs } from './utils';

type UseOrganizationReturn =

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 👍

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment threadpackages/vue/src/errors/messages.ts Outdated
Comment threadpackages/vue/src/global.d.ts Outdated
wobsorianoand others added 2 commits November 6, 2024 10:16
Co-authored-by: Bryce Kalow <bryce@clerk.dev>
Co-authored-by: Bryce Kalow <bryce@clerk.dev>
Comment on lines +17 to +31
export const useClerkLoaded = (callback: (clerk: LoadedClerk) => void) => {
const clerk = useClerk();

watch(
clerk,
unwrappedClerk => {
if (!unwrappedClerk?.loaded) {
return;
}

callback(unwrappedClerk as LoadedClerk);
},
{ immediate: true },
);
};

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 }) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would users benefit from us adding JSDoc comments to these exports or wouldn't it be shown in ones IDE?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question! So the doc shows up in the import statement:

Screenshot 2024-11-07 at 7 51 59 AM

but not when used inside template:

Screenshot 2024-11-07 at 7 52 10 AM

I skipped it because of that but we can definitely add it 🤔

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would it only show up on the import? I guess typically you'd expect this to exist during usage

@wobsorianowobsorianoNov 8, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, looks like it is a limitation when using render functions. When doing normal Vue SFC, it appears when used inside template:

Screenshot 2024-11-08 at 7 33 53 AMScreenshot 2024-11-08 at 7 34 09 AM

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated! thank you


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',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this link to the installation docs of the Clerk Vue SDK instead?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to add a quickstart link to Vue SDK (docs to follow)

@anagstefanagstef left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Amazing work! 🔥

Comment threadpackages/vue/tsconfig.json Outdated
@wobsoriano
wobsoriano merged commit fd30c21 into mainNov 8, 2024
@wobsoriano
wobsoriano deleted the rob/eco-222-port-vue-clerk-to-clerkvue branch November 8, 2024 17:15
wobsoriano added a commit that referenced this pull request Feb 8, 2025
Co-authored-by: Bryce Kalow <bryce@clerk.dev>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@wobsoriano@jacekradko@brkalow@octoper@anagstef@LekoArts@clerk-cookie
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(vue): Introduce Vue SDK - #4461

Merged
wobsoriano merged 20 commits into
mainfrom
rob/eco-222-port-vue-clerk-to-clerkvue
Nov 8, 2024
Merged

feat(vue): Introduce Vue SDK#4461
wobsoriano merged 20 commits into
mainfrom
rob/eco-222-port-vue-clerk-to-clerkvue

Conversation

@wobsoriano

@wobsorianowobsoriano commented Nov 2, 2024

Copy link
Copy Markdown
Member

Description

This PR introduces an experimental version of Vue SDK! It ports the original code of vue-clerk inside this monorepo as a new package called @clerk/vue.

These are the adjustments that happened while porting the original code:

  • Removed IsomorphicClerk as it's hard to keep in sync with @clerk/clerk-react's IsomorphicClerk and there's really no benefit of it in Vue.
  • Custom pages and menu items have been intentionally excluded from this PR to focus on the main components and functions. These features will be implemented in a separate PR to follow.
  • Added unit tests to unstyled components using Vitest
  • Dropped CJS support (nobody's using cjs in Vue since Vue 3)

Things to keep in mind when reviewing this PR:

  • All components (UI, Control, Unstyled) are using Render Functions - this allow us to use pure JavaScript to create Vue components. Another plus is that we dont have to create a separate .vue file for each component and keep all related components in the same file.
  • When you see the term "composables", it's like "hooks" in React for Vue.
  • All components and hooks from @clerk/clerk-react have 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 test runs as expected.
  • npm run build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

@changeset-bot

changeset-botBot commented Nov 2, 2024

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: b443c40

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
NameType
@clerk/vuePatch

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

@wobsoriano
wobsorianoforce-pushed the rob/eco-222-port-vue-clerk-to-clerkvue branch from c47245f to bbb3a0dCompareNovember 2, 2024 02:19

@wobsorianowobsorianoNov 4, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

@wobsorianowobsorianoNov 4, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

Comment on lines +21 to +23
autoPropsPlugin({
include: ['**/*.ts'],
}) as EsbuildPlugin,

@wobsorianowobsorianoNov 4, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of redeclaring all props for all of available components, this plugin will generate a runtime prop using the type prop.

So this component:

Screenshot 2024-11-04 at 2 20 32 PM

will turn into this when built:

Screenshot 2024-11-04 at 9 59 17 AM

So without this plugin, we will have to manually add runtime props to each component, and make sure they will always match.

@wobsoriano
wobsoriano marked this pull request as ready for review November 4, 2024 22:23

import type { VueClerkInjectionKeyType } from './types';

export const ClerkInjectionKey = Symbol('clerk') as InjectionKey<VueClerkInjectionKeyType>;

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read more here regarding Symbols as key

Comment threadpackages/vue/src/components/uiComponents.ts
@wobsoriano
wobsorianoforce-pushed the rob/eco-222-port-vue-clerk-to-clerkvue branch 4 times, most recently from 7a415dd to edb5a4eCompareNovember 5, 2024 15:31
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
@wobsoriano
wobsorianoforce-pushed the rob/eco-222-port-vue-clerk-to-clerkvue branch from 53879bc to fead36bCompareNovember 5, 2024 20:08
h(Portal, {
mount: clerk.value?.mountUserProfile,
unmount: clerk.value?.unmountUserProfile,
updateProps: (clerk.value as any)?.__unstable__updateProps,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Why are we using as any here?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

__unstable__updateProps is an internal unstable property and we didn't add types yet, just like what we have in the React SDK

updateProps={(clerkasany).__unstable__updateProps}

Comment on lines +47 to +53
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);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems like a good candidate to abstract into clerk-js 👀

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a ticket that will address shared types and this 🫡

Comment on lines +62 to +65
watchEffect(() => {
if (!clerk.value) {
return;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seeing lots of this, potentially something we could abstract. Not sure if this is allowed in vue land:

watchEffectWithClerk(clerk=>{voidclerk.value.redirectToSignUp(props);});

@wobsorianowobsorianoNov 6, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

exportconstRedirectToSignUp=withClerk(defineComponent(({ clerk, ...props}: WithClerkProp<SignUpRedirectOptions>)=>{onMounted(()=>{voidprops.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 🤔

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, watchEffectWithClerk is growing on me. Let's do it, less boilerplate 🫡

functiononClerkLoaded(callback){constclerk=useClerk();watchEffect(()=>{if(!clerk.value?.loaded){return}callback(clerk.value)})}

Comment on lines +140 to +160
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;
};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we hae a shared type for this? Can we make it shared?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replied here. Astro will benefit from it too.

};
}

type UseAuthReturn =

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be moved to a shared type so they don't drift

* </div>
* </template>
*/
export const useAuth: UseAuth = () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

more candidates for shared helpers here to avoid drift 👀

import type { ToComputedRefs } from './utils';
import { toComputedRefs } from './utils';

type UseOrganizationReturn =

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 👍

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment threadpackages/vue/src/errors/messages.ts Outdated
Comment threadpackages/vue/src/global.d.ts Outdated
wobsorianoand others added 2 commits November 6, 2024 10:16
Co-authored-by: Bryce Kalow <bryce@clerk.dev>
Co-authored-by: Bryce Kalow <bryce@clerk.dev>
Comment on lines +17 to +31
export const useClerkLoaded = (callback: (clerk: LoadedClerk) => void) => {
const clerk = useClerk();

watch(
clerk,
unwrappedClerk => {
if (!unwrappedClerk?.loaded) {
return;
}

callback(unwrappedClerk as LoadedClerk);
},
{ immediate: true },
);
};

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 }) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would users benefit from us adding JSDoc comments to these exports or wouldn't it be shown in ones IDE?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question! So the doc shows up in the import statement:

Screenshot 2024-11-07 at 7 51 59 AM

but not when used inside template:

Screenshot 2024-11-07 at 7 52 10 AM

I skipped it because of that but we can definitely add it 🤔

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would it only show up on the import? I guess typically you'd expect this to exist during usage

@wobsorianowobsorianoNov 8, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, looks like it is a limitation when using render functions. When doing normal Vue SFC, it appears when used inside template:

Screenshot 2024-11-08 at 7 33 53 AMScreenshot 2024-11-08 at 7 34 09 AM

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated! thank you


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',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this link to the installation docs of the Clerk Vue SDK instead?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to add a quickstart link to Vue SDK (docs to follow)

@anagstefanagstef left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Amazing work! 🔥

Comment threadpackages/vue/tsconfig.json Outdated
@wobsoriano
wobsoriano merged commit fd30c21 into mainNov 8, 2024
@wobsoriano
wobsoriano deleted the rob/eco-222-port-vue-clerk-to-clerkvue branch November 8, 2024 17:15
wobsoriano added a commit that referenced this pull request Feb 8, 2025
Co-authored-by: Bryce Kalow <bryce@clerk.dev>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@wobsoriano@jacekradko@brkalow@octoper@anagstef@LekoArts@clerk-cookie
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(vue): Introduce Vue SDK - #4461

Merged
wobsoriano merged 20 commits into
mainfrom
rob/eco-222-port-vue-clerk-to-clerkvue
Nov 8, 2024
Merged

feat(vue): Introduce Vue SDK#4461
wobsoriano merged 20 commits into
mainfrom
rob/eco-222-port-vue-clerk-to-clerkvue

Conversation

@wobsoriano

@wobsorianowobsoriano commented Nov 2, 2024

Copy link
Copy Markdown
Member

Description

This PR introduces an experimental version of Vue SDK! It ports the original code of vue-clerk inside this monorepo as a new package called @clerk/vue.

These are the adjustments that happened while porting the original code:

  • Removed IsomorphicClerk as it's hard to keep in sync with @clerk/clerk-react's IsomorphicClerk and there's really no benefit of it in Vue.
  • Custom pages and menu items have been intentionally excluded from this PR to focus on the main components and functions. These features will be implemented in a separate PR to follow.
  • Added unit tests to unstyled components using Vitest
  • Dropped CJS support (nobody's using cjs in Vue since Vue 3)

Things to keep in mind when reviewing this PR:

  • All components (UI, Control, Unstyled) are using Render Functions - this allow us to use pure JavaScript to create Vue components. Another plus is that we dont have to create a separate .vue file for each component and keep all related components in the same file.
  • When you see the term "composables", it's like "hooks" in React for Vue.
  • All components and hooks from @clerk/clerk-react have 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 test runs as expected.
  • npm run build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

@changeset-bot

changeset-botBot commented Nov 2, 2024

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: b443c40

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
NameType
@clerk/vuePatch

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

@wobsoriano
wobsorianoforce-pushed the rob/eco-222-port-vue-clerk-to-clerkvue branch from c47245f to bbb3a0dCompareNovember 2, 2024 02:19

@wobsorianowobsorianoNov 4, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

@wobsorianowobsorianoNov 4, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

Comment on lines +21 to +23
autoPropsPlugin({
include: ['**/*.ts'],
}) as EsbuildPlugin,

@wobsorianowobsorianoNov 4, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of redeclaring all props for all of available components, this plugin will generate a runtime prop using the type prop.

So this component:

Screenshot 2024-11-04 at 2 20 32 PM

will turn into this when built:

Screenshot 2024-11-04 at 9 59 17 AM

So without this plugin, we will have to manually add runtime props to each component, and make sure they will always match.

@wobsoriano
wobsoriano marked this pull request as ready for review November 4, 2024 22:23

import type { VueClerkInjectionKeyType } from './types';

export const ClerkInjectionKey = Symbol('clerk') as InjectionKey<VueClerkInjectionKeyType>;

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read more here regarding Symbols as key

Comment threadpackages/vue/src/components/uiComponents.ts
@wobsoriano
wobsorianoforce-pushed the rob/eco-222-port-vue-clerk-to-clerkvue branch 4 times, most recently from 7a415dd to edb5a4eCompareNovember 5, 2024 15:31
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
@wobsoriano
wobsorianoforce-pushed the rob/eco-222-port-vue-clerk-to-clerkvue branch from 53879bc to fead36bCompareNovember 5, 2024 20:08
h(Portal, {
mount: clerk.value?.mountUserProfile,
unmount: clerk.value?.unmountUserProfile,
updateProps: (clerk.value as any)?.__unstable__updateProps,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Why are we using as any here?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

__unstable__updateProps is an internal unstable property and we didn't add types yet, just like what we have in the React SDK

updateProps={(clerkasany).__unstable__updateProps}

Comment on lines +47 to +53
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);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems like a good candidate to abstract into clerk-js 👀

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a ticket that will address shared types and this 🫡

Comment on lines +62 to +65
watchEffect(() => {
if (!clerk.value) {
return;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seeing lots of this, potentially something we could abstract. Not sure if this is allowed in vue land:

watchEffectWithClerk(clerk=>{voidclerk.value.redirectToSignUp(props);});

@wobsorianowobsorianoNov 6, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

exportconstRedirectToSignUp=withClerk(defineComponent(({ clerk, ...props}: WithClerkProp<SignUpRedirectOptions>)=>{onMounted(()=>{voidprops.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 🤔

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, watchEffectWithClerk is growing on me. Let's do it, less boilerplate 🫡

functiononClerkLoaded(callback){constclerk=useClerk();watchEffect(()=>{if(!clerk.value?.loaded){return}callback(clerk.value)})}

Comment on lines +140 to +160
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;
};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we hae a shared type for this? Can we make it shared?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replied here. Astro will benefit from it too.

};
}

type UseAuthReturn =

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be moved to a shared type so they don't drift

* </div>
* </template>
*/
export const useAuth: UseAuth = () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

more candidates for shared helpers here to avoid drift 👀

import type { ToComputedRefs } from './utils';
import { toComputedRefs } from './utils';

type UseOrganizationReturn =

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 👍

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment threadpackages/vue/src/errors/messages.ts Outdated
Comment threadpackages/vue/src/global.d.ts Outdated
wobsorianoand others added 2 commits November 6, 2024 10:16
Co-authored-by: Bryce Kalow <bryce@clerk.dev>
Co-authored-by: Bryce Kalow <bryce@clerk.dev>
Comment on lines +17 to +31
export const useClerkLoaded = (callback: (clerk: LoadedClerk) => void) => {
const clerk = useClerk();

watch(
clerk,
unwrappedClerk => {
if (!unwrappedClerk?.loaded) {
return;
}

callback(unwrappedClerk as LoadedClerk);
},
{ immediate: true },
);
};

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 }) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would users benefit from us adding JSDoc comments to these exports or wouldn't it be shown in ones IDE?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question! So the doc shows up in the import statement:

Screenshot 2024-11-07 at 7 51 59 AM

but not when used inside template:

Screenshot 2024-11-07 at 7 52 10 AM

I skipped it because of that but we can definitely add it 🤔

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would it only show up on the import? I guess typically you'd expect this to exist during usage

@wobsorianowobsorianoNov 8, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, looks like it is a limitation when using render functions. When doing normal Vue SFC, it appears when used inside template:

Screenshot 2024-11-08 at 7 33 53 AMScreenshot 2024-11-08 at 7 34 09 AM

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated! thank you


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',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this link to the installation docs of the Clerk Vue SDK instead?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to add a quickstart link to Vue SDK (docs to follow)

@anagstefanagstef left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Amazing work! 🔥

Comment threadpackages/vue/tsconfig.json Outdated
@wobsoriano
wobsoriano merged commit fd30c21 into mainNov 8, 2024
@wobsoriano
wobsoriano deleted the rob/eco-222-port-vue-clerk-to-clerkvue branch November 8, 2024 17:15
wobsoriano added a commit that referenced this pull request Feb 8, 2025
Co-authored-by: Bryce Kalow <bryce@clerk.dev>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@wobsoriano@jacekradko@brkalow@octoper@anagstef@LekoArts@clerk-cookie
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(vue): Introduce Vue SDK - #4461

Merged
wobsoriano merged 20 commits into
mainfrom
rob/eco-222-port-vue-clerk-to-clerkvue
Nov 8, 2024
Merged

feat(vue): Introduce Vue SDK#4461
wobsoriano merged 20 commits into
mainfrom
rob/eco-222-port-vue-clerk-to-clerkvue

Conversation

@wobsoriano

@wobsorianowobsoriano commented Nov 2, 2024

Copy link
Copy Markdown
Member

Description

This PR introduces an experimental version of Vue SDK! It ports the original code of vue-clerk inside this monorepo as a new package called @clerk/vue.

These are the adjustments that happened while porting the original code:

  • Removed IsomorphicClerk as it's hard to keep in sync with @clerk/clerk-react's IsomorphicClerk and there's really no benefit of it in Vue.
  • Custom pages and menu items have been intentionally excluded from this PR to focus on the main components and functions. These features will be implemented in a separate PR to follow.
  • Added unit tests to unstyled components using Vitest
  • Dropped CJS support (nobody's using cjs in Vue since Vue 3)

Things to keep in mind when reviewing this PR:

  • All components (UI, Control, Unstyled) are using Render Functions - this allow us to use pure JavaScript to create Vue components. Another plus is that we dont have to create a separate .vue file for each component and keep all related components in the same file.
  • When you see the term "composables", it's like "hooks" in React for Vue.
  • All components and hooks from @clerk/clerk-react have 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 test runs as expected.
  • npm run build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

@changeset-bot

changeset-botBot commented Nov 2, 2024

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: b443c40

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
NameType
@clerk/vuePatch

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

@wobsoriano
wobsorianoforce-pushed the rob/eco-222-port-vue-clerk-to-clerkvue branch from c47245f to bbb3a0dCompareNovember 2, 2024 02:19

@wobsorianowobsorianoNov 4, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

@wobsorianowobsorianoNov 4, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

Comment on lines +21 to +23
autoPropsPlugin({
include: ['**/*.ts'],
}) as EsbuildPlugin,

@wobsorianowobsorianoNov 4, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of redeclaring all props for all of available components, this plugin will generate a runtime prop using the type prop.

So this component:

Screenshot 2024-11-04 at 2 20 32 PM

will turn into this when built:

Screenshot 2024-11-04 at 9 59 17 AM

So without this plugin, we will have to manually add runtime props to each component, and make sure they will always match.

@wobsoriano
wobsoriano marked this pull request as ready for review November 4, 2024 22:23

import type { VueClerkInjectionKeyType } from './types';

export const ClerkInjectionKey = Symbol('clerk') as InjectionKey<VueClerkInjectionKeyType>;

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read more here regarding Symbols as key

Comment threadpackages/vue/src/components/uiComponents.ts
@wobsoriano
wobsorianoforce-pushed the rob/eco-222-port-vue-clerk-to-clerkvue branch 4 times, most recently from 7a415dd to edb5a4eCompareNovember 5, 2024 15:31
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
@wobsoriano
wobsorianoforce-pushed the rob/eco-222-port-vue-clerk-to-clerkvue branch from 53879bc to fead36bCompareNovember 5, 2024 20:08
h(Portal, {
mount: clerk.value?.mountUserProfile,
unmount: clerk.value?.unmountUserProfile,
updateProps: (clerk.value as any)?.__unstable__updateProps,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Why are we using as any here?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

__unstable__updateProps is an internal unstable property and we didn't add types yet, just like what we have in the React SDK

updateProps={(clerkasany).__unstable__updateProps}

Comment on lines +47 to +53
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);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems like a good candidate to abstract into clerk-js 👀

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a ticket that will address shared types and this 🫡

Comment on lines +62 to +65
watchEffect(() => {
if (!clerk.value) {
return;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seeing lots of this, potentially something we could abstract. Not sure if this is allowed in vue land:

watchEffectWithClerk(clerk=>{voidclerk.value.redirectToSignUp(props);});

@wobsorianowobsorianoNov 6, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

exportconstRedirectToSignUp=withClerk(defineComponent(({ clerk, ...props}: WithClerkProp<SignUpRedirectOptions>)=>{onMounted(()=>{voidprops.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 🤔

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, watchEffectWithClerk is growing on me. Let's do it, less boilerplate 🫡

functiononClerkLoaded(callback){constclerk=useClerk();watchEffect(()=>{if(!clerk.value?.loaded){return}callback(clerk.value)})}

Comment on lines +140 to +160
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;
};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we hae a shared type for this? Can we make it shared?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replied here. Astro will benefit from it too.

};
}

type UseAuthReturn =

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be moved to a shared type so they don't drift

* </div>
* </template>
*/
export const useAuth: UseAuth = () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

more candidates for shared helpers here to avoid drift 👀

import type { ToComputedRefs } from './utils';
import { toComputedRefs } from './utils';

type UseOrganizationReturn =

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 👍

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment threadpackages/vue/src/errors/messages.ts Outdated
Comment threadpackages/vue/src/global.d.ts Outdated
wobsorianoand others added 2 commits November 6, 2024 10:16
Co-authored-by: Bryce Kalow <bryce@clerk.dev>
Co-authored-by: Bryce Kalow <bryce@clerk.dev>
Comment on lines +17 to +31
export const useClerkLoaded = (callback: (clerk: LoadedClerk) => void) => {
const clerk = useClerk();

watch(
clerk,
unwrappedClerk => {
if (!unwrappedClerk?.loaded) {
return;
}

callback(unwrappedClerk as LoadedClerk);
},
{ immediate: true },
);
};

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 }) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would users benefit from us adding JSDoc comments to these exports or wouldn't it be shown in ones IDE?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question! So the doc shows up in the import statement:

Screenshot 2024-11-07 at 7 51 59 AM

but not when used inside template:

Screenshot 2024-11-07 at 7 52 10 AM

I skipped it because of that but we can definitely add it 🤔

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would it only show up on the import? I guess typically you'd expect this to exist during usage

@wobsorianowobsorianoNov 8, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, looks like it is a limitation when using render functions. When doing normal Vue SFC, it appears when used inside template:

Screenshot 2024-11-08 at 7 33 53 AMScreenshot 2024-11-08 at 7 34 09 AM

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated! thank you


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',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this link to the installation docs of the Clerk Vue SDK instead?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to add a quickstart link to Vue SDK (docs to follow)

@anagstefanagstef left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Amazing work! 🔥

Comment threadpackages/vue/tsconfig.json Outdated
@wobsoriano
wobsoriano merged commit fd30c21 into mainNov 8, 2024
@wobsoriano
wobsoriano deleted the rob/eco-222-port-vue-clerk-to-clerkvue branch November 8, 2024 17:15
wobsoriano added a commit that referenced this pull request Feb 8, 2025
Co-authored-by: Bryce Kalow <bryce@clerk.dev>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@wobsoriano@jacekradko@brkalow@octoper@anagstef@LekoArts@clerk-cookie
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(vue): Introduce Vue SDK - #4461

Merged
wobsoriano merged 20 commits into
mainfrom
rob/eco-222-port-vue-clerk-to-clerkvue
Nov 8, 2024
Merged

feat(vue): Introduce Vue SDK#4461
wobsoriano merged 20 commits into
mainfrom
rob/eco-222-port-vue-clerk-to-clerkvue

Conversation

@wobsoriano

@wobsorianowobsoriano commented Nov 2, 2024

Copy link
Copy Markdown
Member

Description

This PR introduces an experimental version of Vue SDK! It ports the original code of vue-clerk inside this monorepo as a new package called @clerk/vue.

These are the adjustments that happened while porting the original code:

  • Removed IsomorphicClerk as it's hard to keep in sync with @clerk/clerk-react's IsomorphicClerk and there's really no benefit of it in Vue.
  • Custom pages and menu items have been intentionally excluded from this PR to focus on the main components and functions. These features will be implemented in a separate PR to follow.
  • Added unit tests to unstyled components using Vitest
  • Dropped CJS support (nobody's using cjs in Vue since Vue 3)

Things to keep in mind when reviewing this PR:

  • All components (UI, Control, Unstyled) are using Render Functions - this allow us to use pure JavaScript to create Vue components. Another plus is that we dont have to create a separate .vue file for each component and keep all related components in the same file.
  • When you see the term "composables", it's like "hooks" in React for Vue.
  • All components and hooks from @clerk/clerk-react have 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 test runs as expected.
  • npm run build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

@changeset-bot

changeset-botBot commented Nov 2, 2024

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: b443c40

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
NameType
@clerk/vuePatch

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

@wobsoriano
wobsorianoforce-pushed the rob/eco-222-port-vue-clerk-to-clerkvue branch from c47245f to bbb3a0dCompareNovember 2, 2024 02:19

@wobsorianowobsorianoNov 4, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

@wobsorianowobsorianoNov 4, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

Comment on lines +21 to +23
autoPropsPlugin({
include: ['**/*.ts'],
}) as EsbuildPlugin,

@wobsorianowobsorianoNov 4, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of redeclaring all props for all of available components, this plugin will generate a runtime prop using the type prop.

So this component:

Screenshot 2024-11-04 at 2 20 32 PM

will turn into this when built:

Screenshot 2024-11-04 at 9 59 17 AM

So without this plugin, we will have to manually add runtime props to each component, and make sure they will always match.

@wobsoriano
wobsoriano marked this pull request as ready for review November 4, 2024 22:23

import type { VueClerkInjectionKeyType } from './types';

export const ClerkInjectionKey = Symbol('clerk') as InjectionKey<VueClerkInjectionKeyType>;

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read more here regarding Symbols as key

Comment threadpackages/vue/src/components/uiComponents.ts
@wobsoriano
wobsorianoforce-pushed the rob/eco-222-port-vue-clerk-to-clerkvue branch 4 times, most recently from 7a415dd to edb5a4eCompareNovember 5, 2024 15:31
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
@wobsoriano
wobsorianoforce-pushed the rob/eco-222-port-vue-clerk-to-clerkvue branch from 53879bc to fead36bCompareNovember 5, 2024 20:08
h(Portal, {
mount: clerk.value?.mountUserProfile,
unmount: clerk.value?.unmountUserProfile,
updateProps: (clerk.value as any)?.__unstable__updateProps,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Why are we using as any here?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

__unstable__updateProps is an internal unstable property and we didn't add types yet, just like what we have in the React SDK

updateProps={(clerkasany).__unstable__updateProps}

Comment on lines +47 to +53
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);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems like a good candidate to abstract into clerk-js 👀

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a ticket that will address shared types and this 🫡

Comment on lines +62 to +65
watchEffect(() => {
if (!clerk.value) {
return;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seeing lots of this, potentially something we could abstract. Not sure if this is allowed in vue land:

watchEffectWithClerk(clerk=>{voidclerk.value.redirectToSignUp(props);});

@wobsorianowobsorianoNov 6, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

exportconstRedirectToSignUp=withClerk(defineComponent(({ clerk, ...props}: WithClerkProp<SignUpRedirectOptions>)=>{onMounted(()=>{voidprops.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 🤔

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, watchEffectWithClerk is growing on me. Let's do it, less boilerplate 🫡

functiononClerkLoaded(callback){constclerk=useClerk();watchEffect(()=>{if(!clerk.value?.loaded){return}callback(clerk.value)})}

Comment on lines +140 to +160
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;
};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we hae a shared type for this? Can we make it shared?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replied here. Astro will benefit from it too.

};
}

type UseAuthReturn =

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be moved to a shared type so they don't drift

* </div>
* </template>
*/
export const useAuth: UseAuth = () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

more candidates for shared helpers here to avoid drift 👀

import type { ToComputedRefs } from './utils';
import { toComputedRefs } from './utils';

type UseOrganizationReturn =

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 👍

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment threadpackages/vue/src/errors/messages.ts Outdated
Comment threadpackages/vue/src/global.d.ts Outdated
wobsorianoand others added 2 commits November 6, 2024 10:16
Co-authored-by: Bryce Kalow <bryce@clerk.dev>
Co-authored-by: Bryce Kalow <bryce@clerk.dev>
Comment on lines +17 to +31
export const useClerkLoaded = (callback: (clerk: LoadedClerk) => void) => {
const clerk = useClerk();

watch(
clerk,
unwrappedClerk => {
if (!unwrappedClerk?.loaded) {
return;
}

callback(unwrappedClerk as LoadedClerk);
},
{ immediate: true },
);
};

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 }) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would users benefit from us adding JSDoc comments to these exports or wouldn't it be shown in ones IDE?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question! So the doc shows up in the import statement:

Screenshot 2024-11-07 at 7 51 59 AM

but not when used inside template:

Screenshot 2024-11-07 at 7 52 10 AM

I skipped it because of that but we can definitely add it 🤔

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would it only show up on the import? I guess typically you'd expect this to exist during usage

@wobsorianowobsorianoNov 8, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, looks like it is a limitation when using render functions. When doing normal Vue SFC, it appears when used inside template:

Screenshot 2024-11-08 at 7 33 53 AMScreenshot 2024-11-08 at 7 34 09 AM

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated! thank you


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',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this link to the installation docs of the Clerk Vue SDK instead?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to add a quickstart link to Vue SDK (docs to follow)

@anagstefanagstef left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Amazing work! 🔥

Comment threadpackages/vue/tsconfig.json Outdated
@wobsoriano
wobsoriano merged commit fd30c21 into mainNov 8, 2024
@wobsoriano
wobsoriano deleted the rob/eco-222-port-vue-clerk-to-clerkvue branch November 8, 2024 17:15
wobsoriano added a commit that referenced this pull request Feb 8, 2025
Co-authored-by: Bryce Kalow <bryce@clerk.dev>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@wobsoriano@jacekradko@brkalow@octoper@anagstef@LekoArts@clerk-cookie
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(vue): Introduce Vue SDK - #4461

Merged
wobsoriano merged 20 commits into
mainfrom
rob/eco-222-port-vue-clerk-to-clerkvue
Nov 8, 2024
Merged

feat(vue): Introduce Vue SDK#4461
wobsoriano merged 20 commits into
mainfrom
rob/eco-222-port-vue-clerk-to-clerkvue

Conversation

@wobsoriano

@wobsorianowobsoriano commented Nov 2, 2024

Copy link
Copy Markdown
Member

Description

This PR introduces an experimental version of Vue SDK! It ports the original code of vue-clerk inside this monorepo as a new package called @clerk/vue.

These are the adjustments that happened while porting the original code:

  • Removed IsomorphicClerk as it's hard to keep in sync with @clerk/clerk-react's IsomorphicClerk and there's really no benefit of it in Vue.
  • Custom pages and menu items have been intentionally excluded from this PR to focus on the main components and functions. These features will be implemented in a separate PR to follow.
  • Added unit tests to unstyled components using Vitest
  • Dropped CJS support (nobody's using cjs in Vue since Vue 3)

Things to keep in mind when reviewing this PR:

  • All components (UI, Control, Unstyled) are using Render Functions - this allow us to use pure JavaScript to create Vue components. Another plus is that we dont have to create a separate .vue file for each component and keep all related components in the same file.
  • When you see the term "composables", it's like "hooks" in React for Vue.
  • All components and hooks from @clerk/clerk-react have 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 test runs as expected.
  • npm run build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

@changeset-bot

changeset-botBot commented Nov 2, 2024

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: b443c40

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
NameType
@clerk/vuePatch

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

@wobsoriano
wobsorianoforce-pushed the rob/eco-222-port-vue-clerk-to-clerkvue branch from c47245f to bbb3a0dCompareNovember 2, 2024 02:19

@wobsorianowobsorianoNov 4, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

@wobsorianowobsorianoNov 4, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

Comment on lines +21 to +23
autoPropsPlugin({
include: ['**/*.ts'],
}) as EsbuildPlugin,

@wobsorianowobsorianoNov 4, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of redeclaring all props for all of available components, this plugin will generate a runtime prop using the type prop.

So this component:

Screenshot 2024-11-04 at 2 20 32 PM

will turn into this when built:

Screenshot 2024-11-04 at 9 59 17 AM

So without this plugin, we will have to manually add runtime props to each component, and make sure they will always match.

@wobsoriano
wobsoriano marked this pull request as ready for review November 4, 2024 22:23

import type { VueClerkInjectionKeyType } from './types';

export const ClerkInjectionKey = Symbol('clerk') as InjectionKey<VueClerkInjectionKeyType>;

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read more here regarding Symbols as key

Comment threadpackages/vue/src/components/uiComponents.ts
@wobsoriano
wobsorianoforce-pushed the rob/eco-222-port-vue-clerk-to-clerkvue branch 4 times, most recently from 7a415dd to edb5a4eCompareNovember 5, 2024 15:31
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
@wobsoriano
wobsorianoforce-pushed the rob/eco-222-port-vue-clerk-to-clerkvue branch from 53879bc to fead36bCompareNovember 5, 2024 20:08
h(Portal, {
mount: clerk.value?.mountUserProfile,
unmount: clerk.value?.unmountUserProfile,
updateProps: (clerk.value as any)?.__unstable__updateProps,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Why are we using as any here?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

__unstable__updateProps is an internal unstable property and we didn't add types yet, just like what we have in the React SDK

updateProps={(clerkasany).__unstable__updateProps}

Comment on lines +47 to +53
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);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems like a good candidate to abstract into clerk-js 👀

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a ticket that will address shared types and this 🫡

Comment on lines +62 to +65
watchEffect(() => {
if (!clerk.value) {
return;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seeing lots of this, potentially something we could abstract. Not sure if this is allowed in vue land:

watchEffectWithClerk(clerk=>{voidclerk.value.redirectToSignUp(props);});

@wobsorianowobsorianoNov 6, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

exportconstRedirectToSignUp=withClerk(defineComponent(({ clerk, ...props}: WithClerkProp<SignUpRedirectOptions>)=>{onMounted(()=>{voidprops.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 🤔

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, watchEffectWithClerk is growing on me. Let's do it, less boilerplate 🫡

functiononClerkLoaded(callback){constclerk=useClerk();watchEffect(()=>{if(!clerk.value?.loaded){return}callback(clerk.value)})}

Comment on lines +140 to +160
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;
};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we hae a shared type for this? Can we make it shared?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replied here. Astro will benefit from it too.

};
}

type UseAuthReturn =

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be moved to a shared type so they don't drift

* </div>
* </template>
*/
export const useAuth: UseAuth = () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

more candidates for shared helpers here to avoid drift 👀

import type { ToComputedRefs } from './utils';
import { toComputedRefs } from './utils';

type UseOrganizationReturn =

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 👍

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment threadpackages/vue/src/errors/messages.ts Outdated
Comment threadpackages/vue/src/global.d.ts Outdated
wobsorianoand others added 2 commits November 6, 2024 10:16
Co-authored-by: Bryce Kalow <bryce@clerk.dev>
Co-authored-by: Bryce Kalow <bryce@clerk.dev>
Comment on lines +17 to +31
export const useClerkLoaded = (callback: (clerk: LoadedClerk) => void) => {
const clerk = useClerk();

watch(
clerk,
unwrappedClerk => {
if (!unwrappedClerk?.loaded) {
return;
}

callback(unwrappedClerk as LoadedClerk);
},
{ immediate: true },
);
};

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 }) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would users benefit from us adding JSDoc comments to these exports or wouldn't it be shown in ones IDE?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question! So the doc shows up in the import statement:

Screenshot 2024-11-07 at 7 51 59 AM

but not when used inside template:

Screenshot 2024-11-07 at 7 52 10 AM

I skipped it because of that but we can definitely add it 🤔

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would it only show up on the import? I guess typically you'd expect this to exist during usage

@wobsorianowobsorianoNov 8, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, looks like it is a limitation when using render functions. When doing normal Vue SFC, it appears when used inside template:

Screenshot 2024-11-08 at 7 33 53 AMScreenshot 2024-11-08 at 7 34 09 AM

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated! thank you


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',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this link to the installation docs of the Clerk Vue SDK instead?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to add a quickstart link to Vue SDK (docs to follow)

@anagstefanagstef left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Amazing work! 🔥

Comment threadpackages/vue/tsconfig.json Outdated
@wobsoriano
wobsoriano merged commit fd30c21 into mainNov 8, 2024
@wobsoriano
wobsoriano deleted the rob/eco-222-port-vue-clerk-to-clerkvue branch November 8, 2024 17:15
wobsoriano added a commit that referenced this pull request Feb 8, 2025
Co-authored-by: Bryce Kalow <bryce@clerk.dev>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@wobsoriano@jacekradko@brkalow@octoper@anagstef@LekoArts@clerk-cookie
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(vue): Introduce Vue SDK - #4461

Merged
wobsoriano merged 20 commits into
mainfrom
rob/eco-222-port-vue-clerk-to-clerkvue
Nov 8, 2024
Merged

feat(vue): Introduce Vue SDK#4461
wobsoriano merged 20 commits into
mainfrom
rob/eco-222-port-vue-clerk-to-clerkvue

Conversation

@wobsoriano

@wobsorianowobsoriano commented Nov 2, 2024

Copy link
Copy Markdown
Member

Description

This PR introduces an experimental version of Vue SDK! It ports the original code of vue-clerk inside this monorepo as a new package called @clerk/vue.

These are the adjustments that happened while porting the original code:

  • Removed IsomorphicClerk as it's hard to keep in sync with @clerk/clerk-react's IsomorphicClerk and there's really no benefit of it in Vue.
  • Custom pages and menu items have been intentionally excluded from this PR to focus on the main components and functions. These features will be implemented in a separate PR to follow.
  • Added unit tests to unstyled components using Vitest
  • Dropped CJS support (nobody's using cjs in Vue since Vue 3)

Things to keep in mind when reviewing this PR:

  • All components (UI, Control, Unstyled) are using Render Functions - this allow us to use pure JavaScript to create Vue components. Another plus is that we dont have to create a separate .vue file for each component and keep all related components in the same file.
  • When you see the term "composables", it's like "hooks" in React for Vue.
  • All components and hooks from @clerk/clerk-react have 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 test runs as expected.
  • npm run build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

@changeset-bot

changeset-botBot commented Nov 2, 2024

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: b443c40

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
NameType
@clerk/vuePatch

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

@wobsoriano
wobsorianoforce-pushed the rob/eco-222-port-vue-clerk-to-clerkvue branch from c47245f to bbb3a0dCompareNovember 2, 2024 02:19

@wobsorianowobsorianoNov 4, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

@wobsorianowobsorianoNov 4, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

Comment on lines +21 to +23
autoPropsPlugin({
include: ['**/*.ts'],
}) as EsbuildPlugin,

@wobsorianowobsorianoNov 4, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of redeclaring all props for all of available components, this plugin will generate a runtime prop using the type prop.

So this component:

Screenshot 2024-11-04 at 2 20 32 PM

will turn into this when built:

Screenshot 2024-11-04 at 9 59 17 AM

So without this plugin, we will have to manually add runtime props to each component, and make sure they will always match.

@wobsoriano
wobsoriano marked this pull request as ready for review November 4, 2024 22:23

import type { VueClerkInjectionKeyType } from './types';

export const ClerkInjectionKey = Symbol('clerk') as InjectionKey<VueClerkInjectionKeyType>;

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read more here regarding Symbols as key

Comment threadpackages/vue/src/components/uiComponents.ts
@wobsoriano
wobsorianoforce-pushed the rob/eco-222-port-vue-clerk-to-clerkvue branch 4 times, most recently from 7a415dd to edb5a4eCompareNovember 5, 2024 15:31
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
@wobsoriano
wobsorianoforce-pushed the rob/eco-222-port-vue-clerk-to-clerkvue branch from 53879bc to fead36bCompareNovember 5, 2024 20:08
h(Portal, {
mount: clerk.value?.mountUserProfile,
unmount: clerk.value?.unmountUserProfile,
updateProps: (clerk.value as any)?.__unstable__updateProps,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Why are we using as any here?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

__unstable__updateProps is an internal unstable property and we didn't add types yet, just like what we have in the React SDK

updateProps={(clerkasany).__unstable__updateProps}

Comment on lines +47 to +53
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);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems like a good candidate to abstract into clerk-js 👀

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a ticket that will address shared types and this 🫡

Comment on lines +62 to +65
watchEffect(() => {
if (!clerk.value) {
return;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seeing lots of this, potentially something we could abstract. Not sure if this is allowed in vue land:

watchEffectWithClerk(clerk=>{voidclerk.value.redirectToSignUp(props);});

@wobsorianowobsorianoNov 6, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

exportconstRedirectToSignUp=withClerk(defineComponent(({ clerk, ...props}: WithClerkProp<SignUpRedirectOptions>)=>{onMounted(()=>{voidprops.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 🤔

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, watchEffectWithClerk is growing on me. Let's do it, less boilerplate 🫡

functiononClerkLoaded(callback){constclerk=useClerk();watchEffect(()=>{if(!clerk.value?.loaded){return}callback(clerk.value)})}

Comment on lines +140 to +160
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;
};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we hae a shared type for this? Can we make it shared?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replied here. Astro will benefit from it too.

};
}

type UseAuthReturn =

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be moved to a shared type so they don't drift

* </div>
* </template>
*/
export const useAuth: UseAuth = () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

more candidates for shared helpers here to avoid drift 👀

import type { ToComputedRefs } from './utils';
import { toComputedRefs } from './utils';

type UseOrganizationReturn =

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 👍

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment threadpackages/vue/src/errors/messages.ts Outdated
Comment threadpackages/vue/src/global.d.ts Outdated
wobsorianoand others added 2 commits November 6, 2024 10:16
Co-authored-by: Bryce Kalow <bryce@clerk.dev>
Co-authored-by: Bryce Kalow <bryce@clerk.dev>
Comment on lines +17 to +31
export const useClerkLoaded = (callback: (clerk: LoadedClerk) => void) => {
const clerk = useClerk();

watch(
clerk,
unwrappedClerk => {
if (!unwrappedClerk?.loaded) {
return;
}

callback(unwrappedClerk as LoadedClerk);
},
{ immediate: true },
);
};

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 }) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would users benefit from us adding JSDoc comments to these exports or wouldn't it be shown in ones IDE?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question! So the doc shows up in the import statement:

Screenshot 2024-11-07 at 7 51 59 AM

but not when used inside template:

Screenshot 2024-11-07 at 7 52 10 AM

I skipped it because of that but we can definitely add it 🤔

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would it only show up on the import? I guess typically you'd expect this to exist during usage

@wobsorianowobsorianoNov 8, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, looks like it is a limitation when using render functions. When doing normal Vue SFC, it appears when used inside template:

Screenshot 2024-11-08 at 7 33 53 AMScreenshot 2024-11-08 at 7 34 09 AM

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated! thank you


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',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this link to the installation docs of the Clerk Vue SDK instead?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to add a quickstart link to Vue SDK (docs to follow)

@anagstefanagstef left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Amazing work! 🔥

Comment threadpackages/vue/tsconfig.json Outdated
@wobsoriano
wobsoriano merged commit fd30c21 into mainNov 8, 2024
@wobsoriano
wobsoriano deleted the rob/eco-222-port-vue-clerk-to-clerkvue branch November 8, 2024 17:15
wobsoriano added a commit that referenced this pull request Feb 8, 2025
Co-authored-by: Bryce Kalow <bryce@clerk.dev>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@wobsoriano@jacekradko@brkalow@octoper@anagstef@LekoArts@clerk-cookie