@mrgrain/cdk-esbuild

CDK constructs for esbuild, an extremely fast JavaScript bundler

Usage no npm install needed!

<script type="module">
  import mrgrainCdkEsbuild from 'https://cdn.skypack.dev/@mrgrain/cdk-esbuild';
</script>

README

cdk-esbuild

CDK constructs for esbuild, an extremely fast JavaScript bundler

⚠️ This version is compatible with AWS CDK v2. For the previous, AWS CDK v1 compatible release, see cdk-esbuild@v2

Getting started | Migrating to v3 | Documentation | API Reference | Versioning

Why?

esbuild is an extremely fast bundler and minifier for Typescript and JavaScript. This package makes esbuild available to deploy lambda functions, static websites or to publish build artefacts (assets) for further use.

AWS CDK supports esbuild with Lambda Functions. However the implementation cannot be used with any other Constructs and doesn't expose all of esbuild's build interface.

This package is running esbuild directly in Node.js and bypasses Docker which the AWS CDK implementation uses. The approach is quicker and easier to use for Node.js users, but incompatible with other languages.

Production readiness

This package is generally stable and ready to be used in production, as many do. However esbuild not yet released a version 1.0.0 yet and its API is still in active development. Please check their guide on production readiness.

Notably upgrades of the esbuild minimum version requirement will be introduced in minor versions of this package and will inherit breaking changes from esbuild.

Getting started

Install cdk-esbuild:

npm install @mrgrain/cdk-esbuild@3

If peer and optional dependencies are not installed automatically (e.g. when using npm v4-6), please use this command to install all of them:

npm install @mrgrain/cdk-esbuild@3 esbuild

AWS Lambda: Serverless function

💡 See Lambda Function for a complete working example of a how to deploy a lambda function.

Use TypeScriptCode as the code of a lambda function:

import * as lambda from "aws-cdk-lib/aws-lambda";
import { TypeScriptCode } from "@mrgrain/cdk-esbuild";

const bundledCode = new TypeScriptCode("src/index.ts");

const fn = new lambda.Function(stack, "MyFunction", {
  runtime: lambda.Runtime.NODEJS_14_X,
  handler: "index.handler",
  code: bundledCode,
});

AWS S3: Static Website

💡 See Static Website with React for a complete working example of a how to deploy a React app to S3.

Use TypeScriptSource as one of the sources of a static website deployment:

import * as s3 from "aws-cdk-lib/aws-s3";
import * as s3deploy from "aws-cdk-lib/aws-s3-deployment";
import { TypeScriptSource } from "@mrgrain/cdk-esbuild";

const websiteBundle = new TypeScriptSource("src/index.tsx");

const websiteBucket = new s3.Bucket(stack, "WebsiteBucket", {
  autoDeleteObjects: true,
  publicReadAccess: true,
  removalPolicy: RemovalPolicy.DESTROY,
  websiteIndexDocument: "index.html",
});

new s3deploy.BucketDeployment(stack, "DeployWebsite", {
  destinationBucket: websiteBucket,
  sources: [websiteBundle],
});

Amazon CloudWatch Synthetics: Canary monitoring

💡 See Monitored Website for a complete working example of a deployed and monitored website.

Synthetics runs a canary to produce traffic to an application for monitoring purposes. Use TypeScriptCode as the code of a Canary test:

ℹ️ This feature depends on the @aws-cdk/aws-synthetics-alpha package which is a developer preview. You may need to update your source code when upgrading to a newer version of this package.

npm i @aws-cdk/aws-synthetics-alpha
import * as synthetics from "@aws-cdk/aws-synthetics-alpha";
import { TypeScriptCode } from "@mrgrain/cdk-esbuild";

const bundledCode = new TypeScriptCode("src/index.ts", {
  buildOptions: {
    outdir: "nodejs/node_modules", // This is required by Synthetics
  },
});

const canary = new synthetics.Canary(stack, "MyCanary", {
  runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_3_2,
  test: synthetics.Test.custom({
    code: bundledCode,
    handler: "index.handler",
  });
});

Documentation

The package exports various different constructs for use with existing CDK features. A major guiding design principal for this package is to extend, don't replace. Expect constructs that you can provide as props, not complete replacements.

For use in Lambda Functions and Synthetic Canaries, the following classes implement lambda.Code (reference) and synthetics.Code (reference):

  • TypeScriptCode & JavaScriptCode

Inline code is only supported by Lambda:

  • InlineTypeScriptCode & InlineJavaScriptCode
  • InlineTsxCode & InlineJsxCode

