Open codediodeio opened 5 years ago
I'm also interested in this, i am currently leaking apki keys to the client, which is not ideal
I do something similar and I don't know what is best:
any.svelte
<script>
let client;
onMount(async () => {
if(process.browser){
client = await import("../firebase/firebase.js");
// loading firebase stuff here with client.db or client.auth
// which are exported in the firebase.js
}
});
</script>
the initialization I do in the
client.js
import * as sapper from '@sapper/app';
import firebase from 'firebase/app';
const firebaseConfig = { ... };
firebase.initializeApp(firebaseConfig);
sapper.start({
target: document.querySelector('#sapper')
});
I'm in the same boat here folks... having done a sapper project with tailwind which I find terrific and now I want to add the backend portion which will be firebase... I see the exact same issues with SSR that are linked above, I resorted to specifying mainFields
in my rollup conf but as long as the differrent firebase packages like firestore, app or analytics are not unified, that approach just doesn't work for all the firebase packages I need (app, auth, firestore, performance, analytics, storage).
What I ended up doing for now is but this in in my sapper template.html
<script defer src="https://www.gstatic.com/firebasejs/7.6.2/firebase-app.js"></script>
<script defer src="https://www.gstatic.com/firebasejs/7.6.2/firebase-auth.js"></script>
<script defer src="https://www.gstatic.com/firebasejs/7.6.2/firebase-firestore.js"></script>
<script defer src="https://www.gstatic.com/firebasejs/7.6.2/firebase-storage.js"></script>
<script defer src="https://www.gstatic.com/firebasejs/7.6.2/firebase-messaging.js"></script>
<script defer src="https://www.gstatic.com/firebasejs/7.6.2/firebase-performance.js"></script>
<script defer src="https://www.gstatic.com/firebasejs/7.6.2/firebase-analytics.js"></script>
<script defer src="./firebase-init.js"></script>
and then have in my static/firebase-init.js
let config = {
apiKey: "zzzzz",
appId: "1:1xxxxxxxxx",
authDomain: "xxxxxxxxx",
databaseURL: "xxxxxx",
measurementId: "G-"xxxxx,
messagingSenderId: "xxxxxxxxxxx",
projectId: "xxxxxxxxxxx",
storageBucket: ""
}
firebase.initializeApp( config )
firebase.performance()
firebase.analytics()
while this works, it gives me issues related to SSR such as firebase
not defined for the first paint, then when the page gets hydrated, it works because the firebase gets pulled in from the CDN. But for that intermediate second or so, until the client is fully hydrated, I see the SSR version of my Sapper page that of course shows a 500 error because there's no firebase on the server that node could render.
I really hope there's going to be a proper solution for all folks that want to use firebase with some SSR app like Sapper (Svelte toolkit for SSR), React SSR, or Angular SSR... at the moment, all I've seen are just
The issues around SSR builds and esm vs cjs seem to bite quite a few people now as this new issue elaborates too https://github.com/firebase/firebase-js-sdk/issues/1612
I'm also interested in this, i am currently leaking apki keys to the client, which is not ideal
by API keys do you mean the public config information that you put in firebase.initializeApp()
?
I've gotten a version of ssr and preloading up and running following this video: Sveltecasts - Sapper & Firebase Firestore
Essentially there needs to be 2 versions of Firestore loaded. He loads clientside firebase stuff like Jeff in the scripts and then has a conditional definition of Firestore This is my version in firestore.js
export async function firestore() {
if (process.browser) {
return window.db
} else {
const firebase = await import('firebase')
const config = await import('./config');
if (firebase.apps.length == 0) {
let app = firebase.initializeApp(config.default)
return app.firestore()
}
else {
return firebase.apps[0].firestore()
}
}
}
on the page I need preloaded data:
<script context="module">
// load the above file
import { firestore } from "../../firebase/firestore";
export async function preload({ params, query }) {
let db = await firestore();
const data = await db
.collection("collection")
.doc(params.slug)
.get();
if (data.exists) {
return { document: data.data() };
} else {
this.error("no data");
}
}
</script>
<script>
// this document is returned from the preload and ready for usage in html
export let document;
</script>
if the client.js
import * as sapper from '@sapper/app';
import firebase from 'firebase/app';
import 'firebase/firestore';
import {default as config} from './firebase/config'
let app = firebase.initializeApp(config)
window.db = app.firestore()
sapper.start({
target: document.querySelector('#sapper')
});
edit: I forgot to mention that I also updated the rollup.config.js file with names exports as mentioned here.
//--- rollup.config.js ---
commonjs({
namedExports: {
// left-hand side can be an absolute path, a path
// relative to the current directory, or the name
// of a module in node_modules
'node_modules/idb/build/idb.js': ['openDb'],
'node_modules/firebase/dist/index.cjs.js': ['initializeApp', 'firestore'],
},
}),
@pjarnfelt that seems to be a good approach for now, I think it makes the initial app instance and firestore work nicely. Two more things I'd like to ask you
rollup.config.js
like he did https://github.com/sveltecasts/009-sapper-firestore/blob/master/rollup.config.js#L41 I'm getting this error even though I made this change as well:
✗ client
'deleteDb' is not exported by node_modules/idb/build/idb.js
4: import { __values, __spread, __awaiter, __generator, __assign } from 'tslib';
5: import { ErrorFactory } from '@firebase/util';
6: import { deleteDb, openDb } from 'idb';
^
onMount()
function in one of his components like this https://github.com/codediodeio/sveltefire/blob/master/src/FirebaseApp.svelte#L26I fixed the deleteDb
issue from above by importing from firebase/app
rather than just firebase
.
so there's more info on this funthing and it seems others hit this roadblock before https://stackoverflow.com/questions/56315901/how-to-import-firebase-only-on-client-in-sapper
@evdama Yes, I did the named exports from your link. Forgot to mention that. Will edit my comment.
yes I'm using performance and analytics, not storage though, but I suspect that would work too. I added them to the client.js where I instantiate the app.
Now I'm just having trouble hosting it on firebase functions/hosting
Now I'm just having trouble hosting it on firebase functions/hosting
@pjarnfelt there you go https://dev.to/eckhardtd/how-to-host-a-sapper-js-ssr-app-on-firebase-hmb :)
@evdama thanks for the link. I'm already doing that (hosting ssr through functions), but the troubles I'm having is that my performance is terrible. Now I got preloading and ssr on my firestore content and ssr-auth handling to avoid the front-end loading delay of the authentication (also based on a sveltecasts video). Now my first load on normal internet speed is around 10 sec and interaction around 18, which defies the whole purpose of Svelte/Sapper. I need to debug more to find the issues.
@pjarnfelt Interessting, my site is much quicker to first inital paint, about 0.6 seconds or so... Either way, you must be doing something odd otherwise that can't be explained. Try with lighthouse and see what it tells you to improve.
I've gotten a version of ssr and preloading up and running following this video: Sveltecasts - Sapper & Firebase Firestore
Essentially there needs to be 2 versions of Firestore loaded. He loads clientside firebase stuff like Jeff in the scripts and then has a conditional definition of Firestore This is my version in firestore.js
export async function firestore() { if (process.browser) { return window.db } else { const firebase = await import('firebase') const config = await import('./config'); if (firebase.apps.length == 0) { let app = firebase.initializeApp(config.default) return app.firestore() } else { return firebase.apps[0].firestore() } } }
on the page I need preloaded data:
<script context="module"> // load the above file import { firestore } from "../../firebase/firestore"; export async function preload({ params, query }) { let db = await firestore(); const data = await db .collection("collection") .doc(params.slug) .get(); if (data.exists) { return { document: data.data() }; } else { this.error("no data"); } } </script> <script> // this document is returned from the preload and ready for usage in html export let document; </script>
if the client.js
import * as sapper from '@sapper/app'; import firebase from 'firebase/app'; import 'firebase/firestore'; import {default as config} from './firebase/config' let app = firebase.initializeApp(config) window.db = app.firestore() sapper.start({ target: document.querySelector('#sapper') });
edit: I forgot to mention that I also updated the rollup.config.js file with names exports as mentioned here.
//--- rollup.config.js --- commonjs({ namedExports: { // left-hand side can be an absolute path, a path // relative to the current directory, or the name // of a module in node_modules 'node_modules/idb/build/idb.js': ['openDb'], 'node_modules/firebase/dist/index.cjs.js': ['initializeApp', 'firestore'], }, }),
This seems to work wonders, but I can't seem to get this to work with SvelteFire; it complains that I don't have firebase/firestore
imported, which I am not sure how to do because of the whole client-server incompatibility.
Anyone got any suggestions to get both this method of SSR and SvelteFire to work?
I got some errors while using @pjarnfelt answer
firebase.js
export async function firestore() {
if (process.browser) return window.db
const firebase = await import('firebase/app')
if (firebase.apps.length == 0) {
let app = firebase.initializeApp(firebaseConfig)
return app.firestore()
} else {
return firebase.apps[0].firestore()
}
}
Client side firestore works but SSR seems not working
First issue I had was Cannot read property 'length' of undefined
at if (firebase.apps.length == 0) {
. So I updated if condition to !firebase.apps || firebase.apps.length == 0
. Then there's a new error TypeError: firebase.initializeApp is not a function
. I've no clue why is that happening :/
I finally use this way which work fine : create a firebase.js file in src/
import firebase from "firebase/app";
import 'firebase/firestore';
import 'firebase/auth';
import 'firebase/performance';
import 'firebase/analytics';
const firebaseConfig = {...}
export function initFirebase() {
firebase.initializeApp(firebaseConfig);
return firebase;
}
add this in client.js
import { initFirebase } from './firebase.js';
window.firebase = initFirebase();
and then you can access firebase
from window
in onMount
functions
no need to change rollup, just be careful to put firebase libs in packages.json devDependencies (with --save-dev)
for the firestore aspect, because I use SSR and have SEO constraints, I only use this.fetch
and make firebase call with firebase-admin
and put it in packages.json dependencies to not rollup them (all dependencies are interpreted as external in the rollup.config file)
hope this helps
@sebmade Your solution looks nice! I'm having trouble getting it to work though. Do you mind sharing an example of how you use onMount
and this.fetch
? Thanks!
@alfrednerstu if you want to use onMount()
just call window.firebase
onMount(() => { window.firebase.... })
if you want to use this.fetch
in the preload
function, you have to create a function on server side and use firebase-admin
lib.
it don't make sense to use window.firebase
with this.fetch in my opinion, this.fetch is used to make a server call wherever you are on client side or on server side during the ssr processing.
but if you really want you can by testing the env var process.browser
in preload
and calling window.firebase
when it's true and this.fetch
when it's false
if it's not working, send me error you have so I could help
@sebmade So you do everything twice? Both in onMount
and preload
? I would prefer to do everything once in preload
. I have got client side working but server side is still complaining that IDBIndex is not defined
ie the client side library of Firebase is loaded instead of the server side one.
@alfrednerstu no, because you can't compile ssr with firebase libs, so it depends of my needs, for authentication and when user is connected I use client side firebase call, when not I sue server side firebase call
IDBIndex error is due to compilation of firebase lib, be sure to have firebase lib in devDependencies in your package.json and don't make firebase call in directly in preload function, just on the server side files.
@sebmade would you be willing to provide some example code similar to @pjarnfelt post from Jan 16? Thanks
Hey guys, I did not read the entire thread, but I just wanted to add my 2 cents. In my personal Sapper project I've written this file. https://github.com/Evertt/sapper-cms/blob/main/src/store/firebase.ts
And it makes it possible to write import { fbApp } from "./store"
anywhere in your Sapper app and it will give you the correct instance of firebase every time. And it doesn't use any asynchronous loading which is nice because then you don't have to execute an async function first.
So this would work perfectly if sveltefire would just try not to use any database APIs that are different between the web version of firebase and firebase-admin.
Hey Evertt, would you mind to provide a short gist for us, non-dev savvies, how to go on with Sapper and Firestore, please?
@sebmade would you be willing to provide some example code similar to @pjarnfelt post from Jan 16? Thanks
Hello @vc-ca, The code in https://github.com/codediodeio/sveltefire/issues/4#issuecomment-662580999 not suits you ?
Getting Sapper working with Firestore would be awesome. I've gotten Sapper SSR deployed to Firebase Cloud functions, which is really cool, but sapper not supporting firebase is very disappointing!
I moved Firestore to devDependencies, but I'm still getting this error:
@firebase/app:
Warning: This is a browser-targeted Firebase bundle but it appears it is being
run in a Node environment. If running in a Node environment, make sure you
are using the bundle specified by the "main" field in package.json.
If you are using Webpack, you can specify "main" as the first item in
"resolve.mainFields":
https://webpack.js.org/configuration/resolve/#resolvemainfields
If using Rollup, use the rollup-plugin-node-resolve plugin and specify "main"
as the first item in "mainFields", e.g. ['main', 'module'].
https://github.com/rollup/rollup-plugin-node-resolve
I tried adding "mainFields": ['main', 'module']
but it didn't change anything, and I don't understand this.
I also don't get why I'd want firebase used from the server code at all? Isn't it correct that firebase should only run client side? Or am I misunderstanding something fundamental here?
Either way, what's the reasonable solution to using firebase with a Sapper/SSR web app? Is it really one of these methods of side-loading the dependency or sourcing it in template.html !? If so, what's the right way to do it?
@makeitTim Firebase works with Sapper, I've already deploy a complete application using firestore, storage, stripe ... The problem is during the compilation phase. I don't know why but referencing firebase during the SSR process failed. So there is 2 ways to make it works : use firebase only on server side (in server.js or in [file].json.js) or use firebase only in functions (onMount or event functions).
@sebmade Very cool! Are you using Firebase Hosting and Functions to host SSR?
I still don't completely understand those choices ... why would I want to use firebase Firestore db on the sapper/ssr server side at all? (And isn't the firebase library specifically preventing it?)
I think my use case is the typical and obvious one --- With a Sapper app I want to use Firestore as the backend and Firebase Auth for users to login and access their data. The app is comprised of detail screens of Firestore data and forms for updating the data.
How do I do that?
Is this library, sveltefire useful? It looks cool, but this whole lack of setup is worrying and makes me feel like I'd be more comfortable working directly with firebase app.db.
Thanks!
@makeitTim I have a working project using firestore both client-side and server-side (as in during SSR). Not using sveltefire though, but using my own firestore wrapper. My project is still very buggy, but it proves the concept at least.
I'm a bit too lazy to explain though, so I'll just show you the code and the result.
This code: https://github.com/Evertt/sapper-cms/tree/real-world-app
Produces this website: https://mytryout-246d2.web.app/
btw don't think about the fact that the repo is called sapper-cms
. That was the goal when I started it, but I got distracted. It's not a cms.
@makeitTim yes I use firebase hosting, I don't try to use @google-cloud/firestore api independently I don't use sveltefire finally
Opening an issue to determine the best way to work with Firebase in Sapper. Currently, bundling with rollup leads to issues that seem related to https://github.com/firebase/firebase-js-sdk/issues/1797
One possible solution is to make Firebase global with script tags.
template.html
_layout.svelte
This works, but does not address how to preload data from Firebase.