diff --git a/.changeset/two-wombats-confess.md b/.changeset/two-wombats-confess.md new file mode 100644 index 00000000000..eefc241e3a4 --- /dev/null +++ b/.changeset/two-wombats-confess.md @@ -0,0 +1,17 @@ +--- +'@clerk/clerk-expo': major +--- + +Support for [Expo Web](https://docs.expo.dev/workflow/web/) has been added! You can now build fullstack websites with Expo, React, and Clerk. Utilize [Clerk's components](https://clerk.com/docs/components/overview) to build out your app. + +You can access the components from the `/web` subpath import like so: + +```tsx +import { SignUp } from "@clerk/clerk-expo/web"; + +export default function Page() { + return ; +} +``` + +**Breaking change:** You need to use Expo 50 or later. The minimum required React Native version was bumped to `0.73`. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 019263d437f..a9cf6c696d2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -136,7 +136,7 @@ jobs: strategy: matrix: - test-name: ['generic', 'express', 'quickstart', 'ap-flows', 'elements', 'sessions', 'astro'] + test-name: ['generic', 'express', 'quickstart', 'ap-flows', 'elements', 'sessions', 'astro', 'expo-web'] test-project: ['chrome'] include: - test-name: 'nextjs' diff --git a/integration/models/application.ts b/integration/models/application.ts index 2bb8f017edc..3d354a04a4e 100644 --- a/integration/models/application.ts +++ b/integration/models/application.ts @@ -99,7 +99,10 @@ export const application = ( const serverUrl = `http://localhost:${port}`; // If this is ever used as a background process, we need to make sure // it's not using the log function. See the dev() method above - const proc = run(scripts.serve, { cwd: appDirPath, env: { PORT: port.toString() } }); + const proc = run(scripts.serve, { + cwd: appDirPath, + env: { PORT: port.toString() }, + }); cleanupFns.push(() => awaitableTreekill(proc.pid, 'SIGKILL')); await waitForIdleProcess(proc); state.serverUrl = serverUrl; diff --git a/integration/presets/expo.ts b/integration/presets/expo.ts new file mode 100644 index 00000000000..01315bfe11a --- /dev/null +++ b/integration/presets/expo.ts @@ -0,0 +1,18 @@ +import { applicationConfig } from '../models/applicationConfig'; +import { templates } from '../templates'; + +const clerkExpoLocal = `file:${process.cwd()}/packages/expo`; + +const expoWeb = applicationConfig() + .setName('expo-web') + .useTemplate(templates['expo-web']) + .setEnvFormatter('public', key => `EXPO_PUBLIC_${key}`) + .addScript('setup', 'npm i') + .addScript('dev', 'npm run dev') + .addScript('build', 'npm run build') + .addScript('serve', 'npm run start') + .addDependency('@clerk/clerk-expo', clerkExpoLocal); + +export const expo = { + expoWeb, +} as const; diff --git a/integration/presets/index.ts b/integration/presets/index.ts index 97dfab2814a..142af64d664 100644 --- a/integration/presets/index.ts +++ b/integration/presets/index.ts @@ -1,5 +1,6 @@ import { astro } from './astro'; import { elements } from './elements'; +import { expo } from './expo'; import { envs, instanceKeys } from './envs'; import { express } from './express'; import { createLongRunningApps } from './longRunningApps'; @@ -15,6 +16,7 @@ export const appConfigs = { react, remix, elements, + expo, astro, secrets: { instanceKeys, diff --git a/integration/presets/longRunningApps.ts b/integration/presets/longRunningApps.ts index e6077faa291..77f8a27b428 100644 --- a/integration/presets/longRunningApps.ts +++ b/integration/presets/longRunningApps.ts @@ -3,6 +3,7 @@ import { longRunningApplication } from '../models/longRunningApplication'; import { astro } from './astro'; import { elements } from './elements'; import { envs } from './envs'; +import { expo } from './expo'; import { express } from './express'; import { next } from './next'; import { react } from './react'; @@ -24,6 +25,7 @@ export const createLongRunningApps = () => { { id: 'quickstart.next.appRouter', config: next.appRouterQuickstart, env: envs.withEmailCodesQuickstart }, { id: 'elements.next.appRouter', config: elements.nextAppRouter, env: envs.withEmailCodes }, { id: 'astro.node.withCustomRoles', config: astro.node, env: envs.withCustomRoles }, + { id: 'expo.expo-web', config: expo.expoWeb, env: envs.withEmailCodes }, ] as const; const apps = configs.map(longRunningApplication); diff --git a/integration/templates/expo-web/.gitignore b/integration/templates/expo-web/.gitignore new file mode 100644 index 00000000000..ec8a36a257a --- /dev/null +++ b/integration/templates/expo-web/.gitignore @@ -0,0 +1,14 @@ +node_modules/ +.expo/ +dist/ +npm-debug.* +*.jks +*.p8 +*.p12 +*.key +*.mobileprovision +*.orig.* +web-build/ + +# macOS +.DS_Store diff --git a/integration/templates/expo-web/README.md b/integration/templates/expo-web/README.md new file mode 100644 index 00000000000..cd4feb8a3cc --- /dev/null +++ b/integration/templates/expo-web/README.md @@ -0,0 +1,50 @@ +# Welcome to your Expo app 👋 + +This is an [Expo](https://expo.dev) project created with [`create-expo-app`](https://www.npmjs.com/package/create-expo-app). + +## Get started + +1. Install dependencies + + ```bash + npm install + ``` + +2. Start the app + + ```bash + npx expo start + ``` + +In the output, you'll find options to open the app in a + +- [development build](https://docs.expo.dev/develop/development-builds/introduction/) +- [Android emulator](https://docs.expo.dev/workflow/android-studio-emulator/) +- [iOS simulator](https://docs.expo.dev/workflow/ios-simulator/) +- [Expo Go](https://expo.dev/go), a limited sandbox for trying out app development with Expo + +You can start developing by editing the files inside the **app** directory. This project uses [file-based routing](https://docs.expo.dev/router/introduction). + +## Get a fresh project + +When you're ready, run: + +```bash +npm run reset-project +``` + +This command will move the starter code to the **app-example** directory and create a blank **app** directory where you can start developing. + +## Learn more + +To learn more about developing your project with Expo, look at the following resources: + +- [Expo documentation](https://docs.expo.dev/): Learn fundamentals, or go into advanced topics with our [guides](https://docs.expo.dev/guides). +- [Learn Expo tutorial](https://docs.expo.dev/tutorial/introduction/): Follow a step-by-step tutorial where you'll create a project that runs on Android, iOS, and the web. + +## Join the community + +Join our community of developers creating universal apps. + +- [Expo on GitHub](https://github.com/expo/expo): View our open source platform and contribute. +- [Discord community](https://chat.expo.dev): Chat with Expo users and ask questions. diff --git a/integration/templates/expo-web/app.json b/integration/templates/expo-web/app.json new file mode 100644 index 00000000000..1ee635b408f --- /dev/null +++ b/integration/templates/expo-web/app.json @@ -0,0 +1,34 @@ +{ + "expo": { + "name": "expo-web", + "slug": "expo-web", + "version": "1.0.0", + "orientation": "portrait", + "icon": "./assets/images/icon.png", + "scheme": "myapp", + "userInterfaceStyle": "automatic", + "splash": { + "image": "./assets/images/splash.png", + "resizeMode": "contain", + "backgroundColor": "#ffffff" + }, + "ios": { + "supportsTablet": true + }, + "android": { + "adaptiveIcon": { + "foregroundImage": "./assets/images/icon.png", + "backgroundColor": "#ffffff" + } + }, + "web": { + "bundler": "metro", + "output": "static", + "favicon": "./assets/images/icon.png" + }, + "plugins": ["expo-router"], + "experiments": { + "typedRoutes": true + } + } +} diff --git a/integration/templates/expo-web/app/+html.tsx b/integration/templates/expo-web/app/+html.tsx new file mode 100644 index 00000000000..490457a1f3f --- /dev/null +++ b/integration/templates/expo-web/app/+html.tsx @@ -0,0 +1,45 @@ +import { ScrollViewStyleReset } from 'expo-router/html'; +import type { PropsWithChildren } from 'react'; + +/** + * This file is web-only and used to configure the root HTML for every web page during static rendering. + * The contents of this function only run in Node.js environments and do not have access to the DOM or browser APIs. + */ +export default function Root({ children }: PropsWithChildren) { + return ( + + + + + + + {/* + Disable body scrolling on web. This makes ScrollView components work closer to how they do on native. + However, body scrolling is often nice to have for mobile web. If you want to enable it, remove this line. + */} + + + {/* Using raw CSS styles as an escape-hatch to ensure the background color never flickers in dark-mode. */} +