Skip to content

Commit 93cee48

Browse files
committed
initial release
0 parents  commit 93cee48

36 files changed

+7824
-0
lines changed

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.DS_Store
2+
node_modules
3+
yarn.lock
4+
package-lock.json
5+
index.js
6+
index.mjs

README.md

Lines changed: 379 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,379 @@
1+
# SvelteFire
2+
3+
Cybernetically enhanced Firebase apps 💪🔥
4+
5+
## Basics
6+
7+
- Use Firebase declaratively in Svelte components.
8+
- Handle complex relational data with simple loading & fallback states.
9+
- Automatic data disposal to prevent memory/cost leaks, plus enhanced logging.
10+
- Automatic performance monitoring & Google Analytics.
11+
12+
13+
**Psuedo Example**
14+
15+
Handle multiple levels of async relational data (and their loading & fallback states) entirely from the Svelte HTML.
16+
17+
18+
```html
19+
<!-- 1. 🔥 Firebase App -->
20+
<FirebaseApp {firebase}>
21+
22+
<!-- 2. 😀 Get the current user -->
23+
<User let:user>
24+
25+
<p>Howdy, {user.uid}</p>
26+
27+
<!-- 3. 📜 Get a Firestore document owned by a user -->
28+
<Doc path={`posts/${user.uid}`} let:data={post} let:ref={postRef}>
29+
30+
<h2>{post.title}</h2>
31+
32+
<!-- 4. 💬 Get all the comments in its subcollection -->
33+
<Collection path={postRef.collection('comments')} let:data={comments}>
34+
{#each comments as comment}
35+
36+
{/each}
37+
38+
39+
...
40+
```
41+
42+
43+
## Quick Start
44+
45+
```bash
46+
npm install sveltefire firebase
47+
```
48+
49+
50+
Create a web app from the [Firebase Console](https://console.firebase.google.com/) and grab your credentials. Enable **Anonymous Login** and create a **Firestore** database instance in test mode.
51+
52+
53+
Initialize the Firebase app in the `App.svelte` file.
54+
55+
```html
56+
<script>
57+
import { FirebaseApp, User, Doc, Collection } from 'sveltefire';
58+
59+
// Import the Firebase Services you want bundled and call initializeApp
60+
import firebase from "firebase/app";
61+
import 'firebase/firestore';
62+
import 'firebase/auth';
63+
import 'firebase/performance';
64+
import 'firebase/analytics';
65+
66+
const firebaseConfig = {
67+
apiKey: 'api-key',
68+
authDomain: 'project-id.firebaseapp.com',
69+
databaseURL: 'https://project-id.firebaseio.com',
70+
projectId: 'project-id',
71+
storageBucket: 'project-id.appspot.com',
72+
messagingSenderId: 'sender-id',
73+
appId: 'app-id',
74+
measurementId: 'G-measurement-id',
75+
}
76+
77+
firebase.initializeApp(firebaseConfig)
78+
</script>
79+
```
80+
81+
**Full Example**
82+
83+
Start by building an **authenticated realtime CRUD app** . A user can sign-in, create posts, and add comments to that post. Paste this code into your app.
84+
85+
```html
86+
<!-- 1. 🔥 Firebase App -->
87+
<FirebaseApp {firebase}>
88+
89+
<!-- 2. 😀 Get the current user -->
90+
<User let:user let:auth>
91+
92+
<p>Howdy, {user.uid}</p>
93+
<button on:click={() => auth.signOut()}>Sign Out</button>
94+
95+
<div slot="signed-out">
96+
<button on:click={() => auth.signInAnonymously()}>Sign In</button>
97+
</div>
98+
99+
<!-- 3. 📜 Get a Firestore document owned by a user -->
100+
<Doc path={`posts/${user.uid}`} let:data={post} let:ref={postRef} log>
101+
102+
<h2>{post.title}</h2>
103+
104+
<span slot="loading">Loading post...</span>
105+
<span slot="fallback">
106+
<p>Demo post not created yet...</p>
107+
108+
<button on:click={() => postRef.set({ title: 'I like Svelte' })}>
109+
Create it Now
110+
</button>
111+
</span>
112+
113+
<!-- 4. 💬 Get all the comments in its subcollection -->
114+
<Collection
115+
path={postRef.collection('comments')}
116+
let:data={comments}
117+
let:ref={commentsRef}
118+
log>
119+
120+
{#each comments as comment}
121+
<p>{comment.text}</p>
122+
<button on:click={() => comment.ref.delete()}>Delete</button>
123+
{/each}
124+
125+
<hr />
126+
127+
<button on:click={() => commentsRef.add({ text: 'Cool!' })}>
128+
Add Comment
129+
</button>
130+
131+
<span slot="loading">Loading comments...</span>
132+
133+
</Collection>
134+
</Doc>
135+
</User>
136+
</FirebaseApp>
137+
```
138+
139+
Run it on localhost:5000
140+
141+
```
142+
npm run dev
143+
```
144+
145+
If you see the error 'openDb' is not exported by node_modules\idb\build\idb.js`, go in the `rollup.config.js` and add this line:
146+
147+
```js
148+
resolve({
149+
...
150+
mainFields: ['main', 'module'] /// <-- here
151+
}),
152+
```
153+
154+
## Concepts
155+
156+
SvelteFire allows you to use Firebase data anywhere in the Svelte component without the need to manage async state, promises, or streams.
157+
158+
### Slots
159+
160+
[Slots](https://svelte.dev/tutorial/slots) render different UI templates based on the state of your data. The `loading` state is shown until the first response is received from Firebase. The `fallback` state is shown if there is an error or timeout.
161+
162+
In most cases, state flows from *loading* -> *default*. For errors, non-existent data, or high-latency, state flows from *loading* -> *fallback* `maxWait` default is 10000ms).
163+
164+
165+
```html
166+
<Doc path={'foods/ice-cream'}>
167+
168+
<!-- Default Slot -->
169+
Data loaded, yay 🍦!
170+
171+
<!-- Only shown when loading -->
172+
<div slot="loading">
173+
Loading...
174+
</div>
175+
176+
<!-- Shown on error or if nothing loads after maxWait time-->
177+
<div slot="fallback">
178+
whoops!
179+
</div>
180+
</Doc>
181+
```
182+
183+
You can bypass the loading state entirely by passing a `startWith` prop.
184+
185+
```html
186+
<Doc path={'foods/ice-cream'} startWith={ {flavor: 'vanilla'} }>
187+
```
188+
189+
### Slot Props
190+
191+
[Slot props](https://svelte.dev/tutorial/slot-props) **pass data down** to children the component tree. SvelteFire has done the hard work to expose the data you will need in the UI. For example, `let:data` gives you access to the document data, while `={yourVar}` is the name you use to reference it in your code. The `data` is a plain object for showing data in the UI, while the `ref` is a Firestore `DocumentReference` used to execute writes.
192+
193+
194+
```html
195+
<Doc path={`food/ice-cream`} let:data={icecream} let:ref={docRef}>
196+
197+
{icecream.flavor} yay 🍦!
198+
199+
<button on:click={() => docRef.delete()}>Delete</button>
200+
</Doc>
201+
```
202+
203+
204+
205+
### Events
206+
207+
[Events](https://svelte.dev/tutorial/component-events) **emit data up** to the parent. You can use components as a mechanism to read documents without actually rendering UI. Also useful for trigging side effects.
208+
209+
```html
210+
<Doc path={'food/ice-cream'} on:data={(e) => console.log(e.detail.data)} />
211+
```
212+
213+
### Stores
214+
215+
[Stores](https://svelte.dev/tutorial/custom-stores) are used under the hood to manage async data in components. It's an advanced use-case, but they can be used directly in a component script or plain JS.
216+
217+
```js
218+
<script>
219+
import { collectionStore } from 'sveltefire';
220+
221+
const data = collectionStore('things', (ref => ref.orderBy('time') ));
222+
223+
data.subscribe(v => doStuff(v) )
224+
</script>
225+
```
226+
227+
### Firebase App Context
228+
229+
The Firebase SDK is available via the [Context API](https://svelte.dev/tutorial/context-api) under the key of `firebase`.
230+
231+
```js
232+
const db = getContext('firebase').firestore();
233+
```
234+
235+
## API
236+
237+
### `<FirebaseApp>`
238+
239+
Sets Firebase app context
240+
241+
Props:
242+
243+
- *firebase (required)* Firebase app
244+
- *perf* Starts Firebase Performance Monitoring
245+
- *analytics* Starts Firebase/Google Analytics
246+
247+
248+
```html
249+
<FirebaseApp firebase={firebase} perf analytics>
250+
<!-- default slot -->
251+
</FirebaseApp>
252+
```
253+
254+
255+
### `<User>`
256+
257+
Listens to the current user.
258+
259+
Props:
260+
261+
- *persist* user in `sessionStorage` or `localStorage`. Can prevent flash if user refreshes browser. Default `null`;
262+
263+
Slots:
264+
265+
- *default slot* shown to signed-in user
266+
- *signed-out* shown to signed-out user
267+
268+
Slot Props & Events:
269+
270+
- *user* current FirebaseUser or `null`
271+
- *auth* Firebase Auth to call login methods.
272+
273+
```html
274+
<User persist={sessionStorage} let:user={user} let:auth={auth} on:user>
275+
{user.uid}
276+
277+
<div slot="signed-out"></div>
278+
</User>
279+
```
280+
281+
282+
### `<Doc>`
283+
284+
Retrieves and listens to a Firestore document.
285+
286+
Props:
287+
288+
- *path (required)* - Path to document as `string` OR a DocumentReference i.e `db.doc('path')`
289+
- *startWith* any value. Bypasses loading state.
290+
- *maxWait* `number` milliseconds to wait before showing fallback slot if nothing is returned. Default 10000.
291+
- *log* debugging info to the console. Default `false`.
292+
- *traceId* `string` name that runs a Firebase Performance trace for latency.
293+
294+
Slots:
295+
296+
- *default slot* shown when document is available.
297+
- *loading* when waiting for first response.
298+
- *fallback* when error occurs.
299+
300+
301+
Slot Props & Events:
302+
303+
- *data* Document data
304+
- *ref* DocumentReference for writes
305+
- *error* current error
306+
307+
```html
308+
<Doc
309+
path={'posts/postId'}
310+
startWith={defaultData}
311+
log
312+
traceId={'postRead'}
313+
let:data={myData}
314+
let:ref={myRef}
315+
on:data
316+
on:ref
317+
>
318+
319+
320+
{post.title}
321+
322+
<span slot="loading">Loading...</span>
323+
<span slot="fallback">Error...</span>
324+
</Doc>
325+
```
326+
327+
328+
### `<Collection>`
329+
330+
Retrieves and listens to a Firestore collection or query.
331+
332+
Props:
333+
334+
- *path (required)* to document as `string` OR `CollectionReference` i.e `db.collection('path')`
335+
- *query* `function`, i.e (ref) => ref.where('age, '==', 23)
336+
- *startWith* any value. Bypasses loading state.
337+
- *maxWait* `number` milliseconds to wait before showing fallback slot if nothing is returned. Default 10000.
338+
- *log* debugging info to the console. Default `false`.
339+
- *traceId* `string` name that runs a Firebase Performance trace for latency.
340+
341+
Slots:
342+
343+
- *default slot* shown when document is available.
344+
- *loading* when waiting for first response.
345+
- *fallback* when error occurs.
346+
347+
348+
Slot Props & Events:
349+
350+
- *data* collection data as array.
351+
- *ref* CollectionReference for writes
352+
- *error* current error
353+
354+
```html
355+
<Collection
356+
path={'comments'}
357+
query={ (ref) => ref.orderBy(date).limit(10) }
358+
traceId={'readComments'}
359+
log
360+
let:data={comments}
361+
let:ref={commentsRef}
362+
on:data
363+
on:ref
364+
>
365+
366+
{#each comments as comment}
367+
{comment.text}
368+
{/each}
369+
370+
<div slot="loading">Loading...</div>
371+
372+
<div slot="fallback">
373+
Unable to display comments...
374+
</div>
375+
376+
</Collection>
377+
```
378+
379+
Note: Each data item in the collection contains the document data AND fields for the `id` and `ref` (DocumentReference).

cypress.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}

cypress/fixtures/example.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"name": "Using fixtures to represent data",
3+
"email": "[email protected]",
4+
"body": "Fixtures are a great way to mock data for responses to routes"
5+
}

0 commit comments

Comments
 (0)