UNPKG

rwsdk

Version:

Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime

138 lines (134 loc) 7.01 kB
import debug from "debug"; import { SSR_BRIDGE_PATH } from "../lib/constants.mjs"; import { findSsrImportSpecifiers } from "./findSsrSpecifiers.mjs"; const log = debug("rwsdk:vite:ssr-bridge-plugin"); export const VIRTUAL_SSR_PREFIX = "virtual:rwsdk:ssr:"; export const ssrBridgePlugin = ({ clientFiles, serverFiles, }) => { log("Initializing SSR bridge plugin with SSR_BRIDGE_PATH=%s", SSR_BRIDGE_PATH); let devServer; let isDev = false; const ssrBridgePlugin = { name: "rwsdk:ssr-bridge", enforce: "pre", async configureServer(server) { devServer = server; log("Configured dev server"); }, config(_, { command, isPreview }) { isDev = !isPreview && command === "serve"; log("Config: command=%s, isPreview=%s, isDev=%s", command, isPreview, isDev); }, configEnvironment(env, config) { log("Configuring environment: env=%s", env); if (env === "worker") { // Configure esbuild to mark rwsdk/__ssr paths as external for worker environment log("Configuring esbuild options for worker environment"); config.optimizeDeps ??= {}; config.optimizeDeps.esbuildOptions ??= {}; config.optimizeDeps.esbuildOptions.plugins ??= []; config.optimizeDeps.include ??= []; config.optimizeDeps.esbuildOptions.plugins.push({ name: "rwsdk-ssr-external", setup(build) { log("Setting up esbuild plugin to mark rwsdk/__ssr paths as external for worker"); build.onResolve({ filter: /.*$/ }, (args) => { process.env.VERBOSE && log("Esbuild onResolve called for path=%s, args=%O", args.path, args); if (args.path === "rwsdk/__ssr_bridge") { log("Marking as external: %s", args.path); return { path: args.path, external: true, }; } }); }, }); log("Worker environment esbuild configuration complete"); } }, async resolveId(id) { process.env.VERBOSE && log("Resolving id=%s, environment=%s, isDev=%s", id, this.environment?.name, isDev); if (isDev) { // context(justinvdm, 27 May 2025): In dev, we need to dynamically load // SSR modules, so we return the virtual id so that the dynamic loading // can happen in load() if (id.startsWith(VIRTUAL_SSR_PREFIX)) { log("Returning virtual SSR id for dev: %s", id); return id; } // context(justinvdm, 28 May 2025): The SSR bridge module is a special case - // it is the entry point for all SSR modules, so to trigger the // same dynamic loading logic as other SSR modules (as the case above), // we return a virtual id if (id === "rwsdk/__ssr_bridge" && this.environment.name === "worker") { const virtualId = `${VIRTUAL_SSR_PREFIX}${id}`; log("Bridge module case (dev): id=%s matches rwsdk/__ssr_bridge in worker environment, returning virtual id=%s", id, virtualId); return virtualId; } } else { // context(justinvdm, 27 May 2025): In builds, since all SSR import chains // originate at SSR bridge module, we return the path to the already built // SSR bridge bundle - SSR env builds it, worker build tries to resolve it // here and uses it if (id === "rwsdk/__ssr_bridge" && this.environment.name === "worker") { log("Bridge module case (build): id=%s matches rwsdk/__ssr_bridge in worker environment, returning SSR_BRIDGE_PATH=%s", id, SSR_BRIDGE_PATH); return SSR_BRIDGE_PATH; } } process.env.VERBOSE && log("No resolution for id=%s", id); }, async load(id) { process.env.VERBOSE && log("Loading id=%s, isDev=%s, environment=%s", id, isDev, this.environment.name); if (id.startsWith(VIRTUAL_SSR_PREFIX) && this.environment.name === "worker") { const realId = id.slice(VIRTUAL_SSR_PREFIX.length); log("Virtual SSR module load: id=%s, realId=%s", id, realId); if (isDev) { log("Dev mode: fetching SSR module for realPath=%s", realId); const result = await devServer?.environments.ssr.fetchModule(realId); process.env.VERBOSE && log("Fetch module result: id=%s, result=%O", realId, result); const code = "code" in result ? result.code : undefined; if (realId.endsWith(".css")) { process.env.VERBOSE && log("Not a JS file, returning code: %s", code); return code ?? ""; } log("Fetched SSR module code length: %d", code?.length || 0); const { imports, dynamicImports } = findSsrImportSpecifiers(realId, code || "", log); const allSpecifiers = [ ...new Set([...imports, ...dynamicImports]), ].map((id) => id.startsWith("/@id/") ? id.slice("/@id/".length) : id); const switchCases = allSpecifiers .map((specifier) => ` case "${specifier}": void import("${VIRTUAL_SSR_PREFIX}${specifier}");`) .join("\n"); const transformedCode = ` await (async function(__vite_ssr_import__, __vite_ssr_dynamic_import__) {${code}})( __ssrImport.bind(null, false), __ssrImport.bind(null, true) ); function __ssrImport(isDynamic, id, ...args) { id = id.startsWith('/@id/') ? id.slice('/@id/'.length) : id; switch (id) { ${switchCases} } return isDynamic ? __vite_ssr_dynamic_import__("/@id/${VIRTUAL_SSR_PREFIX}" + id, ...args) : __vite_ssr_import__("/@id/${VIRTUAL_SSR_PREFIX}" + id, ...args); } `; log("Transformed SSR module code length: %d", transformedCode.length); process.env.VERBOSE && log("Transformed SSR module code for realId=%s: %s", realId, transformedCode); return transformedCode; } } process.env.VERBOSE && log("No load handling for id=%s", id); }, }; return ssrBridgePlugin; };