Javascript 如何构建 Cloud Functions for Firebase 以从多个文件部署多个函数?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/43486278/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 01:58:09  来源:igfitidea点击:

How do I structure Cloud Functions for Firebase to deploy multiple functions from multiple files?

javascriptfirebasegoogle-cloud-functions

提问by jasonsirota

I would like to create multiple Cloud Functions for Firebase and deploy them all at the same time from one project. I would also like to separate each function into a separate file. Currently I can create multiple functions if I put them both in index.js such as:

我想为 Firebase 创建多个 Cloud Functions 并从一个项目同时部署它们。我还想将每个函数分成一个单独的文件。目前,如果我将它们都放在 index.js 中,我可以创建多个函数,例如:

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

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

However I would like to put foo and bar in separate files. I tried this:

但是我想将 foo 和 bar 放在单独的文件中。我试过这个:

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

where foo.js is

foo.js 在哪里

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

and bar.js is

bar.js 是

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

Is there a way to accomplish this without putting all functions in index.js?

有没有办法在不将所有函数都放在 index.js 中的情况下实现这一点?

采纳答案by jasonsirota

Ah, Cloud Functions for Firebase load node modules normally, so this works

啊,用于 Firebase 加载节点模块的云函数正常,所以这有效

structure:

结构体:

/functions
|--index.js
|--foo.js
|--bar.js
|--package.json

index.js:

索引.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:

foo.js:

exports.handler = (event) => {
    ...
};

bar.js:

酒吧.js:

exports.handler = (event) => {
    ...
};

回答by College Student

The answer by @jasonsirota was very helpful. But it may be useful to see more detailed code, especially in the case of HTTP triggered functions.

@jasonsirota 的回答非常有帮助。但是查看更详细的代码可能会很有用,尤其是在 HTTP 触发函数的情况下。

Using the same structure as in @jasonsirota's answer, lets say you wish to have two separate HTTP trigger functions in two different files:

使用与@jasonsirota 的答案相同的结构,假设您希望在两个不同的文件中拥有两个单独的 HTTP 触发器函数:

directory structure:

目录结构:

    /functions
       |--index.js
       |--foo.js
       |--bar.js
       |--package.json`

index.js:

索引.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:

foo.js:

 exports.handler = function(req, res, database) {
      // Use database to declare databaseRefs:
      usersRef = database.ref('users');
          ...
      res.send('foo ran successfully'); 
   }

bar.js:

酒吧.js:

exports.handler = function(req, res, database) {
  // Use database to declare databaseRefs:
  usersRef = database.ref('users');
      ...
  res.send('bar ran successfully'); 
}

回答by Ced

Update: This doc should help, my answer is older than this doc.

更新:这个文档应该有帮助,我的答案比这个文档早。



Here is how I personnally did it with typescript:

这是我个人使用打字稿的方式:

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

Let me preface this by giving two warnings to make this work:

让我通过给出两个警告来开始这项工作:

  1. the order of import / export matters in index.ts
  2. the db must be a separate file
  1. index.ts中导入/导出的顺序很重要
  2. 数据库必须是一个单独的文件

For point number 2 I'm not sure why. Secundo you should respect my configuration of index, main and db exactly(at least to try it out).

对于第 2 点,我不确定为什么。Secundo你应该尊重我的索引,主数据库的配置完全相同(至少尝试一下)。

index.ts: deals with export. I find it cleaner to let the index.ts deal with exports.

index.ts:处理出口。我发现让 index.ts 处理导出更干净。

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

main.ts: Deals with initialization.

main.ts:处理初始化。

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

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

db.ts: just reexporting the db so its name is shorter than database()

db.ts:只是重新导出数据库所以它的名字比database()

import { database } from "firebase-admin";

export const db = database();

http-functions.ts

http-functions.ts

// db 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);
}

回答by Luke Pighetti

With Node 8 LTS now available with Cloud/Firebase Functions you can do the following with spread operators:

借助 Cloud/Firebase Functions 现在提供的 Node 8 LTS,您可以使用扩展运算符执行以下操作:

/package.json

/package.json

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

/index.js

/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

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

回答by Reza

To be kept simple (but does the work), I have personally structured my code like this.

为了保持简单(但确实有效),我个人将代码结构化为这样。

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

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

索引.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';

Works for directories of any nested levels. Just follow the pattern inside the directories too.

适用于任何嵌套级别的目录。也只需遵循目录中的模式。

credit to @zaidfazil answer

归功于@zaidfazil 的回答

回答by Konstantin Tarkus

In case with Babel/Flowit would look like this:

如果使用Babel/ Flow,它看起来像这样:

Directory Layout

目录布局

.
├── /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- Main export(s)

src/index.js- 主要出口

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



src/db.js- Cloud SQL Client for Postgres

src/db.js- 用于 Postgres 的 Cloud SQL 客户端

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

src/store.js- Firebase Firestore 客户端

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

firebase.initializeApp(config().firebase);

export default firebase.firestore();



src/someFuncA.js- Function A

src/someFuncA.js- 功能 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 = 
  `, ['US']);
  res.send(regions);
});



