Skip to content

Commit

Permalink
[vercel-remix] Add Vite preset (#81)
Browse files Browse the repository at this point in the history
  • Loading branch information
TooTallNate authored Feb 22, 2024
1 parent 2bcbec2 commit 1fe46fe
Show file tree
Hide file tree
Showing 7 changed files with 287 additions and 10 deletions.
2 changes: 2 additions & 0 deletions packages/vercel-remix/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
/globals.d.ts
/server.js
/server.d.ts
/vite.js
/vite.d.ts
17 changes: 17 additions & 0 deletions packages/vercel-remix/defaults/entry.server.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { RemixServer } from "@remix-run/react";
import { handleRequest } from "@vercel/remix";

export default function (
request,
responseStatusCode,
responseHeaders,
remixContext
) {
let remixServer = <RemixServer context={remixContext} url={request.url} />;
return handleRequest(
request,
responseStatusCode,
responseHeaders,
remixServer
);
}
14 changes: 13 additions & 1 deletion packages/vercel-remix/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,21 @@
"browser": "./dist/edge/server.js",
"default": "./server.js"
},
"./vite": {
"types": "./vite.d.ts",
"default": "./vite.js"
},
"./package.json": "./package.json"
},
"dependencies": {
"@remix-run/node": "2.7.2",
"@remix-run/server-runtime": "2.7.2",
"isbot": "^3.6.8"
"@vercel/static-config": "3.0.0",
"isbot": "^3.6.8",
"ts-morph": "12.0.0"
},
"devDependencies": {
"@remix-run/dev": "2.7.2"
},
"peerDependencies": {
"react": "*",
Expand All @@ -38,10 +47,13 @@
},
"files": [
"dist/",
"defaults/",
"globals.js",
"globals.d.ts",
"server.js",
"server.d.ts",
"vite.js",
"vite.d.ts",
"CHANGELOG.md",
"LICENSE.md",
"README.md"
Expand Down
1 change: 1 addition & 0 deletions packages/vercel-remix/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ module.exports = function rollup() {
return [
getAdapterConfig("vercel-remix"),
getAdapterConfig("vercel-remix", "server.ts"),
getAdapterConfig("vercel-remix", "vite.ts"),
edgeConfig,
getAdapterConfig("vercel-remix", "edge/server.ts"),
];
Expand Down
140 changes: 140 additions & 0 deletions packages/vercel-remix/vite.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { Project } from "ts-morph";
import { join, basename, extname } from "path";
import { mkdirSync, writeFileSync, cpSync, rmSync, readdirSync } from "fs";
import { getConfig, type BaseFunctionConfig } from "@vercel/static-config";
import type { Preset, VitePluginConfig } from "@remix-run/dev/vite/plugin";
import type { ConfigRoute } from "@remix-run/dev/config/routes";

function hash(config: Record<string, unknown>): string {
let str = JSON.stringify(config);
return Buffer.from(str).toString("base64url");
}

function flattenAndSort(o: Record<string, unknown>) {
let n: Record<string, unknown> = {};
let keys: string[] = [];
for (let key in o) keys.push(key);
for (let key of keys.sort()) n[key] = o[key];
return n;
}

export function vercelPreset(): Preset {
let project = new Project();
let entryServerPath: string | undefined;
let addedEntryServer = false;
let routeConfigs = new Map<string, BaseFunctionConfig>();
let bundleConfigs = new Map<string, BaseFunctionConfig>();

function getRouteConfig(branch: ConfigRoute[], index = branch.length - 1) {
let route = branch[index];
let config = routeConfigs.get(route.id);
if (!config) {
// @ts-expect-error TODO: figure out why TypeScript is complaining here…
config = getConfig(project, route.file) || {};
if (index > 0) {
Object.setPrototypeOf(config, getRouteConfig(branch, index - 1));
}
routeConfigs.set(route.id, config);
}
return config;
}

function createServerBundles(remixUserConfig: VitePluginConfig): VitePluginConfig['serverBundles'] {
return ({ branch }) => {
let config = getRouteConfig(branch);
if (!config.runtime) {
config.runtime = "nodejs";
}

// If there are any "edge" runtime routes, then a special
// `entry.server` needs to be used. So copy that file into
// the app directory, unless the project has defined their own
if (config.runtime === "edge" && !entryServerPath) {
let appDirectory = remixUserConfig.appDirectory ?? "app";
let entryServerFile = readdirSync(appDirectory).find(
(f) => basename(f, extname(f)) === 'entry.server'
);
if (entryServerFile) {
entryServerPath = join(appDirectory, entryServerFile);
} else {
entryServerPath = join(appDirectory, "entry.server.jsx");
addedEntryServer = true;
cpSync(
join(__dirname, "defaults/entry.server.jsx"),
entryServerPath
);
}
}

config = flattenAndSort(config);
let id = `${config.runtime}-${hash(config)}`;
if (!bundleConfigs.has(id)) {
bundleConfigs.set(id, config);
}
return id;
};
}

let buildEnd: VitePluginConfig['buildEnd'] = ({ buildManifest, remixConfig }) => {
if (addedEntryServer && entryServerPath) {
rmSync(entryServerPath);
}

if (buildManifest?.serverBundles && bundleConfigs.size) {
for (let bundle of Object.values(buildManifest.serverBundles)) {
let bundleWtihConfig = {
...bundle,
config: bundleConfigs.get(bundle.id),
};
buildManifest.serverBundles[bundle.id] = bundleWtihConfig;
}
}

if (buildManifest?.routes && routeConfigs.size) {
for (let route of Object.values(buildManifest.routes)) {
let routeWtihConfig = {
...route,
config: routeConfigs.get(route.id),
};
buildManifest.routes[route.id] = routeWtihConfig;
}
}

let json = JSON.stringify(
{
buildManifest,
remixConfig,
},
null,
2
);

mkdirSync(".vercel", { recursive: true });
writeFileSync(".vercel/remix-build-result.json", `${json}\n`);
}

return {
name: "vercel",
remixConfig({ remixUserConfig }) {
return {
/**
* Invoked once per leaf route. Reads the `export const config`
* of the route file (and all parent routes) and hashes the
* combined config to determine the server bundle ID.
*/
serverBundles: remixUserConfig.ssr !== false
? createServerBundles(remixUserConfig)
: undefined,

/**
* Invoked at the end of the `remix vite:build` command.
* - Clean up the `entry.server` file if one was copied.
* - Serialize the `buildManifest` and `remixConfig` objects
* to the `.vercel/remix-build-result.json` file, including
* the static configs parsed from each route and server bundle.
*/
buildEnd,
};
},
};
}
35 changes: 27 additions & 8 deletions scripts/copy-build-to-dist.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -109,14 +109,19 @@ async function copyBuildToDist() {
],

// `@vercel/remix` stuffs. We want "server" to be placed at the root of the package.
...["globals.js", "globals.d.ts", "server.js", "server.d.ts"].map(
(name) => {
return [
`build/node_modules/@vercel/remix/dist/${name}`,
`packages/vercel-remix/${name}`,
];
}
),
...[
"globals.js",
"globals.d.ts",
"server.js",
"server.d.ts",
"vite.js",
"vite.d.ts",
].map((name) => {
return [
`build/node_modules/@vercel/remix/dist/${name}`,
`packages/vercel-remix/${name}`,
];
}),
];

oneOffCopies.forEach(([srcFile, destFile]) =>
Expand All @@ -131,6 +136,20 @@ async function copyBuildToDist() {
);

await Promise.all(copyQueue);

// For the Vercel Remix Vite preset, the `Preset` type import needs to
// be adjusted, since in this monorepo it's written against the source,
// but consumers of the package will import for the `dist` compiled types.
let vercelRemixViteTypesPath = 'packages/vercel-remix/vite.d.ts';
let vercelRemixViteTypesData = await fse.readFile(vercelRemixViteTypesPath, 'utf8');
await fse.writeFile(
vercelRemixViteTypesPath,
vercelRemixViteTypesData.replace(
"@remix-run/dev/vite/plugin",
"@remix-run/dev/dist/vite/plugin"
)
);

console.log(
chalk.green(
" ✅ Successfully copied build files to package dist directories!"
Expand Down
Loading

0 comments on commit 1fe46fe

Please sign in to comment.