Skip to content
This repository was archived by the owner on Apr 17, 2023. It is now read-only.

Commit 39fdec9

Browse files
committed
I initially tried to go to [email protected], the current release, but it redoes the way that module imports work. It seems straightforward to update our code to use the 'compat' versions of the modules per the instructions at https://firebase.google.com/docs/web/modular-upgrade, but the third-party vuefire package doesn't seem to have been updated yet (vuejs/vuefire#1128) so we're stuck at version 8 for now. Also switch from the deprecated @firebase/testing package to @firebase/rules-unit-testing. Luckily, it seems to be a drop-in replacement. To get all of this working, I also ripped out all of the code I'd written before to asynchronously import Firebase modules. It made the code way more complicated for what was probably only a little bit of improvement in loading time, and I spent a lot of time trying to figure out how to update it to work with version 8 of the Firebase API before giving up. Version 9 finally supports tree-shaking, so hopefully things will get faster again when we're able to upgrade to it.
1 parent 51bacd2 commit 39fdec9

19 files changed

+8097
-5352
lines changed

firestore.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
// https://firebase.google.com/docs/firestore/security/test-rules-emulator
1515
// https://github.com/firebase/quickstart-nodejs/tree/master/firestore-emulator/typescript-quickstart
1616

17-
import * as firebase from '@firebase/testing';
17+
import * as firebase from '@firebase/rules-unit-testing';
1818

1919
const projectId = `firestore-test-${Date.now()}`;
2020

package-lock.json

