## NodeJS - NPM Lambda Builder Using `esbuild` ### Scope The scope for this builder is to take an existing directory containing customer code, including a valid `package.json` manifest specifying third-party dependencies. The builder will use NPM to include production dependencies and exclude test resources in a way that makes them deployable to AWS Lambda. It will then bundle the code using `esbuild` with the properties passed in through the builder options field. ### Additional Tools Packaging with a bundler requires installing additional tools (eg `esbuild`). ### Implementation with a bundler The general algorithm for preparing a node package for use on AWS Lambda with a bundler (`esbuild` or `webpack`) is as follows. #### Step 1: ensure production dependencies are installed If the directory contains `package-lock.json` or `npm-shrinkwrap.json`, execute [`npm ci`](https://docs.npmjs.com/cli/v7/commands/npm-ci). This operation is designed to be faster than installing dependencies using `npm install` in automated CI environments. If the directory does not contain lockfiles, but contains `package.json`, execute [`npm install --production`] to download production dependencies. #### Step 2: bundle the main Lambda file Execute `esbuild` to produce a single JavaScript file by recursively resolving included dependencies, and optionally a source map. Ensure that the target file name is the same as the entry point of the Lambda function. ### Activating the bundler workflow The workflow can be activated by using the `("nodejs", "npm-esbuild", None)` Capability. The distinguishing factor being the `npm-esbuild` dependency-manager property of the builder. An entrypoint or entrypoints array must be included in the options passed to Lambda Builders for this workflow to succeed. The following example is a minimal options object that can be passed to the esbuild workflow, starting from `lambda.js`. It will produce a bundled `lambda.js` in the artifacts folder. ```json { "options": { "entry_points": ["lambda.js"] } } ``` #### Locating the esbuild binary `esbuild` supports platform-independent binary distribution using NPM, by including the `esbuild` package as a dependency. The Lambda builder should first try to locate the binary in the Lambda code repository (allowing the user to include a specific version). Failing that, the Lambda builder should try to locate the `esbuild` binary in the `executable_search_paths` configured for the workflow, then the operating system `PATH` environment variable. The Lambda builder **should not** bring its own `esbuild` binary, but it should clearly point to the error when one is not found, to allow users to configure the build correctly. In the previous example, the esbuild binary is not included in the package dependencies, so the Lambda builder will use the system executable paths to search for it. In the example below, `esbuild` is included in the package, so the Lambda builder should use it directly. ```json { "name": "with-deps-esbuild", "version": "1.0.0", "license": "APACHE2.0", "devDependencies": { "esbuild": "^0.11.23" } } ``` For a full example, see the [`with-deps-esbuild`](../../../tests/integration/workflows/nodejs_npm_esbuild/testdata/with-deps-esbuild/) test project. #### Building typescript `esbuild` supports bundling typescript out of the box and transpiling it to plain javascript. The user just needs to point to a typescript file as the main entry point, as in the example below. There is no transpiling process needed upfront. If no file type is provided for the entrypoint, esbuild will first look for a TypeScript file, and the a JavaScript file with the given filename. ```js { "name": "with-deps-esbuild-typescript", "version": "1.0.0", "license": "APACHE2.0", "dependencies": { "@types/aws-lambda": "^8.10.76" }, "devDependencies": { "esbuild": "^0.11.23" } } ``` For a full example, see the [`with-deps-esbuild-typescript`](../../../tests/integration/workflows/nodejs_npm_esbuild/testdata/with-deps-esbuild-typescript/) test project. **important note:** esbuild does not perform type checking, so users wanting to ensure type-checks need to run the `tsc` process as part of their testing flow before invoking `sam build`. For additional typescript caveats with esbuild, check out . #### Configuring the bundler The Lambda builder invokes `esbuild` with sensible defaults that will work for the majority of cases. Importantly, the following parameters are set by default * `--minify`, as it [produces a smaller runtime package](https://esbuild.github.io/api/#minify) * `--target es2020`, as it allows for javascript features present in Node 14 Users might want to tweak some of these runtime arguments for a specific project, for example not including the source map to further reduce the package size, or restricting javascript features to an older version. The Lambda builder allows this with optional sub-properties of the `aws_sam` configuration property. * `target`: string, corresponding to a supported [esbuild target](https://esbuild.github.io/api/#target) property * `minify`: boolean, defaulting to `true` * `sourcemap`: boolean, defaulting to `false` Here is an example that deactivates minification, enables source maps, and supports JavaScript features compatible with Node.js version 10. ```json { "entry_points": ["included.ts"], "target": "node10", "minify": false, "sourcemap": true }