-
Notifications
You must be signed in to change notification settings - Fork 3.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
unable to split Firebase functions in multiple files #170
Comments
@malikasinger1 I had a similar issue so I had to make a higher order function that takes my firebase objects and returns the cloud function I would like to deploy. cloudFunction.js
index.js
|
Here is my take on it: You can have an index.js file that will import and list all other Cloud Functions. One trick to improve performance is to use the Here is an example index.js for 3 functions: // Sends notifications to users when they get a new follower.
if (!process.env.FUNCTION_TARGET || process.env.FUNCTION_TARGET === 'sendFollowerNotification') {
exports.sendFollowerNotification = require('./sendFollowerNotification');
}
// Blur offensive images uploaded on Cloud Storage.
if (!process.env.FUNCTION_TARGET || process.env.FUNCTION_TARGET === 'blurOffensiveImages') {
exports.blurOffensiveImages = require('./blurOffensiveImages');
}
// Renders my server side app template.
if (!process.env.FUNCTION_TARGET || process.env.FUNCTION_TARGET === 'renderTemplate') {
exports.renderTemplate = require('./renderTemplate');
} Then for instance, the ./blurOffensiveImages.js would be: const functions = require('firebase-functions');
const admin = require('firebase-admin');
try {admin.initializeApp(functions.config().firebase);} catch(e) {} // You do that because the admin SDK can only be initialized once.
const mkdirp = require('mkdirp-promise');
// etc... my imports ...
/**
* Blur offensive images uploaded on Cloud Storage.
*/
exports = module.exports = functions.storage.object().onChange(event => {
// Function's code...
}); This will ensure all the 3 functions are deployed if doing a EDIT: Edited to reflect new name of environment variable. |
Now you could even automate this a bit and register functions based on their file name. For instance with an index.js like this: **UPDATE, this works:** const fs = require('fs');
const path = require('path');
// Folder where all your individual Cloud Functions files are located.
const FUNCTIONS_FOLDER = './my_functs';
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
if (!process.env.FUNCTION_TARGET || process.env.FUNCTION_TARGET === fileBaseName) {
exports[fileBaseName] = require(`${FUNCTIONS_FOLDER}/${fileBaseName}`);
}
}
}); Where your file structure is like:
EDIT: Edited to reflect new name of environment variable. |
@nicolasgarnier when attempting to deploy using this method I get the equivalent of const ENDPOINT_DIR = './endpoints';
fs.readdirSync(fs.existsSync(ENDPOINT_DIR) ? ENDPOINT_DIR : ENDPOINT_DIR.replace('.', './functions')).forEach(file => {
if(file.endsWith('.js')) {
const name = file.slice(0, -3);
const method = name.split(".").reduce(function (method, part) {
return method ? method + part.charAt(0).toUpperCase() + part.slice(1) : part;
}, "");
if (!process.env.FUNCTION_TARGET || process.env.FUNCTION_TARGET === method) {
exports[method] = require(`${ENDPOINT_DIR}/${name}`);
}
}
});
EDIT: Edited to reflect new name of environment variable. |
Ah yes, I actually had a setup where my index.js was at the root (using fs.readdirSync(path.resolve(__dirname, FUNCTIONS_FOLDER)).forEach(file => { ... If you could try that let me know if that works and I'll update my code above :) |
Works perfectly, thanks. |
I've created a slightly modified version of /** EXPORT ALL FUNCTIONS
*
* Loads all `.function.js` files
* Exports a cloud function matching the file name
*
* Based on this thread:
* https://github.com/firebase/functions-samples/issues/170
*/
const glob = require("glob");
const files = glob.sync('./**/*.function.js', { cwd: __dirname });
for(let f=0,fl=files.length; f<fl; f++){
const file = files[f];
const functionName = file.split('/').pop().slice(0, -12); // Strip off '.function.js'
if (!process.env.FUNCTION_TARGET || process.env.FUNCTION_TARGET === functionName) {
exports[functionName] = require(file);
}
} EDIT: Edited to reflect new name of environment variable. |
Thanks for the code snippet, @oodavid . Would it be possible to parse the names of the subfolders as well, e.g. deploy the function in |
@oodavid I have created a simple function at 'use strict';
const functions = require('firebase-functions');
exports = module.exports = functions.https.onRequest((request, response) => {
response.send('Hello from Firebase!');
}); But it fails with Did I missed out anything? |
@andrewspy Do have a top level Something like this: const helloWorld = require('./hello/helloWorld')
module.exports.helloWorld = helloWorld Checkout @nicolasgarnier 's comment above (notice how it sets |
@prescottprue I am actually using @oodavid's code from #170 (comment), which set the reference in if (!process.env.FUNCTION_TARGET || process.env.FUNCTION_TARGET === functionName) {
exports[functionName] = require(file);
} I am new to EDIT: Edited to reflect new name of environment variable. |
@andrewspy with the If not, I'll make an example repo. |
@oodavid I am actually using
|
after trying most of these solutions, only this one worked for me index.js 'use strict';
//Declare all your child functions here
const fooFunction = require('./foo');
const barFunction = require('./bar');
// Note: these tasks need to be initialized 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();
const messaging = admin.messaging();
exports.fooFunction = functions.database.ref('/users/messages_inbox/{sender_id}/message').onWrite(event => {
// Pass whatever tasks to child functions so they have access to it
fooFunction.handler(database, messaging, event);
});
exports.barFunction = functions.database.ref('/users').onWrite(event => {
// Pass whatever tasks to child functions so they have access to it
barFunction.handler(database);
}); foo.js exports.handler = function(database, messaging, event) {
// some function
} bar.js exports.handler = function(database) {
// some function
} |
@oodavid thx for the great example! 😄 /** EXPORT ALL FUNCTIONS
*
* Loads all `.function.js` files
* Exports a cloud function matching the file name
*
* Based on this thread:
* https://github.com/firebase/functions-samples/issues/170
*/
const glob = require("glob");
const files = glob.sync('./**/*.function.js', { cwd: __dirname, ignore: './node_modules/**'});
for(let f=0,fl=files.length; f<fl; f++){
const file = files[f];
const functionName = file.split('/').pop().slice(0, -12); // Strip off '.function.js'
if (!process.env.FUNCTION_TARGET || process.env.FUNCTION_TARGET === functionName) {
exports[functionName] = require(file);
}
} If you don't ignore the And just for the record if someone gets an error for the const functions = require('firebase-functions');
const admin = require('firebase-admin');
try {admin.initializeApp(functions.config().firebase);} catch(e) {} // You do that because the admin SDK can only be initialized once.
const mkdirp = require('mkdirp-promise');
// etc... my imports ...
/**
* Blur offensive images uploaded on Cloud Storage.
*/
exports = module.exports = functions.storage.object().onChange(event => {
// Function's code...
}); The And the camelCase worksout with this little change: const functionName = camelCase(file.slice(0, -12).split('/').join('_')); EDIT: Edited to reflect new name of environment variable. |
This comment was marked as abuse.
This comment was marked as abuse.
I edited this script to allow for multiple functions within each document Where each file may export multiple functions
And the script goes through each one identified in each file
The URL's on firebase are predictably named
|
Hi, Please help. I want to write a program on our own https server to add two number without use fire base. |
This thread is great. Props to @TarikHuber for his Medium post that brought me here. My working implementation for single function/file is as follows:
EDIT: Edited to reflect new name of environment variable. |
The if ((process.env.FUNCTION_TARGET || functionName) === functionName) {
exports[functionName] = require(functionName);
} EDIT: Edited to reflect new name of environment variable. |
I am having an issue similar to the above, please see by SO post: https://stackoverflow.com/questions/50089807/firebase-cloud-functions-functions-predeploy-error-when-structuring-multiple-f |
Following @boon4376 previous comment, I prefer multiple functions in each file. I have added some "common" files like the following to avoid the SDK being initialized multiple times: admin.js // The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require('firebase-admin');
admin.initializeApp();
module.exports = admin; The functions are implemented as follows. realtimedb.js const functions = require('firebase-functions');
const admin = require('./admin');
// ... more imports
exports.onDeleteItemA = functions.database.ref('/ItemA/{itemId}')
.onDelete((snapshot, context) => {
// code
// admin.database().ref('/somePath')...
});
exports.onDeleteItemB = functions.database.ref('/ItemB/{itemId}')
.onDelete((snapshot, context) => {
// code
}); And the main index.js file is just excluding some particular files. index.js const fs = require('fs');
const path = require('path');
// Folder where all your individual Cloud Functions files are located.
const FUNCTIONS_FOLDER = './';
fs.readdirSync(path.resolve(__dirname, FUNCTIONS_FOLDER))
.forEach(file => { // list files in the folder.
if ((file.endsWith('.js')) && (file !== 'index.js') && (file !== 'admin.js')) {
const fileBaseName = file.slice(0, -3); // Remove the '.js' extension
const exportedModule = require(`${FUNCTIONS_FOLDER}/${fileBaseName}`);
for(var functionName in exportedModule) {
if (!process.env.FUNCTION_TARGET || process.env.FUNCTION_TARGET === functionName) {
exports[functionName] = exportedModule[functionName];
}
}
}
}); EDIT: Edited to reflect new name of environment variable. |
@nicolasgarnier thanks for sharing the However, does this mean that we practically can't make use of express routing if we want to boost performance? The
EDIT: Edited to reflect new name of environment variable. |
Well this trick is specifically made when using different Cloud Functions. In your case everything is in the same cloud function but you can still "lazy-load" dependencies the normal way using const express = require('express');
const app = express();
app.get("/users/:uid/posts", async (req, res) => {
require("./users_get.js").default(req, res);
});
app.post("/users/:uid/posts", async (req, res) => {
require("./users_post.js").default(req, res);
});
exports.api = functions.https.onRequest(app); And you'd have the two files "./users_get.js" and "./users_post.js" doing whatever is only needed for one specific handler. This optimisation is not that useful though because eventually both handlers run on the same Cloud functions instance so they will get both called on the same instance after a while. But still you could do it, it would help a little bit spread the static init over time. In the case of entirely different Cloud Functions we do this optimisation because we're entirely sure that the code will never be ran on the same instance so there is really no point in doing all the static initialisation for other functions. |
Makes sense, thanks! |
I've been researching about scalable and convenient firebase functions project structure, and came up with this thread. My implementation below allows for standard firebase function naming convention for objects that will follow your folder structure. import { merge } from 'lodash'
import { sync } from 'glob'
const functionsRoot = 'root'
const files = sync(`./${functionsRoot}/**/*.f.js`, { cwd: __dirname, ignore: './node_modules/**'});
const nameIt = (name, file) => {
const qname = name.slice(0, -5).split('/').filter(e => e) // Strip off '.f.js'
const first = qname.shift()
const fname = [first, ...qname].join('-')
if (!process.env.FUNCTION_TARGET || process.env.FUNCTION_TARGET === fname) {
// lazy loading - require file only when building or running function
const exported = require(file)
const subject = qname.reverse().reduce((obj, el) => ({[el]: obj}), exported.default || exported)
exports[first] = qname.length ? merge({}, exports[first] || {}, subject) : subject
}
}
for (let f=0,fl=files.length; f<fl; f++) {
const file = files[f];
const name = file.replace(new RegExp(`^./${functionsRoot}`), '')
nameIt(name, file)
} This will allow you to have a folder structure like:
and an export structure like:
One thing I didn't like with the original suggestion by @armenr is that it collapsed the function name into a single string and bypassed an entire feature that allows structuring the functions based on objects exported. This not only a styling issue making function names really long and basically hard to use, but also a functional issue as it disables your ability to deploy a group of functions using something like EDIT: Edited to reflect new name of environment variable. |
Firebase project cloud-functions are all registered in a single index file. The comments here document how to dynamically register functions, with their given file name: firebase/functions-samples#170 (comment)
similar to @nicolasgarnier above I would like to create a file structure for api like so: --functions I would like to use @TarikHuber version in index.js already have it separating the file types like so: const glob = require("glob");
const camelCase = require("camelcase");
const files = glob.sync('./**/*.js', { cwd: __dirname, ignore: ['./node_modules/**', './index.js'] });
for (let f = 0, fl = files.length; f < fl; f++) {
const file = files[f];
const pathCheck = file.slice(-6, -3);
if (pathCheck === 'api') {
const path = file.slice(0, -7); // Strip off '.api.js'
app.use(`/${path}`, require(file));
} else {
const functionName = camelCase(file.slice(0, -5).split('/').join('_')); // Strip off '.f.js'
if (!process.env.FUNCTION_TARGET || process.env.FUNCTION_TARGET === functionName) {
exports[functionName] = require(file);
}
}
} new to node so not exactly sure why this shouldn't work. Thanks for any help in advance... Chad EDIT: Edited to reflect new name of environment variable. |
I've had a bullet-proof version of the original approach from @nicolasgarnier running for a few years now. No problems other than a want to revisit to allow globbing inside a deeply nested folder structure (not least to make use of selective group deployments). Having finally had the chance to implement on a new project I quickly discovered the require('./../../../../') woes of deep nesting. Fortunately I was already using dot.notation to flag non-deploying and non-serving files, and so used this technique to allow grouping as well. See the gist below for my updated multi-file function setup. It's tested and working for the latest version of FB Cloud Functions with the following features:
https://gist.github.com/theprojectsomething/2076f856f9c4488366dc88e6e8ab2f20 |
Hey everyone! After doing a bit of research and finding this thread among others, I've decided to release this solution as a package, Your index.js file needs only 2 lines import exportCloudFunctions from 'better-firebase-functions'
exportCloudFunctions(__dirname, __filename, exports, './', GLOB_PATTERN); https://www.npmjs.com/package/better-firebase-functions This is my first open-source repo, so let me know what you guys think. Any pull requests welcome!
|
With typescript >=3.8 just write in index.ts something like:
https://www.typescriptlang.org/docs/handbook/modules.html#export-all-as-x |
Simple way to chose the functions you want:
EDIT: Edited to reflect new name of environment variable. |
Now a documented feature: https://firebase.google.com/docs/functions/organize-functions#write_functions_in_multiple_files |
Please note that the name of the environment variable has been changed from FUNCTION_NAME to FUNCTION_TARGET. |
I'm working with firebase functions and arrived to hundreds of functions, and now it is very hard to manage it in single
index.js
file as shown in their lots of examplesI tried to split that functions in multiple files like:
in this structure i devide my functions in three categories and put in that three files
groupFunctions.js
authFunctions.js
storageFunctions.js
and tried to import that files inindex.js
but i don't know why it is not working for meHere is
groupFunctions.js
Here is
index.js
file:my editor not giving any warning in this code but when i deploy this code with
firebase deploy --only functions
it does not deploy function (if some functions already exist on firebase console, it remove all functions on deploy)So I need help regarding this problem,
looking forward to listen from you guise.
question is also asked on stackoverflow
The text was updated successfully, but these errors were encountered: