Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Firebase cloud functions bundled with webpack?

I've been stuck for a while trying to make a webpack build for my cloud functions files.

My project structure:

ROOT

- FUNCTIONS
  - DIS
    - bundle.js // THIS SHOULD BE GENERATED BY WEBPACK
  - SRC
    - myCloudFunction.js  // SOURCE CODE FOR A CLOUD FUNCTION
    - entryPoint.js  // ENTRY POINT FOR WEBPACK
  - index.js    
  - package.json

- SRC
  - App.js

.babelrc
firebase.json
webpack.prod.js    // THIS BUILDS FOR CLIENT (WORKING FINE)
webpack.server.js  // THIS SHOULD BUILD FOR THE SERVER (NOT WORKING)

My goal is:

  • Write cloud functions files and the index.js in modern JS and transpile and bundle them with webpack (using the same webpack that I use to bundle my client code, but with another config file).

myCloudFunction.js (simple function logging some text)

module.exports = (req,res) => {
  console.log('myCloudFunction is executing...');
  res.status(200).send('From myCloudFunction...');
}

entryPoint.js (basically imports function's code and export them as cloud functions)

const functions = require('firebase-functions');
const myCloudFunction = require('./src/myCloudFunction');

module.exports.myCloudFunction = functions.https.onRequest(myCloudFunction);

If I make the index.js exactly like my entryPoint.js it works just fine. But I want to bundle the files using webpack from the entryPoint.js and set the bundled result to my index.js file. Basically only 2 files will be bundled in this case (entryPoint and myCloudFunction).

I'm using webpack to bundle:

webpack.prod.server.js

const webpack = require('webpack');
const path = require('path');
const Dotenv = require('dotenv-webpack');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const nodeExternals = require('webpack-node-externals');

module.exports = {
  mode: 'development',
  stats: 'verbose',
  devtool: 'inline-source-map',

  entry: {
    app: './functions/src/entryPoint.js'
  },

  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, './functions/dist'),
    publicPath: '/'
  },

  // externals: {
  //   "firebase-admin": true,
  //   "firebase-functions": true
  // },

  target: 'node',
  externals: [nodeExternals(),'firebase-functions', 'firebase-admin','firebase'],

  plugins:[
    new CleanWebpackPlugin(),
    new webpack.HashedModuleIdsPlugin(),
    new webpack.DefinePlugin({
        'process.env.ON_SERVER': true
    }),
    new Dotenv()
  ],

  module: {
    rules:[
      {
        test: /\.js$/,
        include: path.resolve(__dirname, 'src'),
        use: ['babel-loader']
      },   
    ]
  }
};

I read that you shouldn't bundle node_modules to the backend, that's why I'm using the externals property.

bundle.js (the result bundle after running webpack with the config above)

/******/ (function(modules) { // webpackBootstrap
/******/    // The module cache
/******/    var installedModules = {};
/******/
/******/    // The require function
/******/    function __webpack_require__(moduleId) {
/******/
/******/        // Check if module is in cache
/******/        if(installedModules[moduleId]) {
/******/            return installedModules[moduleId].exports;
/******/        }
/******/        // Create a new module (and put it into the cache)
/******/        var module = installedModules[moduleId] = {
/******/            i: moduleId,
/******/            l: false,
/******/            exports: {}
/******/        };
/******/
/******/        // Execute the module function
/******/        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/        // Flag the module as loaded
/******/        module.l = true;
/******/
/******/        // Return the exports of the module
/******/        return module.exports;
/******/    }
/******/
/******/
/******/    // expose the modules object (__webpack_modules__)
/******/    __webpack_require__.m = modules;
/******/
/******/    // expose the module cache
/******/    __webpack_require__.c = installedModules;
/******/
/******/    // define getter function for harmony exports
/******/    __webpack_require__.d = function(exports, name, getter) {
/******/        if(!__webpack_require__.o(exports, name)) {
/******/            Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/        }
/******/    };
/******/
/******/    // define __esModule on exports
/******/    __webpack_require__.r = function(exports) {
/******/        if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/            Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/        }
/******/        Object.defineProperty(exports, '__esModule', { value: true });
/******/    };
/******/
/******/    // create a fake namespace object
/******/    // mode & 1: value is a module id, require it
/******/    // mode & 2: merge all properties of value into the ns
/******/    // mode & 4: return value when already ns object
/******/    // mode & 8|1: behave like require
/******/    __webpack_require__.t = function(value, mode) {
/******/        if(mode & 1) value = __webpack_require__(value);
/******/        if(mode & 8) return value;
/******/        if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/        var ns = Object.create(null);
/******/        __webpack_require__.r(ns);
/******/        Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/        if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/        return ns;
/******/    };
/******/
/******/    // getDefaultExport function for compatibility with non-harmony modules
/******/    __webpack_require__.n = function(module) {
/******/        var getter = module && module.__esModule ?
/******/            function getDefault() { return module['default']; } :
/******/            function getModuleExports() { return module; };
/******/        __webpack_require__.d(getter, 'a', getter);
/******/        return getter;
/******/    };
/******/
/******/    // Object.prototype.hasOwnProperty.call
/******/    __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/    // __webpack_public_path__
/******/    __webpack_require__.p = "/";
/******/
/******/
/******/    // Load entry module and return exports
/******/    return __webpack_require__(__webpack_require__.s = "oFca");
/******/ })
/************************************************************************/
/******/ ({

/***/ "4ouX":
/*!******************************************!*\
  !*** ./functions/src/myCloudFunction.js ***!
  \******************************************/
/*! no static exports found */
/***/ (function(module, exports) {

module.exports = (req,res) => {
  console.log('myCloudFunction is executing...');
  res.status(200).send('From myCloudFunction...');
}

/***/ }),

/***/ "O8Wp":
/*!*************************************!*\
  !*** external "firebase-functions" ***!
  \*************************************/
/*! no static exports found */
/***/ (function(module, exports) {

module.exports = firebase-functions;

/***/ }),

/***/ "oFca":
/*!********************************!*\
  !*** ./functions/src/index.js ***!
  \********************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {

const functions = __webpack_require__(/*! firebase-functions */ "O8Wp");
const myCloudFunction = __webpack_require__(/*! ./myCloudFunction */ "4ouX");

module.exports.myCloudFunction = functions.https.onRequest(myCloudFunction);

/***/ })

