Skip to content

Commit ba8fc32

Browse files
committed
feat(cloudfront): add NodejsEdgeFunction construct
Adds NodejsEdgeFunction construct that combines EdgeFunction's cross-region deployment capabilities with NodejsFunction's TypeScript bundling via esbuild. This addresses the feature request in issue #12671 by providing a convenient way to deploy TypeScript Lambda@Edge functions without manual bundling. Key features: - Extends EdgeFunction for automatic us-east-1 deployment - Reuses NodejsFunction's prepareBundling utility for esbuild integration - Validates runtime is Node.js family only - Supports all NodejsFunction bundling options (minify, sourceMap, etc.) - Includes integration test with AWS API assertions Closes #12671
1 parent 0fa89e8 commit ba8fc32

File tree

4 files changed

+123
-0
lines changed

4 files changed

+123
-0
lines changed
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/// !cdk-integ *
2+
import * as path from 'path';
3+
import * as cdk from 'aws-cdk-lib';
4+
import * as cloudfront from 'aws-cdk-lib/aws-cloudfront';
5+
import * as integ from '@aws-cdk/integ-tests-alpha';
6+
import { ExpectedResult } from '@aws-cdk/integ-tests-alpha';
7+
import { TestOrigin } from './test-origin';
8+
import { STANDARD_NODEJS_RUNTIME } from '../../config';
9+
10+
const app = new cdk.App();
11+
12+
const region = 'eu-west-1';
13+
const stack = new cdk.Stack(app, 'integ-nodejs-edge-function', {
14+
env: { region: region },
15+
});
16+
17+
// Test: NodejsEdgeFunction with cross-region deployment
18+
const edgeFunction = new cloudfront.experimental.NodejsEdgeFunction(stack, 'NodejsEdge', {
19+
entry: path.join(__dirname, 'nodejs-edge-handler', 'index.ts'),
20+
runtime: STANDARD_NODEJS_RUNTIME,
21+
});
22+
23+
// Attach to CloudFront to verify integration
24+
const distribution = new cloudfront.Distribution(stack, 'Dist', {
25+
defaultBehavior: {
26+
origin: new TestOrigin('www.example.com'),
27+
cachePolicy: cloudfront.CachePolicy.CACHING_DISABLED,
28+
edgeLambdas: [{
29+
functionVersion: edgeFunction.currentVersion,
30+
eventType: cloudfront.LambdaEdgeEventType.VIEWER_REQUEST,
31+
}],
32+
},
33+
});
34+
35+
const integTest = new integ.IntegTest(app, 'cdk-integ-nodejs-edge-function', {
36+
testCases: [stack],
37+
diffAssets: true,
38+
});
39+
40+
// Assertion 1: Verify Lambda exists in us-east-1
41+
integTest.assertions.awsApiCall('Lambda', 'getFunction', {
42+
FunctionName: edgeFunction.functionName,
43+
}).expect(ExpectedResult.objectLike({
44+
Configuration: {
45+
FunctionName: edgeFunction.functionName,
46+
},
47+
})).provider.addToRolePolicy({
48+
Effect: 'Allow',
49+
Action: ['lambda:GetFunction'],
50+
Resource: '*',
51+
});
52+
53+
// Assertion 2: Verify CloudFront distribution has the edge lambda attached
54+
integTest.assertions.awsApiCall('CloudFront', 'getDistributionConfig', {
55+
Id: distribution.distributionId,
56+
}).expect(ExpectedResult.objectLike({
57+
DistributionConfig: {
58+
DefaultCacheBehavior: {
59+
LambdaFunctionAssociations: {
60+
Quantity: 1,
61+
},
62+
},
63+
},
64+
})).provider.addToRolePolicy({
65+
Effect: 'Allow',
66+
Action: ['cloudfront:GetDistributionConfig'],
67+
Resource: '*',
68+
});
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { CloudFrontRequestEvent, CloudFrontRequestResult } from 'aws-lambda';
2+
3+
export const handler = async (event: CloudFrontRequestEvent): Promise<CloudFrontRequestResult> => {
4+
const request = event.Records[0].cf.request;
5+
6+
// Simple TypeScript logic to verify bundling works
7+
const customHeader: string = 'x-custom-header';
8+
request.headers[customHeader] = [{
9+
key: customHeader,
10+
value: 'test-value',
11+
}];
12+
13+
return request;
14+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export * from './edge-function';
2+
export * from './nodejs-edge-function';
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { Construct } from 'constructs';
2+
import { EdgeFunction } from './edge-function';
3+
import * as lambda from '../../../aws-lambda';
4+
import { NodejsFunctionProps } from '../../../aws-lambda-nodejs';
5+
import { prepareBundling } from '../../../aws-lambda-nodejs/lib/bundling-preparation';
6+
import { ValidationError } from '../../../core';
7+
8+
/**
9+
* Properties for a NodejsEdgeFunction
10+
*/
11+
export interface NodejsEdgeFunctionProps extends NodejsFunctionProps {
12+
/**
13+
* The stack ID of Lambda@Edge function.
14+
*
15+
* @default - `edge-lambda-stack-${region}`
16+
*/
17+
readonly stackId?: string;
18+
}
19+
20+
/**
21+
* A Node.js Lambda@Edge function bundled using esbuild
22+
*
23+
* @resource AWS::Lambda::Function
24+
*/
25+
export class NodejsEdgeFunction extends EdgeFunction {
26+
constructor(scope: Construct, id: string, props: NodejsEdgeFunctionProps = {}) {
27+
if (props.runtime && props.runtime.family !== lambda.RuntimeFamily.NODEJS) {
28+
throw new ValidationError('Only `NODEJS` runtimes are supported.', scope);
29+
}
30+
31+
const bundlingConfig = prepareBundling(scope, id, props, 'NodejsEdgeFunction');
32+
33+
super(scope, id, {
34+
...props,
35+
runtime: bundlingConfig.runtime,
36+
code: bundlingConfig.code,
37+
handler: bundlingConfig.handler,
38+
});
39+
}
40+
}

0 commit comments

Comments
 (0)