webentwicklung-frage-antwort-db.com.de

Wie strukturiere ich Cloud-Funktionen für Firebase, um mehrere Funktionen aus mehreren Dateien bereitzustellen?

Ich möchte mehrere Cloud-Funktionen für Firebase erstellen und alle gleichzeitig aus einem Projekt bereitstellen. Ich möchte auch jede Funktion in eine separate Datei aufteilen. Derzeit kann ich mehrere Funktionen erstellen, wenn ich beide in index.js stecke, wie zum Beispiel:

exports.foo = functions.database.ref('/foo').onWrite(event => {
    ...
});

exports.bar = functions.database.ref('/bar').onWrite(event => {
    ...
});

Ich möchte jedoch Foo und Bar in separaten Dateien ablegen. Ich habe das versucht:

/functions
|--index.js (blank)
|--foo.js
|--bar.js
|--package.json

wo foo.js ist

exports.foo = functions.database.ref('/foo').onWrite(event => {
    ...
});

und bar.js ist

exports.bar = functions.database.ref('/bar').onWrite(event => {
    ...
});

Gibt es eine Möglichkeit, dies zu erreichen, ohne alle Funktionen in index.js zu setzen?

85
jasonsirota

Ah, Cloud-Funktionen für Firebase laden Knotenmodule normal, also funktioniert das

struktur:

/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) => {
    ...
};
89
jasonsirota

Die Antwort von @jasonsirota war sehr hilfreich. Es kann jedoch nützlich sein, detaillierteren Code anzuzeigen, insbesondere bei durch HTTP ausgelösten Funktionen. 

Wenn Sie dieselbe Struktur wie in @ jasonsirotas Antwort verwenden, möchten wir Ihnen zwei separate HTTP-Triggerfunktionen in zwei verschiedenen Dateien geben:

verzeichnisaufbau:

    /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'); 
}
51
College Student

So habe ich es persönlich mit TypeScript gemacht:

/functions
   |--src
      |--index.ts
      |--http-functions.ts
      |--main.js
      |--db.ts
   |--package.json
   |--tsconfig.json

Lassen Sie mich dies mit zwei Warnungen vorwegnehmen, um dies zu erreichen:

  1. die Reihenfolge der Import/Export-Angelegenheiten in index.ts
  2. die Datenbank muss eine separate Datei sein

Für Punkt Nummer 2 bin ich nicht sicher warum. Außerdem sollten Sie meine Konfiguration von index, main und db genau einhalten (zumindest um es auszuprobieren).

index.ts : befasst sich mit dem Export. Ich finde es sauberer, die index.ts mit Exporten beschäftigen zu lassen.

// main must be before functions
export * from './main';
export * from "./http-functions";

main.ts : Behandelt die Initialisierung.

import { config } from 'firebase-functions';
import { initializeApp } from 'firebase-admin';

initializeApp(config().firebase);
export * from "firebase-functions";

db.ts : einfach die Datenbank erneut exportieren, so dass der Name kürzer ist als 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);
}
29
Ced

Im Falle von Babel / Flow würde es so aussehen:

Verzeichnislayout

.
├── /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 - Hauptexport (e)

export * from './someFuncA.js';
export * from './someFuncB.js';


src/db.js - Cloud SQL-Client für Postgres

import { 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 - Firebase Firestore Client

import firebase from 'firebase-admin';
import { config } from 'firebase-functions';

firebase.initializeApp(config().firebase);

export default firebase.firestore();


src/someFuncA.js - Funktion A

import { 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 - Funktion B

import { 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
8

Mit Node 8 LTS, das jetzt mit Cloud-/Firebase-Funktionen verfügbar ist, können Sie mit Spread-Operatoren Folgendes tun:

/package.json

"engines": {
  "node": "8"
},

/index.js

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
};

/lib/foo.js

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);
  });
6
Luke Pighetti

Um es einfach zu halten (erledigt aber die Arbeit), habe ich meinen Code persönlich so strukturiert.

Layout

├── /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,
};

Funktioniert für Verzeichnisse beliebiger verschachtelter Ebenen. Folgen Sie einfach dem Muster in den Verzeichnissen.

5
zaidfazil

Um es einfach zu halten (erledigt aber die Arbeit), habe ich meinen Code persönlich so strukturiert.

Layout

├── /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';

Funktioniert für Verzeichnisse beliebiger verschachtelter Ebenen. Folgen Sie einfach dem Muster in den Verzeichnissen.