Lines changed: 7823 additions & 4994 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"acorn": "^7.1.1",
2424
"browserslist": "^4.17.2",
2525
"core-js": "^3.4.4",
26-
"firebase": "^7.14.5",
26+
"firebase": "^8.10.0",
2727
"firebaseui": "^4.0.0",
2828
"humanize-duration": "^3.20.1",
2929
"register-service-worker": "^1.6.2",
@@ -34,11 +34,11 @@
3434
"vue-property-decorator": "^8.1.0",
3535
"vue-router": "^3.0.7",
3636
"vue-the-mask": "^0.11.1",
37-
"vuefire": "^2.1.0",
37+
"vuefire": "^2.2.5",
3838
"vuetify": "^2.2.18"
3939
},
4040
"devDependencies": {
41-
"@firebase/testing": "^0.15.1",
41+
"@firebase/rules-unit-testing": "^1.3.15",
4242
"@mdi/font": "^4.4.95",
4343
"@types/humanize-duration": "^3.18.0",
4444
"@types/jest": "^24.0.17",
@@ -58,7 +58,7 @@
5858
"eslint": "^6.6.0",
5959
"eslint-plugin-vue": "^6.2.2",
6060
"eslint-plugin-vuetify": "^1.0.0-beta.6",
61-
"firebase-tools": "^7.2.4",
61+
"firebase-tools": "^9.21.0",
6262
"flush-promises": "^1.0.2",
6363
"jest": "^24.9.0",
6464
"nightwatch": "^1.7.11",

src/App.vue

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
<script lang="ts">
4444
import { Component, Mixins } from 'vue-property-decorator';
4545

46-
import { getAuth } from '@/firebase';
46+
import { app } from '@/firebase';
4747
import Perf from '@/mixins/Perf';
4848
import Toolbar from '@/components/Toolbar.vue';
4949

@@ -77,10 +77,8 @@ export default class App extends Mixins(Perf) {
7777
}
7878

7979
mounted() {
80-
getAuth().then(auth => {
81-
auth.onAuthStateChanged(user => {
82-
this.signedIn = !!user;
83-
});
80+
app.auth().onAuthStateChanged((user: any) => {
81+
this.signedIn = !!user;
8482
});
8583

8684
this.$nextTick(() => {

src/components/Toolbar.vue

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@
9292

9393
<script lang="ts">
9494
import { Component, Prop, Vue } from 'vue-property-decorator';
95-
import { getAuth } from '@/firebase';
95+
import { app } from '@/firebase';
9696
import DialogCard from '@/components/DialogCard.vue';
9797
import LocalePicker from '@/components/LocalePicker.vue';
9898

@@ -195,11 +195,12 @@ export default class Toolbar extends Vue {
195195

196196
// Handles the "Sign out" button being clicked in the "Sign out" dialog.
197197
signOut() {
198-
getAuth().then(auth => {
199-
auth.signOut().then(() => {
198+
app
199+
.auth()
200+
.signOut()
201+
.then(() => {
200202
this.$router.replace('login');
201203
});
202-
});
203204
}
204205
}
205206
</script>

src/firebase/index.ts

Lines changed: 31 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,21 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5-
// This file exports methods that can be used to load different parts of
6-
// Firebase asynchronously.
7-
8-
// Loads the core Firebase library asynchronously.
9-
// Outside code should only call this if it needs access to Firebase's weird
10-
// constant values, e.g. firebase.auth.GoogleAuthProvider or
11-
// firebase.firestore.FieldValue.delete().
12-
export function getFirebase() {
13-
return import(/* webpackChunkName: "firebase-app" */ 'firebase/app');
14-
}
15-
16-
// Loads Firebase auth asynchronously.
17-
export function getAuth(): Promise<firebase.auth.Auth> {
18-
return Promise.all([
19-
import(/* webpackChunkName: "firebase-init" */ './init'),
20-
import(/* webpackChunkName: "firebase-auth" */ 'firebase/auth'),
21-
]).then(([init, _]) => init.app.auth());
22-
}
5+
import firebase from 'firebase/app';
6+
7+
import 'firebase/auth';
8+
import 'firebase/firestore';
9+
import 'firebase/functions';
10+
11+
export const app = firebase.initializeApp({
12+
apiKey: process.env.VUE_APP_FIREBASE_API_KEY,
13+
authDomain: process.env.VUE_APP_FIREBASE_AUTH_DOMAIN,
14+
databaseURL: process.env.VUE_APP_FIREBASE_DATABASE_URL,
15+
projectId: process.env.VUE_APP_FIREBASE_PROJECT_ID,
16+
storageBucket: process.env.VUE_APP_FIREBASE_STORAGE_BUCKET,
17+
messagingSenderId: process.env.VUE_APP_FIREBASE_MESSAGING_SENDER_ID,
18+
appId: process.env.VUE_APP_FIREBASE_APP_ID,
19+
});
2320

2421
// Firestore persistence state.
2522
export enum FirestorePersistence {
@@ -36,44 +33,20 @@ export function getFirestorePersistence() {
3633
return persistence;
3734
}
3835

39-
// Loads Cloud Firestore asynchronously.
40-
export function getFirestore(): Promise<firebase.firestore.Firestore> {
41-
return Promise.all([
42-
import(/* webpackChunkName: "firebase-init" */ './init'),
43-
import(/* webpackChunkName: "firebase-firestore" */ 'firebase/firestore'),
44-
]).then(([init, _]) => {
45-
// Enable persistence the first time the module is loaded:
46-
// https://firebase.google.com/docs/firestore/manage-data/enable-offline
47-
if (persistence == FirestorePersistence.UNINITIALIZED) {
48-
persistence = FirestorePersistence.INITIALIZING;
49-
init.app
50-
.firestore()
51-
.enablePersistence({ synchronizeTabs: true })
52-
.then(() => {
53-
persistence = FirestorePersistence.ENABLED;
54-
})
55-
.catch(err => {
56-
persistence = FirestorePersistence.DISABLED;
57-
import('@/log').then(log => {
58-
log.logError('firestore_persistence_failed', err);
59-
});
60-
});
61-
}
62-
63-
return init.app.firestore();
64-
});
65-
}
66-
67-
// Loads Cloud Functions asynchronously.
68-
export function getFunctions(): Promise<firebase.functions.Functions> {
69-
return Promise.all([
70-
import(/* webpackChunkName: "firebase-init" */ './init'),
71-
import(/* webpackChunkName: "firebase-functions" */ 'firebase/functions'),
72-
]).then(([init, _]) => init.app.functions());
73-
}
74-
75-
// Loads FirebaseUI asynchronously. Note that this isn't part of the core
76-
// Firebase library.
77-
export function getFirebaseUI() {
78-
return import(/* webpackChunkName: "firebaseui" */ 'firebaseui');
36+
// Enable persistence the first time the module is loaded:
37+
// https://firebase.google.com/docs/firestore/manage-data/enable-offline
38+
if (persistence == FirestorePersistence.UNINITIALIZED) {
39+
persistence = FirestorePersistence.INITIALIZING;
40+
app
41+
.firestore()
42+
.enablePersistence({ synchronizeTabs: true })
43+
.then(() => {
44+
persistence = FirestorePersistence.ENABLED;
45+
})
46+
.catch((err) => {
47+
persistence = FirestorePersistence.DISABLED;
48+
import('@/log').then((log) => {
49+
log.logError('firestore_persistence_failed', err);
50+
});
51+
});
7952
}

src/firebase/init.ts

Lines changed: 0 additions & 17 deletions
This file was deleted.

src/firebase/mock.ts

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import Vue from 'vue';
66
import { deepCopy } from '@/testutil';
77

8+
import type firebase from 'firebase';
9+
810
type DocumentReference = firebase.firestore.DocumentReference;
911

1012
// Firestore document data as property names to values.
@@ -144,7 +146,7 @@ class MockWriteBatch {
144146
this._ops.push(new BatchUpdate(ref.path, props));
145147
}
146148
commit() {
147-
return new Promise(resolve => {
149+
return new Promise((resolve) => {
148150
for (const op of this._ops) {
149151
if (op instanceof BatchSet) {
150152
MockFirebase.setDoc(op.path, op.data);
@@ -250,7 +252,7 @@ export const MockFirebase = new (class {
250252
// Map of global mocks that should be passed to vue-test-utils's mount() or
251253
// shallowMount().
252254
mountMocks: Record<string, any> = {
253-
$bind: function(name: string, ref: DocumentReference) {
255+
$bind: function (name: string, ref: DocumentReference) {
254256
if (!Object.prototype.hasOwnProperty.call(MockFirebase._docs, ref.path)) {
255257
return Promise.reject(`No document at ${ref.path}`);
256258
}
@@ -268,21 +270,21 @@ export const MockFirebase = new (class {
268270
return Promise.resolve(data);
269271
},
270272

271-
$unbind: function(name: string) {
273+
$unbind: function (name: string) {
272274
// Unregister the binding so the Vue's data property won't be updated.
273275
const vm = this as Vue;
274276
for (const path of Object.keys(MockFirebase._bindings)) {
275277
MockFirebase._bindings[path] = MockFirebase._bindings[path].filter(
276-
b => !(b.vm == vm && b.name == name)
278+
(b) => !(b.vm == vm && b.name == name)
277279
);
278280
}
279281
vm.$data[name] = null;
280282
},
281283
};
282284
})();
283285

284-
export const MockEmailAuthProviderID = 'email';
285-
export const MockGoogleAuthProviderID = 'google';
286+
export const MockEmailAuthProviderID = 'password';
287+
export const MockGoogleAuthProviderID = 'google.com';
286288

287289
jest.mock('firebase');
288290
jest.mock('firebase/app', () => {
@@ -296,7 +298,7 @@ jest.mock('firebase/app', () => {
296298
return MockFirebase.currentUser;
297299
},
298300
onAuthStateChanged: (observer: any) => {
299-
new Promise(resolve => {
301+
new Promise((resolve) => {
300302
observer(MockFirebase.currentUser);
301303
resolve();
302304
});
@@ -315,8 +317,18 @@ jest.mock('firebase/app', () => {
315317
}),
316318
};
317319

318-
// Also set sentinel value for deleting fields and static Timestamp.fromMillis
319-
// function, both of which live on the firestore method.
320+
// Various Firebase constants and sentinels are implemented via static
321+
// variables or methods on classes in the firebase.auth and firebase.firestore
322+
// namespaces. I've wasted hours trying to find the right way to mock this
323+
// without any luck, so I'm just simulating these by setting properties on the
324+
// corresponding methods instead. This is totally wrong and would fall apart
325+
// if these code were type-checked.
326+
(app.auth as any).EmailAuthProvider = {
327+
PROVIDER_ID: MockEmailAuthProviderID,
328+
};
329+
(app.auth as any).GoogleAuthProvider = {
330+
PROVIDER_ID: MockGoogleAuthProviderID,
331+
};
320332
(app.firestore as any).FieldValue = {
321333
delete: () => mockDeleteSentinel,
322334
};
@@ -328,16 +340,9 @@ jest.mock('firebase/app', () => {
328340
}),
329341
};
330342

331-
// Set some random const properties on the auth method.
332-
const authAny = app.auth as any;
333-
authAny.EmailAuthProvider = { PROVIDER_ID: MockEmailAuthProviderID };
334-
authAny.GoogleAuthProvider = { PROVIDER_ID: MockGoogleAuthProviderID };
335-
336343
return app;
337344
});
338345

339-
// TODO: Is this actually necessary? These modules are imported for their side
340-
// effects in the code that's being tested.
341346
jest.mock('firebase/auth');
342347
jest.mock('firebase/firestore');
343348
jest.mock('firebase/functions');

src/log/index.ts

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// found in the LICENSE file.
44

55
import { Logger, LogFunc } from './logger';
6-
import { getAuth, getFunctions } from '@/firebase';
6+
import { app } from '@/firebase';
77

88
enum LogDest {
99
STACKDRIVER,
@@ -24,12 +24,10 @@ const devLogDest = process.env.VUE_APP_LOG_TO_CONSOLE
2424
function getLogFunc(): LogFunc | Promise<LogFunc> {
2525
// @ts-ignore: Make it easy to manually change |devLogDest|.
2626
if (isProd || (isDev && devLogDest == LogDest.STACKDRIVER)) {
27-
// Return a promise so that importing this module doesn't require
28-
// synchronously loading bulky Firebase code.
29-
return getFunctions().then(functions => functions.httpsCallable('Log'));
27+
return app.functions().httpsCallable('Log');
3028
}
3129
if (isDev && devLogDest == LogDest.CONSOLE) {
32-
return data => {
30+
return (data) => {
3331
for (const rec of data.records) {
3432
console.log(
3533
`${rec.severity} ${rec.code}: ${JSON.stringify(rec.payload)}`
@@ -51,21 +49,15 @@ const defaultLogger = new Logger(
5149
// Helper function that sends a log message to Stackdriver.
5250
// See the Logger class's log method for more details.
5351
function log(severity: string, code: string, payload: Record<string, any>) {
54-
getAuth()
55-
.then(auth => {
56-
const user = auth.currentUser;
57-
if (!user) {
58-
defaultLogger.log(severity, code, payload);
59-
return;
60-
}
61-
user.getIdToken().then(
62-
token => defaultLogger.log(severity, code, payload, token),
63-
err => console.log('Failed to get ID token:', err)
64-
);
65-
})
66-
.catch(err => {
67-
console.error(`Failed to import Firebase auth code: ${err}`);
68-
});
52+
const user = app.auth().currentUser;
53+
if (!user) {
54+
defaultLogger.log(severity, code, payload);
55+
return;
56+
}
57+
user.getIdToken().then(
58+
(token) => defaultLogger.log(severity, code, payload, token),
59+
(err) => console.log('Failed to get ID token:', err)
60+
);
6961
}
7062

7163
// Sends a record to Stackdriver with DEBUG severity.

0 commit comments

Comments
 (0)