Repository: sid88in/serverless-appsync-plugin
Pull request #576 (2023-03-09 21:33:40)

feat: Add bundling and TS support

Scott-Allen-Mind-Gyn: This is super cool, I'm trying to avoid having 2 separate build process for building the pipelineFunctions. Serverless (with the right config) will transpile our TS resolvers (i.e our custom authorizer in the general "functions" section of serverless config) but not the typescript functions within appSync pipelineFunctions section.

If there's anything I can do to help with this PR rather than going down my own path please share.
ebisbe: I can test it.
ebisbe: Also I think that the checkout of this branch has to be done before running `npm install` otherwise `esbuild` is missing.

```
git checkout add-typescript-support
npm install
npm run build
npm link
```
And had to remove / upgrade some packages that are not up to date with TS 5. So if the code is TS 4 compatible then our live will be easier.
bboure: @ebisbe 

> The CloudFormation Resource to generate the appsync definition is in that PR? I don't see the code.
Also the ctx.indentity.claims is not properly defined. I see that ctx.identity is from the type Identity and that is an OR of different types which one is AppSyncIdentityOIDC that includes the claims but it says it's missing

Not sure I understood the question. This PR is changing the following:
- take the `code` js file path and pass it through esbuild in order to bundle it.
- transpile TypeScript code to valid APPSYNC_JS

I think you are referring to the Context type that you can include from `@aws-appsync/utils`. This does not live in this PR. It's [another package](https://www.npmjs.com/package/@aws-appsync/utils)l. Unfortunately it's not open source and I do not have hand on it. Maybe @onlybakam can help.


