首页 文章

如何为Firebase构建 Cloud 功能以从多个文件部署多个功能?

提问于
浏览
77

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

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

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

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

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

foo.js在哪里

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

和bar.js是

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

有没有办法在不将所有函数放在index.js中的情况下完成此操作?

9 回答

  • 4

    啊,Firebase的 Cloud 功能正常加载节点模块,所以这样可行

    结构体:

    /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) => {
        ...
    };
    
  • 25

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

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

    目录结构:

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

    这是我个人用打字稿做的人:

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

    让我作为前言,给出两个警告来完成这项工作:

    • 进口/出口事宜的顺序 index.ts

    • db必须是单独的文件

    对于第2点,我不确定为什么 . Secundo你应该完全尊重我的索引,main和db的配置(至少要尝试一下) .

    index.ts :处理出口 . 我发现让index.ts处理出口更清洁 .

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

    main.ts :处理初始化 .

    import { config } from 'firebase-functions';
    import { initializeApp } from 'firebase-admin';
    
    initializeApp(config().firebase);
    export * from "firebase-functions";
    

    db.ts :只是重新导出数据库,因此其名称短于 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);
    }
    
  • 84

    对于Babel / Flow,它看起来像这样:

    目录布局

    .
    ├── /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 - 主要出口

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

    src / db.js - Postgres的Cloud SQL Client

    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客户端

    import firebase from 'firebase-admin';
    import { config } from 'firebase-functions';
    
    firebase.initializeApp(config().firebase);
    
    export default firebase.firestore();
    

    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 = $1
      `, ['US']);
      res.send(regions);
    });
    

    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

    {
      "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
    
  • 3

    为了保持简单(但是工作),我亲自构建了我的代码 .

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

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

  • 0

    使用Node 8 LTS现在可以使用Cloud / Firebase功能,您可以使用扩展运算符执行以下操作:

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

    此格式允许您的入口点查找其他功能文件,并自动导出每个文件中的每个功能 .

    Main Entry Point Script

    查找函数文件夹中的所有.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可访问 endpoints 已适当命名

    ✔ 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

    如果您只有一些其他文件(例如只有一个),您可以使用:

    const your_functions = require('./path_to_your_functions');
    
    for (var i in your_functions) {
      exports[i] = your_functions[i];
    }
    
  • 1

    有一种非常好的方法可以长期组织所有 Cloud 功能 . 我最近这样做了,它完美无缺 .

    我所做的是根据触发 endpoints 在单独的文件夹中组织每个 Cloud 功能 . 每个 Cloud 函数文件名都以 *.f.js 结尾 . 例如,如果 user/{userId}/document/{documentId} 上有 onCreateonUpdate 触发器,则在目录 functions/user/document/ 中创建两个文件 onCreate.f.jsonUpdate.f.js ,您的函数将分别命名为 userDocumentOnCreateuserDocumentOnUpdate . (1)

    这是一个示例目录结构:

    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;
    

    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):您可以使用任何您想要的名称 . 对我来说,onCreate.f.js,onUpdate.f.js等似乎与它们的触发类型更相关 .

  • 6

    我使用vanilla JS bootloader来自动包含我想要使用的所有函数 .

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

    此示例index.js文件仅自动包含根目录中的目录 . 它可以扩展到步行目录,荣誉.gitignore等 . 这对我来说已经足够了 .

    使用索引文件,添加新函数是微不足道的 .

    /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 收益率:

    λ ~/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
    

    这个工作流几乎就是“编写和运行”,无需在每次添加/修改/删除新函数/文件时修改index.js文件 .

相关问题