Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Core refactor part 2 #1057

Merged
merged 25 commits into from
May 14, 2021
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b429d82
Refactor data routes
jvarho May 8, 2021
2b7fa13
Refactor page routing
jvarho May 8, 2021
1523935
Locale and header stripping should no longer be needed
jvarho May 9, 2021
e3ad23e
Remove unused code, fix rewrite tests
jvarho May 9, 2021
afb62f9
Refactor api routing
jvarho May 9, 2021
70d7539
Remove unused locale function
jvarho May 9, 2021
ee5796f
Drop now unused direct jwt dependency
jvarho May 9, 2021
20ea496
Fix inconsistent query parameters from no-locale rewrites
jvarho May 9, 2021
3abfb50
Use more explicit typing to remove null checks
jvarho May 9, 2021
7cd4283
Add test for uncovered multi-value query rewrite
jvarho May 9, 2021
d25f71d
Fix and test api rewrite corner cases
jvarho May 10, 2021
b1facb9
Remove impossible html page data routes
jvarho May 10, 2021
5056766
Test for catchall data raquests
jvarho May 10, 2021
845b29b
Use normalised uri for resolving SSG data routes
jvarho May 10, 2021
b3fb795
Restore preview changes from master lost in move
jvarho May 10, 2021
31a9db7
Merge branch 'master' into core-refactor-part-2
dphang May 11, 2021
f4f6234
Remove unnecessary ssg check
jvarho May 11, 2021
2589491
Get rid of ts-ignore, null and let use
jvarho May 11, 2021
ad7f28c
Drop unnecessary build id from image handler
jvarho May 11, 2021
6f7b8a3
Upgrade nextjs versions used in locale tests
jvarho May 11, 2021
8dc3797
Fix stale lockfile
jvarho May 11, 2021
a4e5de4
Fix api rewrites with locales
jvarho May 12, 2021
fa74672
Work around nextjs default path issue in tests
jvarho May 12, 2021
41c2af7
Workarounds for more localised static pages
jvarho May 12, 2021
4b12434
Merge branch 'master' into core-refactor-part-2
dphang May 13, 2021
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
3 changes: 3 additions & 0 deletions packages/libs/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,12 @@
"homepage": "https://github.com/serverless-nextjs/serverless-next.js#readme",
"dependencies": {
"@hapi/accept": "^5.0.1",
"jsonwebtoken": "^8.5.1",
"regex-parser": "^2.2.10"
},
"devDependencies": {
"@types/cookie": "^0.4.0",
"@types/jsonwebtoken": "^8.5.0",
"typescript": "^3.9.6"
}
}
50 changes: 50 additions & 0 deletions packages/libs/core/src/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { normalise } from "./basepath";
import { matchDynamic } from "./match";
import { getRewritePath, isExternalRewrite } from "./rewrite";
import { ApiManifest, ApiRoute, ExternalRoute, RoutesManifest } from "./types";

export const handleApiReq = (
uri: string,
manifest: ApiManifest,
routesManifest: RoutesManifest,
isRewrite?: boolean
): ExternalRoute | ApiRoute | undefined => {
const { apis } = manifest;
const normalisedUri = normalise(uri, routesManifest);

const nonDynamic = apis.nonDynamic[normalisedUri];
if (nonDynamic) {
return {
isApi: true,
page: nonDynamic
};
}

const rewrite = !isRewrite && getRewritePath(uri, routesManifest);
if (rewrite) {
const [path, querystring] = rewrite.split("?");
if (isExternalRewrite(path)) {
return {
isExternal: true,
path,
querystring
};
}
const route = handleApiReq(path, manifest, routesManifest, true);
if (route) {
return {
...route,
querystring
};
}
return route;
}

const dynamic = matchDynamic(normalisedUri, Object.values(apis.dynamic));
if (dynamic) {
return {
isApi: true,
page: dynamic
};
}
};
28 changes: 28 additions & 0 deletions packages/libs/core/src/basepath.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { RoutesManifest } from "./types";

