import { createHash } from "crypto"; import { existsSync } from "fs"; import { basename, dirname, extname, join, resolve } from "path"; import { Code, Function, FunctionOptions, Runtime, RuntimeFamily, SingletonFunction, } from "@aws-cdk/aws-lambda"; import { Construct } from "@aws-cdk/core"; import { Builder } from "./builder"; /** * Properties for a NodejsFunction */ export interface WebpackFunctionProps extends FunctionOptions { /** * Path to the entry file (JavaScript or TypeScript). * * @example - aws/lambda/yourFunction.ts */ readonly entry: string; /** * Path to webpack config file. */ readonly config: string; /** * The name of the exported handler in the entry file. * * @default handler */ readonly handler?: string; /** * The runtime environment. Only runtimes of the Node.js family are * supported. * * @default - NODEJS_14 */ readonly runtime?: Runtime; /** * The build directory * * @default - `.build` in the entry file directory */ readonly buildDir?: string; /** * Ensure a unique build path * * @default - true */ readonly ensureUniqueBuildPath?: boolean; } export interface WebpackSingletonFunctionProps extends WebpackFunctionProps { readonly uuid: string; readonly lambdaPurpose?: string; } /** * A Node.js Lambda function bundled using Parcel */ export class WebpackFunction extends Function { constructor(scope: Construct, id: string, props: WebpackFunctionProps) { const { runtime, handlerDir, outputBasename, handler } = preProcess(props); super(scope, id, { ...props, runtime, code: Code.fromAsset(handlerDir), handler: `${outputBasename}.${handler}`, }); } } export class WebpackSingletonFunction extends SingletonFunction { constructor( scope: Construct, id: string, props: WebpackSingletonFunctionProps ) { const { runtime, handlerDir, outputBasename, handler } = preProcess(props); super(scope, id, { ...props, runtime, code: Code.fromAsset(handlerDir), handler: `${outputBasename}.${handler}`, }); } } function preProcess(props: WebpackFunctionProps) { if (props.runtime && props.runtime.family !== RuntimeFamily.NODEJS) { throw new Error("Only `NODEJS` runtimes are supported."); } if (!/\.(js|ts)$/.test(props.entry)) { throw new Error("Only JavaScript or TypeScript entry files are supported."); } if (!existsSync(props.entry)) { throw new Error(`Cannot find entry file at ${props.entry}`); } if (!existsSync(props.config)) { throw new Error(`Cannot find webpack config file at ${props.config}`); } const handler = props.handler || "handler"; const runtime = props.runtime || Runtime.NODEJS_14_X; const buildDir = props.buildDir || join(dirname(props.entry), ".build"); const ensureUniqueBuildPath = typeof props.ensureUniqueBuildPath === "boolean" ? props.ensureUniqueBuildPath : true; const handlerDir = ensureUniqueBuildPath ? createUniquePath(buildDir, props.entry) : buildDir; const outputBasename = basename(props.entry, extname(props.entry)); // Build with webpack const builder = new Builder({ entry: resolve(props.entry), output: resolve(join(handlerDir, outputBasename + ".js")), config: resolve(props.config), }); builder.build(); return { runtime, handlerDir, outputBasename, handler }; } function createUniquePath(buildDir: string, currentPath: string) { return join(buildDir, createHash("sha256").update(currentPath).digest("hex")); }