src/someFuncB.js- Function B

src/someFuncB.js- 功能 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

.babelrc

{
  "presets": [["env", { "targets": { "node": "6.11" } }]],
}



firebase.json

firebase.json

{
  "functions": {
    "source": ".",
    "ignore": [
      "**/node_modules/**"
    ]
  }
}



package.json

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

回答by Adam Hurwitz

bigcodenerd.orgoutline's a simpler architecture pattern in order to have methods separated into different files and exported in one linewithin the index.jsfile.

bigcodenerd.org轮廓的,以便有方法更简单的架构模式分成不同的文件,并在出口一线的内index.js文件。

The architecture for the project in this sample is the following:

本示例中项目的架构如下:

projectDirectory

项目目录

  • index.js
  • podcast.js
  • profile.js
  • 索引.js
  • 播客.js
  • 配置文件.js

index.js

索引.js

const admin = require('firebase-admin');
const podcast = require('./podcast');
const profile = require('./profile');
admin.initializeApp();

exports.getPodcast = podcast.getPodcast();
exports.removeProfile = profile.removeProfile();

podcast.js

播客.js

const functions = require('firebase-functions');

exports.getPodcast = () => functions.https.onCall(async (data, context) => {
      ...
      return { ... }
  });

The same pattern would be used for the removeProfilemethod in the profilefile.

配置文件中的removeProfile方法将使用相同的模式。

回答by zaidfazil

To be kept simple (but does the work), I have personally structured my code like this.

为了保持简单(但确实有效),我个人将代码结构化为这样。

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

bar.ts

export const barFunction = functions.database()......... {
    //do your function.
}

export const anotherFunction = functions.database().......... {
    // do the thing.
}

index.ts

索引.ts

import * as fooFunctions from './foo';
import * as barFunctions from './bar';

module.exports = {
    ...fooFunctions,
    ...barFunctions,
};

Works for directories of any nested levels. Just follow the pattern inside the directories too.

适用于任何嵌套级别的目录。也只需遵循目录中的模式。

回答by Matthew Rideout

This format allows your entry-point to find additional function files, and export each function within each file, automatically.

这种格式允许您的入口点自动查找其他函数文件,并导出每个文件中的每个函数。

Main Entry Point Script

主入口点脚本

Finds all .js files inside of the functions folder, and exports each function exported from each file.

在函数文件夹中查找所有 .js 文件,并导出从每个文件导出的每个函数。

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

Example Export of Multiple Functions from One File

从一个文件导出多个函数的示例

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 accessible endpoints are appropriately named

http 可访问端点被适当命名

? 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

One file

一档

If you only have a few additional files (e.g. just one), you can use:

如果您只有几个附加文件(例如只有一个),您可以使用:

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

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

回答by ajorquera

So I have this project which has background functions and http functions. I also have tests for unit testing. CI/CD will make your life much easier when deploying cloud functions

所以我有这个具有后台功能和http功能的项目。我也有单元测试的测试。部署云功能时,CI/CD 将使您的生活变得更加轻松

Folder structure

文件夹结构

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

Note:utils/folder is for share code between functions

注意:utils/文件夹用于在函数之间共享代码

functions/index.js

函数/index.js

Here you can just import all the functions you need and declare them. No need to have logic here. It makes it cleaner in my opinion.

在这里,您只需导入所需的所有函数并声明它们。这里不需要有逻辑。在我看来,它使它更干净。

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

持续集成/持续交付

How about having continuos integration and deployment every time you push your changes to the repo? You can have it by using google google cloud build. It's free until certain point :) Check this link.

每次将更改推送到存储库时,如何进行持续集成和部署?您可以使用 google google cloud build来获得它。它是免费的,直到某一点:) 检查此链接

./cloudbuild.yaml

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