export const normalise = (
uri: string,
routesManifest: RoutesManifest
): string => {
const { basePath, i18n } = routesManifest;
if (basePath) {
if (uri.startsWith(basePath)) {
uri = uri.slice(basePath.length);
} else {
// basePath set but URI does not start with basePath, return 404
if (i18n?.defaultLocale) {
return `/${i18n.defaultLocale}/404`;
} else {
return "/404";
}
}
}

// Remove trailing slash for all paths
if (uri.endsWith("/")) {
uri = uri.slice(0, -1);
}

// Empty path should be normalised to "/" as there is no Next.js route for ""
return uri === "" ? "/" : uri;
};
104 changes: 104 additions & 0 deletions packages/libs/core/src/data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { matchDynamic, matchDynamicSSG } from "./match";
import { DataRoute, PageManifest, StaticRoute } from "./types";

/*
* Get page name from data route
*/
const normaliseDataUri = (uri: string, buildId: string) => {
const prefix = `/_next/data/${buildId}`;
if (!uri.startsWith(prefix)) {
return uri;
}
return uri
.slice(prefix.length)
.replace(/\.json$/, "")
.replace(/^(\/index)?$/, "/");
};

/*
* Get full data route uri from page name
*/
const fullDataUri = (uri: string, buildId: string) => {
const prefix = `/_next/data/${buildId}`;
if (uri === "/") {
return `${prefix}/index.js`;
}
return `${prefix}${uri}.json`;
};