For use with S3 bucket deployments, classes implementing s3deploy.ISource (reference):

  • TypeScriptSource & JavaScriptSource

Code and Source constructs seamlessly plugin to high-level CDK features. They share the same set of parameters, props and build options.

Underlying classes power the other features. You normally won't have to use them, but they are there if you need them:

  • TypeScriptAsset & JavaScriptAsset implements s3.Asset (reference)
    creates an asset uploaded to S3 which can be referenced by other constructs

  • EsbuildBundler implements core.BundlingOptions (reference)
    provides an interface for a esbuild bundler wherever needed

API Reference

Auto-generated reference for classes and structs. This information is also available within the code completion of your IDE.

Escape hatches

It's possible that you want to use a implementation of esbuild that's different to the default one. Common reasons are:

  • The current version constraints for esbuild are not suitable
  • To use version of esbuild that is installed by any other means than npm, including Docker
  • Plugin support is needed for the building

For these situations, this package offers an escape hatch to bypass regular the implementation and provide a custom build and transform function.

Custom build function

💡 See Using esbuild with plugins for a complete working example of a custom build function using this escape hatch.

Constructs that result in starting a build, take a buildFn as optional prop. While the defined type for this function is any, it must implement the same signature as esbuild's buildSync function.

new TypeScriptCode("fixtures/handlers/ts-handler.ts", {
  buildFn: (options: BuildOptions): BuildResult => {
    try {
      // custom implementation returning BuildResult
    } catch (error) {
      // throw BuildFailure exception here
    }
  },
});

Instead of esbuild, the provided function will be invoked with the calculated build options. The custom build function can amend, change or discard any of these. However integration with CDK relies heavily on the values outdir/outfile are set to and it's usually required to use them unchanged.

Failures have to cause a BuildFailure exception in order to be fully handled.

Custom transform function

Constructs that result in starting a transformation, take a transformFn as optional prop. While the defined type for this function is any, it must implement the same signature as esbuild's transformSync function.

new InlineTypeScriptCode("let x: number = 1", {
  transformFn: (options: TransformOptions): TransformResult => {
    try {
      // custom implementation returning TransformResult
    } catch (error) {
      // throw TransformFailure exception here
    }
  },,
});

Instead of esbuild, the provided function will be invoked with the calculated transform options. The custom transform function can amend, change or discard any of these.

Failures have to cause a TransformFailure exception in order to be fully handled.

Migrating to v3

The release of cdk-esbuild v3 brings compatibility with AWS CDK v2. Furthermore all deprecated properties and classes have been removed. In particular InlineCode classes now take TransformerProps as second parameter instead of transform options.

Upgrading

  • This version requires AWS CDK v2. Follow the official migration guide to upgrade.

  • Update the package dependency to v3: npm install --save @mrgrain/cdk-esbuild@^3.0.0

  • esbuild is installed as an optional dependency. If your setup does not automatically install optional dependencies, make sure to add it as an explicit dependency.

  • Any use of InlineCode variations has to be updated. Previously the second parameter was either of type TransformerProps or TransformOptions. Now it must be TransformerProps.
    If the passed value is of type TransformOptions, turn it into the correct type like this:

    const oldOptions: TransformOptions = {...}
    
    new InlineTypeScriptCode('// inline code', {
      transformOptions: oldOptions
    });
    

Versioning

This package mostly follows Semantic Versioning, with the exception of upgrades to esbuild. These will be released as minor versions and often include breaking changes from esbuild.

Npm Tags

Some users prefer to use tags over version ranges. The following stable tags are available for use:

  • cdk-v1, cdk-v2 are provided for the latest release compatible with each version of the AWS CDK.

  • latest is the most recent stable release.

These tags also exist, but usage is strongly not recommended:

  • unstable, next are used for pre-release of the current and next major version respectively.

  • cdk-1.x.x tags have been deprecated in favour of cdk-v1. Use that one instead.

Future releases

Stable esbuild

Once esbuild has reached a stable version 1.0, a new major version will be released for all breaking changes, including updates to minimum (peer) dependencies.

Library authors

When developing a library consumed by other packages, you'll most likely have to set buildOptions.absWorkingDir. The easiest way to do this, is to resolve based on the directory name of the file, and traverse the tree upwards to the root of your library package (that's where package.json and tsconfig.json are):

// file: project/src/index.ts
const props = {
  buildOptions: {
    absWorkingDir: path.resolve(__dirname, ".."), // now: /user/project
  },
};

This will dynamically resolve to the correct path, wherever the package is installed.