mrgrain / cdk-esbuild

CDK constructs for esbuild, an extremely fast JavaScript bundler
https://constructs.dev/packages/@mrgrain/cdk-esbuild/
MIT License
109 stars 8 forks source link

Support for Cloudfront Functions #102

Open mrgrain opened 2 years ago

mrgrain commented 2 years ago

Cloudfront supports small inline JavaScript functions. They have some limitations, mainly regarding size and used memory.

We should evaluate if the existing inline code works with it, or add support and document the usage.

moltar commented 2 years ago

They have some peculiar limitations.

Docs: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/functions-javascript-runtime-features.html

The CloudFront Functions JavaScript runtime environment is compliant with ECMAScript (ES) version 5.1 and also supports some features of ES versions 6 through 9. It also provides some nonstandard methods that are not part of the ES specifications. The following topics list all the supported language features.

The const and let statements are not supported.

Restricted features: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/functions-javascript-runtime-features.html#writing-functions-javascript-features-restricted-features

mrgrain commented 2 years ago

Interesting! Thanks for the reading links. I guess with esbuild we would be able to set the compile target to es5. The other limitations will be harder to enforce, and probably have to be left to the user to ensure compatibility. 🤔

moltar commented 2 years ago

The other limitations will be harder to enforce, and probably have to be left to the user to ensure compatibility.

Would probably be a lot of work, and difficult to configure, but maybe eslint rules?

Probably outside the scope of this package though.

github-actions[bot] commented 2 years ago

This issue is now marked as stale because it hasn't seen activity for a while. Add a comment or it will be closed soon.

github-actions[bot] commented 2 years ago

This issue is now marked as stale because it hasn't seen activity for a while. Add a comment or it will be closed soon. If you wish to exclude this issue from being marked as stale, add the "backlog" label.

mrgrain commented 2 years ago

Cloudfront Functions requires ES5 which esbuild cannot target right now. See: https://github.com/evanw/esbuild/issues/297

gunta commented 2 years ago

Definitely having eslint rules for Cloudfront Functions would be amazing

blimmer commented 11 months ago

I've actually had decent luck with this esbuild config:

build({
    entryPoints: [join(cloudFrontFunction, "src", "index.ts")],
    outdir: join(cloudFrontFunction, "dist"),

    // Make compatible with CloudFront Functions limited ES5 JavaScript runtime
    // https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/functions-javascript-runtime-features.html#writing-functions-javascript-features-core
    format: "cjs",
    target: "es5",
    platform: "neutral",
    treeShaking: true,
    banner: {
      js: "var module = {};",  // allows exporting functions from TS files to unit test
    },
    minifyIdentifiers: false,
    supported: {
      "const-and-let": false,  // throws a build-time error if you use `const` or `let`, but at least warns you
      "exponent-operator": true,
      "template-literal": true,
      arrow: true,
      "rest-argument": true,
      "regexp-named-capture-groups": true,
    },
  });

It definitely doesn't transpile everything, but it works pretty well!

mrgrain commented 11 months ago

Cool, than you! I might pull this config out into a Cloudfront Function Construct.

blimmer commented 11 months ago

Sounds good - I'd be happy to collab on that PR if it would be helpful. For me, it has been great to write my CloudFront functions in TS, write tests in jest, and then deploy the ES5-ish compatible version to AWS.

There are some intricacies we'd probably need to document. For instance, with the config I posted and this very simple example:

import type { CloudFrontFunctionsEvent } from "aws-lambda";

export function handler(event: CloudFrontFunctionsEvent) {
  const request = event.request;
  return request;
}

You get this error:

✘ [ERROR] Transforming const to the configured target environment ("es5" + 6 overrides) is not supported yet

    src/stacks/cloudfront-functions/content-preview/src/index.ts:4:2:
      4 │   const request = event.request;
        ╵   ~~~~~

/Users/blimmer/code/company/stacks/node_modules/esbuild/lib/main.js:1650
  let error = new Error(text);
              ^

Error: Build failed with 1 error:
src/stacks/cloudfront-functions/content-preview/src/index.ts:4:2: ERROR: Transforming const to the configured target environment ("es5" + 6 overrides) is not supported yet
    at failureErrorWithLog (/Users/blimmer/code/company/stacks/node_modules/esbuild/lib/main.js:1650:15)
    at /Users/blimmer/code/company/stacks/node_modules/esbuild/lib/main.js:1059:25
    at /Users/blimmer/code/company/stacks/node_modules/esbuild/lib/main.js:1004:52
    at buildResponseToResult (/Users/blimmer/code/company/stacks/node_modules/esbuild/lib/main.js:1057:7)
    at /Users/blimmer/code/company/stacks/node_modules/esbuild/lib/main.js:1086:16
    at responseCallbacks.<computed> (/Users/blimmer/code/company/stacks/node_modules/esbuild/lib/main.js:703:9)
    at handleIncomingPacket (/Users/blimmer/code/company/stacks/node_modules/esbuild/lib/main.js:763:9)
    at Socket.readFromStdout (/Users/blimmer/code/company/stacks/node_modules/esbuild/lib/main.js:679:7)
    at Socket.emit (node:events:514:28)
    at addChunk (node:internal/streams/readable:324:12) {
  errors: [Getter/Setter],
  warnings: [Getter/Setter]
}

Node.js v18.17.1

so you have to write:

import type { CloudFrontFunctionsEvent } from "aws-lambda";

export function handler(event: CloudFrontFunctionsEvent) {
  var request = event.request;
  return request;
}

which is surprising.

Also, writing console.info anywhere doesn't throw an esbuild error, so you wouldn't find out about issues until runtime. So, the experience isn't killer, but it's better than nothing.

moltar commented 11 months ago

Also, writing console.info anywhere doesn't throw an esbuild error, so you wouldn't find out about issues until runtime. So, the experience isn't killer, but it's better than nothing.

Maybe it'd be possible to achieve this via esbuild plugins?

Or ESLint rules?

mrgrain commented 11 months ago

Disabling features in esbuild and causing a build failure seems like the earliest possible point that fits the remit of this construct.

Ideally, we would have eslint rules and plugins available. Maybe a types package that is used instead of @types/node would help as well. If any of these come about, I'd be happy to include it here however it makes sense (code or docs).

blimmer commented 4 months ago

I wonder if this is as relevant anymore now that runtime 2.0 is out: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/functions-javascript-runtime-20.html

moltar commented 4 months ago

There are still many restricted features: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/functions-javascript-runtime-20.html#writing-functions-javascript-features-restricted-features-20

moltar commented 2 months ago

Found this today, but did not try yet: https://www.npmjs.com/package/esbuild-cf-functions-plugin

mrgrain commented 2 months ago

Nice find! Using a plugin is going to be annoying, but the settings might be easy enough to adapt. Although from looking at the source, it seems to do fairly basic stuff.