Skip to main content

CDK constructs for esbuild, an extremely fast JavaScript bundler

Project description

cdk-esbuild

CDK constructs for esbuild, an extremely fast JavaScript bundler

Getting started | Documentation | API Reference | FAQ

View on Construct Hub

Why?

esbuild is an extremely fast bundler and minifier for Typescript and JavaScript. This package makes esbuild available to deploy AWS Lambda Functions, static websites and publish assets for use.

AWS CDK supports esbuild for AWS Lambda Functions, but the implementation cannot be used with other Constructs and doesn't expose all of esbuild's API.

Production readiness

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

Esbuild minor version upgrades are introduced in minor versions of this package and inherit breaking changes from esbuild.

Getting started

Install cdk-esbuild for Node.js with your favorite package manager:

# npm
npm install @mrgrain/cdk-esbuild@4
# Yarn
yarn add @mrgrain/cdk-esbuild@4
# pnpm
pnpm add @mrgrain/cdk-esbuild@4

For Python and Dotnet, use these commands:

# Python
pip install streamlink-serverless

# Dotnet
dotnet add package StreamlinkServerless

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:

# Example automatically generated from non-compiling source. May contain errors.
bundled_code = TypeScriptCode("src/handler.ts")

fn = lambda_.Function(stack, "MyFunction",
    runtime=lambda_.Runtime.NODEJS_18_X,
    handler="index.handler",
    code=bundled_code
)

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:

website_bundle = TypeScriptSource("src/index.tsx")

website_bucket = s3.Bucket(stack, "WebsiteBucket",
    auto_delete_objects=True,
    public_read_access=True,
    removal_policy=cdk.RemovalPolicy.DESTROY,
    website_index_document="index.html"
)

s3deploy.BucketDeployment(stack, "DeployWebsite",
    destination_bucket=website_bucket,
    sources=[website_bundle]
)

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
bundled_code = TypeScriptCode("src/canary.ts",
    build_options=BuildOptions(
        outdir="nodejs/node_modules"
    )
)