antwort auf @zaidfazil

4
RezaRahmati

Dieses Format ermöglicht es Ihrem Einstiegspunkt, zusätzliche Funktionsdateien zu finden und jede Funktion innerhalb jeder Datei automatisch zu exportieren.

Haupteinstiegspunkt-Skript

Findet alle .js-Dateien im Funktionsordner und exportiert jede aus jeder Datei exportierte Funktion.

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];
    }
  }
});

Beispielexport mehrerer Funktionen aus einer Datei

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
}

http-erreichbare Endpunkte werden entsprechend benannt

✔ 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

Eine Datei

Wenn Sie nur wenige zusätzliche Dateien haben (z. B. nur eine), können Sie Folgendes verwenden:

const your_functions = require('./path_to_your_functions');

for (var i in your_functions) {
  exports[i] = your_functions[i];
}

4
Matthew Rideout

Es gibt eine sehr gute Möglichkeit, alle Cloud-Funktionen langfristig zu organisieren. Ich habe das kürzlich gemacht und es funktioniert einwandfrei.

Was ich tat, war, jede Cloud-Funktion in separaten Ordnern zu organisieren, basierend auf ihrem Trigger-Endpunkt. Jeder Dateiname der Cloud-Funktion endet mit *.f.js. Wenn Sie beispielsweise onCreate und onUpdate für user/{userId}/document/{documentId} ausgelöst haben, erstellen Sie zwei Dateien onCreate.f.js und onUpdate.f.js im Verzeichnis functions/user/document/ und Ihre Funktion wird userDocumentOnCreate und userDocumentOnUpdate genannt. (1)

Hier ist ein Beispiel für eine Verzeichnisstruktur:

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

Beispielfunktion

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;

Index.js

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): Sie können einen beliebigen Namen verwenden. Für mich scheinen onCreate.f.js, onUpdate.f.js usw. relevanter für die Art von Trigger, den sie sind.

1
Kumar Hitesh

Also habe ich dieses Projekt, das Hintergrundfunktionen und http-Funktionen hat. Ich habe auch Tests für Unit-Tests. CI/CD erleichtert Ihnen das Bereitstellen von Cloud-Funktionen erheblich

Ordnerstruktur

|-- 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

Hinweis: Der Ordner utils/ dient zum Freigeben von Code zwischen Funktionen

functions/index.js

Hier können Sie einfach alle benötigten Funktionen importieren und deklarieren. Hier muss keine Logik sein. Das macht es meiner Meinung nach sauberer.

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);

CI/CD

Wie wäre es mit einer kontinuierlichen Integration und Bereitstellung jedes Mal, wenn Sie Ihre Änderungen in das Repo übertragen? Sie können es haben, indem Sie google google cloud build verwenden. Es ist bis zu einem bestimmten Punkt kostenlos :) Check this 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
1
ajorquera

bigcodenerd.org gliederung ist ein einfacheres Architekturmuster, um Methoden in verschiedene Dateien zu trennen und in eine Zeile innerhalb der Datei index.js zu exportieren.

Die Architektur für das Projekt in diesem Beispiel ist folgende:

Projektverzeichnis

  • index.js
  • podcast.js
  • profile.js

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 { ... }
  });

Dasselbe Muster würde für die removeProfile-Methode in der Profil-Datei verwendet.

0
Adam Hurwitz

Ich verwende einen Vanilla JS-Bootloader, um alle Funktionen, die ich verwenden möchte, automatisch aufzunehmen.

├── /functions
│   ├── /test/
│   │   ├── testA.js
│   │   └── testB.js
│   ├── index.js
│   └── package.json

index.js (Bootloader)

/**
 * 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))
        }
      })
    }
  }
})

Diese Beispieldatei index.js schließt Verzeichnisse innerhalb des Stammverzeichnisses automatisch ein. Es könnte erweitert werden, um Verzeichnisse zu laufen, Ehre .gitignore usw. zu ehren. Dies war jedoch genug für mich.

Wenn die Indexdatei vorhanden ist, ist das Hinzufügen neuer Funktionen einfach.

/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 ergibt:

λ ~/Workspace/Ventures/Author.io/Firebase/functions/ npm run serve

> [email protected] 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

Dieser Arbeitsablauf ist praktisch nur "Schreiben und Ausführen", ohne dass die Datei index.js jedes Mal geändert werden muss, wenn eine neue Funktion/Datei hinzugefügt/geändert/entfernt wird.

0
Corey