/******/ });

IT SHOULD BE WORKING, BUT I'M GETTING THIS ERROR:

I would expect to copy the contents of bundle.js and paste it to index.js and make it work. But when I do this, I get this error when I'm trying to serve the function. See the error line in the picture below.

>>> firebase serve --only hosting,functions

+  functions: Using node@10 from host.
+  functions: Emulator started at http://localhost:5001
i  functions: Watching "C:\Projects\test-ssr\functions" for Cloud Functions...
i  hosting: Serving hosting files from: public
+  hosting: Local server: http://localhost:5000
!  ReferenceError: firebase is not defined
    at Object.O8Wp (C:\Projects\test-ssr\functions\index.js:110:20)

enter image description here

QUESTION

What am I doing wrong? Any help would be appreciated!


UPDATE

I've just found out that this fixes the problem, but I have no idea why this is necessary. I still would like some better understanding of this issue.

webpack.prod.server.js

output: {
    filename: '[name].[contenthash:5].js',
    // filename: 'index.js',
    path: path.resolve(__dirname, './functions/dist'),
    publicPath: '/',
    libraryTarget: 'commonjs'   // <--- THIS FIXED IT
  },
like image 800
cbdeveloper Avatar asked Oct 28 '19 18:10

cbdeveloper


People also ask

Do I need Webpack for Firebase?

There is no specific configuration needed for Firebase apps and webpack. This section covers a general webpack configuration. The first step is to install webpack from npm as a development dependency. Create a file at the root of your local project named webpack.

What are cloud functions Firebase?

Cloud Functions for Firebase is a serverless framework that lets you automatically run backend code in response to events triggered by Firebase features and HTTPS requests. Your JavaScript or TypeScript code is stored in Google's cloud and runs in a managed environment.

Is Firebase cloud functions expensive?

Cloud functions in general are very cost-effective. It's difficult to compare pricing of cloud providers, but I can say that based on my experience, Firebase Cloud Functions have been ridiculously cheap.

Can I use cloud functions Firebase for free?

Cloud Functions includes a perpetual free tier for invocations to allow you to experiment with the platform at no charge. Note that even for free tier usage, we require a valid billing account.


Video Answer


1 Answers

As you found, you need to set libraryTarget in order to build a webpack bundle that can itself be imported by another module outside the webpack build (e.g. from cloud functions).

It's a good idea to bundle your functions app using webpack for alot of reasons. As you've pointed out, it means you can compile the same code in a consistent way for different purposes - e.g. SSR and browser side. Other benefits:

  • access to webpack loaders/plugins
  • much better developer experience with HMR (that works better than the functions runtime's implementation)
  • better performance for deployment and runtime (one or a few optimized chunks vs hundreds/thousands of unoptimised/unused files)

In any case, you might have an easier time using webpack-cloud-functions (of which I'm the author). It abstracts away much of the configuration you need to do that, including configuring the libraryTarget.

like image 102
hedgepigdaniel Avatar answered Oct 19 '22 03:10

hedgepigdaniel