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

@wobsoriano wobsoriano 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-bot Bot 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
Name Type
@clerk/vue Patch

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
wobsoriano force-pushed the rob/eco-222-port-vue-clerk-to-clerkvue branch from c47245f to bbb3a0d Compare November 2, 2024 02:19

@wobsoriano wobsoriano Nov 4, 2024

Copy link
Copy Markdown
Member Author

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>

@wobsoriano wobsoriano Nov 4, 2024

Copy link
Copy Markdown
Member Author

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,

@wobsoriano wobsoriano Nov 4, 2024

Copy link
Copy Markdown
Member Author

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
Comment thread packages/vue/src/keys.ts

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

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

Copy link
Copy Markdown
Member Author

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 thread packages/vue/src/components/uiComponents.ts
@wobsoriano
wobsoriano requested a review from anagstef November 5, 2024 14:41
@wobsoriano
wobsoriano force-pushed the rob/eco-222-port-vue-clerk-to-clerkvue branch 4 times, most recently from 7a415dd to edb5a4e Compare November 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
wobsoriano force-pushed the rob/eco-222-port-vue-clerk-to-clerkvue branch from 53879bc to fead36b Compare November 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
Member Author

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={(clerk as any).__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
Member Author

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 => {
  void clerk.value.redirectToSignUp(props);
});

@wobsoriano wobsoriano Nov 6, 2024

Copy link
Copy Markdown
Member Author

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:

export const RedirectToSignUp = withClerk(
  defineComponent(({ clerk, ...props }: WithClerkProp<SignUpRedirectOptions>) => {
    onMounted(() => {
      void props.clerk.redirectToSignUp(props);
    });

    return () => null;
  }),
)

this gives the same mental model as what we have in the React SDK but requires slightly more setup code 🤔

Copy link
Copy Markdown
Member Author

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 🫡

function onClerkLoaded(callback) {
  const clerk = 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
Member Author

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
Member Author

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 thread packages/vue/src/errors/messages.ts Outdated
Comment thread packages/vue/src/global.d.ts Outdated
wobsoriano and 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
Member Author

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
Member Author

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

@wobsoriano wobsoriano Nov 8, 2024

Copy link
Copy Markdown
Member Author

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 AM Screenshot 2024-11-08 at 7 34 09 AM

Copy link
Copy Markdown
Member Author

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
Member Author

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
Member Author

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)

@anagstef anagstef 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 thread packages/vue/tsconfig.json Outdated
@wobsoriano
wobsoriano merged commit fd30c21 into main Nov 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 free to 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