Skip to content

Commit

Permalink
feat(examples/esbuild-with-plugins): example of how to use the escape…
Browse files Browse the repository at this point in the history
… hatch to run esbuild with plugins (#142)
  • Loading branch information
mrgrain authored Nov 25, 2021
1 parent cbbab05 commit 0876f0e
Show file tree
Hide file tree
Showing 11 changed files with 22,780 additions and 0 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ For these situations, this package offers an escape hatch to bypass regular the

### Custom build function

> 💡 See [Using esbuild with plugins](examples/esbuild-with-plugin) 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.

```ts
Expand Down
3 changes: 3 additions & 0 deletions examples/esbuild-with-plugins/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.cache
cdk.out
node_modules
78 changes: 78 additions & 0 deletions examples/esbuild-with-plugins/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Using esbuild with plugins

`@mrgrain/cdk-esbuild` has an escape hatch that allows users to provide a custom implementation of the `buildSync` and `transformSyn` functions. This example demonstrates how to utilize the escape hatch to run `esbuild` with plugins.

## Demo

Run `npm ci` to get setup.

This example has loaded two plugins:

- `esbuild-plugin-cache` which allows to use `https` in imports (checkout `lambda.ts`), and
- `esbuild-plugin-time` which times the bundler executions.

Next run `npm run synth` to see them in action. You should get something like this:

```
Bundling asset Function/Lambda/TypeScriptCode/Stage...
Build started
Download https://cdn.skypack.dev/lodash.capitalize
Download https://cdn.skypack.dev/-/[email protected]/dist=es2019,mode=imports/optimized/lodash.capitalize.js
Build ended: 233ms
```

Run the same command again, and notice how nothing is downloaded this time and how the build time improved.

```
Bundling asset Function/Lambda/TypeScriptCode/Stage...
Build started
Build ended: 15ms
```

To clear the cache, run `npm run clean` and the next time you run the synth command, the package will be downloaded again.

This integration also works with tests, run `npm test` to try it out!

If you feel like it, inspect the bundled `lambda.js` file inside the asset output directory `cdk.out/asset.????`. You should notice that `lodash.capitalize` was successfully included in the bundle.

## How it's working

Unfortunately AWS CDK [does not work well with asynchronous code](https://github.com/aws/aws-cdk/issues/8273). However esbuild's plugin API is async and we therefore need to use the escape hatch.

In `app.ts` we pass a custom build function to our `TypeScriptCode` object:

```ts
new TypeScriptCode("./lambda.ts", {
buildFn: (options: BuildOptions): BuildResult => {
try {
execSync(`node build.mjs '${JSON.stringify(options)}'`, {
stdio: "inherit",
});
return { errors: [], warnings: [] };
} catch (error) {
throw { errors: [], warnings: [] };
}
},
});
```

In this function, we start a new node process: A special esbuild build-script, that can take the usual build options as a command line argument in form of stringified JSON. This build script is very simple. Have a look at `build.mjs`.

First we recover the build options from the cli input:

```js
const options = JSON.parse(process.argv.slice(2, 3));
```

Then we call out the async build function. We pass in our regular options, but can also make any changes or additions we like. For example adding plugins:

```js
await esbuild
.build({
...options,
plugins: [time(), cache({ directory: ".cache" })],
})
.catch(() => process.exit(1));
```

Finally the above statement is `await`'ed. And since we are in a `.mjs` ECMA module file, the top-level await is supported out-of-the-box by recent Node.js versions.
36 changes: 36 additions & 0 deletions examples/esbuild-with-plugins/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/usr/bin/env node
import * as cdk from "@aws-cdk/core";
import { Function, Runtime } from "@aws-cdk/aws-lambda";
import { BuildOptions, TypeScriptCode } from "@mrgrain/cdk-esbuild";
import { execSync } from "child_process";
import { BuildResult } from "esbuild";

export class LambdaStack extends cdk.Stack {
constructor(scope?: cdk.Construct, id?: string, props?: cdk.StackProps) {
super(scope, id, props);

const lambda = new Function(this, "Lambda", {
runtime: Runtime.NODEJS_14_X,
handler: "lambda.handler",
code: new TypeScriptCode("./lambda.ts", {
buildFn: (options: BuildOptions): BuildResult => {
try {
execSync(`node build.mjs '${JSON.stringify(options)}'`, {
stdio: "inherit",
});
return { errors: [], warnings: [] };
} catch (error) {
throw { errors: [], warnings: [] };
}
},
}),
});

new cdk.CfnOutput(this, "LambdaArn", {
value: lambda.functionArn,
});
}
}

const app = new cdk.App();
new LambdaStack(app, "Function");
12 changes: 12 additions & 0 deletions examples/esbuild-with-plugins/build.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import esbuild from "esbuild";
import { cache } from "esbuild-plugin-cache";
import time from "esbuild-plugin-time";

const options = JSON.parse(process.argv.slice(2, 3));

await esbuild
.build({
...options,
plugins: [time(), cache({ directory: ".cache" })],
})
.catch(() => process.exit(1));
3 changes: 3 additions & 0 deletions examples/esbuild-with-plugins/cdk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"app": "npx ts-node --prefer-ts-exts app.ts"
}
17 changes: 17 additions & 0 deletions examples/esbuild-with-plugins/lambda.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import "@aws-cdk/assert/jest";
import { LambdaStack } from "./app";

test("Creates a Lambda Function", () => {
// WHEN
const stack = new LambdaStack();

// THEN
expect(stack).toHaveResourceLike("AWS::Lambda::Function", {
Handler: "lambda.handler",
Runtime: "nodejs14.x",
});

expect(stack).toHaveOutput({
outputName: "LambdaArn",
});
});
4 changes: 4 additions & 0 deletions examples/esbuild-with-plugins/lambda.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import capitalize from "https://cdn.skypack.dev/lodash.capitalize";

export const handler = async (input: { body: string }) =>
capitalize(input?.body);
Loading

0 comments on commit 0876f0e

Please sign in to comment.