ebisbe: ok, sorry as I didn't explain me well enough.  I meant having types for the appsync configuration defined in this package. It's pretty complex and can get complex. So having types to define the configuration would be really good. It's probably for a different PR reading all the context of this one. 
bboure: @ebisbe  [this](https://github.com/sid88in/serverless-appsync-plugin/pull/576/commits/965e2f41510d9d1bd447649e60abe26c8b28204b) should do the trick, and it makes sense to add it in this PR.

```ts
import type { AppSyncConfig } from 'serverless-appsync-plugin';
```

lmk if this works
ebisbe: It works but some parts are missing. All this issues come from my configuration that is currently working so it should be valid. 

- **Appsync.schema:** type should be `string | string[]` it's `string[]`
- **Appsync.dataSource.name:** Should be optional.
- **PipelineFunctionConfig.name** should be optional.
- **BaseResolverConfig.field** should be optional.
- **BaseResolverConfig.type** should be optional.
- **PipelineResolverConfig.functions** should accept also a pipelineFunctionConfig object `functions: string[] | PipelineFunctionConfig[];`
- Conditional ( and others ) CF operations should be accepted. I have this: 
```
PaddleHttpDataSource: {
      type: "HTTP",
      config: {
        endpoint: {
          "Fn::If": ["IsProd", "https://vendors.paddle.com", "https://sandbox-vendors.paddle.com"],
        },
      },
    },
```

That's all I found but looking awesome!
ebisbe: For me the esbuild configuration is not working. The bundle resolver ends up like: `import{runtime as U,util as D}from\"@aws-appsync/utils\";`


Patch:
diff --git a/doc/general-config.md b/doc/general-config.md
index e91972d..eb9400a 100644
--- a/doc/general-config.md
+++ b/doc/general-config.md
@@ -51,6 +51,7 @@ appSync:
 - `xrayEnabled`: Boolean. Enable or disable X-Ray tracing.
 - `visibility`: Optional. `GLOBAL` or `PRIVATE`. **Changing this value requires the replacement of the API.**
 - `tags`: A key-value pair for tagging this AppSync API
+- `esbuild`: Custom esbuild options, or `false` See [Esbuild](#Esbuild)
 
 ## Schema
 
@@ -186,3 +187,22 @@ appSync:
 - `excludeVerboseContent`: Boolean, Optional. Exclude or not verbose content (headers, response headers, context, and evaluated mapping templates), regardless of field logging level. Defaults to `false`.
 - `retentionInDays`: Optional. Number of days to retain the logs. Defaults to [`provider.logRetentionInDays`](https://www.serverless.com/framework/docs/providers/aws/guide/serverless.yml#general-function-settings).
 - `roleArn`: Optional. The role ARN to use for AppSync to write into CloudWatch. If not specified, a new role is created by default.
+
+## Esbuild
+
+By default, this plugin uses esbuild in order to bundle Javascript resolvers. TypeScript files are also transpiled into compatible JavaScript. This option allows you to pass custom options that must be passed to the esbuild command.
+
+⚠️ Use these options carefully. Some options are not compatible with AWS AppSync. For more details about using esbuild with AppSync, see the [official guidelines](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-reference-overview-js.html#additional-utilities)
+
+Set this option to `false` to disable esbuild completely. You code will be sent as-is to AppSync.
+
+Example:
+
+Override the target and disable sourcemap.
+
+```yml
+appSync:
+  esbuild:
+    target: 'es2020',
+    sourcemap: false
+```
diff --git a/doc/resolvers.md b/doc/resolvers.md
index 2c56782..cfd5f77 100644
--- a/doc/resolvers.md
+++ b/doc/resolvers.md
@@ -64,6 +64,76 @@ appSync:
       code: getUser.js
 ```
 
+## Bundling
+
+AppSync requires resolvers to be bundled in one single file. By default, this plugin bundles your code with [esbuild](https://esbuild.github.io/), using the given path as the entry point.
+
+This means that you can import external libraries and utilities. e.g.
+
+```js
+import { Context, util } from '@aws-appsync/utils';
+import { generateUpdateExpressions, updateItem } from '../lib/helpers';
+
+export function request(ctx) {
+  const { id, ...post } = ctx.args.post;
+
+  const item = updateItem(post);
+
+  return {
+    operation: 'UpdateItem',
+    key: {
+      id: util.dynamodb.toDynamoDB(id),
+    },
+    update: generateUpdateExpressions(item),
+    condition: {
+      expression: 'attribute_exists(#id)',
+      expressionNames: {
+        '#id': 'id',
+      },
+    },
+  };
+}
+
+export function response(ctx: Context) {
+  return ctx.result;
+}
+```
+
+For more information, also see the [esbuild option](./general-config.md#Esbuild).
+
+## TypeScript support
+
+You can write JS resolver in TypeScript. Resolver files with the `.ts` extension are automatically transpiled and bundled using esbuild.
+
+```yaml
+resolvers:
+  Query.user:
+    kind: UNIT
+    dataSource: 'users'
+    code: 'getUser.ts'
+```
+
+```ts
+// getUser.ts
+import { Context, util } from '@aws-appsync/utils';
+
+export function request(ctx: Context) {
+  const {
+    args: { id },
+  } = ctx;
+  return {
+    operation: 'GetItem',
+    key: util.dynamodb.toMapValues({ id }),
+  };
+}
+
+export function response(ctx: Context) {
+  return ctx.result;
+}
+```
+
+For more information, also see the [esbuild option](./general-config.md#Esbuild).
+
 ## PIPELINE resolvers
 
 When `kind` is `PIPELINE`, you can specify the [pipeline function](pipeline-functions.md) names to use:
diff --git a/package-lock.json b/package-lock.json
index 621ee40..4cc95f9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -17,6 +17,7 @@
         "ajv-merge-patch": "^5.0.1",
         "aws-sdk": "^2.1265.0",
         "chalk": "^4.1.2",
+        "esbuild": "^0.17.11",
         "globby": "^11.1.0",
         "graphql": "^16.6.0",
         "lodash": "^4.17.21",
@@ -683,6 +684,336 @@
         "@jridgewell/sourcemap-codec": "^1.4.10"
       }
     },
+    "node_modules/@esbuild/android-arm": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.11.tgz",
+      "integrity": "sha512-CdyX6sRVh1NzFCsf5vw3kULwlAhfy9wVt8SZlrhQ7eL2qBjGbFhRBWkkAzuZm9IIEOCKJw4DXA6R85g+qc8RDw==",
+      "cpu": [
+        "arm"
+      ],
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/android-arm64": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.11.tgz",
+      "integrity": "sha512-QnK4d/zhVTuV4/pRM4HUjcsbl43POALU2zvBynmrrqZt9LPcLA3x1fTZPBg2RRguBQnJcnU059yKr+bydkntjg==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/android-x64": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.11.tgz",
+      "integrity": "sha512-3PL3HKtsDIXGQcSCKtWD/dy+mgc4p2Tvo2qKgKHj9Yf+eniwFnuoQ0OUhlSfAEpKAFzF9N21Nwgnap6zy3L3MQ==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/darwin-arm64": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.11.tgz",
+      "integrity": "sha512-pJ950bNKgzhkGNO3Z9TeHzIFtEyC2GDQL3wxkMApDEghYx5Qers84UTNc1bAxWbRkuJOgmOha5V0WUeh8G+YGw==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/darwin-x64": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.11.tgz",
+      "integrity": "sha512-iB0dQkIHXyczK3BZtzw1tqegf0F0Ab5texX2TvMQjiJIWXAfM4FQl7D909YfXWnB92OQz4ivBYQ2RlxBJrMJOw==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/freebsd-arm64": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.11.tgz",
+      "integrity": "sha512-7EFzUADmI1jCHeDRGKgbnF5sDIceZsQGapoO6dmw7r/ZBEKX7CCDnIz8m9yEclzr7mFsd+DyasHzpjfJnmBB1Q==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/freebsd-x64": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.11.tgz",
+      "integrity": "sha512-iPgenptC8i8pdvkHQvXJFzc1eVMR7W2lBPrTE6GbhR54sLcF42mk3zBOjKPOodezzuAz/KSu8CPyFSjcBMkE9g==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-arm": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.11.tgz",
+      "integrity": "sha512-M9iK/d4lgZH0U5M1R2p2gqhPV/7JPJcRz+8O8GBKVgqndTzydQ7B2XGDbxtbvFkvIs53uXTobOhv+RyaqhUiMg==",
+      "cpu": [
+        "arm"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-arm64": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.11.tgz",
+      "integrity": "sha512-Qxth3gsWWGKz2/qG2d5DsW/57SeA2AmpSMhdg9TSB5Svn2KDob3qxfQSkdnWjSd42kqoxIPy3EJFs+6w1+6Qjg==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-ia32": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.11.tgz",
+      "integrity": "sha512-dB1nGaVWtUlb/rRDHmuDQhfqazWE0LMro/AIbT2lWM3CDMHJNpLckH+gCddQyhhcLac2OYw69ikUMO34JLt3wA==",
+      "cpu": [
+        "ia32"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-loong64": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.11.tgz",
+      "integrity": "sha512-aCWlq70Q7Nc9WDnormntGS1ar6ZFvUpqr8gXtO+HRejRYPweAFQN615PcgaSJkZjhHp61+MNLhzyVALSF2/Q0g==",
+      "cpu": [
+        "loong64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-mips64el": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.11.tgz",
+      "integrity": "sha512-cGeGNdQxqY8qJwlYH1BP6rjIIiEcrM05H7k3tR7WxOLmD1ZxRMd6/QIOWMb8mD2s2YJFNRuNQ+wjMhgEL2oCEw==",
+      "cpu": [
+        "mips64el"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-ppc64": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.11.tgz",
+      "integrity": "sha512-BdlziJQPW/bNe0E8eYsHB40mYOluS+jULPCjlWiHzDgr+ZBRXPtgMV1nkLEGdpjrwgmtkZHEGEPaKdS/8faLDA==",
+      "cpu": [
+        "ppc64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-riscv64": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.11.tgz",
+      "integrity": "sha512-MDLwQbtF+83oJCI1Cixn68Et/ME6gelmhssPebC40RdJaect+IM+l7o/CuG0ZlDs6tZTEIoxUe53H3GmMn8oMA==",
+      "cpu": [
+        "riscv64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-s390x": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.11.tgz",
+      "integrity": "sha512-4N5EMESvws0Ozr2J94VoUD8HIRi7X0uvUv4c0wpTHZyZY9qpaaN7THjosdiW56irQ4qnJ6Lsc+i+5zGWnyqWqQ==",
+      "cpu": [
+        "s390x"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-x64": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.11.tgz",
+      "integrity": "sha512-rM/v8UlluxpytFSmVdbCe1yyKQd/e+FmIJE2oPJvbBo+D0XVWi1y/NQ4iTNx+436WmDHQBjVLrbnAQLQ6U7wlw==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/netbsd-x64": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.11.tgz",
+      "integrity": "sha512-4WaAhuz5f91h3/g43VBGdto1Q+X7VEZfpcWGtOFXnggEuLvjV+cP6DyLRU15IjiU9fKLLk41OoJfBFN5DhPvag==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "netbsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/openbsd-x64": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.11.tgz",
+      "integrity": "sha512-UBj135Nx4FpnvtE+C8TWGp98oUgBcmNmdYgl5ToKc0mBHxVVqVE7FUS5/ELMImOp205qDAittL6Ezhasc2Ev/w==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/sunos-x64": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.11.tgz",
+      "integrity": "sha512-1/gxTifDC9aXbV2xOfCbOceh5AlIidUrPsMpivgzo8P8zUtczlq1ncFpeN1ZyQJ9lVs2hILy1PG5KPp+w8QPPg==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "sunos"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/win32-arm64": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.11.tgz",
+      "integrity": "sha512-vtSfyx5yRdpiOW9yp6Ax0zyNOv9HjOAw8WaZg3dF5djEHKKm3UnoohftVvIJtRh0Ec7Hso0RIdTqZvPXJ7FdvQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/win32-ia32": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.11.tgz",
+      "integrity": "sha512-GFPSLEGQr4wHFTiIUJQrnJKZhZjjq4Sphf+mM76nQR6WkQn73vm7IsacmBRPkALfpOCHsopSvLgqdd4iUW2mYw==",
+      "cpu": [
+        "ia32"
+      ],
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/win32-x64": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.11.tgz",
+      "integrity": "sha512-N9vXqLP3eRL8BqSy8yn4Y98cZI2pZ8fyuHx6lKjiG2WABpT2l01TXdzq5Ma2ZUBzfB7tx5dXVhge8X9u0S70ZQ==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
     "node_modules/@eslint/eslintrc": {
       "version": "1.3.3",
       "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz",
@@ -4645,6 +4976,42 @@
         "es6-symbol": "^3.1.1"
       }
     },
+    "node_modules/esbuild": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.11.tgz",
+      "integrity": "sha512-pAMImyokbWDtnA/ufPxjQg0fYo2DDuzAlqwnDvbXqHLphe+m80eF++perYKVm8LeTuj2zUuFXC+xgSVxyoHUdg==",
+      "hasInstallScript": true,
+      "bin": {
+        "esbuild": "bin/esbuild"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "optionalDependencies": {
+        "@esbuild/android-arm": "0.17.11",
+        "@esbuild/android-arm64": "0.17.11",
+        "@esbuild/android-x64": "0.17.11",
+        "@esbuild/darwin-arm64": "0.17.11",
+        "@esbuild/darwin-x64": "0.17.11",
+        "@esbuild/freebsd-arm64": "0.17.11",
+        "@esbuild/freebsd-x64": "0.17.11",
+        "@esbuild/linux-arm": "0.17.11",
+        "@esbuild/linux-arm64": "0.17.11",
+        "@esbuild/linux-ia32": "0.17.11",
+        "@esbuild/linux-loong64": "0.17.11",
+        "@esbuild/linux-mips64el": "0.17.11",
+        "@esbuild/linux-ppc64": "0.17.11",
+        "@esbuild/linux-riscv64": "0.17.11",
+        "@esbuild/linux-s390x": "0.17.11",
+        "@esbuild/linux-x64": "0.17.11",
+        "@esbuild/netbsd-x64": "0.17.11",
+        "@esbuild/openbsd-x64": "0.17.11",
+        "@esbuild/sunos-x64": "0.17.11",
+        "@esbuild/win32-arm64": "0.17.11",
+        "@esbuild/win32-ia32": "0.17.11",
+        "@esbuild/win32-x64": "0.17.11"
+      }
+    },
     "node_modules/escalade": {
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@@ -15918,6 +16285,138 @@
         }
       }
     },
+    "@esbuild/android-arm": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.11.tgz",
+      "integrity": "sha512-CdyX6sRVh1NzFCsf5vw3kULwlAhfy9wVt8SZlrhQ7eL2qBjGbFhRBWkkAzuZm9IIEOCKJw4DXA6R85g+qc8RDw==",
+      "optional": true
+    },
+    "@esbuild/android-arm64": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.11.tgz",
+      "integrity": "sha512-QnK4d/zhVTuV4/pRM4HUjcsbl43POALU2zvBynmrrqZt9LPcLA3x1fTZPBg2RRguBQnJcnU059yKr+bydkntjg==",
+      "optional": true
+    },
+    "@esbuild/android-x64": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.11.tgz",
+      "integrity": "sha512-3PL3HKtsDIXGQcSCKtWD/dy+mgc4p2Tvo2qKgKHj9Yf+eniwFnuoQ0OUhlSfAEpKAFzF9N21Nwgnap6zy3L3MQ==",
+      "optional": true
+    },
+    "@esbuild/darwin-arm64": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.11.tgz",
+      "integrity": "sha512-pJ950bNKgzhkGNO3Z9TeHzIFtEyC2GDQL3wxkMApDEghYx5Qers84UTNc1bAxWbRkuJOgmOha5V0WUeh8G+YGw==",
+      "optional": true
+    },
+    "@esbuild/darwin-x64": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.11.tgz",
+      "integrity": "sha512-iB0dQkIHXyczK3BZtzw1tqegf0F0Ab5texX2TvMQjiJIWXAfM4FQl7D909YfXWnB92OQz4ivBYQ2RlxBJrMJOw==",
+      "optional": true
+    },
+    "@esbuild/freebsd-arm64": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.11.tgz",
+      "integrity": "sha512-7EFzUADmI1jCHeDRGKgbnF5sDIceZsQGapoO6dmw7r/ZBEKX7CCDnIz8m9yEclzr7mFsd+DyasHzpjfJnmBB1Q==",
+      "optional": true
+    },
+    "@esbuild/freebsd-x64": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.11.tgz",
+      "integrity": "sha512-iPgenptC8i8pdvkHQvXJFzc1eVMR7W2lBPrTE6GbhR54sLcF42mk3zBOjKPOodezzuAz/KSu8CPyFSjcBMkE9g==",
+      "optional": true
+    },
+    "@esbuild/linux-arm": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.11.tgz",
+      "integrity": "sha512-M9iK/d4lgZH0U5M1R2p2gqhPV/7JPJcRz+8O8GBKVgqndTzydQ7B2XGDbxtbvFkvIs53uXTobOhv+RyaqhUiMg==",
+      "optional": true
+    },
+    "@esbuild/linux-arm64": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.11.tgz",
+      "integrity": "sha512-Qxth3gsWWGKz2/qG2d5DsW/57SeA2AmpSMhdg9TSB5Svn2KDob3qxfQSkdnWjSd42kqoxIPy3EJFs+6w1+6Qjg==",
+      "optional": true
+    },
+    "@esbuild/linux-ia32": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.11.tgz",
+      "integrity": "sha512-dB1nGaVWtUlb/rRDHmuDQhfqazWE0LMro/AIbT2lWM3CDMHJNpLckH+gCddQyhhcLac2OYw69ikUMO34JLt3wA==",
+      "optional": true
+    },
+    "@esbuild/linux-loong64": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.11.tgz",
+      "integrity": "sha512-aCWlq70Q7Nc9WDnormntGS1ar6ZFvUpqr8gXtO+HRejRYPweAFQN615PcgaSJkZjhHp61+MNLhzyVALSF2/Q0g==",
+      "optional": true
+    },
+    "@esbuild/linux-mips64el": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.11.tgz",
+      "integrity": "sha512-cGeGNdQxqY8qJwlYH1BP6rjIIiEcrM05H7k3tR7WxOLmD1ZxRMd6/QIOWMb8mD2s2YJFNRuNQ+wjMhgEL2oCEw==",
+      "optional": true
+    },
+    "@esbuild/linux-ppc64": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.11.tgz",
+      "integrity": "sha512-BdlziJQPW/bNe0E8eYsHB40mYOluS+jULPCjlWiHzDgr+ZBRXPtgMV1nkLEGdpjrwgmtkZHEGEPaKdS/8faLDA==",
+      "optional": true
+    },
+    "@esbuild/linux-riscv64": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.11.tgz",
+      "integrity": "sha512-MDLwQbtF+83oJCI1Cixn68Et/ME6gelmhssPebC40RdJaect+IM+l7o/CuG0ZlDs6tZTEIoxUe53H3GmMn8oMA==",
+      "optional": true
+    },
+    "@esbuild/linux-s390x": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.11.tgz",
+      "integrity": "sha512-4N5EMESvws0Ozr2J94VoUD8HIRi7X0uvUv4c0wpTHZyZY9qpaaN7THjosdiW56irQ4qnJ6Lsc+i+5zGWnyqWqQ==",
+      "optional": true
+    },
+    "@esbuild/linux-x64": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.11.tgz",
+      "integrity": "sha512-rM/v8UlluxpytFSmVdbCe1yyKQd/e+FmIJE2oPJvbBo+D0XVWi1y/NQ4iTNx+436WmDHQBjVLrbnAQLQ6U7wlw==",
+      "optional": true
+    },
+    "@esbuild/netbsd-x64": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.11.tgz",
+      "integrity": "sha512-4WaAhuz5f91h3/g43VBGdto1Q+X7VEZfpcWGtOFXnggEuLvjV+cP6DyLRU15IjiU9fKLLk41OoJfBFN5DhPvag==",
+      "optional": true
+    },
+    "@esbuild/openbsd-x64": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.11.tgz",
+      "integrity": "sha512-UBj135Nx4FpnvtE+C8TWGp98oUgBcmNmdYgl5ToKc0mBHxVVqVE7FUS5/ELMImOp205qDAittL6Ezhasc2Ev/w==",
+      "optional": true
+    },
+    "@esbuild/sunos-x64": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.11.tgz",
+      "integrity": "sha512-1/gxTifDC9aXbV2xOfCbOceh5AlIidUrPsMpivgzo8P8zUtczlq1ncFpeN1ZyQJ9lVs2hILy1PG5KPp+w8QPPg==",
+      "optional": true
+    },
+    "@esbuild/win32-arm64": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.11.tgz",
+      "integrity": "sha512-vtSfyx5yRdpiOW9yp6Ax0zyNOv9HjOAw8WaZg3dF5djEHKKm3UnoohftVvIJtRh0Ec7Hso0RIdTqZvPXJ7FdvQ==",
+      "optional": true
+    },
+    "@esbuild/win32-ia32": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.11.tgz",
+      "integrity": "sha512-GFPSLEGQr4wHFTiIUJQrnJKZhZjjq4Sphf+mM76nQR6WkQn73vm7IsacmBRPkALfpOCHsopSvLgqdd4iUW2mYw==",
+      "optional": true
+    },
+    "@esbuild/win32-x64": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.11.tgz",
+      "integrity": "sha512-N9vXqLP3eRL8BqSy8yn4Y98cZI2pZ8fyuHx6lKjiG2WABpT2l01TXdzq5Ma2ZUBzfB7tx5dXVhge8X9u0S70ZQ==",
+      "optional": true
+    },
     "@eslint/eslintrc": {
       "version": "1.3.3",
       "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz",
@@ -19045,6 +19544,35 @@
         "es6-symbol": "^3.1.1"
       }
     },
+    "esbuild": {
+      "version": "0.17.11",
+      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.11.tgz",
+      "integrity": "sha512-pAMImyokbWDtnA/ufPxjQg0fYo2DDuzAlqwnDvbXqHLphe+m80eF++perYKVm8LeTuj2zUuFXC+xgSVxyoHUdg==",
+      "requires": {
+        "@esbuild/android-arm": "0.17.11",
+        "@esbuild/android-arm64": "0.17.11",
+        "@esbuild/android-x64": "0.17.11",
+        "@esbuild/darwin-arm64": "0.17.11",
+        "@esbuild/darwin-x64": "0.17.11",
+        "@esbuild/freebsd-arm64": "0.17.11",
+        "@esbuild/freebsd-x64": "0.17.11",
+        "@esbuild/linux-arm": "0.17.11",
+        "@esbuild/linux-arm64": "0.17.11",
+        "@esbuild/linux-ia32": "0.17.11",
+        "@esbuild/linux-loong64": "0.17.11",
+        "@esbuild/linux-mips64el": "0.17.11",
+        "@esbuild/linux-ppc64": "0.17.11",
+        "@esbuild/linux-riscv64": "0.17.11",
+        "@esbuild/linux-s390x": "0.17.11",
+        "@esbuild/linux-x64": "0.17.11",
+        "@esbuild/netbsd-x64": "0.17.11",
+        "@esbuild/openbsd-x64": "0.17.11",
+        "@esbuild/sunos-x64": "0.17.11",
+        "@esbuild/win32-arm64": "0.17.11",
+        "@esbuild/win32-ia32": "0.17.11",
+        "@esbuild/win32-x64": "0.17.11"
+      }
+    },
     "escalade": {
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
diff --git a/package.json b/package.json
index b86c274..c1eb6f6 100644
--- a/package.json
+++ b/package.json
@@ -3,6 +3,7 @@
   "version": "0.0.0-development",
   "description": "AWS AppSync support for the Serverless Framework",
   "main": "lib/index.js",
+  "types": "lib/types/index.d.ts",
   "repository": "https://github.com/sid88in/serverless-appsync-plugin",
   "license": "MIT",
   "files": [
@@ -45,6 +46,7 @@
     "ajv-merge-patch": "^5.0.1",
     "aws-sdk": "^2.1265.0",
     "chalk": "^4.1.2",
+    "esbuild": "^0.17.11",
     "globby": "^11.1.0",
     "graphql": "^16.6.0",
     "lodash": "^4.17.21",
diff --git a/src/getAppSyncConfig.ts b/src/getAppSyncConfig.ts
index bdfc2fa..a942772 100644
--- a/src/getAppSyncConfig.ts
+++ b/src/getAppSyncConfig.ts
@@ -1,53 +1,13 @@
+import { AppSyncConfig } from './types';
 import {
   ApiKeyConfig,
-  AppSyncConfig,
+  AppSyncConfig as PluginAppSyncConfig,
   DataSourceConfig,
   PipelineFunctionConfig,
   ResolverConfig,
 } from './types/plugin';
-import { A, O } from 'ts-toolbelt';
 import { forEach, merge } from 'lodash';
 
-/* Completely replaces keys of O1 with those of O */
-type Replace<O extends object, O1 extends object> = O.Merge<
-  O,
-  O.Omit<O1, A.Keys<O>>
->;
-
-export type DataSourceConfigInput = O.Omit<DataSourceConfig, 'name'>;
-
-export type ResolverConfigInput = O.Update<
-  O.Update<
-    O.Optional<ResolverConfig, 'type' | 'field'>,
-    'dataSource',
-    string | DataSourceConfigInput
-  >,
-  'functions',
-  (string | FunctionConfigInput)[]
->;
-
-export type FunctionConfigInput = Replace<
-  { dataSource: string | DataSourceConfigInput },
-  O.Omit<PipelineFunctionConfig, 'name'>
->;
-
-export type AppSyncConfigInput = Replace<
-  {
-    schema?: string | string[];
-    apiKeys?: (ApiKeyConfig | string)[];
-    resolvers?:
-      | Record<string, ResolverConfigInput>[]
-      | Record<string, ResolverConfigInput>;
-    pipelineFunctions?:
-      | Record<string, FunctionConfigInput>[]
-      | Record<string, FunctionConfigInput>;
-    dataSources:
-      | Record<string, DataSourceConfigInput>[]
-      | Record<string, DataSourceConfigInput>;
-  },
-  O.Optional<AppSyncConfig, 'additionalAuthentications'>
->;
-
 const flattenMaps = <T>(
   input?: Record<string, T> | Record<string, T>[],
 ): Record<string, T> => {
@@ -74,7 +34,9 @@ const toResourceName = (name: string) => {
   return name.replace(/[^a-z_]/i, '_');
 };
 
-export const getAppSyncConfig = (config: AppSyncConfigInput): AppSyncConfig => {
+export const getAppSyncConfig = (
+  config: AppSyncConfig,
+): PluginAppSyncConfig => {
   const schema = Array.isArray(config.schema)
     ? config.schema
     : [config.schema || 'schema.graphql'];
@@ -129,6 +91,7 @@ export const getAppSyncConfig = (config: AppSyncConfigInput): AppSyncConfig => {
               if (typeof f === 'string') {
                 return f;
               }
+
               const name = `${toResourceName(typeAndField)}_${index}`;
               pipelineFunctions[name] = {
                 ...f,
diff --git a/src/index.ts b/src/index.ts
index 6d93c2e..89e15d1 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -326,6 +326,11 @@ class ServerlessAppsyncPlugin {
       'before:appsync:domain:delete-record:run': async () =>
         this.initDomainCommand(),
       'appsync:domain:delete-record:run': async () => this.deleteRecord(),
+      finalize: () => {
+        writeText(
+          '\nLooking for a better AppSync development experience? Have you tried GraphBolt? https://graphbolt.dev',
+        );
+      },
     };
 
     // These hooks need the config to be loaded and
diff --git a/src/resources/JsResolver.ts b/src/resources/JsResolver.ts
index 3e1bca8..0cc7848 100644
--- a/src/resources/JsResolver.ts
+++ b/src/resources/JsResolver.ts
@@ -2,6 +2,7 @@ import { IntrinsicFunction } from '../types/cloudFormation';
 import fs from 'fs';
 import { Substitutions } from '../types/plugin';
 import { Api } from './Api';
+import { buildSync } from 'esbuild';
 
 type JsResolverConfig = {
   path: string;
@@ -18,8 +19,47 @@ export class JsResolver {
       );
     }
 
-    const requestTemplateContent = fs.readFileSync(this.config.path, 'utf8');
-    return this.processTemplateSubstitutions(requestTemplateContent);
+    return this.processTemplateSubstitutions(this.getResolverContent());
+  }
+
+  getResolverContent(): string {
+    if (this.api.config.esbuild === false) {
+      return fs.readFileSync(this.config.path, 'utf8');
+    }
+
+    // process with esbuild
+    // this will:
+    // - Bundle the code into one file if necessary
+    // - Transpile typescript to javascript if necessary
+    const buildResult = buildSync({
+      target: 'esnext',
+      sourcemap: 'inline',
+      sourcesContent: false,
+      treeShaking: true,
+      // custom config overrides
+      ...this.api.config.esbuild,
+      // These options are required and can't be changed
+      platform: 'node',
+      format: 'esm',
+      entryPoints: [this.config.path],
+      bundle: true,
+      write: false,
+      external: ['@aws-appsync/utils'],
+    });
+
+    if (buildResult.errors.length > 0) {
+      throw new this.api.plugin.serverless.classes.Error(
+        `Failed to compile resolver handler file '${this.config.path}': ${buildResult.errors[0].text}`,
+      );
+    }
+
+    if (buildResult.outputFiles.length === 0) {
+      throw new this.api.plugin.serverless.classes.Error(
+        `Failed to compile resolver handler file '${this.config.path}': No output files`,
+      );
+    }
+
+    return buildResult.outputFiles[0].text;
   }
 
   processTemplateSubstitutions(template: string): string | IntrinsicFunction {
diff --git a/src/types/index.ts b/src/types/index.ts
new file mode 100644
index 0000000..f454314
--- /dev/null
+++ b/src/types/index.ts
@@ -0,0 +1,50 @@
+import { A, O } from 'ts-toolbelt';
+import {
+  DataSourceConfig as InternalDataSourceConfig,
+  ResolverConfig as InternalResolverConfig,
+  PipelineFunctionConfig as InternalPipelineFunctionConfig,
+  ApiKeyConfig,
+  AppSyncConfig as InternalAppSyncConfig,
+} from './plugin';
+
+/* Completely replaces keys of O1 with those of O */
+type Replace<O extends object, O1 extends object> = O.Merge<
+  O,
+  O.Omit<O1, A.Keys<O>>
+>;
+
+export * from './plugin';
+
+export type DataSourceConfig = O.Omit<InternalDataSourceConfig, 'name'>;
+
+export type PipelineFunctionConfig = Replace<
+  { dataSource: string | DataSourceConfig },
+  O.Omit<InternalPipelineFunctionConfig, 'name'>
+>;
+
+export type ResolverConfig = O.Update<
+  O.Update<
+    O.Optional<InternalResolverConfig, 'type' | 'field'>,
+    'dataSource',
+    string | DataSourceConfig
+  >,
+  'functions',
+  (string | PipelineFunctionConfig)[]
+>;
+
+export type AppSyncConfig = Replace<
+  {
+    schema?: string | string[];
+    apiKeys?: (ApiKeyConfig | string)[];
+    resolvers?:
+      | Record<string, ResolverConfig>[]
+      | Record<string, ResolverConfig>;
+    pipelineFunctions?:
+      | Record<string, PipelineFunctionConfig>[]
+      | Record<string, PipelineFunctionConfig>;
+    dataSources:
+      | Record<string, DataSourceConfig>[]
+      | Record<string, DataSourceConfig>;
+  },
+  O.Optional<InternalAppSyncConfig, 'additionalAuthentications'>
+>;
diff --git a/src/types/plugin.ts b/src/types/plugin.ts
index f29fbed..8e5538d 100644
--- a/src/types/plugin.ts
+++ b/src/types/plugin.ts
@@ -1,4 +1,5 @@
 import { CfnWafRuleStatement, IntrinsicFunction } from './cloudFormation';
+import { BuildOptions } from 'esbuild';
 
 export type AppSyncConfig = {
   name: string;
@@ -17,6 +18,7 @@ export type AppSyncConfig = {
   waf?: WafConfig;
   tags?: Record<string, string>;
   visibility?: 'GLOBAL' | 'PRIVATE';
+  esbuild?: BuildOptions | false;
 };
 
 export type IamStatement = {
diff --git a/src/types/serverless.d.ts b/src/types/serverless.d.ts
index 1038c06..3d298c5 100644
--- a/src/types/serverless.d.ts
+++ b/src/types/serverless.d.ts
@@ -24,7 +24,7 @@ declare module '@serverless/utils/log' {
 }
 
 declare module 'serverless/lib/Serverless' {
-  import { AppSyncConfigInput } from 'plugin';
+  import { AppSyncConfig } from 'plugin';
   import Provider from 'serverless/lib/plugins/aws/provider.js';
   import type { AWS } from '@serverless/typescript';
 
@@ -59,7 +59,7 @@ declare module 'serverless/lib/Serverless' {
       options: Record<string, unknown>;
     };
     configurationInput: AWS & {
-      appSync: AppSyncConfigInput;
+      appSync: AppSyncConfig;
     };
     service: AWS & {
       setFunctionNames(rawOptions: Record<string, unknown>): void;
diff --git a/src/validation.ts b/src/validation.ts
index a187049..bc2887e 100644
--- a/src/validation.ts
+++ b/src/validation.ts
@@ -838,6 +838,15 @@ export const appSyncSchema = {
       ],
       errorMessage: 'contains invalid pipeline function definitions',
     },
+    esbuild: {
+      oneOf: [
+        {
+          type: 'object',
+        },
+        { const: false },
+      ],
+      errorMessage: 'must be an esbuild config object or false',
+    },
   },
   required: ['name', 'authentication'],
   additionalProperties: {
diff --git a/tsconfig.json b/tsconfig.json
index 2077e07..84c8630 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -10,7 +10,8 @@
     "resolveJsonModule": true,
     "skipLibCheck": true,
     "outDir": "lib",
-    "noImplicitAny": false
+    "noImplicitAny": false,
+    "declaration": true
   },
   "include": ["src/**/*.ts"],
   "exclude": ["node_modules", "src/__tests__"]


Test patch:
diff --git a/src/__tests__/basicConfig.ts b/src/__tests__/basicConfig.ts
index 9c30b12..7d8692f 100644
--- a/src/__tests__/basicConfig.ts
+++ b/src/__tests__/basicConfig.ts
@@ -1,6 +1,6 @@
-import { AppSyncConfigInput } from '../getAppSyncConfig';
+import { AppSyncConfig } from '../types';
 
-export const basicConfig: AppSyncConfigInput = {
+export const basicConfig: AppSyncConfig = {
   name: 'My Api',
   authentication: {
     type: 'API_KEY',
diff --git a/src/__tests__/getAppSyncConfig.test.ts b/src/__tests__/getAppSyncConfig.test.ts
index d68400f..db93954 100644
--- a/src/__tests__/getAppSyncConfig.test.ts
+++ b/src/__tests__/getAppSyncConfig.test.ts
@@ -1,6 +1,7 @@
 import { pick } from 'lodash';
-import { getAppSyncConfig, ResolverConfigInput } from '../getAppSyncConfig';
+import { getAppSyncConfig } from '../getAppSyncConfig';
 import { basicConfig } from './basicConfig';
+import { ResolverConfig } from '../types';
 
 test('returns basic config', async () => {
   expect(getAppSyncConfig(basicConfig)).toMatchSnapshot();
@@ -228,7 +229,7 @@ describe('Resolvers', () => {
             field: 'getUsers',
           },
         },
-      ] as Record<string, ResolverConfigInput>[],
+      ] as Record<string, ResolverConfig>[],
     });
     expect(config.resolvers).toMatchSnapshot();
   });
diff --git a/src/__tests__/resolvers.test.ts b/src/__tests__/resolvers.test.ts
index 36ac663..e1dff5b 100644
--- a/src/__tests__/resolvers.test.ts
+++ b/src/__tests__/resolvers.test.ts
@@ -1,24 +1,122 @@
+import * as esbuild from 'esbuild';
 import fs from 'fs';
 import { Api } from '../resources/Api';
 import * as given from './given';
 
 const plugin = given.plugin();
 
+jest.mock('esbuild', () => ({
+  buildSync: jest.fn().mockImplementation((config) => {
+    return {
+      errors: [],
+      warnings: [],
+      metafile: undefined,
+      mangleCache: undefined,
+      outputFiles: [
+        {
+          path: 'path/to/file',
+          contents: Uint8Array.from([]),
+          text: `Bundled content of ${`${config.entryPoints?.[0]}`.replace(
+            /\\/g,
+            '/',
+          )}`,
+        },
+      ],
+    };
+  }),
+}));
+
 describe('Resolvers', () => {
   let mock: jest.SpyInstance;
-  let mockEists: jest.SpyInstance;
+  let mockExists: jest.SpyInstance;
+  let mockEsbuild: jest.SpyInstance;
   beforeEach(() => {
     mock = jest
       .spyOn(fs, 'readFileSync')
       .mockImplementation(
         (path) => `Content of ${`${path}`.replace(/\\/g, '/')}`,
       );
-    mockEists = jest.spyOn(fs, 'existsSync').mockReturnValue(true);
+    mockExists = jest.spyOn(fs, 'existsSync').mockReturnValue(true);
+    mockEsbuild = jest
+      .spyOn(esbuild, 'buildSync')
+      .mockImplementation((config) => {
+        return {
+          errors: [],
+          warnings: [],
+          metafile: undefined,
+          mangleCache: undefined,
+          outputFiles: [
+            {
+              path: 'path/to/file',
+              contents: Uint8Array.from([]),
+              text: `Bundled content of ${`${config.entryPoints?.[0]}`.replace(
+                /\\/g,
+                '/',
+              )}`,
+            },
+          ],
+        };
+      });
   });
 
   afterEach(() => {
     mock.mockRestore();
-    mockEists.mockRestore();
+    mockExists.mockRestore();
+    mockEsbuild.mockRestore();
+  });
+
+  describe('esbuild', () => {
+    it('should skip esbuild when disabled', () => {
+      const api = new Api(
+        given.appSyncConfig({
+          esbuild: false,
+          dataSources: {
+            myTable: {
+              name: 'myTable',
+              type: 'AMAZON_DYNAMODB',
+              config: { tableName: 'data' },
+            },
+          },
+        }),
+        plugin,
+      );
+      expect(
+        api.compilePipelineFunctionResource({
+          dataSource: 'myTable',
+          code: 'path/to/my-resolver.js',
+          name: 'my-function',
+        }),
+      ).toMatchInlineSnapshot(`
+        Object {
+          "GraphQlFunctionConfigurationmyfunction": Object {
+            "Properties": Object {
+              "ApiId": Object {
+                "Fn::GetAtt": Array [
+                  "GraphQlApi",
+                  "ApiId",
+                ],
+              },
+              "Code": "Content of path/to/my-resolver.js",
+              "DataSourceName": Object {
+                "Fn::GetAtt": Array [
+                  "GraphQlDsmyTable",
+                  "Name",
+                ],
+              },
+              "Description": undefined,
+              "FunctionVersion": "2018-05-29",
+              "MaxBatchSize": undefined,
+              "Name": "my-function",
+              "Runtime": Object {
+                "Name": "APPSYNC_JS",
+                "RuntimeVersion": "1.0.0",
+              },
+            },
+            "Type": "AWS::AppSync::FunctionConfiguration",
+          },
+        }
+      `);
+    });
   });
 
   describe('Unit Resolvers', () => {
@@ -98,37 +196,37 @@ describe('Resolvers', () => {
           code: 'resolvers/getUserFunction.js',
         }),
       ).toMatchInlineSnapshot(`
-      Object {
-        "GraphQlResolverQueryuser": Object {
-          "DependsOn": Array [
-            "GraphQlSchema",
-          ],
-          "Properties": Object {
-            "ApiId": Object {
-              "Fn::GetAtt": Array [
-                "GraphQlApi",
-                "ApiId",
-              ],
-            },
-            "Code": "Content of resolvers/getUserFunction.js",
-            "DataSourceName": Object {
-              "Fn::GetAtt": Array [
-                "GraphQlDsmyTable",
-                "Name",
-              ],
-            },
-            "FieldName": "user",
-            "Kind": "UNIT",
-            "MaxBatchSize": undefined,
-            "Runtime": Object {
-              "Name": "APPSYNC_JS",
-              "RuntimeVersion": "1.0.0",
-            },
-            "TypeName": "Query",
-          },
-          "Type": "AWS::AppSync::Resolver",
-        },
-      }
+        Object {
+          "GraphQlResolverQueryuser": Object {
+            "DependsOn": Array [
+              "GraphQlSchema",
+            ],
+            "Properties": Object {
+              "ApiId": Object {
+                "Fn::GetAtt": Array [
+                  "GraphQlApi",
+                  "ApiId",
+                ],
+              },
+              "Code": "Bundled content of resolvers/getUserFunction.js",
+              "DataSourceName": Object {
+                "Fn::GetAtt": Array [
+                  "GraphQlDsmyTable",
+                  "Name",
+                ],
+              },
+              "FieldName": "user",
+              "Kind": "UNIT",
+              "MaxBatchSize": undefined,
+              "Runtime": Object {
+                "Name": "APPSYNC_JS",
+                "RuntimeVersion": "1.0.0",
+              },
+              "TypeName": "Query",
+            },
+            "Type": "AWS::AppSync::Resolver",
+          },
+        }
       `);
     });
 
@@ -331,7 +429,7 @@ describe('Resolvers', () => {
 
   describe('Pipeline Resovlers', () => {
     it('should generate JS Resources with default empty resolver', () => {
-      mockEists.mockReturnValue(false);
+      mockExists.mockReturnValue(false);
       const api = new Api(
         given.appSyncConfig({
           dataSources: {
@@ -514,7 +612,7 @@ describe('Resolvers', () => {
                   "ApiId",
                 ],
               },
-              "Code": "Content of resolvers/getUserFunction.js",
+              "Code": "Bundled content of resolvers/getUserFunction.js",
               "FieldName": "user",
               "Kind": "PIPELINE",
               "PipelineConfig": Object {
@@ -602,7 +700,7 @@ describe('Resolvers', () => {
                   "ApiId",
                 ],
               },
-              "Code": "Content of funciton1.js",
+              "Code": "Bundled content of funciton1.js",
               "DataSourceName": Object {
                 "Fn::GetAtt": Array [
                   "GraphQlDsmyTable",
diff --git a/src/__tests__/validation/__snapshots__/base.test.ts.snap b/src/__tests__/validation/__snapshots__/base.test.ts.snap
index c0639dc..79de313 100644
--- a/src/__tests__/validation/__snapshots__/base.test.ts.snap
+++ b/src/__tests__/validation/__snapshots__/base.test.ts.snap
@@ -56,5 +56,6 @@ exports[`Valdiation should validate  1`] = `
 : must have required property 'authentication'
 /unknownPorp: invalid (unknown) property
 /xrayEnabled: must be boolean
-/visibility: must be \\"GLOBAL\\" or \\"PRIVATE\\""
+/visibility: must be \\"GLOBAL\\" or \\"PRIVATE\\"
+/esbuild: must be an esbuild config object or false"
 `;
diff --git a/src/__tests__/validation/auth.test.ts b/src/__tests__/validation/auth.test.ts
index 6cc4cdf..2849065 100644
--- a/src/__tests__/validation/auth.test.ts
+++ b/src/__tests__/validation/auth.test.ts
@@ -1,4 +1,4 @@
-import { AppSyncConfigInput } from '../../getAppSyncConfig';
+import { AppSyncConfig } from '../../types';
 import { validateConfig } from '../../validation';
 import { basicConfig } from '../basicConfig';
 
@@ -12,7 +12,7 @@ describe('Valdiation', () => {
           authentication: {
             type: 'API_KEY',
           },
-        } as AppSyncConfigInput,
+        } as AppSyncConfig,
       },
       {
         name: 'Cognito',
@@ -27,7 +27,7 @@ describe('Valdiation', () => {
               appIdClientRegex: '.*',
             },
           },
-        } as AppSyncConfigInput,
+        } as AppSyncConfig,
       },
       {
         name: 'Cognito with Refs',
@@ -44,7 +44,7 @@ describe('Valdiation', () => {
               },
             },
           },
-        } as AppSyncConfigInput,
+        } as AppSyncConfig,
       },
       {
         name: 'OIDC',
@@ -59,7 +59,7 @@ describe('Valdiation', () => {
               authTTL: 3600,
             },
           },
-        } as AppSyncConfigInput,
+        } as AppSyncConfig,
       },
       {
         name: 'OIDC without a clientId',
@@ -73,7 +73,7 @@ describe('Valdiation', () => {
               authTTL: 3600,
             },
           },
-        } as AppSyncConfigInput,
+        } as AppSyncConfig,
       },
       {
         name: 'IAM',
@@ -82,7 +82,7 @@ describe('Valdiation', () => {
           authentication: {
             type: 'AWS_IAM',
           },
-        } as AppSyncConfigInput,
+        } as AppSyncConfig,
       },
       {
         name: 'Lambda with functionName',
@@ -96,7 +96,7 @@ describe('Valdiation', () => {
               authorizerResultTtlInSeconds: 600,
             },
           },
-        } as AppSyncConfigInput,
+        } as AppSyncConfig,
       },
       {
         name: 'Lambda with functionArn',
@@ -108,7 +108,7 @@ describe('Valdiation', () => {
               functionArn: 'arn:aws:lambda:...',
             },
           },
-        } as AppSyncConfigInput,
+        } as AppSyncConfig,
       },
     ];
 
diff --git a/src/__tests__/validation/base.test.ts b/src/__tests__/validation/base.test.ts
index bd9b7ee..4f899d2 100644
--- a/src/__tests__/validation/base.test.ts
+++ b/src/__tests__/validation/base.test.ts
@@ -1,4 +1,4 @@
-import { AppSyncConfigInput } from '../../getAppSyncConfig';
+import { AppSyncConfig } from '../../types';
 import { validateConfig } from '../../validation';
 import { basicConfig } from '../basicConfig';
 
@@ -12,6 +12,11 @@ describe('Valdiation', () => {
         tags: {
           foo: 'bar',
         },
+        esbuild: {
+          target: 'es2020',
+          sourcemap: false,
+          treeShaking: false,
+        },
       }),
     ).toBe(true);
 
@@ -20,6 +25,7 @@ describe('Valdiation', () => {
         visibility: 'FOO',
         xrayEnabled: 'BAR',
         unknownPorp: 'foo',
+        esbuild: 'bad',
       });
     }).toThrowErrorMatchingSnapshot();
   });
@@ -34,7 +40,7 @@ describe('Valdiation', () => {
             logging: {
               level: 'ALL',
             },
-          } as AppSyncConfigInput,
+          } as AppSyncConfig,
         },
         {
           name: 'Full',
@@ -46,7 +52,7 @@ describe('Valdiation', () => {
               excludeVerboseContent: true,
               loggingRoleArn: { Ref: 'MyLogGorupArn' },
             },
-          } as AppSyncConfigInput,
+          } as AppSyncConfig,
         },
       ];
 
@@ -94,7 +100,7 @@ describe('Valdiation', () => {
             waf: {
               rules: [],
             },
-          } as AppSyncConfigInput,
+          } as AppSyncConfig,
         },
         {
           name: 'Full',
@@ -164,7 +170,7 @@ describe('Valdiation', () => {
                 },
               ],
             },
-          } as AppSyncConfigInput,
+          } as AppSyncConfig,
         },
         {
           name: 'Using arn',
@@ -260,7 +266,7 @@ describe('Valdiation', () => {
               name: 'api.example.com',
               certificateArn: 'arn:aws:',
             },
-          } as AppSyncConfigInput,
+          } as AppSyncConfig,
         },
         {
           name: 'Full',
@@ -274,7 +280,7 @@ describe('Valdiation', () => {
               hostedZoneName: 'example.com.',
               route53: true,
             },
-          } as AppSyncConfigInput,
+          } as AppSyncConfig,
         },
         {
           name: 'useCloudFormation: false, missing certificateArn',
@@ -284,7 +290,7 @@ describe('Valdiation', () => {
               name: 'api.example.com',
               useCloudFormation: false,
             },
-          } as AppSyncConfigInput,
+          } as AppSyncConfig,
         },
       ];
 
@@ -364,7 +370,7 @@ describe('Valdiation', () => {
             caching: {
               behavior: 'PER_RESOLVER_CACHING',
             },
-          } as AppSyncConfigInput,
+          } as AppSyncConfig,
         },
         {
           name: 'Full',
@@ -378,7 +384,7 @@ describe('Valdiation', () => {
               atRestEncryption: true,
               transitEncryption: true,
             },
-          } as AppSyncConfigInput,
+          } as AppSyncConfig,
         },
       ];
 
diff --git a/src/__tests__/validation/pipelineFunctions.test.ts b/src/__tests__/validation/pipelineFunctions.test.ts
index 7341e10..c372a6f 100644
--- a/src/__tests__/validation/pipelineFunctions.test.ts
+++ b/src/__tests__/validation/pipelineFunctions.test.ts
@@ -1,4 +1,4 @@
-import { FunctionConfigInput } from '../../getAppSyncConfig';
+import { PipelineFunctionConfig } from '../../types';
 import { validateConfig } from '../../validation';
 import { basicConfig } from '../basicConfig';
 
@@ -19,7 +19,7 @@ describe('Basic', () => {
               request: 'request.vtl',
               response: 'response.vtl',
             },
-          } as Record<string, FunctionConfigInput>,
+          } as Record<string, PipelineFunctionConfig>,
         },
       },
       {
@@ -51,7 +51,7 @@ describe('Basic', () => {
               },
               function4: 'ds1',
             },
-          ] as Record<string, FunctionConfigInput>[],
+          ] as Record<string, PipelineFunctionConfig>[],
         },
       },
     ];