@netlify/remix-edge-adapter
Version:
Remix Adapter for Netlify Edge Functions
287 lines (281 loc) • 9.92 kB
JavaScript
;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name2 in all)
__defProp(target, name2, { get: all[name2], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/plugin.ts
var plugin_exports = {};
__export(plugin_exports, {
netlifyPlugin: () => netlifyPlugin
});
module.exports = __toCommonJS(plugin_exports);
var import_node_adapter = require("@remix-run/dev/dist/vite/node-adapter.js");
var import_promises = require("fs/promises");
var import_node_path = require("path");
var import_posix = require("path/posix");
var import_node_module = require("module");
// package.json
var name = "@netlify/remix-edge-adapter";
var version = "4.0.0";
// src/plugin.ts
var NETLIFY_EDGE_FUNCTIONS_DIR = ".netlify/edge-functions";
var EDGE_FUNCTION_FILENAME = "remix-server.mjs";
var EDGE_FUNCTION_HANDLER_CHUNK = "server";
var EDGE_FUNCTION_HANDLER_MODULE_ID = "virtual:netlify-server";
var RESOLVED_EDGE_FUNCTION_HANDLER_MODULE_ID = `\0${EDGE_FUNCTION_HANDLER_MODULE_ID}`;
var toPosixPath = (path) => path.split(import_node_path.sep).join(import_posix.sep);
var notImplemented = () => {
throw new Error(`
This is a fake Netlify context object for local dev. It is not supported here, but it will work with
\`netlify serve\` and in a production build. To fix this, add it as custom context in your
\`createAppLoadContext\` conditionally in dev.
`);
};
var getFakeNetlifyContext = (url) => ({
url: new URL(url),
requestId: "fake-netlify-request-id-for-dev",
next: async () => new Response("", { status: 404 }),
geo: {
city: "Mock City",
country: { code: "MC", name: "Mock Country" },
subdivision: { code: "MS", name: "Mock Subdivision" },
longitude: 0,
latitude: 0,
timezone: "UTC"
},
waitUntil: async (_p) => {
},
get cookies() {
return notImplemented();
},
get deploy() {
return notImplemented();
},
get ip() {
return notImplemented();
},
get json() {
return notImplemented();
},
get log() {
return notImplemented();
},
get params() {
return notImplemented();
},
get rewrite() {
return notImplemented();
},
get site() {
return notImplemented();
},
get account() {
return notImplemented();
},
get server() {
return notImplemented();
}
});
var EDGE_FUNCTION_HANDLER = (
/* js */
`
import { createRequestHandler } from "@netlify/remix-edge-adapter";
import * as build from "virtual:remix/server-build";
export default createRequestHandler({
build,
getLoadContext: async (_req, ctx) => ctx,
});
`
);
function generateEdgeFunction(handlerPath, excludedPath) {
return (
/* js */
`
export { default } from "${handlerPath}";
export const config = {
name: "Remix server handler",
generator: "${name}@${version}",
cache: "manual",
path: "/*",
excludedPath: ${JSON.stringify(excludedPath)},
};`
);
}
var ALLOWED_USER_EDGE_FUNCTION_HANDLER_FILENAMES = [
"server.ts",
"server.mts",
"server.cts",
"server.mjs",
"server.cjs",
"server.js"
];
var findUserEdgeFunctionHandlerFile = async (root) => {
for (const filename of ALLOWED_USER_EDGE_FUNCTION_HANDLER_FILENAMES) {
try {
await (0, import_promises.access)((0, import_node_path.join)(root, filename));
return filename;
} catch {
}
}
throw new Error(
"Your Hydrogen site must include a `server.ts` (or js/mjs/cjs/mts/cts) file at the root to deploy to Netlify. See https://github.com/netlify/hydrogen-template."
);
};
var getEdgeFunctionHandlerModuleId = async (root, isHydrogenSite) => {
if (!isHydrogenSite) return EDGE_FUNCTION_HANDLER_MODULE_ID;
return findUserEdgeFunctionHandlerFile(root);
};
function netlifyPlugin(options = {}) {
const additionalExcludedPaths = options.excludedPaths ?? [];
let resolvedConfig;
let currentCommand;
let isSsr;
let isHydrogenSite;
return {
name: "vite-plugin-remix-netlify-edge",
config(config, { command, isSsrBuild }) {
currentCommand = command;
isSsr = isSsrBuild;
if (command === "build") {
if (isSsrBuild) {
config.ssr = {
...config.ssr,
target: "webworker",
// Only externalize Node builtins
noExternal: /^(?!node:).*$/
};
}
}
},
configResolved: {
order: "pre",
async handler(config) {
resolvedConfig = config;
isHydrogenSite = resolvedConfig.plugins.find((plugin) => plugin.name === "hydrogen:main") != null;
if (currentCommand === "build" && isSsr) {
if (typeof config.build?.rollupOptions?.input === "string") {
const edgeFunctionHandlerModuleId = await getEdgeFunctionHandlerModuleId(
resolvedConfig.root,
isHydrogenSite
);
config.build.rollupOptions.input = {
[EDGE_FUNCTION_HANDLER_CHUNK]: edgeFunctionHandlerModuleId,
index: config.build.rollupOptions.input
};
if (config.build.rollupOptions.output && !Array.isArray(config.build.rollupOptions.output)) {
config.build.rollupOptions.output.entryFileNames = () => "[name].js";
}
}
} else if (isHydrogenSite && currentCommand === "serve") {
config.build.ssr = await findUserEdgeFunctionHandlerFile(resolvedConfig.root);
}
}
},
resolveId: {
order: "pre",
async handler(source, importer, options2) {
if (source === "virtual:netlify-server-entry") {
if (currentCommand === "build" && options2.ssr) {
return this.resolve("@netlify/remix-edge-adapter/entry.server", importer, {
...options2,
skipSelf: true
});
} else {
return this.resolve("@remix-run/dev/dist/config/defaults/entry.server.node", importer, {
...options2,
skipSelf: true
});
}
}
if (source === EDGE_FUNCTION_HANDLER_MODULE_ID) {
return RESOLVED_EDGE_FUNCTION_HANDLER_MODULE_ID;
}
if (isSsr && (0, import_node_module.isBuiltin)(source)) {
return {
// Deno needs Node builtins to be prefixed
id: source.startsWith("node:") ? source : `node:${source}`,
external: true,
moduleSideEffects: false
};
}
return null;
}
},
// See https://vitejs.dev/guide/api-plugin#virtual-modules-convention.
load(id) {
if (id === RESOLVED_EDGE_FUNCTION_HANDLER_MODULE_ID) {
return EDGE_FUNCTION_HANDLER;
}
},
configureServer: {
order: "pre",
handler(viteDevServer) {
return () => {
if (isHydrogenSite && !viteDevServer.config.server.middlewareMode) {
viteDevServer.middlewares.use(async (nodeReq, nodeRes, next) => {
try {
const edgeFunctionHandlerModuleId = await findUserEdgeFunctionHandlerFile(resolvedConfig.root);
let build = await viteDevServer.ssrLoadModule(edgeFunctionHandlerModuleId);
const handleRequest = build.default;
let req = (0, import_node_adapter.fromNodeRequest)(nodeReq, nodeRes);
const res = await handleRequest(req, getFakeNetlifyContext(req.url));
if (res instanceof Response) return await (0, import_node_adapter.toNodeRequest)(res, nodeRes);
if (res instanceof URL) {
next(new Error("URLs are not supported in dev server middleware"));
return;
}
next();
} catch (error) {
next(error);
}
});
}
};
}
},
// See https://rollupjs.org/plugin-development/#writebundle.
async writeBundle() {
if (currentCommand === "build" && isSsr) {
const excludedPath = ["/.netlify/*"];
try {
const clientDirectory = (0, import_node_path.join)(resolvedConfig.build.outDir, "..", "client");
const entries = await (0, import_promises.readdir)(clientDirectory, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory()) {
excludedPath.push(`/${entry.name}/*`);
} else if (entry.isFile()) {
excludedPath.push(`/${entry.name}`);
}
}
} catch {
}
excludedPath.push(...additionalExcludedPaths);
const edgeFunctionsDirectory = (0, import_node_path.join)(resolvedConfig.root, NETLIFY_EDGE_FUNCTIONS_DIR);
await (0, import_promises.mkdir)(edgeFunctionsDirectory, { recursive: true });
const handlerPath = (0, import_node_path.join)(resolvedConfig.build.outDir, `${EDGE_FUNCTION_HANDLER_CHUNK}.js`);
const relativeHandlerPath = toPosixPath((0, import_node_path.relative)(edgeFunctionsDirectory, handlerPath));
await (0, import_promises.writeFile)(
(0, import_node_path.join)(edgeFunctionsDirectory, EDGE_FUNCTION_FILENAME),
generateEdgeFunction(relativeHandlerPath, excludedPath)
);
}
}
};
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
netlifyPlugin
});