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

Commit a8a3952

Browse files
Merge branch 'master' into use-cloudfront-native-http-compression
2 parents dd31e3d + 69574c7 commit a8a3952

File tree

61 files changed

+933
-102
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+933
-102
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,7 @@ The fourth cache behaviour handles next API requests `api/*`.
431431
| logLambdaExecutionTimes | `boolean` | `false` | Logs to CloudWatch the default handler performance metrics. |
432432
| minifyHandlers | `boolean` | `false` | Use minified handlers to reduce code size. |
433433
| enableHTTPCompression | `boolean` | `false` | When set to `true` the Lambda@Edge functions for SSR and API requests will use Gzip to compress the response. Note that you shouldn't need to enable this because CloudFront will compress responses for you out of the box. |
434+
| deploy | `boolean` | `true` | Whether to deploy resources to AWS. Useful if you just need the Lambdas and assets but want to deploy them yourself (available in latest alpha). |
434435

435436
Custom inputs can be configured like this:
436437

packages/libs/lambda-at-edge/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
All notable changes to this project will be documented in this file.
44
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
55

6+
# [1.7.0-alpha.13](https://github.com/serverless-nextjs/serverless-next.js/compare/@sls-next/[email protected]...@sls-next/[email protected]) (2020-10-18)
7+
8+
**Note:** Version bump only for package @sls-next/lambda-at-edge
9+
610
# [1.7.0-alpha.12](https://github.com/serverless-nextjs/serverless-next.js/compare/@sls-next/[email protected]...@sls-next/[email protected]) (2020-10-16)
711

812
### Bug Fixes

packages/libs/lambda-at-edge/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"publishConfig": {
44
"access": "public"
55
},
6-
"version": "1.7.0-alpha.12",
6+
"version": "1.7.0-alpha.13",
77
"description": "Provides handlers that can be used in CloudFront Lambda@Edge to deploy next.js applications to the edge",
88
"main": "dist/index.js",
99
"types": "dist/index.d.ts",
@@ -39,8 +39,10 @@
3939
"@types/execa": "^2.0.0",
4040
"@types/fs-extra": "^9.0.1",
4141
"@types/jsonwebtoken": "^8.5.0",
42+
"@types/klaw": "^3.0.1",
4243
"@types/node": "^14.0.14",
4344
"@types/path-to-regexp": "^1.7.0",
45+
"klaw": "^3.0.0",
4446
"rollup": "^2.26.6",
4547
"rollup-plugin-node-externals": "^2.2.0",
4648
"rollup-plugin-terser": "^7.0.2",

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

Lines changed: 186 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,14 @@ import pathToRegexStr from "./lib/pathToRegexStr";
1717
import normalizeNodeModules from "./lib/normalizeNodeModules";
1818
import createServerlessConfig from "./lib/createServerlessConfig";
1919
import { isTrailingSlashRedirect } from "./routing/redirector";
20+
import readDirectoryFiles from "./lib/readDirectoryFiles";
21+
import filterOutDirectories from "./lib/filterOutDirectories";
22+
import { PrerenderManifest } from "next/dist/build";
23+
import { Item } from "klaw";
2024

2125
export const DEFAULT_LAMBDA_CODE_DIR = "default-lambda";
2226
export const API_LAMBDA_CODE_DIR = "api-lambda";
27+
export const ASSETS_DIR = "assets";
2328

