1. Antes de comenzar
El SDK modular de Firebase JS es una nueva versión del SDK de JS existente y se lanzará como la próxima versión principal. Permite que los desarrolladores excluyan el código sin usar del SDK de Firebase JS para crear paquetes más pequeños y lograr un mejor rendimiento.
La diferencia más notable en el SDK modular de JS es que las funciones ahora se organizan en funciones flotantes que importará, en lugar de un solo espacio de nombres firebase
que lo incluya todo. Esta nueva forma de organización del código es lo que permite el desprendimiento de árboles, y aprenderás a actualizar cualquier app que actualmente use el SDK de Firebase JS v8 al nuevo SDK modular.
Para proporcionar un proceso de actualización fluido, se proporciona un conjunto de paquetes de compatibilidad. En este codelab, aprenderás a usar los paquetes de compatibilidad para portar la app por partes.
Qué compilarás
En este codelab, migrarás gradualmente una app web de lista de seguimiento de acciones existente que usa el SDK de JS v8 al nuevo SDK modular de JS en tres etapas:
- Actualiza la app para usar los paquetes de compatibilidad
- Actualiza la app de los paquetes de compatibilidad a la API modular por partes
- Usa Firestore Lite, una implementación liviana del SDK de Firestore, para mejorar aún más el rendimiento de la app
Este codelab se enfoca en actualizar el SDK de Firebase. Otros conceptos y bloques de código se pasan por alto y se proporcionan para que simplemente los copies y pegues.
Requisitos
2. Prepárate
Obtén el código
Todo lo que necesitas para este proyecto reside en un repositorio de Git. Para comenzar, deberás obtener el código y abrirlo en tu entorno de desarrollo favorito.
Clona el repositorio de GitHub del codelab desde la línea de comandos:
git clone https://github.com/FirebaseExtended/codelab-modular-sdk.git
Como alternativa, si no tienes git instalado, puedes descargar el repositorio como archivo ZIP y descomprimirlo.
Importa la app
- En tu IDE, abre o importa el directorio
codelab-modular-sdk
. - Ejecuta
npm install
para instalar las dependencias necesarias para compilar y ejecutar la app de forma local. - Ejecuta
npm run build
para compilar la app. - Ejecuta
npm run serve
para iniciar el servidor web. - Abre una pestaña del navegador y ve a http://localhost:8080.
3. Establece un modelo de referencia
¿Cuál es tu punto de partida?
Tu punto de partida es una app de lista de seguimiento de acciones diseñada para este codelab. El código se simplificó para ilustrar los conceptos de este codelab y tiene poca administración de errores. Si decides reutilizar este código en una app de producción, asegúrate de controlar los errores y probar todo el código por completo.
Asegúrate de que todo funcione en la app:
- Accede de forma anónima con el botón Acceder que se encuentra en la esquina superior derecha.
- Después de acceder, busca y agrega "NFLX", "SBUX" y "T" a la lista de seguimiento. Para ello, haz clic en el botón Agregar, escribe las letras y haz clic en la fila de resultados de la búsqueda que aparece debajo.
- Para quitar una acción de la lista de seguimiento, haz clic en la x que aparece al final de la fila.
- Mira las actualizaciones en tiempo real del precio de las acciones.
- Abre las herramientas para desarrolladores de Chrome, ve a la pestaña Red y marca Inhabilitar la caché y Usar filas de solicitud grandes. Inhabilitar la caché garantiza que siempre obtengamos los cambios más recientes después de una actualización, y Usar filas de solicitudes grandes hace que la fila muestre el tamaño transmitido y el tamaño del recurso. En este codelab, nos interesa principalmente el tamaño de
main.js
.
- Carga la app en diferentes condiciones de red con la limitación simulada. En este codelab, usarás 3G lento para medir el tiempo de carga, ya que es donde un tamaño de paquete más pequeño ayuda más.
Ahora, comienza a migrar la app a la nueva API modular.
4. Usa los paquetes de compatibilidad
Los paquetes de compatibilidad te permiten actualizar a la nueva versión del SDK sin cambiar todo el código de Firebase de una sola vez. Puedes actualizarlos a la API modular de forma gradual.
En este paso, actualizarás la biblioteca de Firebase de la versión 8 a la nueva y cambiarás el código para usar los paquetes de compatibilidad. En los siguientes pasos, aprenderás a actualizar solo el código de Firebase Auth para usar primero la API modular y, luego, actualizar el código de Firestore.
Al final de cada paso, deberías poder compilar y ejecutar la app sin errores y ver una disminución en el tamaño del paquete a medida que migramos cada producto.
Obtén el nuevo SDK
Busca la sección de dependencias en package.json
y reemplázala por lo siguiente:
package.json
"dependencies": {
"firebase": "^9.0.0"
}
Cómo reinstalar las dependencias
Como cambiamos la versión de la dependencia, debemos volver a ejecutar npm install
para obtener la nueva versión de la dependencia.
Cómo cambiar las rutas de importación
Los paquetes de compatibilidad se exponen en el submódulo firebase/compat
, por lo que actualizaremos las rutas de importación según corresponda:
- Ve al archivo
src/firebase.ts
- Reemplaza las importaciones existentes por las siguientes:
src/firebase.ts
import firebase from 'firebase/compat/app';
import 'firebase/compat/auth';
import 'firebase/compat/firestore';
Verifica que la app funcione
- Ejecuta
npm run build
para volver a compilar la app. - Abre una pestaña del navegador y ve a http://localhost:8080 o actualiza la pestaña existente.
- Juega con la app. Todo debería seguir funcionando.
5. Actualiza Auth para usar la API modular
Puedes actualizar los productos de Firebase en cualquier orden. En este codelab, primero actualizarás Auth para aprender los conceptos básicos, ya que la API de Auth es relativamente simple. La actualización de Firestore es un poco más compleja, y aprenderás a hacerlo a continuación.
Actualiza la inicialización de Auth
- Ve al archivo
src/firebase.ts
- Agrega la siguiente importación:
src/firebase.ts
import { initializeAuth, indexedDBLocalPersistence } from 'firebase/auth';
- Borrar
import ‘firebase/compat/auth'.
- Reemplaza
export const firebaseAuth = app.auth();
por lo siguiente:
src/firebase.ts
export const firebaseAuth = initializeAuth(app, { persistence: [indexedDBLocalPersistence] });
- Quita
export type User = firebase.User;
al final del archivo.User
se exportará directamente ensrc/auth.ts
, que cambiarás a continuación.
Actualiza el código de autenticación
- Ve al archivo
src/auth.ts
- Agrega las siguientes importaciones a la parte superior del archivo:
src/auth.ts
import {
signInAnonymously,
signOut,
onAuthStateChanged,
User
} from 'firebase/auth';
- Quita
User
deimport { firebaseAuth, User } from './firebase';
, ya que ya importasteUser
desde‘firebase/auth'.
. - Actualiza las funciones para usar la API modular.
Como ya viste antes cuando actualizamos la sentencia de importación, los paquetes de la versión 9 se organizan en torno a funciones que puedes importar, a diferencia de las APIs de la versión 8, que se basan en un espacio de nombres y un patrón de servicio de cadena de puntos. Esta nueva organización del código permite el uso de Tree Shaking para el código no utilizado, ya que permite que las herramientas de compilación analicen qué código se usa y qué no.
En la versión 9, los servicios se pasan como el primer argumento a las funciones. Los servicios son los objetos que obtienes cuando inicializas un servicio de Firebase, p.ej., el objeto que se muestra desde getAuth()
o initializeAuth()
. Contienen el estado de un servicio de Firebase en particular, y la función usa el estado para realizar sus tareas. Apliquemos este patrón para implementar las siguientes funciones:
src/auth.ts
export function firebaseSignInAnonymously() {
return signInAnonymously(firebaseAuth);
}
export function firebaseSignOut() {
return signOut(firebaseAuth);
}
export function onUserChange(callback: (user: User | null) => void) {
return onAuthStateChanged(firebaseAuth, callback);
}
export { User } from 'firebase/auth';
Verifica que la app funcione
- Ejecuta
npm run build
para volver a compilar la app. - Abre una pestaña del navegador en http://localhost:8080 o actualiza la pestaña existente.
- Juega con la app. Todo debería seguir funcionando.
Cómo verificar el tamaño del paquete
- Abre las Herramientas para desarrolladores de Chrome.
- Cambia a la pestaña Red.
- Actualiza la página para capturar las solicitudes de red.
- Busca main.js y verifica su tamaño. Solo con cambiar algunas líneas de código, redujiste el tamaño del paquete en 100 KB (36 KB comprimidos con gzip), o un 22%. El sitio también se carga 0.75 s más rápido en una conexión 3G lenta.
6. Actualiza la app de Firebase y Firestore para usar la API modular
Actualiza la inicialización de Firebase
- Ve al archivo
src/firebase.ts.
- Reemplaza
import firebase from ‘firebase/compat/app';
por lo siguiente:
src/firebase.ts
import { initializeApp } from 'firebase/app';
- Reemplaza
const app = firebase.initializeApp({...});
por lo siguiente:
src/firebase.ts
const app = initializeApp({
apiKey: "AIzaSyBnRKitQGBX0u8k4COtDTILYxCJuMf7xzE",
authDomain: "exchange-rates-adcf6.firebaseapp.com",
databaseURL: "https://exchange-rates-adcf6.firebaseio.com",
projectId: "exchange-rates-adcf6",
storageBucket: "exchange-rates-adcf6.firebasestorage.app",
messagingSenderId: "875614679042",
appId: "1:875614679042:web:5813c3e70a33e91ba0371b"
});
Actualiza la inicialización de Firestore
- En el mismo archivo
src/firebase.ts,
, reemplazaimport 'firebase/compat/firestore';
por
src/firebase.ts
import { getFirestore } from 'firebase/firestore';
- Reemplaza
export const firestore = app.firestore();
por lo siguiente:
src/firebase.ts
export const firestore = getFirestore();
- Quita todas las líneas después de "
export const firestore = ...
".
Actualiza las importaciones
- Abrir el archivo
src/services.ts.
- Se quitan
FirestoreFieldPath
,FirestoreFieldValue
yQuerySnapshot
de la importación. La importación desde'./firebase'
ahora debería verse de la siguiente manera:
src/services.ts
import { firestore } from './firebase';
- Importa las funciones y los tipos que usarás en la parte superior del archivo:
**src/services.ts**
import {
collection,
getDocs,
doc,
setDoc,
arrayUnion,
arrayRemove,
onSnapshot,
query,
where,
documentId,
QuerySnapshot
} from 'firebase/firestore';
Actualiza search()
- Crea una referencia a la colección que contiene todos los tickers:
src/services.ts
const tickersCollRef = collection(firestore, 'current');
- Usa
getDocs()
para recuperar todos los documentos de la colección:
src/services.ts
const tickers = await getDocs(tickersCollRef);
Consulta search()
para ver el código terminado.
Actualiza addToWatchList()
Usa doc()
para crear una referencia de documento a la lista de seguimiento del usuario y, luego, agrégale un valor con setDoc()
con arrayUnion()
:
src/services.ts
export function addToWatchList(ticker: string, user: User) {
const watchlistRef = doc(firestore, `watchlist/${user.uid}`);
return setDoc(watchlistRef, {
tickers: arrayUnion(ticker)
}, { merge: true });
}
Actualiza deleteFromWatchList()
Del mismo modo, quita un valor de la lista de seguimiento del usuario con setDoc()
con arrayRemove()
:
src/services.ts
export function deleteFromWatchList(ticker: string, user: User) {
const watchlistRef = doc(firestore, `watchlist/${user.uid}`);
return setDoc(watchlistRef, {
tickers: arrayRemove(ticker)
}, { merge: true });
}
Actualiza subscribeToTickerChanges()
- Usa
doc()
para crear primero una referencia de documento a la lista de seguimiento del usuario y, luego, escucha los cambios en la lista de seguimiento cononSnapshot()
:
src/services.ts
const watchlistRef = doc(firestore, `watchlist/${user.uid}`);
const unsubscribe = onSnapshot(watchlistRef, snapshot => {
/* subscribe to ticker price changes */
});
- Una vez que tengas los símbolos en la lista de seguimiento, usa
query()
para crear una consulta que recupere sus precios y usaonSnapshot()
para escuchar sus cambios de precio:
src/services.ts
const priceQuery = query(
collection(firestore, 'current'),
where(documentId(), 'in', tickers)
);
unsubscribePrevTickerChanges = onSnapshot(priceQuery, snapshot => {
if (firstload) {
performance && performance.measure("initial-data-load");
firstload = false;
logPerformance();
}
const stocks = formatSDKStocks(snapshot);
callback(stocks);
});
Consulta subscribeToTickerChanges() para ver la implementación completa.
Actualiza subscribeToAllTickerChanges()
Primero, usarás collection()
para crear una referencia a la colección que contiene los precios de todos los tickers y, luego, usarás onSnapshot()
para escuchar los cambios de precios:
src/services.ts
export function subscribeToAllTickerChanges(callback: TickerChangesCallBack) {
const tickersCollRef = collection(firestore, 'current');
return onSnapshot(tickersCollRef, snapshot => {
if (firstload) {
performance && performance.measure("initial-data-load");
firstload = false;
logPerformance();
}
const stocks = formatSDKStocks(snapshot);
callback(stocks);
});
}
Verifica que la app funcione
- Ejecuta
npm run build
para volver a compilar la app. - Abre una pestaña del navegador en http://localhost:8080 o actualiza la pestaña existente.
- Juega con la app. Todo debería seguir funcionando.
Cómo verificar el tamaño del paquete
- Abre las Herramientas para desarrolladores de Chrome.
- Cambia a la pestaña Red.
- Actualiza la página para capturar las solicitudes de red.
- Busca
main.js
y verifica su tamaño. Vuelve a compararlo con el tamaño del paquete original. Redujimos el tamaño del paquete en más de 200 KB (63.8 KB comprimidos con gzip), o un 50% más pequeño, lo que se traduce en un tiempo de carga 1.3 s más rápido.
7. Usa Firestore Lite para acelerar la renderización inicial de la página
¿Qué es Firestore Lite?
El SDK de Firestore ofrece almacenamiento en caché complejo, transmisión en tiempo real, almacenamiento persistente, sincronización sin conexión en varias pestañas, reintentos, simultaneidad optimista y mucho más, por lo que es bastante grande. Sin embargo, es posible que solo quieras obtener los datos una vez, sin necesidad de usar ninguna de las funciones avanzadas. Para esos casos, Firestore creó una solución simple y liviana, un paquete nuevo: Firestore Lite.
Un gran caso de uso de Firestore Lite es optimizar el rendimiento de la renderización de la página inicial, en la que solo necesitas saber si un usuario accedió o no y, luego, leer algunos datos de Firestore para mostrarlos.
En este paso, aprenderás a usar Firestore Lite para reducir el tamaño del paquete y acelerar la renderización inicial de la página. Luego, cargarás el SDK principal de Firestore de forma dinámica para suscribirte a las actualizaciones en tiempo real.
Refactorizarás el código para lo siguiente:
- Mueve los servicios en tiempo real a un archivo independiente para que se puedan cargar de forma dinámica con la importación dinámica.
- Crea funciones nuevas para usar Firestore Lite y recuperar la lista de seguimiento y los precios de las acciones.
- Usa las nuevas funciones de Firestore Lite para recuperar datos y renderizar la página inicial. Luego, carga de forma dinámica los servicios en tiempo real para escuchar las actualizaciones en tiempo real.
Cómo mover servicios en tiempo real a un archivo nuevo
- Crea un archivo nuevo llamado
src/services.realtime.ts.
. - Mueve las funciones
subscribeToTickerChanges()
ysubscribeToAllTickerChanges()
desrc/services.ts
al archivo nuevo. - Agrega las importaciones necesarias a la parte superior del archivo nuevo.
Aún debes hacer algunos cambios:
- Primero, crea una instancia de Firestore desde el SDK principal de Firestore en la parte superior del archivo que se usará en las funciones. No puedes importar la instancia de Firestore desde
firebase.ts
aquí porque la cambiarás a una instancia de Firestore Lite en unos pocos pasos, que se usará solo para la renderización inicial de la página. - En segundo lugar, quítate de encima la variable
firstload
y el bloque if que la protege. Sus funciones se trasladarán a las funciones nuevas que crearás en el siguiente paso.
src/services.realtime.ts
import { User } from './auth'
import { TickerChange } from './models';
import { collection, doc, onSnapshot, query, where, documentId, getFirestore } from 'firebase/firestore';
import { formatSDKStocks } from './services';
const firestore = getFirestore();
type TickerChangesCallBack = (changes: TickerChange[]) => void
export function subscribeToTickerChanges(user: User, callback: TickerChangesCallBack) {
let unsubscribePrevTickerChanges: () => void;
// Subscribe to watchlist changes. We will get an update whenever a ticker is added/deleted to the watchlist
const watchlistRef = doc(firestore, `watchlist/${user.uid}`);
const unsubscribe = onSnapshot(watchlistRef, snapshot => {
const doc = snapshot.data();
const tickers = doc ? doc.tickers : [];
if (unsubscribePrevTickerChanges) {
unsubscribePrevTickerChanges();
}
if (tickers.length === 0) {
callback([]);
} else {
// Query to get current price for tickers in the watchlist
const priceQuery = query(
collection(firestore, 'current'),
where(documentId(), 'in', tickers)
);
// Subscribe to price changes for tickers in the watchlist
unsubscribePrevTickerChanges = onSnapshot(priceQuery, snapshot => {
const stocks = formatSDKStocks(snapshot);
callback(stocks);
});
}
});
return () => {
if (unsubscribePrevTickerChanges) {
unsubscribePrevTickerChanges();
}
unsubscribe();
};
}
export function subscribeToAllTickerChanges(callback: TickerChangesCallBack) {
const tickersCollRef = collection(firestore, 'current');
return onSnapshot(tickersCollRef, snapshot => {
const stocks = formatSDKStocks(snapshot);
callback(stocks);
});
}
Cómo usar Firestore Lite para recuperar datos
- Abrir
src/services.ts.
- Cambia la ruta de importación de
‘firebase/firestore'
a‘firebase/firestore/lite',
, agregagetDoc
y quitaonSnapshot
de la lista de importación:
src/services.ts
import {
collection,
getDocs,
doc,
setDoc,
arrayUnion,
arrayRemove,
// onSnapshot, // firestore lite doesn't support realtime updates
query,
where,
documentId,
QuerySnapshot,
getDoc // add this import
} from 'firebase/firestore/lite';
- Agrega funciones para recuperar los datos necesarios para la renderización inicial de la página con Firestore Lite:
src/services.ts
export async function getTickerChanges(tickers: string[]): Promise<TickerChange[]> {
if (tickers.length === 0) {
return [];
}
const priceQuery = query(
collection(firestore, 'current'),
where(documentId(), 'in', tickers)
);
const snapshot = await getDocs(priceQuery);
performance && performance.measure("initial-data-load");
logPerformance();
return formatSDKStocks(snapshot);
}
export async function getTickers(user: User): Promise<string[]> {
const watchlistRef = doc(firestore, `watchlist/${user.uid}`);
const data = (await getDoc(watchlistRef)).data();
return data ? data.tickers : [];
}
export async function getAllTickerChanges(): Promise<TickerChange[]> {
const tickersCollRef = collection(firestore, 'current');
const snapshot = await getDocs(tickersCollRef);
performance && performance.measure("initial-data-load");
logPerformance();
return formatSDKStocks(snapshot);
}
- Abre
src/firebase.ts
y cambia la ruta de importación de‘firebase/firestore'
a‘firebase/firestore/lite':
.
src/firebase.ts
import { getFirestore } from 'firebase/firestore/lite';
Reúne todo
- Abrir
src/main.ts.
- Necesitarás las funciones recién creadas para recuperar datos para la renderización inicial de la página y un par de funciones auxiliares para administrar el estado de la app. Ahora, actualiza las importaciones:
src/main.ts
import { renderLoginPage, renderUserPage } from './renderer';
import { getAllTickerChanges, getTickerChanges, getTickers } from './services';
import { onUserChange } from './auth';
import { getState, setRealtimeServicesLoaded, setUser } from './state';
import './styles.scss';
- Carga
src/services.realtime
con una importación dinámica en la parte superior del archivo. La variableloadRealtimeService
es una promesa que se resolverá con los servicios en tiempo real una vez que se cargue el código. La usarás más adelante para suscribirte a las actualizaciones en tiempo real.
src/main.ts
const loadRealtimeService = import('./services.realtime');
loadRealtimeService.then(() => {
setRealtimeServicesLoaded(true);
});
- Cambia la devolución de llamada de
onUserChange()
a una funciónasync
para que podamos usarawait
en el cuerpo de la función:
src/main.ts
onUserChange(async user => {
// callback body
});
- Ahora recupera los datos para que se renderice la página inicial con las funciones nuevas que creamos en el paso anterior.
En la devolución de llamada de onUserChange()
, busca la condición si en la que un usuario accedió y copia y pega el código dentro de la sentencia if:
src/main.ts
onUserChange(async user => {
// LEAVE THE EXISTING CODE UNCHANGED HERE
...
if (user) {
// REPLACE THESE LINES
// user page
setUser(user);
// show loading screen in 500ms
const timeoutId = setTimeout(() => {
renderUserPage(user, {
loading: true,
tableData: []
});
}, 500);
// get data once if realtime services haven't been loaded
if (!getState().realtimeServicesLoaded) {
const tickers = await getTickers(user);
const tickerData = await getTickerChanges(tickers);
clearTimeout(timeoutId);
renderUserPage(user, { tableData: tickerData });
}
// subscribe to realtime updates once realtime services are loaded
loadRealtimeService.then(({ subscribeToTickerChanges }) => {
unsubscribeTickerChanges = subscribeToTickerChanges(user, stockData => {
clearTimeout(timeoutId);
renderUserPage(user, { tableData: stockData })
});
});
} else {
// DON'T EDIT THIS PART, YET
}
}
- En el bloque else, en el que ningún usuario accedió, recupera la información de precios de todas las acciones con Firestore Lite, renderiza la página y, luego, escucha los cambios de precios una vez que se carguen los servicios en tiempo real:
src/main.ts
if (user) {
// DON'T EDIT THIS PART, WHICH WE JUST CHANGED ABOVE
...
} else {
// REPLACE THESE LINES
// login page
setUser(null);
// show loading screen in 500ms
const timeoutId = setTimeout(() => {
renderLoginPage('Landing page', {
loading: true,
tableData: []
});
}, 500);
// get data once if realtime services haven't been loaded
if (!getState().realtimeServicesLoaded) {
const tickerData = await getAllTickerChanges();
clearTimeout(timeoutId);
renderLoginPage('Landing page', { tableData: tickerData });
}
// subscribe to realtime updates once realtime services are loaded
loadRealtimeService.then(({ subscribeToAllTickerChanges }) => {
unsubscribeAllTickerChanges = subscribeToAllTickerChanges(stockData => {
clearTimeout(timeoutId);
renderLoginPage('Landing page', { tableData: stockData })
});
});
}
Consulta src/main.ts para ver el código terminado.
Verifica que la app funcione
- Ejecuta
npm run build
para volver a compilar la app. - Abre una pestaña del navegador y ve a http://localhost:8080 o actualiza la pestaña existente.
Cómo verificar el tamaño del paquete
- Abre las Herramientas para desarrolladores de Chrome.
- Cambia a la pestaña Red.
- Actualiza la página para capturar las solicitudes de red
- Busca
main.js
y verifica su tamaño. - Ahora solo son 115 KB (34.5 KB comprimidos con gzip). Eso es un 75% más pequeño que el tamaño del paquete original, que era de 446 KB(138 KB comprimidos con gzip). Como resultado, el sitio se carga más de 2 segundos más rápido con una conexión 3G, lo que representa una gran mejora en el rendimiento y la experiencia del usuario.
8. Felicitaciones
Felicitaciones, actualizaste correctamente la app y la hiciste más pequeña y rápida.
Usaste los paquetes de compatibilidad para actualizar la app por partes y usaste Firestore Lite para acelerar la renderización inicial de la página y, luego, cargaste de forma dinámica el Firestore principal para transmitir los cambios de precio.
También redujiste el tamaño del paquete y mejoraste su tiempo de carga durante este codelab:
main.js | tamaño del recurso (KB) | Tamaño comprimido (KB) | Tiempo de carga (s) (con 3G lento) |
v8 | 446 | 138 | 4.92 |
Compatibilidad con v9 | 429 | 124 | 4.65 |
Solo Auth modular de la versión 9 | 348 | 102 | 4.2 |
v9 completamente modular | 244 | 74.6 | 3.66 |
v9 completamente modular + Firestore Lite | 117 | 34.9 | 2.88 |
Ahora conoces los pasos clave necesarios para actualizar una app web que usa el SDK de Firebase JS v8 para usar el nuevo SDK modular de JS.