Server Functions Plugin
Examine the Rollup compiler hooks used to secure database calls and serialize Server Functions during production bundling.
Key File Location: ./dinou/rollup/rollup-plugins/rollup-plugin-server-functions.js💡 Overview
Server Functions contain sensitive database hooks and APIs that must never leak into client-side JS bundles.
The rollup-plugin-server-functions.js plugin intercepts modules containing the "use server" directive during the transform stage. It strips out all server-side logic and replaces it with dynamic fetch proxy stubs. When the bundle is created, it writes the Server Function whitelist index: server-functions-manifest.json.
📊 Server Functions Flow
The flowchart below shows how Server Functions are extracted and transformed into client-side proxy skeletons:
⚙️ Complete Code Walkthrough
Below is the full, complete code of the Server Functions plugin:
// rollup-plugin-server-functions.js
const path = require("path");
const fs = require("fs/promises");
const manifestGeneratorPlugin = require("./manifest-generator-plugin");
const parseExports = require("../../core/parse-exports.js");
const { useServerRegex } = require("../../constants.js");
function serverFunctionsPlugin() {
const root = process.cwd();
const serverFunctions = new Map(); // Collect here: Map<relativePath, Set<exports>>
return {
name: "server-functions-proxy",
transform(code, id) {
if (!useServerRegex.test(code.trim())) return null;
const exports = parseExports(code);
if (exports.length === 0) return null;
const relativePath = path.relative(root, id).replace(/\\/g, "/");
serverFunctions.set(relativePath, new Set(exports));
const fileUrl = `file:///${relativePath}`;
let proxyCode = `
import { createServerFunctionProxy } from "/__SERVER_FUNCTION_PROXY__";
`;
for (const exp of exports) {
const key =
exp === "default" ? `${fileUrl}#default` : `${fileUrl}#${exp}`;
if (exp === "default") {
proxyCode += `export default createServerFunctionProxy(${JSON.stringify(
key
)});
`;
} else {
proxyCode += `export const ${exp} = createServerFunctionProxy(${JSON.stringify(
key
)});
`;
}
}
return {
code: proxyCode,
map: null,
};
},
async generateBundle(options, bundle) {
const manifest = manifestGeneratorPlugin.manifestData;
const hashedPath =
"/" + (manifest["serverFunctionProxy.js"] || "serverFunctionProxy.js");
for (const file of Object.keys(bundle)) {
const chunk = bundle[file];
if (chunk.type === "asset" || !chunk.code) continue;
if (chunk.code.includes("/__SERVER_FUNCTION_PROXY__")) {
chunk.code = chunk.code.replace(
/\/__SERVER_FUNCTION_PROXY__/g,
hashedPath
);
}
}
const manifestObj = {};
for (const [relPath, exportsSet] of serverFunctions.entries()) {
manifestObj[relPath] = Array.from(exportsSet);
}
const manifestPath = path.join(
"server_functions_manifest",
"server-functions-manifest.json"
);
try {
await fs.mkdir(path.dirname(manifestPath), { recursive: true });
await fs.writeFile(manifestPath, JSON.stringify(manifestObj, null, 2));
} catch (err) {
console.error(err);
}
},
};
}
module.exports = serverFunctionsPlugin;