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

Commit d60982f

Browse files
authored
fix(lambda-at-edge): resolve dependencies using .next/serverless as base path (#425)
1 parent cfcf463 commit d60982f

File tree

9 files changed

+110
-20
lines changed

9 files changed

+110
-20
lines changed

integration/__tests__/local-deploy.test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ const path = require("path");
33
const serverlessOfflineStart = require("../../packages/serverless-plugin/utils/test/serverlessOfflineStart");
44
const httpGet = require("../../packages/serverless-plugin/utils/test/httpGet");
55

6-
describe("Local Deployment Tests (via serverless-offline)", () => {
6+
describe.skip("Local Deployment Tests (via serverless-offline)", () => {
77
let slsOffline;
88

99
beforeAll(() => {

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"graduate": "lerna publish --conventional-commits --conventional-graduate",
2424
"lint": "eslint .",
2525
"coveralls": "jest --runInBand --coverage && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js",
26-
"integration": "jest --config jest.integration.config.json --setupTestFrameworkScriptFile=./jest.integration.setup.js",
26+
"integration": "jest --runInBand --config jest.integration.config.json --setupTestFrameworkScriptFile=./jest.integration.setup.js",
2727
"postinstall": "opencollective-postinstall || true"
2828
},
2929
"repository": {
@@ -77,6 +77,7 @@
7777
"<rootDir>/packages/serverless-plugin/utils/test",
7878
"/.serverless_nextjs/",
7979
"/fixtures/",
80+
"/fixture/",
8081
"/examples/",
8182
"/dist/"
8283
],

packages/lambda-at-edge/src/build.ts

+18-18
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import isDynamicRoute from "./lib/isDynamicRoute";
1313
import pathToPosix from "./lib/pathToPosix";
1414
import expressifyDynamicRoute from "./lib/expressifyDynamicRoute";
1515
import pathToRegexStr from "./lib/pathToRegexStr";
16+
import normalizeNodeModules from "./lib/normalizeNodeModules";
1617
import createServerlessConfig from "./lib/createServerlessConfig";
1718

1819
export const DEFAULT_LAMBDA_CODE_DIR = "default-lambda";
@@ -36,7 +37,8 @@ const defaultBuildOptions = {
3637

3738
class Builder {
3839
nextConfigDir: string;
39-
dotNextDirectory: string;
40+
dotNextDir: string;
41+
serverlessDir: string;
4042
outputDir: string;
4143
buildOptions: BuildOptions = defaultBuildOptions;
4244

@@ -46,7 +48,8 @@ class Builder {
4648
buildOptions?: BuildOptions
4749
) {
4850
this.nextConfigDir = path.resolve(nextConfigDir);
49-
this.dotNextDirectory = path.join(this.nextConfigDir, ".next");
51+
this.dotNextDir = path.join(this.nextConfigDir, ".next");
52+
this.serverlessDir = path.join(this.dotNextDir, "serverless");
5053
this.outputDir = outputDir;
5154
if (buildOptions) {
5255
this.buildOptions = buildOptions;
@@ -70,10 +73,7 @@ class Builder {
7073
}
7174

7275
async readPagesManifest(): Promise<{ [key: string]: string }> {
73-
const path = join(
74-
this.nextConfigDir,
75-
".next/serverless/pages-manifest.json"
76-
);
76+
const path = join(this.serverlessDir, "pages-manifest.json");
7777
const hasServerlessPageManifest = await fse.pathExists(path);
7878

7979
if (!hasServerlessPageManifest) {
@@ -121,7 +121,9 @@ class Builder {
121121
})
122122
.map((filePath: string) => {
123123
const resolvedFilePath = path.resolve(filePath);
124-
const dst = path.relative(this.nextConfigDir, resolvedFilePath);
124+
const dst = normalizeNodeModules(
125+
path.relative(this.serverlessDir, resolvedFilePath)
126+
);
125127

126128
return fse.copy(
127129
resolvedFilePath,
@@ -149,7 +151,7 @@ class Builder {
149151
].filter(ignoreAppAndDocumentPages);
150152

151153
const ssrPages = Object.values(allSsrPages).map(pageFile =>
152-
path.join(this.dotNextDirectory, "serverless", pageFile)
154+
path.join(this.serverlessDir, pageFile)
153155
);
154156

155157
const { fileList, reasons } = await nodeFileTrace(ssrPages, {
@@ -182,7 +184,7 @@ class Builder {
182184
)
183185
),
184186
fse.copy(
185-
join(this.nextConfigDir, ".next/serverless/pages"),
187+
join(this.serverlessDir, "pages"),
186188
join(this.outputDir, DEFAULT_LAMBDA_CODE_DIR, "pages"),
187189
{
188190
filter: (file: string) => {
@@ -199,7 +201,7 @@ class Builder {
199201
}
200202
),
201203
fse.copy(
202-
join(this.nextConfigDir, ".next/prerender-manifest.json"),
204+
join(this.dotNextDir, "prerender-manifest.json"),
203205
join(this.outputDir, DEFAULT_LAMBDA_CODE_DIR, "prerender-manifest.json")
204206
)
205207
]);
@@ -217,7 +219,7 @@ class Builder {
217219
];
218220

219221
const apiPages = Object.values(allApiPages).map(pageFile =>
220-
path.join(this.dotNextDirectory, "serverless", pageFile)
222+
path.join(this.serverlessDir, pageFile)
221223
);
222224

223225
const { fileList, reasons } = await nodeFileTrace(apiPages, {
@@ -246,11 +248,11 @@ class Builder {
246248
)
247249
),
248250
fse.copy(
249-
join(this.nextConfigDir, ".next/serverless/pages/api"),
251+
join(this.serverlessDir, "pages/api"),
250252
join(this.outputDir, API_LAMBDA_CODE_DIR, "pages/api")
251253
),
252254
fse.copy(
253-
join(this.nextConfigDir, ".next/serverless/pages/_error.js"),
255+
join(this.serverlessDir, "pages/_error.js"),
254256
join(this.outputDir, API_LAMBDA_CODE_DIR, "pages/_error.js")
255257
),
256258
fse.writeJson(
@@ -342,19 +344,17 @@ class Builder {
342344
}
343345

344346
async cleanupDotNext(): Promise<void> {
345-
const dotNextDirectory = join(this.nextConfigDir, ".next");
346-
347-
const exists = await fse.pathExists(dotNextDirectory);
347+
const exists = await fse.pathExists(this.dotNextDir);
348348

349349
if (exists) {
350-
const fileItems = await fse.readdir(dotNextDirectory);
350+
const fileItems = await fse.readdir(this.dotNextDir);
351351

352352
await Promise.all(
353353
fileItems
354354
.filter(
355355
fileItem => fileItem !== "cache" // avoid deleting the cache folder as that would lead to slow builds!
356356
)
357-
.map(fileItem => fse.remove(join(dotNextDirectory, fileItem)))
357+
.map(fileItem => fse.remove(join(this.dotNextDir, fileItem)))
358358
);
359359
}
360360
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// removes parent paths of node_modules dir
2+
// ../../node_modules/module/file.js -> node_modules/module/file.js
3+
4+
const normalizeNodeModules = (path: string): string => {
5+
return path.substring(path.indexOf("node_modules"));
6+
};
7+
8+
export default normalizeNodeModules;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.next
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default () => <span>Hello World</span>;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export default (req, res) => {
2+
res.statusCode = 200
3+
res.setHeader('Content-Type', 'application/json')
4+
res.end(JSON.stringify({ name: 'John Doe' }))
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import dynamic from "next/dynamic";
2+
3+
const DynamicComponent = dynamic(() => import("../components/hello"));
4+
5+
const Page = () => <DynamicComponent />;
6+
7+
Page.getInitialProps = () => {
8+
// just forcing this page to be server side rendered
9+
return {
10+
foo: "bar"
11+
};
12+
};
13+
14+
export default Page;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { remove, readdir, pathExists } from "fs-extra";
2+
import path from "path";
3+
import os from "os";
4+
import Builder from "../../../src/build";
5+
import { getNextBinary } from "../../test-utils";
6+
7+
jest.unmock("execa");
8+
9+
describe("Serverless Trace With Dynamic Import", () => {
10+
const nextBinary = getNextBinary();
11+
const fixtureDir = path.join(__dirname, "./fixture");
12+
let outputDir: string;
13+
14+
beforeAll(async () => {
15+
outputDir = os.tmpdir();
16+
const builder = new Builder(fixtureDir, outputDir, {
17+
cwd: fixtureDir,
18+
cmd: nextBinary,
19+
args: ["build"],
20+
useServerlessTraceTarget: true
21+
});
22+
23+
await builder.build();
24+
});
25+
26+
afterAll(() => {
27+
return Promise.all(
28+
[".next"].map(file => remove(path.join(fixtureDir, file)))
29+
);
30+
});
31+
32+
it("copies node_modules to default lambda artefact", async () => {
33+
const nodeModules = await readdir(
34+
path.join(outputDir, "default-lambda/node_modules")
35+
);
36+
expect(nodeModules.length).toBeGreaterThan(5); // 5 is just an arbitrary number to ensure dependencies are being copied
37+
});
38+
39+
it("copies node_modules to api lambda artefact", async () => {
40+
const nodeModules = await readdir(
41+
path.join(outputDir, "api-lambda/node_modules")
42+
);
43+
expect(nodeModules).toEqual(["next", "next-aws-cloudfront"]);
44+
});
45+
46+
it("copies dynamic chunk to default lambda artefact", async () => {
47+
const chunkFileName = (
48+
await readdir(path.join(fixtureDir, ".next/serverless"))
49+
).find(file => {
50+
return /^[\d]+\.+[\w,\s-]+\.(js)$/.test(file);
51+
});
52+
53+
expect(chunkFileName).toBeDefined();
54+
55+
const chunkExistsInOutputBuild = await pathExists(
56+
path.join(outputDir, "default-lambda", chunkFileName as string)
57+
);
58+
expect(chunkExistsInOutputBuild).toBe(true);
59+
});
60+
});

0 commit comments

Comments
 (0)