Skip to content

Svelte 4 Upgrades #98

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .firebaserc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"projects": {
"default": "sveltefire-testing"
}
}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ node_modules
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
package
/test-results
*-debug.log
/dist
122 changes: 72 additions & 50 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Experimental! Do not use in production.
<FirebaseApp {auth} {firestore}>

<!-- 2. 👤 Get the current user -->
<User let:user>
<SignedIn let:user>

<p>Howdy, {user.uid}</p>

Expand Down Expand Up @@ -38,21 +38,28 @@ Svelte makes it possible to dramatically simplify the way developers work with F

## Quick Start

1. Install Firebase `npm i firebase` v9+ and initialize it in a file like `lib/firebase.js`:
1. Install Firebase `npm i firebase` v9+ and initialize it in a layout `+layout.svelte`:

```
npm i sveltefire firebase
```

```ts
import { initializeApp } from 'firebase/app';
import { getFirestore } from 'firebase/firestore';
import { getAuth } from 'firebase/auth';

// Initialize Firebase
const app = initializeApp(/* your firebase config */);
export const db = getFirestore(app);
export const auth = getAuth(app);
```svelte
<script lang="ts">
import { FirebaseApp } from 'sveltefire';
import { initializeApp } from 'firebase/app';
import { getFirestore } from 'firebase/firestore';
import { getAuth } from 'firebase/auth';

// Initialize Firebase
const app = initializeApp(/* your firebase config */);
const db = getFirestore(app);
const auth = getAuth(app);
</script>

<FirebaseApp {auth} {firestore}>
<slot />
</FirebaseApp>
```

2. Get the Current user
Expand All @@ -61,7 +68,7 @@ export const auth = getAuth(app);
<script>
import { auth } from '$lib/firebase';
import { userStore } from 'sveltefire';
const user = userStore(auth);
const user = userStore();
</script>

Hello {$user?.uid}
Expand All @@ -76,7 +83,7 @@ Use the `$` as much as you want - it will only result in one Firebase read reque
import { firestore } from '$lib/firebase';
import { docStore } from 'sveltefire';

const post = docStore(firestore, 'posts/test');
const post = docStore('posts/test');
</script>

{$post?.content}
Expand All @@ -98,7 +105,7 @@ Listen to the current user. Render UI conditionally based on the auth state:
<script>
import { userStore } from 'sveltefire';

const user = userStore(auth);
const user = userStore();
</script>

{#if $user}
Expand All @@ -116,16 +123,16 @@ Subscribe to realtime data. The store will unsubscribe automatically to avoid un
<script>
import { docStore, collectionStore } from 'sveltefire';

const post = docStore(firestore, 'posts/test');
const post = docStore('posts/test');

// OR

const posts = collectionStore(firestore, 'posts');
const posts = collectionStore('posts');
</script>

{$post?.content}

{#each $posts as p}
{#each $posts as post}

{/each}
```
Expand All @@ -138,18 +145,36 @@ interface Post {
title: string;
content: string;
}
const post = docStore<Post>(firestore, 'posts/test');
const posts = collectionStore<Post>(firestore, 'posts'); // returns
const post = docStore<Post>('posts/test');
const posts = collectionStore<Post>('posts');
```

## SSR

SvelteFire is a client-side library, but allows you to hydrate server data into a realtime stream.

First, fetch data from a load function like so:

```ts
import { doc, getDoc } from 'firebase/firestore';

export const load = (async () => {
const ref = doc(firestore, 'posts', 'first-post');
const snapshot = await getDoc(ref);
return {
post: snapshot.data();
};
});
```

Hydrate server-fetched data from SvelteKit into a realtime feed:
Second, pass the server data as the `startWith` value to a store. This will bypass the loading state and ensure the data is rendered in the server HTML, then realtime listeners will be attached afterwards.

```ts
// Data fetched via server
export let data: PageData;

// Just give the store a startWith value
const store = docStore(db, 'posts/test', data.thing);
const post = docStore('posts/test', data.post);
```

## Realtime Components
Expand All @@ -158,7 +183,8 @@ In addition to stores, SvelteFire provides a set of components that can build co

### FirebaseApp

Technically optional, this component puts Firebase into Svelte context. This avoids the need to pass `auth` and `firestore` down to every component. All other components should be nested below it.
The `FirebaseApp` component puts the FirebaseSDK into Svelte context. This avoids the need to pass `auth` and `firestore` down to every component/store. It is typically placed in root layout.

```svelte
<script>
// Initialize Firebase...
Expand All @@ -174,18 +200,27 @@ Technically optional, this component puts Firebase into Svelte context. This avo
</FirebaseApp>
```

Note: Components outside a FirebaseApp will need the auth/firestore prop, i.e `<User auth={auth}>`
You can easily access the Firebase SDK in any component via context. This is useful when using the Firebase SDK directly, which requires the SDK as an argument.

```svelte
<script>
import { getFirebaseContext } from "sveltefire";
const { auth, firestore } = getFirebaseContext();
</script>
```

### User

Get the current user.

```svelte
<User let:user>
<SignedIn let:user>
Hello {user.uid}
</SignedIn>

<div slot="signedOut">You are signed out</div>
</User>
<SignedOut>
You need to sign in!
</SignedOut>
```

### Doc
Expand All @@ -208,7 +243,7 @@ Slot props can be renamed:
</Doc>
```

All Firestore components can also handle loading states:
Firestore components can also handle loading states:

