[remix] Allow runtime to be defined from a parent route (#9473)

Make it so that the `export const config = { runtime }` value is "inherited" from a parent route, if it is defined.

For example, "edge" can be defined in `app/root.tsx`, and so all routes will use the Edge runtime by default, unless `"runtime": "nodejs"` is used more specifically in a route deeper in the route hierarchy.
This commit is contained in:
Nathan Rajlich
2023-02-22 12:35:12 -08:00
committed by GitHub
parent 1f51e04387
commit 4da176d26f
6 changed files with 80 additions and 18 deletions

View File

@@ -18,7 +18,7 @@ import {
scanParentDirs, scanParentDirs,
walkParentDirs, walkParentDirs,
} from '@vercel/build-utils'; } from '@vercel/build-utils';
import { getConfig } from '@vercel/static-config'; import { getConfig, BaseFunctionConfig } from '@vercel/static-config';
import { nodeFileTrace } from '@vercel/nft'; import { nodeFileTrace } from '@vercel/nft';
import { readConfig, RemixConfig } from '@remix-run/dev/dist/config'; import { readConfig, RemixConfig } from '@remix-run/dev/dist/config';
import type { import type {
@@ -33,6 +33,7 @@ import {
findConfig, findConfig,
getPathFromRoute, getPathFromRoute,
getRegExpFromPath, getRegExpFromPath,
getRouteIterator,
isLayoutRoute, isLayoutRoute,
} from './utils'; } from './utils';
@@ -181,16 +182,17 @@ module.exports = config;`;
const remixRoutes = Object.values(remixConfig.routes); const remixRoutes = Object.values(remixConfig.routes);
// Figure out which pages should be edge functions // Figure out which pages should be edge functions
const edgePages = new Set<ConfigRoute>(); let hasEdgeRoute = false;
const staticConfigsMap = new Map<ConfigRoute, BaseFunctionConfig>();
const project = new Project(); const project = new Project();
for (const route of remixRoutes) { for (const route of remixRoutes) {
const routePath = join(remixConfig.appDirectory, route.file); const routePath = join(remixConfig.appDirectory, route.file);
const staticConfig = getConfig(project, routePath); const staticConfig = getConfig(project, routePath);
const isEdge = if (staticConfig) {
staticConfig?.runtime === 'edge' || staticConfigsMap.set(route, staticConfig);
staticConfig?.runtime === 'experimental-edge'; }
if (isEdge) { if (staticConfig?.runtime && isEdgeRuntime(staticConfig.runtime)) {
edgePages.add(route); hasEdgeRoute = true;
} }
} }
@@ -212,7 +214,7 @@ module.exports = config;`;
serverBuildPath, serverBuildPath,
nodeVersion nodeVersion
), ),
edgePages.size > 0 hasEdgeRoute
? createRenderEdgeFunction( ? createRenderEdgeFunction(
entrypointFsDirname, entrypointFsDirname,
repoRootPath, repoRootPath,
@@ -238,7 +240,16 @@ module.exports = config;`;
if (isLayoutRoute(route.id, remixRoutes)) continue; if (isLayoutRoute(route.id, remixRoutes)) continue;
const path = getPathFromRoute(route, remixConfig.routes); const path = getPathFromRoute(route, remixConfig.routes);
const isEdge = edgePages.has(route);
let isEdge = false;
for (const currentRoute of getRouteIterator(route, remixConfig.routes)) {
const staticConfig = staticConfigsMap.get(currentRoute);
if (staticConfig?.runtime) {
isEdge = isEdgeRuntime(staticConfig.runtime);
break;
}
}
const fn = const fn =
isEdge && edgeFunction isEdge && edgeFunction
? // `EdgeFunction` currently requires the "name" property to be set. ? // `EdgeFunction` currently requires the "name" property to be set.
@@ -461,3 +472,7 @@ async function ensureResolvable(start: string, base: string, pkgName: string) {
`Failed to resolve "${pkgName}". To fix this error, add "${pkgName}" to "dependencies" in your \`package.json\` file.` `Failed to resolve "${pkgName}". To fix this error, add "${pkgName}" to "dependencies" in your \`package.json\` file.`
); );
} }
function isEdgeRuntime(runtime: string): boolean {
return runtime === 'edge' || runtime === 'experimental-edge';
}

View File

@@ -25,21 +25,27 @@ export function isLayoutRoute(
return routes.some(r => r.parentId === routeId); return routes.some(r => r.parentId === routeId);
} }
export function* getRouteIterator(route: ConfigRoute, routes: RouteManifest) {
let currentRoute: ConfigRoute = route;
do {
yield currentRoute;
if (currentRoute.parentId) {
currentRoute = routes[currentRoute.parentId];
} else {
break;
}
} while (currentRoute);
}
export function getPathFromRoute( export function getPathFromRoute(
route: ConfigRoute, route: ConfigRoute,
routes: RouteManifest routes: RouteManifest
): string { ): string {
let currentRoute: ConfigRoute | undefined = route;
const pathParts: string[] = []; const pathParts: string[] = [];
do { for (const currentRoute of getRouteIterator(route, routes)) {
if (currentRoute.index) pathParts.push('index'); if (currentRoute.index) pathParts.push('index');
if (currentRoute.path) pathParts.push(currentRoute.path); if (currentRoute.path) pathParts.push(currentRoute.path);
if (currentRoute.parentId) { }
currentRoute = routes[currentRoute.parentId];
} else {
currentRoute = undefined;
}
} while (currentRoute);
const path = pathParts.reverse().join('/'); const path = pathParts.reverse().join('/');
return path; return path;
} }

View File

@@ -0,0 +1,12 @@
import { Outlet } from '@remix-run/react';
export const config = { runtime: 'edge' };
export default function Projects() {
return (
<div>
<h1>Projects</h1>
<Outlet />
</div>
);
}

View File

@@ -0,0 +1,13 @@
import { useLoaderData } from "@remix-run/react";
// `"runtime": "edge"` is implied here because the parent route defined it
export async function loader() {
const isEdge = typeof process.version === 'undefined';
return { isEdge };
}
export default function Edge() {
const data = useLoaderData<typeof loader>();
return <div>{JSON.stringify(data)}</div>;
}

View File

@@ -0,0 +1,14 @@
import { useLoaderData } from "@remix-run/react";
// Explicitly override the `"runtime": "edge"` defined by the parent route
export const config = { runtime: 'nodejs' };
export async function loader() {
const isEdge = typeof process.version === 'undefined';
return { isEdge };
}
export default function Node() {
const data = useLoaderData<typeof loader>();
return <div>{JSON.stringify(data)}</div>;
}

View File

@@ -17,6 +17,8 @@
{ "path": "/nested/another", "mustContain": "Nested another page" }, { "path": "/nested/another", "mustContain": "Nested another page" },
{ "path": "/nested/index", "mustContain": "Not Found" }, { "path": "/nested/index", "mustContain": "Not Found" },
{ "path": "/asdf", "mustContain": "Not Found" }, { "path": "/asdf", "mustContain": "Not Found" },
{ "path": "/instanceof", "mustContain": "InstanceOfRequest: true" } { "path": "/instanceof", "mustContain": "InstanceOfRequest: true" },
{ "path": "/projects/edge", "mustContain": "\"isEdge\":true" },
{ "path": "/projects/node", "mustContain": "\"isEdge\":false" }
] ]
} }