diff --git a/.changeset/flat-laws-perform.md b/.changeset/flat-laws-perform.md new file mode 100644 index 0000000..5b1f4e1 --- /dev/null +++ b/.changeset/flat-laws-perform.md @@ -0,0 +1,5 @@ +--- +"esbuild-cf-functions-plugin": minor +--- + +Added support for the Cloudfront Function 2.0 runtime diff --git a/README.md b/README.md index 33b9e27..e76f84f 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ According to them, it > ... is compliant with ECMAScript (ES) version 5.1 and also supports some features of ES versions 6 through 9. -This plugin does its best to enable and disable transpiling features as the [documentation says is available][runtime]. +This plugin does its best to enable and disable transpiling features as the documentation says is available for the [v1 runtime][runtime] and [v2 runtime][runtime-v2]. By default the v1 runtime is assumed. **Check out the [example](./example)!** @@ -52,7 +52,14 @@ void build({ }) ``` +To enable v2 runtime features: + +```js + plugins: [CloudFrontFunctionsPlugin({ runtimeVersion: 2 })], +``` + _The plugin overrides the `format` and `target` options, unless I did something wrong._ [cf-functions]: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/functions-javascript-runtime-features.html [runtime]: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/functions-javascript-runtime-features.html +[runtime-v2]: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/functions-javascript-runtime-20.html diff --git a/src/__snapshots__/plugin.test.ts.snap b/src/__snapshots__/plugin.test.ts.snap index 587a713..4a0545f 100644 --- a/src/__snapshots__/plugin.test.ts.snap +++ b/src/__snapshots__/plugin.test.ts.snap @@ -1,20 +1,20 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`allows exponent operator 1`] = ` +exports[`runtimeVersion 1 > allows exponent operator 1`] = ` " var foo = 2 ** 2; console.log(foo); " `; -exports[`allows template strings 1`] = ` +exports[`runtimeVersion 1 > allows template strings 1`] = ` " var foo = \`Hello\`; var bar = \`\${foo}\`; " `; -exports[`arrays > does not modify supported functions 1`] = ` +exports[`runtimeVersion 1 > arrays > does not modify supported functions 1`] = ` " var foo = Array.of(1, 2, 3); var foo = [].copyWithin(10, 0, 2); @@ -25,7 +25,7 @@ var foo = [].includes("1"); " `; -exports[`arrays > does not modify supported typed arrays 1`] = ` +exports[`runtimeVersion 1 > arrays > does not modify supported typed arrays 1`] = ` " var foo = new Int8Array([1, 2, 3]); var foo = new Uint8Array([1, 2, 3]); @@ -47,14 +47,14 @@ var foo = new Float64Array([1, 2, 3]).toString(); " `; -exports[`const, let, var > lets vars through 1`] = ` +exports[`runtimeVersion 1 > const, let, var > lets vars through 1`] = ` " var foo = "bar"; console.log(foo); " `; -exports[`does not modify crypto imports 1`] = ` +exports[`runtimeVersion 1 > does not modify crypto imports 1`] = ` " import crypto from "crypto"; var hash = crypto.createHash("sha1"); @@ -63,14 +63,14 @@ hash.digest("base64"); " `; -exports[`does not modify named capture groups 1`] = ` +exports[`runtimeVersion 1 > does not modify named capture groups 1`] = ` " var regex = /(?.*+)/; var matches = regex.exec("Hello world"); " `; -exports[`does not modify supported functions 1`] = ` +exports[`runtimeVersion 1 > does not modify supported functions 1`] = ` " var foo = String.fromCodePoint(12); var foo = "bar".codePointAt(0); @@ -85,7 +85,7 @@ var foo = "bar".trimEnd(); " `; -exports[`does not modify supported functions 2`] = ` +exports[`runtimeVersion 1 > does not modify supported functions 2`] = ` " var foo = Number.isFinite(10); var foo = Number.isInteger(10); @@ -107,7 +107,7 @@ var foo = 10 .toPrecision(10); " `; -exports[`does not modify supported functions 3`] = ` +exports[`runtimeVersion 1 > does not modify supported functions 3`] = ` " new Promise((resolve, reject) => resolve()); new Promise((resolve, reject) => reject()); @@ -117,7 +117,7 @@ new Promise((resolve, reject) => reject()).finally(console.log); " `; -exports[`functions > allows arrow functions 1`] = ` +exports[`runtimeVersion 1 > functions > allows arrow functions 1`] = ` " var foo = () => { return true; @@ -126,7 +126,7 @@ console.log(foo()); " `; -exports[`functions > allows spread parameters 1`] = ` +exports[`runtimeVersion 1 > functions > allows spread parameters 1`] = ` " var foo = (...rest) => { return rest[0]; @@ -135,7 +135,216 @@ console.log(foo("test")); " `; -exports[`minification does not rename handler function 1`] = ` +exports[`runtimeVersion 1 > minification does not rename handler function 1`] = ` +" +function handler(event){console.log("test")} +" +`; + +exports[`runtimeVersion 2 > allows await/async 1`] = ` +" +void (async () => { + var func = async () => true; + var result = await func(); +})(); +" +`; + +exports[`runtimeVersion 2 > allows exponent operator 1`] = ` +" +var foo = 2 ** 2; +console.log(foo); +" +`; + +exports[`runtimeVersion 2 > allows template strings 1`] = ` +" +var foo = \`Hello\`; +var bar = \`\${foo}\`; +" +`; + +exports[`runtimeVersion 2 > arrays > does not modify supported functions 1`] = ` +" +var foo = Array.of(1, 2, 3); +var foo = [].copyWithin(10, 0, 2); +var foo = [].fill("foo", 0, 20); +var foo = [].find(() => true); +var foo = [].findIndex(() => true); +var foo = [].includes("1"); +" +`; + +exports[`runtimeVersion 2 > arrays > does not modify supported typed arrays 1`] = ` +" +var foo = new Int8Array([1, 2, 3]); +var foo = new Uint8Array([1, 2, 3]); +var foo = new Uint8ClampedArray([1, 2, 3]); +var foo = new Int16Array([1, 2, 3]); +var foo = new Uint16Array([1, 2, 3]); +var foo = new Int32Array([1, 2, 3]); +var foo = new Uint32Array([1, 2, 3]); +var foo = new Float32Array([1, 2, 3]); +var foo = new Float64Array([1, 2, 3]); +var foo = new Float64Array([1, 2, 3]); +var foo = new Float64Array([1, 2, 3]).copyWithin(10, 0, 2); +var foo = new Float64Array([1, 2, 3]).fill(1, 20); +var foo = new Float64Array([1, 2, 3]).join("\\n"); +new Float64Array([1, 2, 3]).set([1, 2, 3]); +var foo = new Float64Array([1, 2, 3]).slice(0, 1); +var foo = new Float64Array([1, 2, 3]).subarray(0, 1); +var foo = new Float64Array([1, 2, 3]).toString(); +" +`; + +exports[`runtimeVersion 2 > const, let, var > lets const through 1`] = ` +" +const foo = "bar"; +console.log(foo); +" +`; + +exports[`runtimeVersion 2 > const, let, var > lets let through 1`] = ` +" +let foo = "bar"; +console.log(foo); +" +`; + +exports[`runtimeVersion 2 > const, let, var > lets vars through 1`] = ` +" +var foo = "bar"; +console.log(foo); +" +`; + +exports[`runtimeVersion 2 > does not atob and btoa 1`] = ` +" +atob(btao("Hello World")); +" +`; + +exports[`runtimeVersion 2 > does not atob and btoa functions 1`] = ` +" +atob(btao("Hello World")); +" +`; + +exports[`runtimeVersion 2 > does not modify atob and btoa functions 1`] = ` +" +atob(btao("Hello World")); +" +`; + +exports[`runtimeVersion 2 > does not modify buffer imports 1`] = ` +" +import buffer from "buffer"; +var b = buffer.from("aString", "utf8"); +b.toString("utf8"); +" +`; + +exports[`runtimeVersion 2 > does not modify buffer imports 2`] = ` +" +import buffer from "buffer"; +var b = buffer.from("aString", "utf8"); +b.toString("utf8"); +" +`; + +exports[`runtimeVersion 2 > does not modify crypto imports 1`] = ` +" +import crypto from "crypto"; +var hash = crypto.createHash("sha1"); +hash.update("data"); +hash.digest("base64"); +" +`; + +exports[`runtimeVersion 2 > does not modify named capture groups 1`] = ` +" +var regex = /(?.*+)/; +var matches = regex.exec("Hello world"); +" +`; + +exports[`runtimeVersion 2 > does not modify supported Promise functions 1`] = ` +" +new Promise((resolve, reject) => resolve()); +new Promise((resolve, reject) => reject()); +new Promise((resolve, reject) => reject()).catch(console.log); +new Promise((resolve, reject) => reject()).then(console.log); +new Promise((resolve, reject) => reject()).finally(console.log); +Promise.all([new Promise((resolve, reject) => resolve())]); +" +`; + +exports[`runtimeVersion 2 > does not modify supported functions 1`] = ` +" +var foo = String.fromCodePoint(12); +var foo = "bar".codePointAt(0); +var foo = "bar".includes("ar"); +var foo = "bar".startsWith("ba"); +var foo = "bar".endsWith("ar"); +var foo = "bar".repeat(2); +var foo = "bar".padStart(10); +var foo = "bar".padEnd(10); +var foo = "bar".trimStart(); +var foo = "bar".trimEnd(); +" +`; + +exports[`runtimeVersion 2 > does not modify supported functions 2`] = ` +" +var foo = Number.isFinite(10); +var foo = Number.isInteger(10); +var foo = Number.isNaN(10); +var foo = Number.isSafeInteger(10); +var foo = Number.parseFloat("10.0"); +var foo = Number.parseInt("10"); +var foo = Number.EPSILON; +var foo = Number.MAX_SAFE_INTEGER; +var foo = Number.MAX_VALUE; +var foo = Number.MIN_SAFE_INTEGER; +var foo = Number.MIN_VALUE; +var foo = Number.NEGATIVE_INFINITY; +var foo = Number.NaN; +var foo = Number.POSITIVE_INFINITY; +var foo = 10 .toExponential(); +var foo = 10 .toFixed(); +var foo = 10 .toPrecision(10); +" +`; + +exports[`runtimeVersion 2 > does not modify supported functions 3`] = ` +" +new Promise((resolve, reject) => resolve()); +new Promise((resolve, reject) => reject()); +new Promise((resolve, reject) => reject()).catch(console.log); +new Promise((resolve, reject) => reject()).then(console.log); +new Promise((resolve, reject) => reject()).finally(console.log); +" +`; + +exports[`runtimeVersion 2 > functions > allows arrow functions 1`] = ` +" +var foo = () => { + return true; +}; +console.log(foo()); +" +`; + +exports[`runtimeVersion 2 > functions > allows spread parameters 1`] = ` +" +var foo = (...rest) => { + return rest[0]; +}; +console.log(foo("test")); +" +`; + +exports[`runtimeVersion 2 > minification does not rename handler function 1`] = ` " function handler(event){console.log("test")} " diff --git a/src/plugin.test.ts b/src/plugin.test.ts index ab8308e..906aac9 100644 --- a/src/plugin.test.ts +++ b/src/plugin.test.ts @@ -39,200 +39,500 @@ const buildFile = async ( ctx: TestContext, contents: string, extraOptions?: BuildOptions, + runtimeVersion?: 1 | 2, ) => { const inputFilePath = path.join(ctx.tempDirPath, "index.ts") await fs.writeFile(inputFilePath, dedent(contents)) return build({ entryPoints: [inputFilePath], - plugins: [CloudFrontFunctionsPlugin()], + plugins: [CloudFrontFunctionsPlugin({ runtimeVersion })], ...extraOptions, write: false, }) } -describe("const, let, var", () => { - test("lets vars through", async (ctx) => { - const result = await buildFile( - ctx, - ` +describe("runtimeVersion 1", () => { + describe("const, let, var", () => { + test("lets vars through", async (ctx) => { + const result = await buildFile( + ctx, + ` var foo = "bar" console.log(foo) `, - ) + ) - expect(result.outputFiles).toBeDefined() - expect(getOutput(result)).toMatchSnapshot() - }) + expect(result.outputFiles).toBeDefined() + expect(getOutput(result)).toMatchSnapshot() + }) - test("does not allow const", async (ctx) => { - const promise = buildFile( - ctx, - ` + test("does not allow const", async (ctx) => { + const promise = buildFile( + ctx, + ` const foo = "bar" console.log(foo) `, - ) + ) - await expect(promise).rejects.toThrowError( - "Transforming const to the configured target environment", - ) - }) + await expect(promise).rejects.toThrowError( + "Transforming const to the configured target environment", + ) + }) - test("does not allow let", async (ctx) => { - const promise = buildFile( - ctx, - ` + test("does not allow let", async (ctx) => { + const promise = buildFile( + ctx, + ` let foo = "bar" console.log(foo) `, - ) + ) - await expect(promise).rejects.toThrowError( - "Transforming let to the configured target environment", - ) + await expect(promise).rejects.toThrowError( + "Transforming let to the configured target environment", + ) + }) }) -}) -test("does not allow destructing", async (ctx) => { - const promise = buildFile( - ctx, - ` + test("does not allow destructing", async (ctx) => { + const promise = buildFile( + ctx, + ` var foo = { bar: true } var { bar } = foo console.log(bar) `, - ) + ) - await expect(promise).rejects.toThrowError( - "Transforming destructuring to the configured target", - ) -}) + await expect(promise).rejects.toThrowError( + "Transforming destructuring to the configured target", + ) + }) -test("allows exponent operator", async (ctx) => { - const result = await buildFile( - ctx, - ` + test("allows exponent operator", async (ctx) => { + const result = await buildFile( + ctx, + ` var foo = 2 ** 2 console.log(foo) `, - ) + ) - expect(result.outputFiles).toBeDefined() - expect(getOutput(result)).toMatchSnapshot() -}) + expect(result.outputFiles).toBeDefined() + expect(getOutput(result)).toMatchSnapshot() + }) -test("allows template strings", async (ctx) => { - const result = await buildFile( - ctx, - ` + test("allows template strings", async (ctx) => { + const result = await buildFile( + ctx, + ` var foo = \`Hello\` var bar = \`\${foo}\` `, - ) + ) - expect(result.outputFiles).toBeDefined() - expect(getOutput(result)).toMatchSnapshot() -}) + expect(result.outputFiles).toBeDefined() + expect(getOutput(result)).toMatchSnapshot() + }) -describe("functions", () => { - test("allows arrow functions", async (ctx) => { - const result = await buildFile( - ctx, - ` + describe("functions", () => { + test("allows arrow functions", async (ctx) => { + const result = await buildFile( + ctx, + ` var foo = () => { return true } console.log(foo()) `, + ) + + expect(result.outputFiles).toBeDefined() + expect(getOutput(result)).toMatchSnapshot() + }) + + test("allows spread parameters", async (ctx) => { + const result = await buildFile( + ctx, + ` + var foo = (...rest: string[]) => { + return rest[0] + } + console.log(foo("test")) + `, + ) + + expect(result.outputFiles).toBeDefined() + expect(getOutput(result)).toMatchSnapshot() + }) + }) + + test("does not modify supported functions", async (ctx) => { + const input = dedent` + var foo = String.fromCodePoint(12); + var foo = "bar".codePointAt(0); + var foo = "bar".includes("ar"); + var foo = "bar".startsWith("ba"); + var foo = "bar".endsWith("ar"); + var foo = "bar".repeat(2); + var foo = "bar".padStart(10); + var foo = "bar".padEnd(10); + var foo = "bar".trimStart(); + var foo = "bar".trimEnd(); + ` + + const result = await buildFile(ctx, input) + + expect(result.outputFiles).toBeDefined() + + const output = getOutput(result) + expect(output.trim()).toStrictEqual(input) + expect(output).toMatchSnapshot() + }) + + test("does not modify supported functions", async (ctx) => { + const input = dedent` + var foo = Number.isFinite(10); + var foo = Number.isInteger(10); + var foo = Number.isNaN(10); + var foo = Number.isSafeInteger(10); + var foo = Number.parseFloat("10.0"); + var foo = Number.parseInt("10"); + var foo = Number.EPSILON; + var foo = Number.MAX_SAFE_INTEGER; + var foo = Number.MAX_VALUE; + var foo = Number.MIN_SAFE_INTEGER; + var foo = Number.MIN_VALUE; + var foo = Number.NEGATIVE_INFINITY; + var foo = Number.NaN; + var foo = Number.POSITIVE_INFINITY; + var foo = 10 .toExponential(); + var foo = 10 .toFixed(); + var foo = 10 .toPrecision(10); + ` + + const result = await buildFile(ctx, input) + + expect(result.outputFiles).toBeDefined() + + const output = getOutput(result) + expect(output.trim()).toStrictEqual(input) + expect(output).toMatchSnapshot() + }) + + describe("arrays", () => { + test("does not modify supported functions", async (ctx) => { + const input = dedent` + var foo = Array.of(1, 2, 3); + var foo = [].copyWithin(10, 0, 2); + var foo = [].fill("foo", 0, 20); + var foo = [].find(() => true); + var foo = [].findIndex(() => true); + var foo = [].includes("1"); + ` + + const result = await buildFile(ctx, input) + + expect(result.outputFiles).toBeDefined() + + const output = getOutput(result) + expect(output.trim()).toStrictEqual(input) + expect(output).toMatchSnapshot() + }) + + test("does not modify supported typed arrays", async (ctx) => { + const input = dedent` + var foo = new Int8Array([1, 2, 3]); + var foo = new Uint8Array([1, 2, 3]); + var foo = new Uint8ClampedArray([1, 2, 3]); + var foo = new Int16Array([1, 2, 3]); + var foo = new Uint16Array([1, 2, 3]); + var foo = new Int32Array([1, 2, 3]); + var foo = new Uint32Array([1, 2, 3]); + var foo = new Float32Array([1, 2, 3]); + var foo = new Float64Array([1, 2, 3]); + var foo = new Float64Array([1, 2, 3]); + var foo = new Float64Array([1, 2, 3]).copyWithin(10, 0, 2); + var foo = new Float64Array([1, 2, 3]).fill(1, 20); + var foo = new Float64Array([1, 2, 3]).join("\\n"); + new Float64Array([1, 2, 3]).set([1, 2, 3]); + var foo = new Float64Array([1, 2, 3]).slice(0, 1); + var foo = new Float64Array([1, 2, 3]).subarray(0, 1); + var foo = new Float64Array([1, 2, 3]).toString(); + ` + + const result = await buildFile(ctx, input) + + expect(result.outputFiles).toBeDefined() + + const output = getOutput(result) + expect(output.trim()).toStrictEqual(input) + expect(output).toMatchSnapshot() + }) + }) + + test("does not modify named capture groups", async (ctx) => { + const input = dedent` + var regex = /(?.*+)/; + var matches = regex.exec("Hello world"); + ` + + const result = await buildFile(ctx, input) + + expect(result.outputFiles).toBeDefined() + + const output = getOutput(result) + expect(output.trim()).toStrictEqual(input) + expect(output).toMatchSnapshot() + }) + + test("does not modify supported functions", async (ctx) => { + const input = dedent` + new Promise((resolve, reject) => resolve()); + new Promise((resolve, reject) => reject()); + new Promise((resolve, reject) => reject()).catch(console.log); + new Promise((resolve, reject) => reject()).then(console.log); + new Promise((resolve, reject) => reject()).finally(console.log); + ` + + const result = await buildFile(ctx, input) + + expect(result.outputFiles).toBeDefined() + + const output = getOutput(result) + expect(output.trim()).toStrictEqual(input) + expect(output).toMatchSnapshot() + }) + + test("does not allow await/async", async (ctx) => { + const input = dedent` + void (async () => { + var func = async () => true + + var result = await func() + })() + ` + + const promise = buildFile(ctx, input) + + await expect(promise).rejects.toThrowError( + "Transforming async functions to the configured target environment", + ) + }) + + test("does not modify crypto imports", async (ctx) => { + const input = dedent` + import crypto from "crypto" + + var hash = crypto.createHash("sha1") + hash.update("data") + hash.digest("base64") + ` + + const result = await buildFile(ctx, input) + + expect(result.outputFiles).toBeDefined() + + const output = getOutput(result) + expect(output).toMatchSnapshot() + }) + + test("minification does not rename handler function", async (ctx) => { + const input = dedent` + function handler(event: Record) { + console.log("test") + } + ` + + const result = await buildFile(ctx, input, { minify: true }) + + expect(result.outputFiles).toBeDefined() + + const output = getOutput(result) + expect(output).toContain("handler(event)") + expect(output).toMatchSnapshot() + }) +}) + +describe("runtimeVersion 2", () => { + describe("const, let, var", () => { + test("lets vars through", async (ctx) => { + const result = await buildFile( + ctx, + ` + var foo = "bar" + console.log(foo) + `, + {}, + 2, + ) + + expect(result.outputFiles).toBeDefined() + expect(getOutput(result)).toMatchSnapshot() + }) + + test("lets const through", async (ctx) => { + const result = await buildFile( + ctx, + ` + const foo = "bar" + console.log(foo) + `, + {}, + 2, + ) + + expect(result.outputFiles).toBeDefined() + expect(getOutput(result)).toMatchSnapshot() + }) + + test("lets let through", async (ctx) => { + const result = await buildFile( + ctx, + ` + let foo = "bar" + console.log(foo) + `, + {}, + 2, + ) + + expect(result.outputFiles).toBeDefined() + expect(getOutput(result)).toMatchSnapshot() + }) + }) + + test("does not allow destructing", async (ctx) => { + const promise = buildFile( + ctx, + ` + var foo = { bar: true } + var { bar } = foo + console.log(bar) + `, + {}, + 2, + ) + + await expect(promise).rejects.toThrowError( + "Transforming destructuring to the configured target", + ) + }) + + test("allows exponent operator", async (ctx) => { + const result = await buildFile( + ctx, + ` + var foo = 2 ** 2 + console.log(foo) + `, + {}, + 2, ) expect(result.outputFiles).toBeDefined() expect(getOutput(result)).toMatchSnapshot() }) - test("allows spread parameters", async (ctx) => { + test("allows template strings", async (ctx) => { const result = await buildFile( ctx, ` + var foo = \`Hello\` + var bar = \`\${foo}\` + `, + {}, + 2, + ) + + expect(result.outputFiles).toBeDefined() + expect(getOutput(result)).toMatchSnapshot() + }) + + describe("functions", () => { + test("allows arrow functions", async (ctx) => { + const result = await buildFile( + ctx, + ` + var foo = () => { + return true + } + console.log(foo()) + `, + {}, + 2, + ) + + expect(result.outputFiles).toBeDefined() + expect(getOutput(result)).toMatchSnapshot() + }) + + test("allows spread parameters", async (ctx) => { + const result = await buildFile( + ctx, + ` var foo = (...rest: string[]) => { return rest[0] } console.log(foo("test")) `, - ) + {}, + 2, + ) - expect(result.outputFiles).toBeDefined() - expect(getOutput(result)).toMatchSnapshot() + expect(result.outputFiles).toBeDefined() + expect(getOutput(result)).toMatchSnapshot() + }) }) -}) -test("does not modify supported functions", async (ctx) => { - const input = dedent` - var foo = String.fromCodePoint(12); - var foo = "bar".codePointAt(0); - var foo = "bar".includes("ar"); - var foo = "bar".startsWith("ba"); - var foo = "bar".endsWith("ar"); - var foo = "bar".repeat(2); - var foo = "bar".padStart(10); - var foo = "bar".padEnd(10); - var foo = "bar".trimStart(); - var foo = "bar".trimEnd(); - ` - - const result = await buildFile(ctx, input) - - expect(result.outputFiles).toBeDefined() - - const output = getOutput(result) - expect(output.trim()).toStrictEqual(input) - expect(output).toMatchSnapshot() -}) + test("does not modify supported functions", async (ctx) => { + const input = dedent` + var foo = String.fromCodePoint(12); + var foo = "bar".codePointAt(0); + var foo = "bar".includes("ar"); + var foo = "bar".startsWith("ba"); + var foo = "bar".endsWith("ar"); + var foo = "bar".repeat(2); + var foo = "bar".padStart(10); + var foo = "bar".padEnd(10); + var foo = "bar".trimStart(); + var foo = "bar".trimEnd(); + ` -test("does not modify supported functions", async (ctx) => { - const input = dedent` - var foo = Number.isFinite(10); - var foo = Number.isInteger(10); - var foo = Number.isNaN(10); - var foo = Number.isSafeInteger(10); - var foo = Number.parseFloat("10.0"); - var foo = Number.parseInt("10"); - var foo = Number.EPSILON; - var foo = Number.MAX_SAFE_INTEGER; - var foo = Number.MAX_VALUE; - var foo = Number.MIN_SAFE_INTEGER; - var foo = Number.MIN_VALUE; - var foo = Number.NEGATIVE_INFINITY; - var foo = Number.NaN; - var foo = Number.POSITIVE_INFINITY; - var foo = 10 .toExponential(); - var foo = 10 .toFixed(); - var foo = 10 .toPrecision(10); - ` - - const result = await buildFile(ctx, input) - - expect(result.outputFiles).toBeDefined() - - const output = getOutput(result) - expect(output.trim()).toStrictEqual(input) - expect(output).toMatchSnapshot() -}) + const result = await buildFile(ctx, input, {}, 2) + + expect(result.outputFiles).toBeDefined() + + const output = getOutput(result) + expect(output.trim()).toStrictEqual(input) + expect(output).toMatchSnapshot() + }) -describe("arrays", () => { test("does not modify supported functions", async (ctx) => { const input = dedent` - var foo = Array.of(1, 2, 3); - var foo = [].copyWithin(10, 0, 2); - var foo = [].fill("foo", 0, 20); - var foo = [].find(() => true); - var foo = [].findIndex(() => true); - var foo = [].includes("1"); + var foo = Number.isFinite(10); + var foo = Number.isInteger(10); + var foo = Number.isNaN(10); + var foo = Number.isSafeInteger(10); + var foo = Number.parseFloat("10.0"); + var foo = Number.parseInt("10"); + var foo = Number.EPSILON; + var foo = Number.MAX_SAFE_INTEGER; + var foo = Number.MAX_VALUE; + var foo = Number.MIN_SAFE_INTEGER; + var foo = Number.MIN_VALUE; + var foo = Number.NEGATIVE_INFINITY; + var foo = Number.NaN; + var foo = Number.POSITIVE_INFINITY; + var foo = 10 .toExponential(); + var foo = 10 .toFixed(); + var foo = 10 .toPrecision(10); ` - const result = await buildFile(ctx, input) + const result = await buildFile(ctx, input, {}, 2) expect(result.outputFiles).toBeDefined() @@ -241,28 +541,64 @@ describe("arrays", () => { expect(output).toMatchSnapshot() }) - test("does not modify supported typed arrays", async (ctx) => { + describe("arrays", () => { + test("does not modify supported functions", async (ctx) => { + const input = dedent` + var foo = Array.of(1, 2, 3); + var foo = [].copyWithin(10, 0, 2); + var foo = [].fill("foo", 0, 20); + var foo = [].find(() => true); + var foo = [].findIndex(() => true); + var foo = [].includes("1"); + ` + + const result = await buildFile(ctx, input, {}, 2) + + expect(result.outputFiles).toBeDefined() + + const output = getOutput(result) + expect(output.trim()).toStrictEqual(input) + expect(output).toMatchSnapshot() + }) + + test("does not modify supported typed arrays", async (ctx) => { + const input = dedent` + var foo = new Int8Array([1, 2, 3]); + var foo = new Uint8Array([1, 2, 3]); + var foo = new Uint8ClampedArray([1, 2, 3]); + var foo = new Int16Array([1, 2, 3]); + var foo = new Uint16Array([1, 2, 3]); + var foo = new Int32Array([1, 2, 3]); + var foo = new Uint32Array([1, 2, 3]); + var foo = new Float32Array([1, 2, 3]); + var foo = new Float64Array([1, 2, 3]); + var foo = new Float64Array([1, 2, 3]); + var foo = new Float64Array([1, 2, 3]).copyWithin(10, 0, 2); + var foo = new Float64Array([1, 2, 3]).fill(1, 20); + var foo = new Float64Array([1, 2, 3]).join("\\n"); + new Float64Array([1, 2, 3]).set([1, 2, 3]); + var foo = new Float64Array([1, 2, 3]).slice(0, 1); + var foo = new Float64Array([1, 2, 3]).subarray(0, 1); + var foo = new Float64Array([1, 2, 3]).toString(); + ` + + const result = await buildFile(ctx, input, {}, 2) + + expect(result.outputFiles).toBeDefined() + + const output = getOutput(result) + expect(output.trim()).toStrictEqual(input) + expect(output).toMatchSnapshot() + }) + }) + + test("does not modify named capture groups", async (ctx) => { const input = dedent` - var foo = new Int8Array([1, 2, 3]); - var foo = new Uint8Array([1, 2, 3]); - var foo = new Uint8ClampedArray([1, 2, 3]); - var foo = new Int16Array([1, 2, 3]); - var foo = new Uint16Array([1, 2, 3]); - var foo = new Int32Array([1, 2, 3]); - var foo = new Uint32Array([1, 2, 3]); - var foo = new Float32Array([1, 2, 3]); - var foo = new Float64Array([1, 2, 3]); - var foo = new Float64Array([1, 2, 3]); - var foo = new Float64Array([1, 2, 3]).copyWithin(10, 0, 2); - var foo = new Float64Array([1, 2, 3]).fill(1, 20); - var foo = new Float64Array([1, 2, 3]).join("\\n"); - new Float64Array([1, 2, 3]).set([1, 2, 3]); - var foo = new Float64Array([1, 2, 3]).slice(0, 1); - var foo = new Float64Array([1, 2, 3]).subarray(0, 1); - var foo = new Float64Array([1, 2, 3]).toString(); + var regex = /(?.*+)/; + var matches = regex.exec("Hello world"); ` - const result = await buildFile(ctx, input) + const result = await buildFile(ctx, input, {}, 2) expect(result.outputFiles).toBeDefined() @@ -270,86 +606,97 @@ describe("arrays", () => { expect(output.trim()).toStrictEqual(input) expect(output).toMatchSnapshot() }) -}) -test("does not modify named capture groups", async (ctx) => { - const input = dedent` - var regex = /(?.*+)/; - var matches = regex.exec("Hello world"); - ` + test("does not modify supported Promise functions", async (ctx) => { + const input = dedent` + new Promise((resolve, reject) => resolve()); + new Promise((resolve, reject) => reject()); + new Promise((resolve, reject) => reject()).catch(console.log); + new Promise((resolve, reject) => reject()).then(console.log); + new Promise((resolve, reject) => reject()).finally(console.log); + Promise.all([new Promise((resolve, reject) => resolve())]); + ` - const result = await buildFile(ctx, input) + const result = await buildFile(ctx, input, {}, 2) - expect(result.outputFiles).toBeDefined() + expect(result.outputFiles).toBeDefined() - const output = getOutput(result) - expect(output.trim()).toStrictEqual(input) - expect(output).toMatchSnapshot() -}) + const output = getOutput(result) + expect(output.trim()).toStrictEqual(input) + expect(output).toMatchSnapshot() + }) -test("does not modify supported functions", async (ctx) => { - const input = dedent` - new Promise((resolve, reject) => resolve()); - new Promise((resolve, reject) => reject()); - new Promise((resolve, reject) => reject()).catch(console.log); - new Promise((resolve, reject) => reject()).then(console.log); - new Promise((resolve, reject) => reject()).finally(console.log); - ` + test("allows await/async", async (ctx) => { + const input = dedent` + void (async () => { + var func = async () => true + + var result = await func() + })() + ` + const result = await buildFile(ctx, input, {}, 2) - const result = await buildFile(ctx, input) + expect(result.outputFiles).toBeDefined() + expect(getOutput(result)).toMatchSnapshot() + }) - expect(result.outputFiles).toBeDefined() + test("does not modify crypto imports", async (ctx) => { + const input = dedent` + import crypto from "crypto" - const output = getOutput(result) - expect(output.trim()).toStrictEqual(input) - expect(output).toMatchSnapshot() -}) + var hash = crypto.createHash("sha1") + hash.update("data") + hash.digest("base64") + ` -test("does not allow await/async", async (ctx) => { - const input = dedent` - void (async () => { - var func = async () => true - - var result = await func() - })() - ` + const result = await buildFile(ctx, input, {}, 2) - const promise = buildFile(ctx, input) + expect(result.outputFiles).toBeDefined() - await expect(promise).rejects.toThrowError( - "Transforming async functions to the configured target environment", - ) -}) + const output = getOutput(result) + expect(output).toMatchSnapshot() + }) -test("does not modify crypto imports", async (ctx) => { - const input = dedent` - import crypto from "crypto" + test("does not modify buffer imports", async (ctx) => { + const input = dedent` + import buffer from "buffer" - var hash = crypto.createHash("sha1") - hash.update("data") - hash.digest("base64") - ` + var b = buffer.from("aString", "utf8") + b.toString("utf8"); + ` - const result = await buildFile(ctx, input) + const result = await buildFile(ctx, input, {}, 2) - expect(result.outputFiles).toBeDefined() + expect(result.outputFiles).toBeDefined() - const output = getOutput(result) - expect(output).toMatchSnapshot() -}) + const output = getOutput(result) + expect(output).toMatchSnapshot() + }) + test("minification does not rename handler function", async (ctx) => { + const input = dedent` + function handler(event: Record) { + console.log("test") + } + ` -test("minification does not rename handler function", async (ctx) => { - const input = dedent` - function handler(event: Record) { - console.log("test") - } - ` + const result = await buildFile(ctx, input, { minify: true }, 2) + + expect(result.outputFiles).toBeDefined() + + const output = getOutput(result) + expect(output).toContain("handler(event)") + expect(output).toMatchSnapshot() + }) - const result = await buildFile(ctx, input, { minify: true }) + test("does not modify atob and btoa functions", async (ctx) => { + const input = dedent` + atob(btao("Hello World")); + ` - expect(result.outputFiles).toBeDefined() + const result = await buildFile(ctx, input, {}, 2) - const output = getOutput(result) - expect(output).toContain("handler(event)") - expect(output).toMatchSnapshot() + expect(result.outputFiles).toBeDefined() + const output = getOutput(result) + expect(output).toMatchSnapshot() + }) }) diff --git a/src/plugin.ts b/src/plugin.ts index e63fd59..574c997 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -2,7 +2,7 @@ import { formatMessages, PartialMessage } from "esbuild" import type { Plugin } from "esbuild" // https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/functions-javascript-runtime-features.html -const config = { +const supportedV1 = { // Note: The const and let statements are not supported. "const-and-let": false, // The ES 7 exponentiation operator (**) is supported. @@ -16,7 +16,25 @@ const config = { "regexp-named-capture-groups": true, } -export const CloudFrontFunctionsPlugin = (): Plugin => ({ +const supportedV2 = { + ...supportedV1, + // Const and let statements are supported in v2. + "const-and-let": true, + // ES 6 await expressions are supported in v2. + "async-await": true, +} + +export type PluginConfig = { + /** + * CloudFront Functions JavaScript runtime environment version to build for + * + * @default 1 + */ + runtimeVersion?: 1 | 2 +} +export const CloudFrontFunctionsPlugin = ({ + runtimeVersion, +}: PluginConfig = {}): Plugin => ({ name: "cloudfront", setup: (build) => { @@ -56,7 +74,7 @@ export const CloudFrontFunctionsPlugin = (): Plugin => ({ } build.initialOptions.supported = { - ...config, + ...(runtimeVersion === 2 ? supportedV2 : supportedV1), ...build.initialOptions.supported, } },