Skip to content

Commit

Permalink
Feature: Custom handler (#57)
Browse files Browse the repository at this point in the history
* feat: add support for custom handlers
  • Loading branch information
mekwall authored and danielcondemarin committed Apr 30, 2019
1 parent cf22c36 commit 5ead4ba
Show file tree
Hide file tree
Showing 14 changed files with 145 additions and 110 deletions.
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ The plugin targets [Next 8 serverless mode](https://nextjs.org/blog/next-8/#serv
- [Overriding page configuration](#overriding-page-configuration)
- [Custom page routing](#custom-page-routing)
- [Custom error page](#custom-error-page)
- [Custom handler](#custom-handler)
- [Examples](#examples)
- [Contributing](#contributing)

Expand Down Expand Up @@ -267,6 +268,36 @@ class Error extends React.Component {
export default Error;
```

### Custom handler

If you need to customize the lambda handler you can do so by providing a path to your own handler in the `customHandler` field. Note that it resolves the path to the custom handler relative to your `next.config.js`.

```yml
plugins:
- serverless-nextjs-plugin

custom:
serverless-nextjs:
nextConfigDir: ./
customHandler: ./handler.js
```
The custom handler needs to look something like this:
```js
const compat = require('serverless-nextjs-plugin/aws-lambda-compat');

module.exports = (page) => {
const handler = (event, context, callback) => {
// do any stuff you like
// make sure the next page renders etc.
compat(page)(event, context, callback);
// do any other stuff you like
};
return handler;
};
```

## Examples

See the `examples/` directory.
Expand Down
3 changes: 2 additions & 1 deletion __tests__/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ describe("ServerlessNextJsPlugin", () => {
return plugin.buildNextPages().then(() => {
expect(build).toBeCalledWith(
new PluginBuildDir(nextConfigDir),
pageConfig
pageConfig,
undefined
);
});
});
Expand Down
8 changes: 8 additions & 0 deletions aws-lambda-compat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const reqResMapper = require("./lib/compatLayer");

const handlerFactory = page => (event, _context, callback) => {
const { req, res } = reqResMapper(event, callback);
page.render(req, res);
};

module.exports = handlerFactory;
9 changes: 6 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,12 @@ class ServerlessNextJsPlugin {
servicePackage.include.push(
path.posix.join(pluginBuildDir.posixBuildDir, "**")
);
return build(pluginBuildDir, this.getPluginConfigValue("pageConfig")).then(
nextPages => this.setNextPages(nextPages)
);

return build(
pluginBuildDir,
this.getPluginConfigValue("pageConfig"),
this.getPluginConfigValue("customHandler")
).then(nextPages => this.setNextPages(nextPages));
}

setNextPages(nextPages) {
Expand Down
28 changes: 3 additions & 25 deletions lib/__tests__/build.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,30 +130,8 @@ describe("build", () => {
nextPages => {
expect(getNextPagesFromBuildDir).toBeCalledWith(
new PluginBuildDir(nextConfigDir).buildDir,
pageConfig
);
expect(nextPages).toEqual(mockNextPages);
}
);
});

it("should call getNextPagesFromBuildDir and return NextPage instances for each nextPage copied", () => {
expect.assertions(2);

const parsedConfig = parsedNextConfigurationFactory();
parseNextConfiguration.mockResolvedValueOnce(parsedConfig);
const mockNextPages = [new NextPage("/foo/bar"), new NextPage("/foo/baz")];
getNextPagesFromBuildDir.mockResolvedValueOnce(mockNextPages);

const nextConfigDir = "path/to/next-app";

const pageConfig = {};

return build(new PluginBuildDir(nextConfigDir), pageConfig).then(
nextPages => {
expect(getNextPagesFromBuildDir).toBeCalledWith(
new PluginBuildDir(nextConfigDir).buildDir,
pageConfig
pageConfig,
undefined
);
expect(nextPages).toEqual(mockNextPages);
}
Expand All @@ -174,7 +152,7 @@ describe("build", () => {
getNextPagesFromBuildDir.mockResolvedValueOnce(nextPages);

return build(new PluginBuildDir(nextConfigDir)).then(() => {
expect(rewritePageHandlers).toBeCalledWith(nextPages);
expect(rewritePageHandlers).toBeCalledWith(nextPages, undefined);
});
});
});
5 changes: 4 additions & 1 deletion lib/__tests__/copyBuildFiles.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,10 @@ describe("copyBuildFiles", () => {
it("should call fs copy with the compatLayer file", () => {
expect(fse.copy).toBeCalledWith(
path.join(__dirname, "..", `compatLayer.js`),
path.join(pluginBuildDir, "compatLayer.js")
path.join(
pluginBuildDir,
"./node_modules/serverless-nextjs-plugin/lib/compatLayer.js"
)
);
});
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,38 +1,44 @@
const path = require("path");
const getCompatLayerCode = require("../getCompatLayerCode");
const getFactoryHandlerCode = require("../getFactoryHandlerCode");
const PluginBuildDir = require("../../classes/PluginBuildDir");

describe("getCompatLayerCode", () => {
describe("getFactoryHandlerCode", () => {
it("should require compatLayer", () => {
const compatHandlerContent = getCompatLayerCode(
const compatHandlerContent = getFactoryHandlerCode(
path.join(PluginBuildDir.BUILD_DIR_NAME, "my-page.js")
);
expect(compatHandlerContent).toContain('require("./compatLayer")');
expect(compatHandlerContent).toContain(
'require("serverless-nextjs-plugin/aws-lambda-compat")'
);
});

it("should require compatLayer with correct path when page is nested", () => {
const compatHandlerContent = getCompatLayerCode(
const compatHandlerContent = getFactoryHandlerCode(
path.join(PluginBuildDir.BUILD_DIR_NAME, "categories/fridge/fridges.js")
);
expect(compatHandlerContent).toContain('require("../../compatLayer")');
expect(compatHandlerContent).toContain(
'require("serverless-nextjs-plugin/aws-lambda-compat")'
);
});

it("should require compatLayer with correct path when buildDir is nested", () => {
const compatHandlerContent = getCompatLayerCode(
const compatHandlerContent = getFactoryHandlerCode(
path.join(`app/${PluginBuildDir.BUILD_DIR_NAME}`, "page.js")
);
expect(compatHandlerContent).toContain('require("./compatLayer")');
expect(compatHandlerContent).toContain(
'require("serverless-nextjs-plugin/aws-lambda-compat")'
);
});

it("should require next page provided", () => {
const compatHandlerContent = getCompatLayerCode(
const compatHandlerContent = getFactoryHandlerCode(
path.join(PluginBuildDir.BUILD_DIR_NAME, "my-page.js")
);
expect(compatHandlerContent).toContain('require("./my-page.original.js")');
});

it("should export render method", () => {
const compatHandlerContent = getCompatLayerCode(
const compatHandlerContent = getFactoryHandlerCode(
path.join(PluginBuildDir.BUILD_DIR_NAME, "my-page.js")
);
expect(compatHandlerContent).toContain("module.exports.render");
Expand Down
18 changes: 11 additions & 7 deletions lib/__tests__/rewritePageHandlers.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
const path = require("path");
const fs = require("fs");
const rewritePageHandlers = require("../rewritePageHandlers");
const getCompatLayerCode = require("../getCompatLayerCode");
const getFactoryHandlerCode = require("../getFactoryHandlerCode");
const NextPage = require("../../classes/NextPage");
const logger = require("../../utils/logger");

jest.mock("fs");
jest.mock("../../utils/logger");
jest.mock("../getCompatLayerCode");
jest.mock("../getFactoryHandlerCode");

describe("rewritePageHandlers", () => {
describe("when compat layer is injected successfully", () => {
Expand All @@ -20,7 +20,7 @@ describe("rewritePageHandlers", () => {
cb(null, undefined);
});

getCompatLayerCode.mockReturnValue("module.exports.render={...}");
getFactoryHandlerCode.mockReturnValue("module.exports.render={...}");

rewritePageHandlersPromise = rewritePageHandlers([
new NextPage(path.join(pagesDir, "home.js")),
Expand All @@ -43,10 +43,14 @@ describe("rewritePageHandlers", () => {
);
});

it("should call getCompatLayerCode with the next page path", () => {
expect(getCompatLayerCode).toBeCalledWith(path.join(pagesDir, "home.js"));
expect(getCompatLayerCode).toBeCalledWith(
path.join(pagesDir, "about.js")
it("should call getFactoryHandlerCode with the next page path", () => {
expect(getFactoryHandlerCode).toBeCalledWith(
path.join(pagesDir, "home.js"),
undefined
);
expect(getFactoryHandlerCode).toBeCalledWith(
path.join(pagesDir, "about.js"),
undefined
);
});

Expand Down
16 changes: 13 additions & 3 deletions lib/build.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const nextBuild = require("next/dist/build").default;
const path = require("path");
const fse = require("fs-extra");
const parseNextConfiguration = require("./parseNextConfiguration");
const logger = require("../utils/logger");
const copyBuildFiles = require("./copyBuildFiles");
Expand All @@ -17,7 +18,7 @@ const overrideTargetIfNotServerless = nextConfiguration => {
}
};

module.exports = async (pluginBuildDir, pageConfig) => {
module.exports = async (pluginBuildDir, pageConfig, customHandler) => {
logger.log("Started building next app ...");

const nextConfigDir = pluginBuildDir.nextConfigDir;
Expand All @@ -31,12 +32,21 @@ module.exports = async (pluginBuildDir, pageConfig) => {
pluginBuildDir
);

if (customHandler) {
// Copy custom handler
await fse.copy(
path.resolve(nextConfigDir, customHandler),
path.join(pluginBuildDir.buildDir, customHandler)
);
}

const nextPages = await getNextPagesFromBuildDir(
pluginBuildDir.buildDir,
pageConfig
pageConfig,
customHandler ? [path.basename(customHandler)] : undefined
);

await rewritePageHandlers(nextPages);
await rewritePageHandlers(nextPages, customHandler);

return nextPages;
};
12 changes: 11 additions & 1 deletion lib/copyBuildFiles.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@ module.exports = async (nextBuildDir, pluginBuildDirObj) => {
await fse.copy(pagesBuildDir, pluginBuildDirObj.buildDir);
await fse.copy(
path.join(__dirname, "./compatLayer.js"),
path.join(pluginBuildDirObj.buildDir, "./compatLayer.js")
path.join(
pluginBuildDirObj.buildDir,
"./node_modules/serverless-nextjs-plugin/lib/compatLayer.js"
)
);
await fse.copy(
path.join(__dirname, "../aws-lambda-compat.js"),
path.join(
pluginBuildDirObj.buildDir,
"./node_modules/serverless-nextjs-plugin/aws-lambda-compat.js"
)
);
};
51 changes: 0 additions & 51 deletions lib/getCompatLayerCode.js

This file was deleted.

23 changes: 23 additions & 0 deletions lib/getFactoryHandlerCode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const path = require("path");
const PAGE_BUNDLE_PATH = "/*page_bundle_path_placeholder*/";
const HANDLER_FACTORY_PATH = "/*handler_factory_path_placeholder*/";

const lambdaHandlerWithFactory = `
const page = require("${PAGE_BUNDLE_PATH}");
const handlerFactory = require("${HANDLER_FACTORY_PATH}");
module.exports.render = (event, context, callback) => {
const handler = handlerFactory(page);
handler(event, context, callback);
};
`;

module.exports = (jsHandlerPath, customHandlerPath) => {
const basename = path.basename(jsHandlerPath, ".js");
return lambdaHandlerWithFactory
.replace(PAGE_BUNDLE_PATH, `./${basename}.original.js`)
.replace(
HANDLER_FACTORY_PATH,
customHandlerPath || "serverless-nextjs-plugin/aws-lambda-compat"
);
};
Loading

0 comments on commit 5ead4ba

Please sign in to comment.