与 webpack 捆绑的 Firebase 云功能?

cbd*_*per 6 javascript node.js firebase webpack google-cloud-functions

我一直在尝试为我的云函数文件构建webpack

我的项目结构:

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)
Run Code Online (Sandbox Code Playgroud)

我的目标是:

  • 编写云函数文件和index.js现代 JS文件,然后将它们转译并与 webpack 捆绑(使用与webpack捆绑我的客户端代码相同的方法,但使用另一个配置文件)。

myCloudFunction.js(记录一些文本的简单函数)

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

entryPoint.js(基本上是导入函数的代码并将它们导出为云函数)

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

module.exports.myCloudFunction = functions.https.onRequest(myCloudFunction);
Run Code Online (Sandbox Code Playgroud)

如果我制作的index.js和我的完全一样,entryPoint.js它就可以正常工作。但我想使用 webpackentryPoint.js捆绑index.js文件并将捆绑结果设置为我的文件。在这种情况下,基本上只会捆绑 2 个文件 (entryPointmyCloudFunction)。

我正在使用 webpack 捆绑:

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']
      },   
    ]
  }
};
Run Code Online (Sandbox Code Playgroud)

我读到您不应该将 node_modules 捆绑到后端,这就是我使用该externals属性的原因。

bundle.js(使用上面的配置运行 webpack 后的结果包)

/******/ (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);

/***/ })

/******/ });
Run Code Online (Sandbox Code Playgroud)

它应该可以工作,但我遇到了这个错误:

我希望复制内容bundle.js并将其粘贴到index.js并使其工作。但是当我这样做时,当我尝试提供该功能时会出现此错误。请参阅下图中的错误行。

>>> 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)
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

我究竟做错了什么?任何帮助,将不胜感激!


更新

我刚刚发现这解决了问题,但我不知道为什么这是必要的。我仍然希望对这个问题有更好的了解。

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
  },
Run Code Online (Sandbox Code Playgroud)

小智 3

正如您所发现的,您需要进行设置libraryTarget才能构建一个 webpack 捆绑包,该捆绑包本身可以由 webpack 构建之外的另一个模块(例如从云函数)导入。

出于多种原因,使用 webpack 捆绑函数应用程序是个好主意。正如您所指出的,这意味着您可以以一致的方式编译相同的代码以用于不同的目的 - 例如 SSR 和浏览器端。其他福利:

  • 访问 webpack 加载器/插件
  • 更好的 HMR 开发体验(比函数运行时的实现更好)
  • 更好的部署和运行时性能(一个或几个优化的块与数百/数千个未优化/未使用的文件)

无论如何,您可能会更轻松地使用webpack-cloud-functions(我是其作者)。它抽象了执行此操作所需的大部分配置,包括配置libraryTarget.