How to tell a Vue app to use Firebase emulator?
UPDATED on 14 Jan 2021 to reflect changes to Firebase SDK.
Official documentation on connecting to Firestore emulator is here: https://firebase.google.com/docs/emulator-suite/connect_firestore
Official documentation on connecting to Functions emulator is here: https://firebase.google.com/docs/emulator-suite/connect_functions
In practice the setup will look something like this:
import * as Firebase from 'firebase/app';
import 'firebase/auth';
import 'firebase/firestore';
import 'firebase/functions';
import 'firebase/storage';
const firebaseConfig = { <Per Firebase Console> };
!Firebase.apps.length ? Firebase.initializeApp(firebaseConfig) : '';
if(window.location.hostname === 'localhost') {
Firestore.firestore().useEmulator('localhost', 8080);
Firestore.functions().useEmulator('localhost', 5001);
/* OLD implementation */
// Firebase.firestore().settings({ host: 'localhost:8080', ssl: false });
// Firebase.functions().useFunctionsEmulator('http://localhost:5001');
}
export const GoogleAuthProvider = new Firebase.auth.GoogleAuthProvider();
export const FirebaseAuth = Firebase.auth();
export const Firestore = Firebase.firestore();
export const FirebaseFunctions = Firebase.functions();
export const FirebaseStorage = Firebase.storage();
export default Firebase;
This can be imported into Vuex store, or any other page like this:
import { Firestore, FirebaseFunctions } from '@/services/firebase.js';
Then in command prompt / terminal run:
firebase emulators:start
This will also work with Nuxt.
Ok, so this is going to be a long answer, but I'm hoping to answer your question as completely as possible. The process really works in two stages: making the emulator (including hot reloading) work with Vue, then making Vue work with the emulated version of Firebase.
Step 1: Making the Firebase emulator work with Vue
For the first step, you need to edit your package.json
to setup Vue to do a watch/build cycle instead of a hot reload cycle, as shown below. The only thing that won't work (AFAIK) is the Vue DevTools extention. (Quick note, the run-s
and run-p
commands I'm using are from the npm-run-all
package because I'm on Windows and cmd.exe
doesn't like single ampersands &
). Also, to use the firebase
command from your scripts, you need to install the firebase-tools
package as a dev dependency.
"scripts": {
"build": "vue-cli-service build",
"build:dev": "vue-cli-service build --mode development",
"build:watch": "vue-cli-service build --mode development --watch --no-clean",
"lint": "vue-cli-service lint",
"serve": "run-s build:dev watch",
"serve:firebase": "firebase serve",
"watch": "run-p build:watch serve:firebase"
}
to install the dev dependencies required
npm i --save-dev firebase-tools npm-run-all
So, that's a lot. Let me breakdown what each command is doing:
watch
: This command is just the shell command that gets everything going. It runs thebuild
andserve
commands in series. More on this laterserve
: This command starts the actual watch/build cycle. It starts the Firebase emulator and starts thevue-cli-service
to watch for changes.serve:firebase
: This command starts the Firebase emulator...that's all.
build
: This command does a production build...not really used here, but it's left in for completeness.
build:dev
: This command is a bit important. If you notice, in the initialwatch
script, we called thebuild:dev
script first. This is because if you start the Firebase emulator and your "public" directory (note: this is Firebase's public directory, not Vue's) gets deleted, then Firebase will actually crash. So, to counteract this, we complete a build before starting the build/watch cycle.build:watch
: This is where the hot reload magic happens. We tell thevue-cli-service
to build the app, but also to watch for changes and not clean the build directory. The watching for changes starts the watch/build cycle mentioned earlier. We tell it to not clean the build directory because Firebase doesn't care if the files inside the directory its serving from change, but it will crash if the directory gets deleted.
The only downside to this method is that the Vue DevTools do not work.
Step 2: Making Vue work with the Firebase emulator
Turns out there is actually a very simple solution to this problem thanks to the Firebase Documentation. What you need to do is request a special file from the Firebase reserved URLs. In my example, I use Axios, but by all means, feel free to use whatever library for making requests you would like.
import axios from 'axios';
import firebase from '@firebase/app';
import '@firebase/auth';
import '@firebase/firestore';
import '@firebase/functions';
axios.get('/__/firebase/init.json').then(async response => {
firebase.initializeApp(await response.data);
});
export default firebase;
Also, to add a property to the Vue instance, it would be better to do this, to avoid any issues with garbage collection or naming collisions. Then, within any Vue component, you can just use this.$firebase
.
import Vue from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
import firebase from './plugins/firebase';
Vue.config.productionTip = false;
Vue.prototype.$firebase = firebase;
new Vue({
router,
store,
render: h => h(App),
}).$mount('#app');
Ideally, there would be some way to distinguish whether the application is running within the emulator or not, but in practice the only problem it would solve is the ability to use the Vue DevTools extension, which I don't really view (pun intended) as a requirement. But, with all of that out of the way, you should be up and running in the emulator, with live reloading; and, to top it all off, once your ready, you don't have to make any changes to your application to deploy it.
Bonus: Deployment
So, here is another scripts section that has all of the same things as above, but also includes a 1-command deploy to make sure you deploy a production build from Vue to Firebase.
"scripts": {
"build": "vue-cli-service build",
"build:dev": "vue-cli-service build --mode development",
"build:watch": "vue-cli-service build --mode development --watch --no-clean",
"deploy": "run-s build deploy:firebase",
"deploy:firebase": "firebase deploy",
"lint": "vue-cli-service lint",
"serve": "run-s build:dev watch",
"serve:firebase": "firebase serve",
"watch": "run-p build:watch serve:firebase"
}
Updated with firebase web version 9 in Nov 2021: Using the same configuration setup as starleaf1 change the firebase.js to the following:
import { initializeApp } from "firebase/app";
import { getAuth, connectAuthEmulator } from "firebase/auth";
import { getFirestore, connectFirestoreEmulator } from "firebase/firestore";
initializeApp({
apiKey: "xxx",
authDomain: "xxx",
projectId: "xxx",
storageBucket: "xxx",
messagingSenderId: "xxx",
appId: "xxx"
});
const db = getFirestore();
const auth = getAuth();
// If on localhost, use all firebase services locally
if (location.hostname === 'localhost') {
connectFirestoreEmulator(db, 'localhost', 8080);
connectAuthEmulator(auth, "http://localhost:9099");
// add more services as described in the docs: https://firebase.google.com/docs/emulator-suite/connect_firestore
}
export { db, auth };
That's it :-) It took me over 10 h to figure this out.