2429
type BuildOptions = {
2530
args?: string[];
@@ -47,6 +52,7 @@ const defaultBuildOptions = {
4752

4853
class Builder {
4954
nextConfigDir: string;
55+
nextStaticDir: string;
5056
dotNextDir: string;
5157
serverlessDir: string;
5258
outputDir: string;
@@ -55,9 +61,11 @@ class Builder {
5561
constructor(
5662
nextConfigDir: string,
5763
outputDir: string,
58-
buildOptions?: BuildOptions
64+
buildOptions?: BuildOptions,
65+
nextStaticDir?: string
5966
) {
6067
this.nextConfigDir = path.resolve(nextConfigDir);
68+
this.nextStaticDir = path.resolve(nextStaticDir ?? nextConfigDir);
6169
this.dotNextDir = path.join(this.nextConfigDir, ".next");
6270
this.serverlessDir = path.join(this.dotNextDir, "serverless");
6371
this.outputDir = outputDir;
@@ -466,6 +474,175 @@ class Builder {
466474
};
467475
}
468476

477+
/**
478+
* Build static assets such as client-side JS, public files, static pages, etc.
479+
* Note that the upload to S3 is done in a separate deploy step.
480+
*/
481+
async buildStaticAssets(routesManifest: RoutesManifest) {
482+
const basePath = routesManifest.basePath;
483+
const nextConfigDir = this.nextConfigDir;
484+
const nextStaticDir = this.nextStaticDir;
485+
486+
const dotNextDirectory = path.join(this.nextConfigDir, ".next");
487+
488+
const assetOutputDirectory = path.join(this.outputDir, ASSETS_DIR);
489+
490+
const normalizedBasePath = basePath ? basePath.slice(1) : "";
491+
const withBasePath = (key: string): string =>
492+
path.join(normalizedBasePath, key);
493+
494+
const copyIfExists = async (
495+
source: string,
496+
destination: string
497+
): Promise<void> => {
498+
if (await fse.pathExists(source)) {
499+
await fse.copy(source, destination);
500+
}
501+
};
502+
503+
const buildStaticFiles = await readDirectoryFiles(
504+
path.join(dotNextDirectory, "static")
505+
);
506+
507+
const staticFileAssets = buildStaticFiles
508+
.filter(filterOutDirectories)
509+
.map(async (fileItem: Item) => {
510+
const source = fileItem.path;
511+
const destination = path.join(
512+
assetOutputDirectory,
513+
withBasePath(
514+
path
515+
.relative(path.resolve(nextConfigDir), source)
516+
.replace(/^.next/, "_next")
517+
)
518+
);
519+
520+
return copyIfExists(source, destination);
521+
});
522+
523+
const pagesManifest = await fse.readJSON(
524+
path.join(dotNextDirectory, "serverless/pages-manifest.json")
525+
);
526+
527+
const htmlPageAssets = Object.values(pagesManifest)
528+
.filter((pageFile) => (pageFile as string).endsWith(".html"))
529+
.map((relativePageFilePath) => {
530+
const source = path.join(
531+
dotNextDirectory,
532+
`serverless/${relativePageFilePath}`
533+
);
534+
const destination = path.join(
535+
assetOutputDirectory,
536+
withBasePath(
537+
`static-pages/${(relativePageFilePath as string).replace(
538+
/^pages\//,
539+
""
540+
)}`
541+
)
542+
);
543+
544+
return copyIfExists(source, destination);
545+
});
546+
547+
const prerenderManifest: PrerenderManifest = await fse.readJSON(
548+
path.join(dotNextDirectory, "prerender-manifest.json")
549+
);
550+
551+
const prerenderManifestJSONPropFileAssets = Object.keys(
552+
prerenderManifest.routes
553+
).map((key) => {
554+
const source = path.join(
555+
dotNextDirectory,
556+
`serverless/pages/${
557+
key.endsWith("/") ? key + "index.json" : key + ".json"
558+
}`
559+
);
560+
const destination = path.join(
561+
assetOutputDirectory,
562+
withBasePath(prerenderManifest.routes[key].dataRoute.slice(1))
563+
);
564+
565+
return copyIfExists(source, destination);
566+
});
567+
568+
const prerenderManifestHTMLPageAssets = Object.keys(
569+
prerenderManifest.routes
570+
).map((key) => {
571+
const relativePageFilePath = key.endsWith("/")
572+
? path.join(key, "index.html")
573+
: key + ".html";
574+
575+
const source = path.join(
576+
dotNextDirectory,
577+
`serverless/pages/${relativePageFilePath}`
578+
);
579+
const destination = path.join(
580+
assetOutputDirectory,
581+
withBasePath(path.join("static-pages", relativePageFilePath))
582+
);
583+
584+
return copyIfExists(source, destination);
585+
});
586+
587+
const fallbackHTMLPageAssets = Object.values(
588+
prerenderManifest.dynamicRoutes || {}
589+
)
590+
.filter(({ fallback }) => {
591+
return !!fallback;
592+
})
593+
.map((routeConfig) => {
594+
const fallback = routeConfig.fallback as string;
595+
596+
const source = path.join(
597+
dotNextDirectory,
598+
`serverless/pages/${fallback}`
599+
);
600+
601+
const destination = path.join(
602+
assetOutputDirectory,
603+
withBasePath(path.join("static-pages", fallback))
604+
);
605+
606+
return copyIfExists(source, destination);
607+
});
608+
609+
const buildPublicOrStaticDirectory = async (
610+
directory: "public" | "static"
611+
) => {
612+
const directoryPath = path.join(nextStaticDir, directory);
613+
if (!(await fse.pathExists(directoryPath))) {
614+
return Promise.resolve([]);
615+
}
616+
617+
const files = await readDirectoryFiles(directoryPath);
618+
619+
return files.filter(filterOutDirectories).map((fileItem: Item) => {
620+
const source = fileItem.path;
621+
const destination = path.join(
622+
assetOutputDirectory,
623+
withBasePath(
624+
path.relative(path.resolve(nextStaticDir), fileItem.path)
625+
)
626+
);
627+
628+
return fse.copy(source, destination);
629+
});
630+
};
631+
632+
const publicDirAssets = await buildPublicOrStaticDirectory("public");
633+
const staticDirAssets = await buildPublicOrStaticDirectory("static");
634+
635+
return Promise.all([
636+
...staticFileAssets, // .next/static
637+
...htmlPageAssets, // prerendered html pages
638+
...prerenderManifestJSONPropFileAssets, // SSG json files
639+
...prerenderManifestHTMLPageAssets, // SSG html files
640+
...fallbackHTMLPageAssets, // fallback files
641+
...publicDirAssets, // public dir
642+
...staticDirAssets // static dir
643+
]);
644+
}
645+
469646
async cleanupDotNext(): Promise<void> {
470647
const exists = await fse.pathExists(this.dotNextDir);
471648

@@ -492,6 +669,7 @@ class Builder {
492669

493670
await fse.emptyDir(join(this.outputDir, DEFAULT_LAMBDA_CODE_DIR));
494671
await fse.emptyDir(join(this.outputDir, API_LAMBDA_CODE_DIR));
672+
await fse.emptyDir(join(this.outputDir, ASSETS_DIR));
495673

496674
const { restoreUserConfig } = await createServerlessConfig(
497675
cwd,
@@ -529,6 +707,13 @@ class Builder {
529707
if (hasAPIPages) {
530708
await this.buildApiLambda(apiBuildManifest);
531709
}
710+
711+
// Copy static assets to .serverless_nextjs directory
712+
const routesManifest = require(join(
713+
this.dotNextDir,
714+
"routes-manifest.json"
715+
));
716+
await this.buildStaticAssets(routesManifest);
532717
}
533718

534719
/**
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { Item } from "klaw";
2+
3+
export default (fileItem: Item): boolean => !fileItem.stats.isDirectory();
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import klaw, { Item } from "klaw";
2+
import fse from "fs-extra";
3+
4+
const readDirectoryFiles = async (directory: string): Promise<Array<Item>> => {
5+
const directoryExists = await fse.pathExists(directory);
6+
if (!directoryExists) {
7+
return Promise.resolve([]);
8+
}
9+
10+
const items: Item[] = [];
11+
return new Promise((resolve, reject) => {
12+
klaw(directory.trim())
13+
.on("data", (item: Item) => items.push(item))
14+
.on("end", () => {
15+
resolve(items);
16+
})
17+
.on("error", reject);
18+
});
19+
};
20+
21+
export default readDirectoryFiles;

packages/libs/lambda-at-edge/tests/build/build-no-api.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,7 @@ import execa from "execa";
44
import Builder from "../../src/build";
55
import { DEFAULT_LAMBDA_CODE_DIR, API_LAMBDA_CODE_DIR } from "../../src/build";
66
import { cleanupDir, removeNewLineChars } from "../test-utils";
7-
import {
8-
OriginRequestDefaultHandlerManifest,
9-
OriginRequestApiHandlerManifest
10-
} from "../../types";
7+
import { OriginRequestDefaultHandlerManifest } from "../../types";
118

129
jest.mock("execa");
1310

@@ -60,6 +57,9 @@ describe("Builder Tests (no API routes)", () => {
6057
expect(fseEmptyDirSpy).toBeCalledWith(
6158
expect.stringContaining(join(".test_sls_next_output", "api-lambda"))
6259
);
60+
expect(fseEmptyDirSpy).toBeCalledWith(
61+
expect.stringContaining(join(".test_sls_next_output", "assets"))
62+
);
6363
});
6464
});
6565

packages/libs/lambda-at-edge/tests/build/build.test.ts

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import { join } from "path";
22
import fse from "fs-extra";
33
import execa from "execa";
4-
import Builder from "../../src/build";
5-
import { DEFAULT_LAMBDA_CODE_DIR, API_LAMBDA_CODE_DIR } from "../../src/build";
4+
import Builder, {
5+
ASSETS_DIR,
6+
DEFAULT_LAMBDA_CODE_DIR,
7+
API_LAMBDA_CODE_DIR
8+
} from "../../src/build";
69
import { cleanupDir, removeNewLineChars } from "../test-utils";
710
import {
811
OriginRequestDefaultHandlerManifest,
@@ -80,6 +83,9 @@ describe("Builder Tests", () => {
8083
expect(fseEmptyDirSpy).toBeCalledWith(
8184
expect.stringContaining(join(".test_sls_next_output", "api-lambda"))
8285
);
86+
expect(fseEmptyDirSpy).toBeCalledWith(
87+
expect.stringContaining(join(".test_sls_next_output", "assets"))
88+
);
8389
});
8490
});
8591

@@ -266,6 +272,40 @@ describe("Builder Tests", () => {
266272
expect(countLines(apiHandler.toString())).toBeGreaterThan(100); // Arbitrary choice
267273
});
268274
});
275+
276+
describe("Assets Artefact Files", () => {
277+
it("copies all assets", async () => {
278+
const publicFiles = await fse.readdir(
279+
join(outputDir, `${ASSETS_DIR}/public`)
280+
);
281+
expect(publicFiles).toEqual(["favicon.ico", "sub", "sw.js"]);
282+
283+
const staticFiles = await fse.readdir(
284+
join(outputDir, `${ASSETS_DIR}/static`)
285+
);
286+
expect(staticFiles).toEqual(["donotdelete.txt"]);
287+
288+
const nextDataFiles = await fse.readdir(
289+
join(outputDir, `${ASSETS_DIR}/_next/data/zsWqBqLjpgRmswfQomanp`)
290+
);
291+
expect(nextDataFiles).toEqual(["contact.json", "index.json"]);
292+
293+
const nextStaticFiles = await fse.readdir(
294+
join(outputDir, `${ASSETS_DIR}/_next/static`)
295+
);
296+
expect(nextStaticFiles).toEqual(["chunks"]);
297+
298+
const staticPagesFiles = await fse.readdir(
299+
join(outputDir, `${ASSETS_DIR}/static-pages`)
300+
);
301+
expect(staticPagesFiles).toEqual([
302+
"about.html",
303+
"contact.html",
304+
"index.html",
305+
"terms.html"
306+
]);
307+
});
308+
});
269309
});
270310

271311
describe("Minified handlers build", () => {

packages/libs/lambda-at-edge/tests/build/simple-app-fixture/.next/static/chunks/chunk1.js

Whitespace-only changes.
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
1-
{}
1+
{
2+
"version": 2,
3+
"routes": {
4+
}
5+
}

0 commit comments

Comments
 (0)