8000 feat(lambda-at-edge, nextjs-component): allow `handler` input for custom handler code by emhagman · Pull Request #649 · serverless-nextjs/serverless-next.js · GitHub
[go: up one dir, main page]

Skip to content
This repository was archived by the owner on Jan 28, 2025. It is now read-only.

feat(lambda-at-edge, nextjs-component): allow handler input for custom handler code #649

Merged
merged 13 commits into from
Oct 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions documentation/docs/inputs.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,21 @@
id: inputs
title: Inputs
sidebar_label: Inputs
---
---

| Name | Type | Default Value | Description |
| ------------------------ | ----------------- | ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| domain | `Array` | `null` | For example `['admin', 'portal.com']` |
| bucketName | `string` | `null` | Custom bucket name where static assets are stored. By default is autogenerated. 10000 |
| bucketRegion | `string` | `us-east-1` | Region where you want to host your s3 bucket. Make sure this is geographically closer to the majority of your end users to reduce latency when CloudFront proxies a request to S3. |
| nextConfigDir | `string` | `./` | Directory where your application `next.config.js` file is. This input is useful when the `serverless.yml` is not in the same directory as the next app. **Note:** `nextConfigDir` should be set if `next.config.js` `distDir` is used |
| nextConfigDir | `string` | `./` | Directory where your application `next.config.js` file is. This input is useful when the `serverless.yml` is not in the same directory as the next app. **Note:** `nextConfigDir` should be set if `next.config.js` `distDir` is used |
| nextStaticDir | `string` | `./` | If your `static` or `public` directory is not a direct child of `nextConfigDir` this is needed |
| description | `string` | `*lambda-type*@Edge for Next CloudFront distribution` | The description that will be used for both lambdas. Note that "(API)" will be appended to the API lambda description. |
| policy | `string` | `arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole` | The arn policy that will be assigned to both lambdas. |
| runtime | `string\|object` | `nodejs12.x` | When assigned a value, both the default and api lambdas will be assigned the runtime defined in the value. When assigned to an object, values for the default and api lambdas can be separately defined | |
| memory | `number\|object` | `512` | When assigned a number, both the default and api lambdas will be assigned memory of that value. When assigned to an object, values for the default and api lambdas can be separately defined | |
| timeout | `number\|object` | `10` | Same as above |
| handler | `string` | `index.handler` | When assigned a value, overrides the default function handler to allow for configuration. Copies `handler.js` in route into the Lambda folders. Your handler MUST still call the `default-handler` afterwards or your function won't work with Next.JS |
| name | `string\|object` | / | When assigned a string, both the default and api lambdas will assigned name of that value. When assigned to an object, values for the default and api lambdas can be separately defined |
| build | `boolean\|object` | `true` | When true builds and deploys app, when false assume the app has been built and uses the `.next` `.serverless_nextjs` directories in `nextConfigDir` to deploy. If an object is passed `build` allows for overriding what script gets called and with what arguments. |
| build.cmd | `string` | `node_modules/.bin/next` | Build command |
Expand All @@ -36,4 +37,4 @@ myNextApp:
component: serverless-next.js
inputs:
bucketName: my-bucket
```
```
17 changes: 17 additions & 0 deletions packages/libs/lambda-at-edge/src/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type BuildOptions = {
logLambdaExecutionTimes?: boolean;
domainRedirects?: { [key: string]: string };
minifyHandlers?: boolean;
handler?: string;
};