/*
* Handles a data route
*/
export const handleDataReq = (
uri: string,
manifest: PageManifest,
isPreview: boolean
): DataRoute | StaticRoute => {
const { buildId, pages } = manifest;
const normalisedUri = normaliseDataUri(uri, buildId);
if (pages.ssg.nonDynamic[normalisedUri] && !isPreview) {
return {
isData: true,
isStatic: true,
file: uri
};
}
if (
pages.ssr.nonDynamic[normalisedUri] ||
pages.ssg.nonDynamic[normalisedUri]
jvarho marked this conversation as resolved.
Show resolved Hide resolved
) {
return {
isData: true,
isRender: true,
page: pages.ssr.nonDynamic[normalisedUri]
};
}
// TODO: this order reproduces default-handler logic,
// should sort all dynamic routes together in build
const dynamicSSG = matchDynamicSSG(
fullDataUri(normalisedUri, buildId),
pages.ssg.dynamic,
true
);
if (dynamicSSG) {
return {
isData: true,
isStatic: true,
file: fullDataUri(normalisedUri, buildId)
};
}
const dynamicSSR = matchDynamic(
normalisedUri,
Object.values(pages.ssr.dynamic)
);
if (dynamicSSR) {
return {
isData: true,
isRender: true,
page: dynamicSSR
};
}
const catchAll = matchDynamic(
normalisedUri,
Object.values(pages.ssr.catchAll)
);
if (catchAll) {
return {
isData: true,
isRender: true,
page: catchAll
};
}
if (pages.html.nonDynamic["/404"]) {
return {
isData: false,
isStatic: true,
file: "pages/404.html"
};
}
return {
isData: true,
isRender: true,
page: "pages/_error.js"
};
};
91 changes: 62 additions & 29 deletions packages/libs/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { handleApiReq } from "./api";
import { getUnauthenticatedResponse } from "./auth";
import { normalise } from "./basepath";
import { handleDataReq } from "./data";
import { handlePageReq } from "./page";
import { isValidPreviewRequest } from "./preview";
import {
createRedirectResponse,
getDomainRedirectPath,
Expand All @@ -7,12 +12,17 @@ import {
getTrailingSlashPath
} from "./redirect";
import {
UnauthorizedRoute,
ApiManifest,
ApiRoute,
ExternalRoute,
Manifest,
PageManifest,
PrerenderManifest,
RedirectRoute,
Request,
Route,
RoutesManifest
RoutesManifest,
UnauthorizedRoute
} from "./types";

export const handleAuth = (
Expand All @@ -26,7 +36,7 @@ export const handleAuth = (
);
};

export const handleCustomRedirects = (
const handleCustomRedirects = (
req: Request,
routesManifest: RoutesManifest
): RedirectRoute | undefined => {
Expand All @@ -47,7 +57,7 @@ export const handleDomainRedirects = (
}
};

export const handleLanguageRedirect = async (
const handleLanguageRedirect = async (
req: Request,
manifest: Manifest,
routesManifest: RoutesManifest
Expand All @@ -72,12 +82,12 @@ const handlePublicFiles = (
if (isPublicFile) {
return {
isPublicFile: true,
file: decodedUri
file: uri
};
}
};

export const handleTrailingSlash = (
const handleTrailingSlash = (
req: Request,
manifest: Manifest,
isFile: boolean
Expand All @@ -88,41 +98,48 @@ export const handleTrailingSlash = (
}
};

const normalise = (uri: string, routesManifest: RoutesManifest): string => {
const { basePath, i18n } = routesManifest;
if (basePath) {
if (uri.startsWith(basePath)) {
uri = uri.slice(basePath.length);
} else {
// basePath set but URI does not start with basePath, return 404
if (i18n?.defaultLocale) {
return `/${i18n.defaultLocale}/404`;
} else {
return "/404";
}
}
/*
* Routes:
* - auth
* - redirects
* - api routes
* - rewrites (external and api)
*/
export const routeApi = (
req: Request,
manifest: ApiManifest,
routesManifest: RoutesManifest
): ApiRoute | ExternalRoute | RedirectRoute | UnauthorizedRoute | undefined => {
const auth = handleAuth(req, manifest);
if (auth) {
return auth;
}

// Remove trailing slash for all paths
if (uri.endsWith("/")) {
uri = uri.slice(0, -1);
const redirect =
handleDomainRedirects(req, manifest) ||
handleCustomRedirects(req, routesManifest);
if (redirect) {
return redirect;
}

// Empty path should be normalised to "/" as there is no Next.js route for ""
return uri === "" ? "/" : uri;
return handleApiReq(req.uri, manifest, routesManifest);
};

/*
* Routes:
* - auth
* - redirects
* - public files
* - data routes
* - pages
* - rewrites (external and page)
*/
export const routeDefault = async (
req: Request,
manifest: Manifest,
manifest: PageManifest,
prerenderManifest: PrerenderManifest,
routesManifest: RoutesManifest
): Promise<Route | undefined> => {
): Promise<Route> => {
const auth = handleAuth(req, manifest);
if (auth) {
return auth;
Expand All @@ -145,11 +162,27 @@ export const routeDefault = async (
return trailingSlash;
}

return (
publicFile ||
if (publicFile) {
return publicFile;
}

const otherRedirect =
handleCustomRedirects(req, routesManifest) ||
(await handleLanguageRedirect(req, manifest, routesManifest))
(await handleLanguageRedirect(req, manifest, routesManifest));
if (otherRedirect) {
return otherRedirect;
}

const isPreview = await isValidPreviewRequest(
req.headers.cookie,
prerenderManifest.preview.previewModeSigningKey
);

if (isDataReq) {
return handleDataReq(uri, manifest, isPreview);
} else {
return handlePageReq(req.uri, manifest, routesManifest, isPreview);
}
};

export * from "./types";
27 changes: 27 additions & 0 deletions packages/libs/core/src/match.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/

import { compile, Match, match } from "path-to-regexp";
import { Dynamic, DynamicSSG } from "./types";

/**
* Match the given path against a source path.
Expand Down Expand Up @@ -55,3 +56,29 @@ export function compileDestination(
return null;
}
}

export const matchDynamic = (
uri: string,
routes: Dynamic[]
): string | undefined => {
for (const { file, regex } of routes) {
const re = new RegExp(regex, "i");
if (re.test(uri)) {
return file;
}
}
};

export const matchDynamicSSG = (
uri: string,
routes: { [key: string]: DynamicSSG },
isData: boolean
): string | undefined => {
for (const [key, route] of Object.entries(routes)) {
const regex = isData ? route.dataRouteRegex : route.routeRegex;
const re = new RegExp(regex, "i");
if (re.test(uri)) {
return key;
}
}
};
Loading