canary = synthetics.Canary(stack, "MyCanary",
    runtime=synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_3_2,
    test=synthetics.Test.custom(
        code=bundled_code,
        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

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

  • TypeScriptSource & JavaScriptSource

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

The following 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
  • EsbuildProvider implements IBuildProvider and ITransformProvider
    provides the default esbuild API implementation and can be replaced with a custom implementation

API Reference

Auto-generated reference for Classes and Structs. This information is also available as part of your IDE's code completion.

Python and Dotnet

Because esbuild requires a platform and architecture specific binary, it currently has to be installed using npm (or any other Node.js package manager).

When cdk-esbuild is used with Python or Dotnet, it will automatically detect a local or global installation of the esbuild npm package - or fallback to dynamically installing a copy into a temporary location. The exact algorithm of this mechanism must be treated as an implementation detail and should not be relied on. It can however be configured to a certain extent. See the examples below for more details.

While this "best effort" approach makes it easy to get started, it is not always desirable. For example in environments with limited network access or when guaranteed repeatability of builds is a concern. You have several options to opt-out of this behavior:

  • Recommended - Install esbuild as a local package
    The recommended approach is to manage an additional Node.js project in the root of your AWS CDK project. esbuild should then be added to the package.json file and it is your responsibility to ensure the required setup steps are run in every environment (development machines & CI/CD systems). The esbuild package will then be detected automatically.

  • Install esbuild as a global package
    Instead of installing the package in a local project, it can also be installed globally with npm install -g esbuild or a similar command. The esbuild package will be detected automatically from the global installation. This approach might be preferred if a build container is prepared ahead of time, thus avoiding repeated package installation.

  • Set CDK_ESBUILD_MODULE_PATH env variable
    If an installed esbuild module cannot be reliably detected by the algorithm, you can provide the absolute path to the module as an environment variable. This approach has the advantage that the location of the module can be different on different systems. While it can be combined with either installation approach, it is usually used with a global installation of the esbuild package.

  • Set esbuildModulePath prop
    Similar to setting the module path via env variable, it can also be passed as the esbuildModulePath prop to a EsbuildProvider:

    EsbuildProvider(
        esbuild_module_path="/home/user/node_modules/esbuild/lib/main.js"
    )
    
  • Override the default implementation provider
    Using the previous approach, but setting it for every usage:

    custom_module = EsbuildProvider(
        esbuild_module_path="/home/user/node_modules/esbuild/lib/main.js"
    )
    EsbuildProvider.override_default_provider(custom_module)
    

The esbuildModulePath can be provided as a known, absolute or relative path. When using the programmatic interface, this package additionally offers some helper methods to influence to fine-tune to automatic detection algorithm:

# Example automatically generated from non-compiling source. May contain errors.
# This will force installation to a temporary location
EsbuildProvider(
    esbuild_module_path=EsbuildSource.install()
)

Please see the EsbuildSource reference for a list of available methods.

Customizing the Esbuild API

This package uses the esbuild JavaScript API. For most use cases the default configuration will be suitable.

In some cases it might be useful to configure esbuild differently or even provide a completely custom implementation. Common examples for this are:

  • To use a pre-installed version of esbuild with Python and Dotnet
  • If features not supported by the synchronous API are required, e.g. support for plugins
  • If the default version constraints for esbuild are not suitable
  • To use a version of esbuild that is installed by any other means than npm, including Docker

For these scenarios, this package offers customization options and an interface to provide a custom implementation:

# my_custom_build_provider: IBuildProvider

# my_custom_transform_provider: ITransformProvider


TypeScriptCode("src/handler.ts",
    build_provider=my_custom_build_provider
)

InlineTypeScriptCode("let x: number = 1",
    transform_provider=my_custom_transform_provider
)

Esbuild binary path

It is possible to override the binary used by esbuild by setting a property on EsbuildProvider. This is the same as setting the ESBUILD_BINARY_PATH environment variable. Defining the esbuildBinaryPath prop takes precedence.

build_provider = EsbuildProvider(
    esbuild_binary_path="path/to/esbuild/binary"
)

# This will use a different esbuild binary
TypeScriptCode("src/handler.ts", build_provider=build_provider)

Esbuild module path

The Node.js module discovery algorithm will normally be used to find the esbuild package. It can be useful to use specify a different module path, for example if a globally installed package should be used instead of a local version.

build_provider = EsbuildProvider(
    esbuild_module_path="/home/user/node_modules/esbuild/lib/main.js"
)

# This will use a different esbuild module
TypeScriptCode("src/handler.ts", build_provider=build_provider)

Alternatively supported by setting the CDK_ESBUILD_MODULE_PATH environment variable, which will apply to all uses. Defining the esbuildModulePath prop takes precedence.

Custom Build and Transform API implementations

💡 See Using esbuild with plugins for a complete working example of a custom Build API implementation.

A custom implementation can be provided by implementing IBuildProvider or ITransformProvider:

@jsii.implements(IBuildProvider)
@jsii.implements(ITransformProvider)
class CustomEsbuild:
    def build_sync(self, *, bundle=None, splitting=None, preserveSymlinks=None, outfile=None, metafile=None, outdir=None, outbase=None, external=None, packages=None, alias=None, loader=None, resolveExtensions=None, mainFields=None, conditions=None, write=None, allowOverwrite=None, tsconfig=None, outExtension=None, publicPath=None, entryNames=None, chunkNames=None, assetNames=None, inject=None, banner=None, footer=None, incremental=None, absWorkingDir=None, nodePaths=None, sourcemap=None, legalComments=None, sourceRoot=None, sourcesContent=None, format=None, globalName=None, target=None, supported=None, platform=None, mangleProps=None, reserveProps=None, mangleQuoted=None, mangleCache=None, drop=None, minify=None, minifyWhitespace=None, minifyIdentifiers=None, minifySyntax=None, charset=None, treeShaking=None, ignoreAnnotations=None, jsx=None, jsxFactory=None, jsxFragment=None, jsxImportSource=None, jsxDev=None, jsxSideEffects=None, define=None, pure=None, keepNames=None, color=None, logLevel=None, logLimit=None, logOverride=None):
        pass

    def transform_sync(self, code, *, tsconfigRaw=None, sourcefile=None, loader=None, banner=None, footer=None, sourcemap=None, legalComments=None, sourceRoot=None, sourcesContent=None, format=None, globalName=None, target=None, supported=None, platform=None, mangleProps=None, reserveProps=None, mangleQuoted=None, mangleCache=None, drop=None, minify=None, minifyWhitespace=None, minifyIdentifiers=None, minifySyntax=None, charset=None, treeShaking=None, ignoreAnnotations=None, jsx=None, jsxFactory=None, jsxFragment=None, jsxImportSource=None, jsxDev=None, jsxSideEffects=None, define=None, pure=None, keepNames=None, color=None, logLevel=None, logLimit=None, logOverride=None):
        # custom implementation goes here, return transformed code
        return "transformed code"

# These will use the custom implementation
TypeScriptCode("src/handler.ts",
    build_provider=CustomEsbuild()
)
InlineTypeScriptCode("let x: number = 1",
    transform_provider=CustomEsbuild()
)

Instead of esbuild, the custom methods will be invoked with all computed options. The custom implementation can amend, change or discard any of these. However with IBuildProvider the integration with CDK relies heavily on the outdir/outfile values and it's usually required to use them unchanged. ITransformProvider must return the transformed code as a string.

Failures and warnings should be printed to stderr and thrown as the respective esbuild error.

Overriding the default implementation providers

It is also possible to change the default Esbuild API implementation for all usages of this package. You can change the default for both APIs or set a different implementation for each of them.

my_custom_esbuild_provider = MyCustomEsbuildProvider()

EsbuildProvider.override_default_provider(my_custom_esbuild_provider)
EsbuildProvider.override_default_build_provider(my_custom_esbuild_provider)
EsbuildProvider.override_default_transformation_provider(my_custom_esbuild_provider)

# This will use the custom provider without the need to define it as a prop
TypeScriptCode("src/handler.ts")

Roadmap & Contributions

The project's roadmap is available on GitHub. Please submit feature requests as issues to the repository.

All contributions are welcome, no matter if they are for already planned or completely new features.

FAQ

How do I upgrade from @mrgrain/cdk-esbuild v3?

Please refer to the v4 release notes for a list of backwards incompatible changes and respective upgrade instructions.

[TS/JS] Why am I getting the error Cannot find module 'esbuild'?

This package depends on esbuild as an optional dependencies. If optional dependencies are not installed automatically on your system (e.g. when using npm v4-6), install esbuild explicitly:

npm install esbuild

[TS/JS] How can I use a different version of esbuild?

Use the override instructions for your package manager to force a specific version, for example:

{
  "overrides": {
    "esbuild": "0.14.7"
  }
}

Build and Transform interfaces are relatively stable across esbuild versions. However if any incompatibilities occur, buildOptions / transformOptions can be cast to any:

bundled_code = TypeScriptCode("src/handler.ts",
    build_options={
        "unsupported_option": "value"
    }
)

[Python/Dotnet] How can I use a different version of esbuild?

Install the desired version of esbuild locally or globally as described in the documentation above.

Build and Transform interfaces are relatively stable across esbuild versions. However if any incompatibilities occur, use the appropriate language features to cast any incompatible buildOptions / transformOptions to the correct types.

Can I use this package in my published AWS CDK Construct?

It is possible to use cdk-esbuild in a published AWS CDK Construct library, but not recommended. Always prefer to ship a compiled .js file or even bundle a zip archive in your package. For AWS Lambda Functions, projen provides an excellent solution.

If you do end up consuming cdk-esbuild, you will have to set buildOptions.absWorkingDir. The easiest way to do this, is to resolve the path based on the directory name of the calling file:

// file: node_modules/construct-library/src/index.ts
const props = {
  buildOptions: {
    absWorkingDir: path.resolve(__dirname, ".."),
    // now: /user/local-app/node_modules/construct-library
  },
};

This will dynamically resolve to the correct path, wherever the package is installed. Please open an issue if you encounter any difficulties.

Can I use this package with AWS CDK v1?

Yes, the 2.x.x line of cdk-esbuild is compatible with AWS CDK v1. You can find the documentation for it on the v2 branch.

However, in line with AWS CDK v2, this version release now only receives security updates and critical bug fixes and support will fully end on June 1, 2023.

Project details


Release history Release notifications | RSS feed

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mrgrain.cdk-esbuild-4.0.0b0.tar.gz (145.3 kB view hashes)

Uploaded Source

Built Distribution

mrgrain.cdk_esbuild-4.0.0b0-py3-none-any.whl (143.1 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page