```svelte
<Doc path="posts/test">
Expand All @@ -220,7 +255,7 @@ All Firestore components can also handle loading states:
Pass a `startWith` value to bypass the loading state. This is useful in SvelteKit when you need to hydrate server data into a realtime stream:

```svelte
<Doc ref="posts/test" startWith={dataFromServer} />
<Doc ref="posts/test" startWith={dataFromServer}>
```


Expand All @@ -243,34 +278,21 @@ Collections can also take a Firestore Query instead of a path:

```svelte
<script>
const testQuery = query(collection(firestore, 'posts'), where('test', '==', 'test'));
const myQuery = query(collection(firestore, 'posts'), where('test', '==', 'test'));
</script>

<Collection ref={testQuery} let:data>
<Collection ref={myQuery} let:data>
</Collection>
```

For complex queries that required dynamic data, it can be useful to build the query reactively.

```svelte
<script>
$: buildQuery = (uid:string) => {
return query(collection(firestore, 'posts'), where('uid', '==', uid));
}
</script>

<User let:user>
<Collection ref={buildQuery(user.uid)} />
</User>
```
### Using Components Together

These components can be combined to build complex realtime apps. It's especially powerful when fetching data that requires the current user's UID or a related document's path.


```svelte
<FirebaseApp {auth} {firestore}>
<User let:user>
<SignedIn let:user>
<p>UID: {user.uid}</p>


Expand All @@ -293,14 +315,14 @@ These components can be combined to build complex realtime apps. It's especially
</Doc>

<div slot="signedOut">Signed out</div>
</User>
</SignedIn>
</FirebaseApp>
```


## Notes
## Roadmap

- This library should only run the the client, it is not for server-side data fetching.
- Requires Firebase v9 or greater.
- I've have not been able to get TS generics to work right in the components yet, so no intellisense on the `data` slot prop.
- How should we bundle it properly?
- Add support for Firebase Storage
- Add support for Firebase RTDB
- Add support for Firebase Analytics in SvelteKit
- Find a way to make TS generics with with Doc/Collection components
11 changes: 11 additions & 0 deletions dist/components/Collection.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<script>import { collectionStore } from "../stores/firestore.js";
export let ref;
export let startWith = void 0;
let store = collectionStore(ref, startWith);
</script>

{#if $store !== undefined}
<slot data={$store} ref={store.ref} count={$store?.length ?? 0} />
{:else}
<slot name="loading" />
{/if}
25 changes: 25 additions & 0 deletions dist/components/Collection.svelte.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { SvelteComponent } from "svelte";
import type { CollectionReference, Query } from 'firebase/firestore';
declare const __propDef: {
props: {
ref: string | CollectionReference | Query;
startWith?: any;
};
events: {
[evt: string]: CustomEvent<any>;
};
slots: {
default: {
data: any[];
ref: CollectionReference | Query | null;
count: number;
};
loading: {};
};
};
export type CollectionProps = typeof __propDef.props;
export type CollectionEvents = typeof __propDef.events;
export type CollectionSlots = typeof __propDef.slots;
export default class Collection extends SvelteComponent<CollectionProps, CollectionEvents, CollectionSlots> {
}
export {};
11 changes: 11 additions & 0 deletions dist/components/Doc.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<script>import { docStore } from "../stores/firestore.js";
export let ref;
export let startWith = void 0;
let store = docStore(ref, startWith);
</script>

{#if $store !== undefined}
<slot data={$store} ref={store.ref} />
{:else}
<slot name="loading" />
{/if}
24 changes: 24 additions & 0 deletions dist/components/Doc.svelte.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { SvelteComponent } from "svelte";
import type { DocumentReference } from 'firebase/firestore';
declare const __propDef: {
props: {
ref: string | DocumentReference;
startWith?: any;
};
events: {
[evt: string]: CustomEvent<any>;
};
slots: {
default: {
data: any;
ref: DocumentReference | null;
};
loading: {};
};
};
export type DocProps = typeof __propDef.props;
export type DocEvents = typeof __propDef.events;
export type DocSlots = typeof __propDef.slots;
export default class Doc extends SvelteComponent<DocProps, DocEvents, DocSlots> {
}
export {};
8 changes: 8 additions & 0 deletions dist/components/FirebaseApp.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<script>import { setFirebaseContext } from "../stores/sdk.js";
export let firestore;
export let auth;
setFirebaseContext({ firestore, auth });
</script>


<slot />
21 changes: 21 additions & 0 deletions dist/components/FirebaseApp.svelte.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { SvelteComponent } from "svelte";
import type { Auth } from 'firebase/auth';
import type { Firestore } from 'firebase/firestore';
declare const __propDef: {
props: {
firestore: Firestore;
auth: Auth;
};
events: {
[evt: string]: CustomEvent<any>;
};
slots: {
default: {};
};
};
export type FirebaseAppProps = typeof __propDef.props;
export type FirebaseAppEvents = typeof __propDef.events;
export type FirebaseAppSlots = typeof __propDef.slots;
export default class FirebaseApp extends SvelteComponent<FirebaseAppProps, FirebaseAppEvents, FirebaseAppSlots> {
}
export {};
10 changes: 10 additions & 0 deletions dist/components/SignedIn.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<script>import { userStore } from "../stores/auth.js";
import { getFirebaseContext } from "../stores/sdk.js";
import { signOut } from "firebase/auth";
const auth = getFirebaseContext().auth;
const user = userStore();
</script>

{#if $user}
<slot user={$user} {auth} signOut={() => signOut(auth)} />
{/if}
Loading