typescriptamazon-web-servicesaws-lambdawebpack-5

Webpack 5 Builds for AWS Lambda with TypeScript - Runtime.HandlerNotFound


I have deployed an AWS Lambda function using Node.js 18 as the runtime and configured the handler as bundle.handler. The code is written in TypeScript and compiled with Webpack 5. However, upon testing the function in the AWS console, I got the following error:

"errorType": "Runtime.HandlerNotFound",
"errorMessage": "bundle.handler is undefined or not exported"

webpack.config.ts

import { Configuration } from "webpack";
import { resolve } from "path";

const config: Configuration = {
  name: "bundle",
  mode: "none",
  entry: {
    bundle: "./src/main.ts",
  },
  target: "node",
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        exclude: /node_modules/,
        use: {
          loader: "ts-loader",
          options: {
            configFile: "tsconfig.json",
          },
        },
      },
    ],
  },
  resolve: {
    modules: ["node_modules"],
    extensions: [".tsx", ".ts", ".js", ".json"],
    alias: {
      "@": resolve(__dirname, "../code/src"),
    },
  },
  output: {
    filename: "[name].js",
    path: resolve(__dirname, "dist"),
    globalObject: "this",
    library: {
      name: "bundle",
      type: "commonjs2",
    },
  },
};

export default config;

main.ts fragment

export const handler: Handler = async (event, context): Promise<any> => {
  ...
  return context.logStreamName;
};

bundle.js fragment

const handler = (event, context) => __awaiter(void 0, void 0, void 0, function* () {
    ...
    return context.logStreamName;
});
exports.handler = handler;

...

/******/    // module cache are used so entry inlining is disabled
/******/    // startup
/******/    // Load entry module and return exports
/******/    var __webpack_exports__ = __webpack_require__(__webpack_require__.s = 0);
/******/    module.exports.bundle = __webpack_exports__;

Any pointers in the right direction would be greatly appreciated!


Solution

  • When using webpack's output.library.type with a value of commonjs the built library is exposed to consumers as such:

    The return value of your entry point will be assigned to the exports object using the output.library.name value. As the name implies, this is used in CommonJS environments.

    exports['MyLibrary'] = _entry_return_;
    
    require('MyLibrary').doSomething();
    

    You are running into issues because you are also using an object for entry which produces chunks named after their associated keys, in this case bundle, the same name used for your library.

    According to the AWS docs for a lambda handler function in Node.js

    When you configure a function, the value of the handler setting is the file name and the name of the exported handler method, separated by a dot.

    So when the lambda runtime attempts to load your handler it will call the bundle file name (bundle) and the name of your handler (handler), separated by a dot: bundle.handler. But your library build is named bundle, so your handler will be exposed as function of that namespace: bundle.bundle.handler.

    An easy solution with your current configuration would be to not use the output.library.name and let your named entry export of handler be directly attached to the exports object under no namespace. There is a warning when in the webpack docs when not using output.library.name:

    Note that not setting a output.library.name will cause all properties returned by the entry point to be assigned to the given object; there are no checks against existing property names.

    Thanks for the warning, noted, but in this case the runtime expects the exports to be directly attached based on the name already given to your bundle, and naming collisions are not anticipated, so removing the library name is acceptable.

    There are other ways to produce a webpack build that is supported by the AWS lambda runtime, and uses output.library.name. In those other cases you most likely would not also use an object for your entry, but rather a string or array of strings, and then explicitly set output.filename to your library's name.