const defaultBuildOptions = {
Expand Down Expand Up @@ -258,6 +259,16 @@ class Builder {
join(this.outputDir, DEFAULT_LAMBDA_CODE_DIR, "index.js"),
!!this.buildOptions.minifyHandlers
),
this.buildOptions?.handler
? fse.copy(
join(this.nextConfigDir, this.buildOptions.handler),
join(
this.outputDir,
DEFAULT_LAMBDA_CODE_DIR,
this.buildOptions.handler
)
)
: Promise.resolve(),
fse.writeJson(
join(this.outputDir, DEFAULT_LAMBDA_CODE_DIR, "manifest.json"),
buildManifest
Expand Down Expand Up @@ -338,6 +349,12 @@ class Builder {
join(this.outputDir, API_LAMBDA_CODE_DIR, "index.js"),
!!this.buildOptions.minifyHandlers
),
this.buildOptions?.handler
? fse.copy(
join(this.nextConfigDir, this.buildOptions.handler),
join(this.outputDir, API_LAMBDA_CODE_DIR, this.buildOptions.handler)
)
: Promise.resolve(),
fse.copy(
join(this.serverlessDir, "pages/api"),
join(this.outputDir, API_LAMBDA_CODE_DIR, "pages/api")
Expand Down
67 changes: 67 additions & 0 deletions packages/libs/lambda-at-edge/tests/build/build.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,4 +340,71 @@ describe("Builder Tests", () => {
expect(countLines(apiHandler.toString())).toEqual(2);
});
});

describe("Custom handler", () => {
let fseRemoveSpy: jest.SpyInstance;
let fseEmptyDirSpy: jest.SpyInstance;
let defaultBuildManifest: OriginRequestDefaultHandlerManifest;
let apiBuildManifest: OriginRequestApiHandlerManifest;

const fixturePath = join(__dirname, "./simple-app-fixture");
const outputDir = join(fixturePath, ".test_sls_next_output");

beforeEach(async () => {
const mockExeca = execa as jest.Mock;
mockExeca.mockResolvedValueOnce();

fseRemoveSpy = jest
.spyOn(fse, "remove")
.mockImplementation(() => undefined);
fseEmptyDirSpy = jest.spyOn(fse, "emptyDir");
const builder = new Builder(fixturePath, outputDir, {
handler: "testFile.js"
});
await builder.build();

defaultBuildManifest = await fse.readJSON(
join(outputDir, `${DEFAULT_LAMBDA_CODE_DIR}/manifest.json`)
);

apiBuildManifest = await fse.readJSON(
join(outputDir, `${API_LAMBDA_CODE_DIR}/manifest.json`)
);
});

afterEach(() => {
fseEmptyDirSpy.mockRestore();
fseRemoveSpy.mockRestore();
return cleanupDir(outputDir);
});

it("copies build files", async () => {
expect.assertions(2);

const defaultFiles = await fse.readdir(
join(outputDir, `${DEFAULT_LAMBDA_CODE_DIR}`)
);

expect(defaultFiles).toEqual([
"index.js",
"manifest.json",
"pages",
"prerender-manifest.json",
"routes-manifest.json",
"testFile.js"
]);

const apiFiles = await fse.readdir(
join(outputDir, `${API_LAMBDA_CODE_DIR}`)
);

expect(apiFiles).toEqual([
"index.js",
"manifest.json",
"pages",
"routes-manifest.json",
"testFile.js"
]);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// Test handler file to check if copied
Original file line number Diff line number Diff line change
Expand Up @@ -1011,4 +1011,43 @@ describe("Custom inputs", () => {
});
});
});

describe.each([
[undefined, "index.handler"],
["customHandler.handler", "customHandler.handler"]
])("Lambda handler input", (inputHandler, expectedHandler) => {
const fixturePath = path.join(__dirname, "./fixtures/generic-fixture");
let tmpCwd;

beforeEach(async () => {
tmpCwd = process.cwd();
process.chdir(fixturePath);
mockServerlessComponentDependencies({});
const component = createNextComponent({ handler: inputHandler });
componentOutputs = await component({ handler: inputHandler });
});

afterEach(() => {
process.chdir(tmpCwd);
return cleanupFixtureDirectory(fixturePath);
});

it(`sets handler to ${expectedHandler} and api lambda handler to ${expectedHandler}`, () => {
// default Lambda
expect(mockLambda).toBeCalledWith(
expect.objectContaining({
code: path.join(fixturePath, DEFAULT_LAMBDA_CODE_DIR),
handler: expectedHandler
})
);

// api Lambda
expect(mockLambda).toBeCalledWith(
expect.objectContaining({
code: path.join(fixturePath, API_LAMBDA_CODE_DIR),
handler: expectedHandler
})
);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// Test custom handler to be copied
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,10 @@ class NextjsComponent extends Component {
useServerlessTraceTarget: inputs.useServerlessTraceTarget || false,
logLambdaExecutionTimes: inputs.logLambdaExecutionTimes || false,
domainRedirects: inputs.domainRedirects || {},
minifyHandlers: inputs.minifyHandlers || false
minifyHandlers: inputs.minifyHandlers || false,
handler: inputs.handler
? `${inputs.handler.split(".")[0]}.js`
: undefined
},
nextStaticPath
);
Expand Down Expand Up @@ -408,7 +411,7 @@ class NextjsComponent extends Component {
description: inputs.description
? `${inputs.description} (API)`
: "API Lambda@Edge for Next CloudFront distribution",
handler: "index.handler",
handler: inputs.handler || "index.handler",
code: join(nextConfigPath, API_LAMBDA_CODE_DIR),
role: {
service: ["lambda.amazonaws.com", "edgelambda.amazonaws.com"],
Expand Down Expand Up @@ -456,7 +459,7 @@ class NextjsComponent extends Component {
description:
inputs.description ||
"Default Lambda@Edge for Next CloudFront distribution",
handler: "index.handler",
handler: inputs.handler || "index.handler",
code: join(nextConfigPath, DEFAULT_LAMBDA_CODE_DIR),
role: {
service: ["lambda.amazonaws.com", "edgelambda.amazonaws.com"],
Expand Down
1 change: 1 addition & 0 deletions packages/serverless-components/nextjs-component/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export type ServerlessComponentInputs = {
timeout?: number | { defaultLambda?: number; apiLambda?: number };
name?: string | { defaultLambda?: string; apiLambda?: string };
runtime?: string | { defaultLambda?: string; apiLambda?: string };
handler?: string;
description?: string;
policy?: string;
domain?: string | string[];
Expand Down
0