Skip to content

Commit

Permalink
feat(lambda-nodejs): esbuild bundling (#11289)
Browse files Browse the repository at this point in the history
Replace Parcel with esbuild for bundling.

esbuild offers [impressive performances](https://esbuild.github.io/) compared to Parcel.
Moreover everything can be configured via the CLI. This means that
we don't  need to play with the user `package.json` file anymore.

Add full Windows support for local bundling.

Refactor and clean-up.

Closes #10286
Closes #9130
Closes #9312
Resolves #11222

BREAKING CHANGE: local bundling now requires `esbuild` to be installed.
* **lambda-nodejs**: `projectRoot` has been replaced by `depsLockFilePath`. It should point to your dependency lock file (`package-lock.json` or `yarn.lock`)
* **lambda-nodejs**: `parcelEnvironment` has been renamed to `bundlingEnvironment`
* **lambda-nodejs**: `sourceMaps` has been renamed to `sourceMap`
  • Loading branch information
jogold authored Nov 18, 2020
1 parent 0a8971c commit 7a82850
Show file tree
Hide file tree
Showing 20 changed files with 1,044 additions and 4,652 deletions.
5 changes: 2 additions & 3 deletions .github/workflows/yarn-upgrade.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,18 @@ jobs:
- name: Run "ncu -u"
# We special-case @types/node because we want to stay on the current major (minimum supported node release)
# We special-case @types/fs-extra because the current major (9.x) is broken with @types/node >= 10
# We special-case parcel because we are currently on a pre-release and don't want to move to nightlies
# We special-case aws-sdk because of breaking changes with TS interface exports in recent minor versions - https://github.com/aws/aws-sdk-js/issues/3453
# We special-case typescript because it's not semantically versionned
# We special-case constructs because we want to stay in control of the minimum compatible version
run: |-
# Upgrade dependencies at repository root
ncu --upgrade --filter=@types/node,@types/fs-extra --target=minor
ncu --upgrade --filter=typescript --target=patch
ncu --upgrade --reject=@types/node,@types/fs-extra,constructs,parcel,typescript --target=minor
ncu --upgrade --reject=@types/node,@types/fs-extra,constructs,typescript --target=minor
# Upgrade all the packages
lerna exec --parallel ncu -- --upgrade --filter=@types/node,@types/fs-extra --target=minor
lerna exec --parallel ncu -- --upgrade --filter=typescript --target=patch
lerna exec --parallel ncu -- --upgrade --reject='@types/node,@types/fs-extra,constructs,parcel,typescript,aws-sdk,${{ steps.list-packages.outputs.list }}' --target=minor
lerna exec --parallel ncu -- --upgrade --reject='@types/node,@types/fs-extra,constructs,typescript,aws-sdk,${{ steps.list-packages.outputs.list }}' --target=minor
# This will create a brand new `yarn.lock` file (this is more efficient than `yarn install && yarn upgrade`)
- name: Run "yarn install --force"
run: yarn install --force
Expand Down
2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,6 @@
"@aws-cdk/aws-ecr-assets/minimatch/**",
"@aws-cdk/aws-eks/yaml",
"@aws-cdk/aws-eks/yaml/**",
"@aws-cdk/aws-lambda-nodejs/parcel-bundler",
"@aws-cdk/aws-lambda-nodejs/parcel-bundler/**",
"@aws-cdk/cloud-assembly-schema/jsonschema",
"@aws-cdk/cloud-assembly-schema/jsonschema/**",
"@aws-cdk/cloud-assembly-schema/semver",
Expand Down
63 changes: 25 additions & 38 deletions packages/@aws-cdk/aws-lambda-nodejs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ up the entry file:
├── stack.my-handler.ts # exports a function named 'handler'
```

This file is used as "entry" for [Parcel](https://parceljs.org/). This means that your code is
automatically transpiled and bundled whether it's written in JavaScript or TypeScript.
This file is used as "entry" for [esbuild](https://esbuild.github.io/). This means that your code is automatically transpiled and bundled whether it's written in JavaScript or TypeScript.

Alternatively, an entry file and handler can be specified:

Expand All @@ -45,11 +44,11 @@ All other properties of `lambda.Function` are supported, see also the [AWS Lambd
The `NodejsFunction` construct automatically [reuses existing connections](https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/node-reusing-connections.html)
when working with the AWS SDK for JavaScript. Set the `awsSdkConnectionReuse` prop to `false` to disable it.

Use the `parcelEnvironment` prop to define environments variables when Parcel runs:
Use the `bundlingEnvironment` prop to define environments variables when esbuild runs:

```ts
new lambda.NodejsFunction(this, 'my-handler', {
parcelEnvironment: {
bundlingEnvironment: {
NODE_ENV: 'production',
},
});
Expand All @@ -73,37 +72,25 @@ new lambda.NodejsFunction(this, 'my-handler', {
});
```

This image should have Parcel installed at `/`. If you plan to use `nodeModules` it
This image should have esbuild installed globally. If you plan to use `nodeModules` it
should also have `npm` or `yarn` depending on the lock file you're using.

Use the [default image provided by `@aws-cdk/aws-lambda-nodejs`](https://github.com/aws/aws-cdk/blob/master/packages/%40aws-cdk/aws-lambda-nodejs/parcel/Dockerfile)
Use the [default image provided by `@aws-cdk/aws-lambda-nodejs`](https://github.com/aws/aws-cdk/blob/master/packages/%40aws-cdk/aws-lambda-nodejs/lib/Dockerfile)
as a source of inspiration.

### Project root
The `NodejsFunction` tries to automatically determine your project root, that is
the root of your node project. This is usually where the top level `node_modules`
folder of your project is located. When bundling in a Docker container, the
project root is used as the source (`/asset-input`) for the volume mounted in
the container.
### Lock file
The `NodejsFunction` requires a dependencies lock file (`yarn.lock` or
`package-lock.json`). When bundling in a Docker container, the path containing this
lock file is used as the source (`/asset-input`) for the volume mounted in the
container.

The following folders are considered by walking up parent folders starting from
the current working directory (order matters):
* the folder containing your `.git` folder
* the folder containing a `yarn.lock` file
* the folder containing a `package-lock.json` file
* the folder containing a `package.json` file
By default, it will try to automatically determine your project lock file.
Alternatively, you can specify the `depsLockFilePath` prop manually. In this
case you need to ensure that this path includes `entry` and any module/dependencies
used by your function. Otherwise bundling will fail.

Alternatively, you can specify the `projectRoot` prop manually. In this case you
need to ensure that this path includes `entry` and any module/dependencies used
by your function. Otherwise bundling will fail.

### Configuring Parcel
The `NodejsFunction` construct exposes some [Parcel](https://parceljs.org/) options via properties: `minify`, `sourceMaps` and `cacheDir`.

Parcel transpiles your code (every internal module) with [@babel/preset-env](https://babeljs.io/docs/en/babel-preset-env) and uses the
runtime version of your Lambda function as target.

Configuring Babel with Parcel is possible via a `.babelrc` or a `babel` config in `package.json`.
### Configuring esbuild
The `NodejsFunction` construct exposes some [esbuild](https://esbuild.github.io/) options via properties: `minify`, `sourceMaps` and `target`.

### Working with modules

Expand All @@ -121,10 +108,10 @@ new lambda.NodejsFunction(this, 'my-handler', {
```

#### Install modules
By default, all node modules referenced in your Lambda code will be bundled by Parcel.
By default, all node modules referenced in your Lambda code will be bundled by esbuild.
Use the `nodeModules` prop to specify a list of modules that should not be bundled
but instead included in the `node_modules` folder of the Lambda package. This is useful
when working with native dependencies or when Parcel fails to bundle a module.
when working with native dependencies or when esbuild fails to bundle a module.

```ts
new lambda.NodejsFunction(this, 'my-handler', {
Expand All @@ -133,25 +120,25 @@ new lambda.NodejsFunction(this, 'my-handler', {
```

The modules listed in `nodeModules` must be present in the `package.json`'s dependencies. The
same version will be used for installation. If a lock file is detected (`package-lock.json` or
`yarn.lock`) it will be used along with the right installer (`npm` or `yarn`).
same version will be used for installation. The lock file (`yarn.lock` or `package-lock.json`)
will be used along with the right installer (`yarn` or `npm`).

### Local bundling
If Parcel v2.0.0-beta.1 is available it will be used to bundle your code in your environment. Otherwise,
If esbuild is available it will be used to bundle your code in your environment. Otherwise,
bundling will happen in a [Lambda compatible Docker container](https://hub.docker.com/r/amazon/aws-sam-cli-build-image-nodejs12.x).

For macOS the recommendend approach is to install Parcel as Docker volume performance is really poor.
For macOS the recommendend approach is to install esbuild as Docker volume performance is really poor.

Parcel v2.0.0-beta.1 can be installed with:
esbuild can be installed with:

```bash
$ npm install --save-dev --save-exact [email protected]
$ npm install --save-dev esbuild@0
```

OR

```bash
$ yarn add --dev --exact [email protected]
$ yarn add --dev esbuild@0
```

To force bundling in a Docker container, set the `forceDockerBundling` prop to `true`. This
Expand Down
10 changes: 1 addition & 9 deletions packages/@aws-cdk/aws-lambda-nodejs/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,2 @@
const baseConfig = require('cdk-build-tools/config/jest.config');
module.exports = {
...baseConfig,
coverageThreshold: {
global: {
...baseConfig.coverageThreshold.global,
branches: 50,
},
},
};
module.exports = baseConfig;
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ FROM $IMAGE
# Install yarn
RUN npm install --global [email protected]

# Install parcel 2 (fix the version since it's still in beta)
# install at "/" so that node_modules will be in the path for /asset-input
ARG PARCEL_VERSION=2.0.0-beta.1
RUN cd / && npm install parcel@$PARCEL_VERSION --no-package-lock
# Install esbuild
# (unsafe-perm because esbuild has a postinstall script)
ARG ESBUILD_VERSION=0
RUN npm install --global --unsafe-perm=true esbuild@$ESBUILD_VERSION

# Ensure all users can write to npm cache
RUN mkdir /tmp/npm-cache && \
Expand All @@ -22,4 +22,4 @@ RUN npm config --global set update-notifier false
# create non root user and change allow execute command for non root user
RUN /sbin/useradd -u 1000 user && chmod 711 /

CMD [ "parcel" ]
CMD [ "esbuild" ]
193 changes: 0 additions & 193 deletions packages/@aws-cdk/aws-lambda-nodejs/lib/bundlers.ts

This file was deleted.

Loading

0 comments on commit 7a82850

Please sign in to comment.