mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-09 12:57:46 +00:00
[remix] Split Edge and Node server builds using serverBundles config (#9504)
Utilize the [`serverBundles`](https://github.com/remix-run/remix/pull/5479) config option to generate two server bundle builds. One contains only the routes that should run in Node.js, and the other contains only the routes that should run in the Edge runtime. In the future we could update this configuration to generate more than two bundles to be more granular and allow for infinite scalability. Because the `serverBundles` PR is not yet merged, this PR introduces usage of a forked version of `@remix-run/dev` which incorporates our changes. Hopefully usage of a fork is temporary, but it gets us unblocked for now.
This commit is contained in:
@@ -41,6 +41,10 @@ import {
|
||||
|
||||
const _require: typeof require = eval('require');
|
||||
|
||||
const REMIX_RUN_DEV_PATH = dirname(
|
||||
_require.resolve('@remix-run/dev/package.json')
|
||||
);
|
||||
|
||||
export const build: BuildV2 = async ({
|
||||
entrypoint,
|
||||
files,
|
||||
@@ -101,19 +105,84 @@ export const build: BuildV2 = async ({
|
||||
await fs.readFile(remixDevPackageJsonPath, 'utf8')
|
||||
).version;
|
||||
|
||||
// Make our version of `remix` CLI available to the project's build
|
||||
// command by creating a symlink to the copy in our node modules,
|
||||
// so that `serverBundles` works: https://github.com/remix-run/remix/pull/5479
|
||||
const nodeModulesDir = join(entrypointFsDirname, 'node_modules');
|
||||
const remixRunDevPath = join(nodeModulesDir, '@remix-run/dev');
|
||||
let backupRemixRunDevPath:
|
||||
| string
|
||||
| false = `${remixRunDevPath}.__vercel_backup`;
|
||||
|
||||
try {
|
||||
await fs.rename(remixRunDevPath, backupRemixRunDevPath);
|
||||
} catch (err: any) {
|
||||
if (err.code !== 'ENOENT') {
|
||||
throw err;
|
||||
}
|
||||
backupRemixRunDevPath = false;
|
||||
}
|
||||
|
||||
await fs.symlink(REMIX_RUN_DEV_PATH, remixRunDevPath);
|
||||
|
||||
// Make `remix build` output production mode
|
||||
spawnOpts.env.NODE_ENV = 'production';
|
||||
|
||||
const remixConfig = await chdirAndReadConfig(entrypointFsDirname);
|
||||
const remixRoutes = Object.values(remixConfig.routes);
|
||||
|
||||
// Read the `export const config` (if any) for each route
|
||||
const project = new Project();
|
||||
const staticConfigsMap = new Map<ConfigRoute, BaseFunctionConfig>();
|
||||
for (const route of remixRoutes) {
|
||||
const routePath = join(remixConfig.appDirectory, route.file);
|
||||
const staticConfig = getConfig(project, routePath);
|
||||
if (staticConfig) {
|
||||
staticConfigsMap.set(route, staticConfig);
|
||||
}
|
||||
}
|
||||
|
||||
// Figure out which pages should be edge functions
|
||||
const edgeRoutes = new Set<ConfigRoute>();
|
||||
const nodeRoutes = new Set<ConfigRoute>();
|
||||
for (const route of remixRoutes) {
|
||||
if (isLayoutRoute(route.id, remixRoutes)) continue;
|
||||
|
||||
// Support runtime inheritance when defined in a parent route,
|
||||
// by iterating through the route's parent hierarchy until a
|
||||
// config containing "runtime" is found.
|
||||
let isEdge = false;
|
||||
for (const currentRoute of getRouteIterator(route, remixConfig.routes)) {
|
||||
const staticConfig = staticConfigsMap.get(currentRoute);
|
||||
if (staticConfig?.runtime) {
|
||||
isEdge = isEdgeRuntime(staticConfig.runtime);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isEdge) {
|
||||
edgeRoutes.add(route);
|
||||
} else {
|
||||
nodeRoutes.add(route);
|
||||
}
|
||||
}
|
||||
const serverBundles = [
|
||||
{
|
||||
serverBuildPath: 'build/build-edge.js',
|
||||
routes: Array.from(edgeRoutes).map(r => r.id),
|
||||
},
|
||||
{
|
||||
serverBuildPath: 'build/build-node.js',
|
||||
routes: Array.from(nodeRoutes).map(r => r.id),
|
||||
},
|
||||
];
|
||||
|
||||
// We need to patch the `remix.config.js` file to force some values necessary
|
||||
// for a build that works on either Node.js or the Edge runtime
|
||||
const remixConfigPath = findConfig(entrypointFsDirname, 'remix.config');
|
||||
const renamedRemixConfigPath = remixConfigPath
|
||||
? `${remixConfigPath}.original${extname(remixConfigPath)}`
|
||||
: undefined;
|
||||
const serverBuildPath = 'build/index.js';
|
||||
if (remixConfigPath && renamedRemixConfigPath) {
|
||||
await fs.rename(remixConfigPath, renamedRemixConfigPath);
|
||||
|
||||
@@ -133,7 +202,8 @@ export const build: BuildV2 = async ({
|
||||
config.serverBuildTarget = undefined;
|
||||
config.serverModuleFormat = 'cjs';
|
||||
config.serverPlatform = 'node';
|
||||
config.serverBuildPath = ${JSON.stringify(serverBuildPath)}
|
||||
config.serverBuildPath = undefined;
|
||||
config.serverBundles = ${JSON.stringify(serverBundles)};
|
||||
export default config;`;
|
||||
} else {
|
||||
patchedConfig = `const config = require('./${basename(
|
||||
@@ -142,7 +212,8 @@ export default config;`;
|
||||
config.serverBuildTarget = undefined;
|
||||
config.serverModuleFormat = 'cjs';
|
||||
config.serverPlatform = 'node';
|
||||
config.serverBuildPath = ${JSON.stringify(serverBuildPath)}
|
||||
config.serverBuildPath = undefined;
|
||||
config.serverBundles = ${JSON.stringify(serverBundles)};
|
||||
module.exports = config;`;
|
||||
}
|
||||
await fs.writeFile(remixConfigPath, patchedConfig);
|
||||
@@ -182,20 +253,12 @@ module.exports = config;`;
|
||||
if (remixConfigPath && renamedRemixConfigPath) {
|
||||
await fs.rename(renamedRemixConfigPath, remixConfigPath);
|
||||
}
|
||||
}
|
||||
|
||||
// Figure out which pages should be edge functions
|
||||
let hasEdgeRoute = false;
|
||||
const staticConfigsMap = new Map<ConfigRoute, BaseFunctionConfig>();
|
||||
const project = new Project();
|
||||
for (const route of remixRoutes) {
|
||||
const routePath = join(remixConfig.appDirectory, route.file);
|
||||
const staticConfig = getConfig(project, routePath);
|
||||
if (staticConfig) {
|
||||
staticConfigsMap.set(route, staticConfig);
|
||||
}
|
||||
if (staticConfig?.runtime && isEdgeRuntime(staticConfig.runtime)) {
|
||||
hasEdgeRoute = true;
|
||||
// Remove `@remix-run/dev` symlink
|
||||
await fs.unlink(remixRunDevPath);
|
||||
if (backupRemixRunDevPath) {
|
||||
// Restore previous version if it was existed
|
||||
await fs.rename(backupRemixRunDevPath, remixRunDevPath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,18 +275,18 @@ module.exports = config;`;
|
||||
const [staticFiles, nodeFunction, edgeFunction] = await Promise.all([
|
||||
glob('**', join(entrypointFsDirname, 'public')),
|
||||
createRenderNodeFunction(
|
||||
nodeVersion,
|
||||
entrypointFsDirname,
|
||||
repoRootPath,
|
||||
join(entrypointFsDirname, serverBuildPath),
|
||||
join(entrypointFsDirname, 'build/build-node.js'),
|
||||
remixConfig.serverEntryPoint,
|
||||
nodeVersion,
|
||||
remixVersion
|
||||
),
|
||||
hasEdgeRoute
|
||||
edgeRoutes.size > 0
|
||||
? createRenderEdgeFunction(
|
||||
entrypointFsDirname,
|
||||
repoRootPath,
|
||||
join(entrypointFsDirname, serverBuildPath),
|
||||
join(entrypointFsDirname, 'build/build-edge.js'),
|
||||
remixConfig.serverEntryPoint,
|
||||
remixVersion
|
||||
)
|
||||
@@ -254,17 +317,8 @@ module.exports = config;`;
|
||||
continue;
|
||||
}
|
||||
|
||||
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 =
|
||||
isEdge && edgeFunction
|
||||
edgeRoutes.has(route) && edgeFunction
|
||||
? // `EdgeFunction` currently requires the "name" property to be set.
|
||||
// Ideally this property will be removed, at which point we can
|
||||
// return the same `edgeFunction` instance instead of creating a
|
||||
@@ -307,11 +361,11 @@ function hasScript(scriptName: string, pkg: PackageJson | null) {
|
||||
}
|
||||
|
||||
async function createRenderNodeFunction(
|
||||
nodeVersion: NodeVersion,
|
||||
entrypointDir: string,
|
||||
rootDir: string,
|
||||
serverBuildPath: string,
|
||||
serverEntryPoint: string | undefined,
|
||||
nodeVersion: NodeVersion,
|
||||
remixVersion: string
|
||||
): Promise<NodejsLambda> {
|
||||
const files: Files = {};
|
||||
|
||||
@@ -41,7 +41,10 @@ export function getPathFromRoute(
|
||||
route: ConfigRoute,
|
||||
routes: RouteManifest
|
||||
): string {
|
||||
if (route.id === 'root' || (route.parentId === 'root' && route.index)) {
|
||||
if (
|
||||
route.id === 'root' ||
|
||||
(route.parentId === 'root' && !route.path && route.index)
|
||||
) {
|
||||
return 'index';
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user