J'aimerais créer plusieurs fonctions de cloud pour Firebase et les déployer en même temps à partir d'un projet. Je voudrais aussi séparer chaque fonction dans un fichier séparé. Actuellement, je peux créer plusieurs fonctions si je les mets toutes les deux dans index.js, telles que:
exports.foo = functions.database.ref('/foo').onWrite(event => {
...
});
exports.bar = functions.database.ref('/bar').onWrite(event => {
...
});
Cependant, j'aimerais mettre foo et bar dans des fichiers séparés. J'ai essayé ceci:
/functions
|--index.js (blank)
|--foo.js
|--bar.js
|--package.json
où foo.js est
exports.foo = functions.database.ref('/foo').onWrite(event => {
...
});
et bar.js est
exports.bar = functions.database.ref('/bar').onWrite(event => {
...
});
Y at-il un moyen d'accomplir cela sans mettre toutes les fonctions dans index.js?
Ah, les fonctions de nuage pour les modules de nœud de chargement Firebase normalement, donc cela fonctionne
structure:
/functions
|--index.js
|--foo.js
|--bar.js
|--package.json
index.js:
const functions = require('firebase-functions');
const fooModule = require('./foo');
const barModule = require('./bar');
exports.foo = functions.database.ref('/foo').onWrite(fooModule.handler);
exports.bar = functions.database.ref('/bar').onWrite(barModule.handler);
foo.js:
exports.handler = (event) => {
...
};
bar.js:
exports.handler = (event) => {
...
};
La réponse de @jasonsirota a été très utile. Mais il peut être utile de voir un code plus détaillé, en particulier dans le cas de fonctions déclenchées par HTTP.
En utilisant la même structure que dans la réponse de @ jasonsirota, disons que vous souhaitez avoir deux fonctions de déclencheur HTTP distinctes dans deux fichiers différents:
structure de répertoire:
/functions
|--index.js
|--foo.js
|--bar.js
|--package.json`
index.js:
'use strict';
const fooFunction = require('./foo');
const barFunction = require('./bar');
// Note do below initialization tasks in index.js and
// NOT in child functions:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
const database = admin.database();
// Pass database to child functions so they have access to it
exports.fooFunction = functions.https.onRequest((req, res) => {
fooFunction.handler(req, res, database);
});
exports.barFunction = functions.https.onRequest((req, res) => {
barFunction.handler(req, res, database);
});
foo.js:
exports.handler = function(req, res, database) {
// Use database to declare databaseRefs:
usersRef = database.ref('users');
...
res.send('foo ran successfully');
}
bar.js:
exports.handler = function(req, res, database) {
// Use database to declare databaseRefs:
usersRef = database.ref('users');
...
res.send('bar ran successfully');
}
Voici comment je l'ai fait personnellement avec TypeScript:
/functions
|--src
|--index.ts
|--http-functions.ts
|--main.js
|--db.ts
|--package.json
|--tsconfig.json
Permettez-moi de commencer par donner deux avertissements pour que cela fonctionne:
Pour le point numéro 2, je ne sais pas pourquoi. Secundo, vous devez respecter ma configuration de index, main et db exactement (au moins pour l'essayer).
index.ts : traite de l'exportation. Je trouve plus propre de laisser l’index.ts s’occuper des exportations.
// main must be before functions
export * from './main';
export * from "./http-functions";
main.ts : Traite l'initialisation.
import { config } from 'firebase-functions';
import { initializeApp } from 'firebase-admin';
initializeApp(config().firebase);
export * from "firebase-functions";
db.ts : il suffit de réexporter la base de données pour que son nom soit plus court que database()
import { database } from "firebase-admin";
export const db = database();
http-functions.ts
// de must be imported like this
import { db } from './db';
// you can now import everything from index.
import { https } from './index';
// or (both work)
// import { https } from 'firebase-functions';
export let newComment = https.onRequest(createComment);
export async function createComment(req: any, res: any){
db.ref('comments').Push(req.body.comment);
res.send(req.body.comment);
}
Dans le cas où Babel / Flow cela ressemblerait à ceci:
.
├── /build/ # Compiled output for Node.js 6.x
├── /src/ # Application source files
│ ├── db.js # Cloud SQL client for Postgres
│ ├── index.js # Main export(s)
│ ├── someFuncA.js # Function A
│ ├── someFuncA.test.js # Function A unit tests
│ ├── someFuncB.js # Function B
│ ├── someFuncB.test.js # Function B unit tests
│ └── store.js # Firebase Firestore client
├── .babelrc # Babel configuration
├── firebase.json # Firebase configuration
└── package.json # List of project dependencies and NPM scripts
src/index.js
- Principale (s) exportation (s)export * from './someFuncA.js';
export * from './someFuncB.js';
src/db.js
- Client Cloud SQL pour Postgresimport { Pool } from 'pg';
import { config } from 'firebase-functions';
export default new Pool({
max: 1,
user: '<username>',
database: '<database>',
password: config().db.password,
Host: `/cloudsql/${process.env.GCP_PROJECT}:<region>:<instance>`,
});
src/store.js
- Client Firebase Firestoreimport firebase from 'firebase-admin';
import { config } from 'firebase-functions';
firebase.initializeApp(config().firebase);
export default firebase.firestore();
src/someFuncA.js
- Fonction Aimport { https } from 'firebase-functions';
import db from './db';
export const someFuncA = https.onRequest(async (req, res) => {
const { rows: regions } = await db.query(`
SELECT * FROM regions WHERE country_code = $1
`, ['US']);
res.send(regions);
});
src/someFuncB.js
- Fonction Bimport { https } from 'firebase-functions';
import store from './store';
export const someFuncB = https.onRequest(async (req, res) => {
const { docs: regions } = await store
.collection('regions')
.where('countryCode', '==', 'US')
.get();
res.send(regions);
});
.babelrc
{
"presets": [["env", { "targets": { "node": "6.11" } }]],
}
firebase.json
{
"functions": {
"source": ".",
"ignore": [
"**/node_modules/**"
]
}
}
package.json
{
"name": "functions",
"verson": "0.0.0",
"private": true,
"main": "build/index.js",
"dependencies": {
"firebase-admin": "^5.9.0",
"firebase-functions": "^0.8.1",
"pg": "^7.4.1"
},
"devDependencies": {
"babel-cli": "^6.26.0",
"babel-core": "^6.26.0",
"babel-jest": "^22.2.2",
"babel-preset-env": "^1.6.1",
"jest": "^22.2.2"
},
"scripts": {
"test": "jest --env=node",
"predeploy": "rm -rf ./build && babel --out-dir ./build src",
"deploy": "firebase deploy --only functions"
}
}
$ yarn install # Install project dependencies
$ yarn test # Run unit tests
$ yarn deploy # Deploy to Firebase
Avec Node 8 LTS maintenant disponible avec les fonctions Cloud/Firebase, vous pouvez effectuer les opérations suivantes avec des opérateurs étendus:
"engines": {
"node": "8"
},
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
module.exports = {
...require("./lib/foo.js"),
// ...require("./lib/bar.js") // add as many as you like
};
const functions = require("firebase-functions");
const admin = require("firebase-admin");
exports.fooHandler = functions.database
.ref("/food/{id}")
.onCreate((snap, context) => {
let id = context.params["id"];
return admin
.database()
.ref(`/bar/${id}`)
.set(true);
});
Pour rester simple (mais fait le travail), j'ai personnellement structuré mon code comme ça.
Disposition
├── /src/
│ ├── index.ts
│ ├── foo.ts
│ ├── bar.ts
└── package.json
foo.ts
export const fooFunction = functions.database()......... {
//do your function.
}
export const someOtherFunction = functions.database().......... {
// do the thing.
}
bar.ts
export const barFunction = functions.database()......... {
//do your function.
}
export const anotherFunction = functions.database().......... {
// do the thing.
}
index.ts
import * as fooFunctions from './foo';
import * as barFunctions from './bar';
module.exports = {
...fooFunctions,
...barFunctions,
};
Fonctionne pour les répertoires de tous niveaux imbriqués. Suivez simplement le modèle à l'intérieur des répertoires aussi.
Pour rester simple (mais fait le travail), j'ai personnellement structuré mon code comme ça.
Disposition
├── /src/
│ ├── index.ts
│ ├── foo.ts
│ ├── bar.ts
| ├── db.ts
└── package.json
foo.ts
import * as functions from 'firebase-functions';
export const fooFunction = functions.database()......... {
//do your function.
}
export const someOtherFunction = functions.database().......... {
// do the thing.
}
bar.ts
import * as functions from 'firebase-functions';
export const barFunction = functions.database()......... {
//do your function.
}
export const anotherFunction = functions.database().......... {
// do the thing.
}
db.ts
import * as admin from 'firebase-admin';
import * as functions from 'firebase-functions';
export const firestore = admin.firestore();
export const realtimeDb = admin.database();
index.ts
import * as admin from 'firebase-admin';
import * as functions from 'firebase-functions';
admin.initializeApp(functions.config().firebase);
// above codes only needed if you use firebase admin
export * from './foo';
export * from './bar';
Fonctionne pour les répertoires de tous niveaux imbriqués. Suivez simplement le modèle à l'intérieur des répertoires aussi.
crédit à @zaidfazil answer
Ce format permet à votre point d’entrée de rechercher des fichiers de fonction supplémentaires et d’exporter automatiquement chaque fonction de chaque fichier.
Script de point d'entrée principal
Recherche tous les fichiers .js dans le dossier des fonctions et exporte chaque fonction exportée à partir de chaque fichier.
const fs = require('fs');
const path = require('path');
// Folder where all your individual Cloud Functions files are located.
const FUNCTIONS_FOLDER = './scFunctions';
fs.readdirSync(path.resolve(__dirname, FUNCTIONS_FOLDER)).forEach(file => { // list files in the folder.
if(file.endsWith('.js')) {
const fileBaseName = file.slice(0, -3); // Remove the '.js' extension
const thisFunction = require(`${FUNCTIONS_FOLDER}/${fileBaseName}`);
for(var i in thisFunction) {
exports[i] = thisFunction[i];
}
}
});
Exemple d'exportation de plusieurs fonctions à partir d'un fichier
const functions = require('firebase-functions');
const query = functions.https.onRequest((req, res) => {
let query = req.query.q;
res.send({
"You Searched For": query
});
});
const searchTest = functions.https.onRequest((req, res) => {
res.send({
"searchTest": "Hi There!"
});
});
module.exports = {
query,
searchTest
}
les points d'accès http accessibles sont nommés de manière appropriée
✔ functions: query: http://localhost:5001/PROJECT-NAME/us-central1/query
✔ functions: helloWorlds: http://localhost:5001/PROJECT-NAME/us-central1/helloWorlds
✔ functions: searchTest: http://localhost:5001/PROJECT-NAME/us-central1/searchTest
Un fichier
Si vous ne disposez que de quelques fichiers supplémentaires (un seul par exemple), vous pouvez utiliser:
const your_functions = require('./path_to_your_functions');
for (var i in your_functions) {
exports[i] = your_functions[i];
}
Il existe un très bon moyen d'organiser toutes vos fonctions cloud à long terme. Je l'ai fait récemment et cela fonctionne parfaitement.
Ce que j'ai fait est d'organiser chaque fonction de nuage dans des dossiers distincts en fonction de leur point de terminaison de déclenchement. Chaque nom de fichier de fonction cloud se termine par *.f.js
. Par exemple, si vous avez des déclencheurs onCreate
et onUpdate
sur user/{userId}/document/{documentId}
, créez deux fichiers onCreate.f.js
et onUpdate.f.js
dans le répertoire functions/user/document/
et votre fonction sera nommée userDocumentOnCreate
et userDocumentOnUpdate
. (1)
Voici un exemple de structure de répertoire:
functions/
|----package.json
|----index.js
/----user/
|-------onCreate.f.js
|-------onWrite.f.js
/-------document/
|------------onCreate.f.js
|------------onUpdate.f.js
/----books/
|-------onCreate.f.js
|-------onUpdate.f.js
|-------onDelete.f.js
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const db = admin.database();
const documentsOnCreate = functions.database
.ref('user/{userId}/document/{documentId}')
.onCreate((snap, context) => {
// your code goes here
});
exports = module.exports = documentsOnCreate;
const glob = require("glob");
const camelCase = require('camelcase');
const admin = require('firebase-admin');
const serviceAccount = require('./path/to/ServiceAccountKey.json');
try {
admin.initializeApp({ credential: admin.credential.cert(serviceAccount),
databaseURL: "Your database URL" });
} catch (e) {
console.log(e);
}
const files = glob.sync('./**/*.f.js', { cwd: __dirname });
for (let f = 0, fl = files.length; f < fl; f++) {
const file = files[f];
const functionName = camelCase(file.slice(0, -5).split('/'));
if (!process.env.FUNCTION_NAME || process.env.FUNCTION_NAME === functionName) {
exports[functionName] = require(file);
}
}
(1): Vous pouvez utiliser le nom de votre choix. Pour moi, onCreate.f.js, onUpdate.f.js, etc. semblent plus pertinents pour le type de déclencheur qu’ils sont.
J'ai donc ce projet qui a des fonctions d'arrière-plan et des fonctions http. J'ai aussi des tests pour les tests unitaires. CI/CD vous simplifiera la vie lors du déploiement de fonctions cloud
|-- package.json
|-- cloudbuild.yaml
|-- functions
|-- index.js
|-- background
| |-- onCreate
| |-- index.js
|-- create.js
|
|-- http
| |-- stripe
| |-- index.js
| |-- payment.js
|-- utils
|-- firebaseHelpers.js
|-- test
|-- ...
|-- package.json
Remarque: le dossier utils/
est destiné au code de partage entre les fonctions.
Ici, vous pouvez simplement importer toutes les fonctions dont vous avez besoin et les déclarer. Pas besoin d'avoir de logique ici. Cela le rend plus propre à mon avis.
require('module-alias/register');
const functions = require('firebase-functions');
const onCreate = require('@background/onCreate');
const onDelete = require('@background/onDelete');
const onUpdate = require('@background/onUpdate');
const tours = require('@http/tours');
const stripe = require('@http/stripe');
const docPath = 'tours/{tourId}';
module.exports.onCreate = functions.firestore.document(docPath).onCreate(onCreate);
module.exports.onDelete = functions.firestore.document(docPath).onDelete(onDelete);
module.exports.onUpdate = functions.firestore.document(docPath).onUpdate(onUpdate);
module.exports.tours = functions.https.onRequest(tours);
module.exports.stripe = functions.https.onRequest(stripe);
Pourquoi ne pas avoir une intégration et un déploiement continus à chaque fois que vous appliquez vos modifications au référentiel? Vous pouvez l'avoir en utilisant google google cloud build . C'est gratuit jusqu'à un certain point :) Cochez cette link .
./cloudbuild.yaml
steps:
- name: "gcr.io/cloud-builders/npm"
args: ["run", "install:functions"]
- name: "gcr.io/cloud-builders/npm"
args: ["test"]
- name: "gcr.io/${PROJECT_ID}/firebase"
args:
[
"deploy",
"--only",
"functions",
"-P",
"${PROJECT_ID}",
"--token",
"${_FIREBASE_TOKEN}"
]
substitutions:
_FIREBASE_TOKEN: nothing
bigcodenerd.org outline est un modèle d'architecture plus simple permettant de séparer les méthodes dans différents fichiers et de les exporter dans une ligne dans le fichier index.js.
L'architecture du projet dans cet exemple est la suivante:
projectDirectory
index.js
const admin = require('firebase-admin');
const podcast = require('./podcast');
const profile = require('./profile');
admin.initializeApp();
exports.getPodcast = podcast.getPodcast();
exports.removeProfile = user.removeProfile();
podcast.js
const functions = require('firebase-functions');
exports.getPodcast = () => functions.https.onCall(async (data, context) => {
...
return { ... }
});
Le même modèle serait utilisé pour la méthode removeProfile
dans le fichier profil.
J'utilise un chargeur de démarrage Vanilla JS pour inclure automatiquement toutes les fonctions que je souhaite utiliser.
├── /functions
│ ├── /test/
│ │ ├── testA.js
│ │ └── testB.js
│ ├── index.js
│ └── package.json
index.js (chargeur de démarrage)
/**
* The bootloader reads all directories (single level, NOT recursively)
* to include all known functions.
*/
const functions = require('firebase-functions');
const fs = require('fs')
const path = require('path')
fs.readdirSync(process.cwd()).forEach(location => {
if (!location.startsWith('.')) {
location = path.resolve(location)
if (fs.statSync(location).isDirectory() && path.dirname(location).toLowerCase() !== 'node_modules') {
fs.readdirSync(location).forEach(filepath => {
filepath = path.join(location, filepath)
if (fs.statSync(filepath).isFile() && path.extname(filepath).toLowerCase() === '.js') {
Object.assign(exports, require(filepath))
}
})
}
}
})
Cet exemple de fichier index.js n'inclut que automatiquement les répertoires de la racine. Il pourrait être étendu pour marcher dans les répertoires, honorer .gitignore, etc. Cela me suffisait cependant.
Avec le fichier d'index en place, l'ajout de nouvelles fonctions est simple.
/test/testA.js
const functions = require('firebase-functions');
exports.helloWorld = functions.https.onRequest((request, response) => {
response.send("Hello from Firebase!");
});
/test/testB.js
const functions = require('firebase-functions');
exports.helloWorld2 = functions.https.onRequest((request, response) => {
response.send("Hello again, from Firebase!");
});
npm run serve
donne:
λ ~/Workspace/Ventures/Author.io/Firebase/functions/ npm run serve
> functions@ serve /Users/cbutler/Workspace/Ventures/Author.io/Firebase/functions
> firebase serve --only functions
=== Serving from '/Users/cbutler/Workspace/Ventures/Author.io/Firebase'...
i functions: Preparing to emulate functions.
Warning: You're using Node.js v9.3.0 but Google Cloud Functions only supports v6.11.5.
✔ functions: helloWorld: http://localhost:5000/authorio-ecorventures/us-central1/helloWorld
✔ functions: helloWorld2: http://localhost:5000/authorio-ecorventures/us-central1/helloWorld2
Ce flux de travail consiste en gros à "écrire et exécuter", sans avoir à modifier le fichier index.js chaque fois qu'une nouvelle fonction/un nouveau fichier est ajouté/modifié/supprimé.