UNPKG

@redwoodjs/sdk

Version:

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

136 lines (135 loc) 5.8 kB
import { cloudflare } from "@cloudflare/vite-plugin"; import { resolve } from "node:path"; import colors from "picocolors"; import { readFile } from "node:fs/promises"; import { getShortName } from "../lib/getShortName.mjs"; import { pathExists } from "fs-extra"; const hasEntryAsAncestor = (module, entryFile, seen = new Set()) => { // Prevent infinite recursion if (seen.has(module)) return false; seen.add(module); // Check direct importers for (const importer of module.importers) { if (importer.file === entryFile) return true; // Recursively check importers if (hasEntryAsAncestor(importer, entryFile, seen)) return true; } return false; }; // Cache for "use client" status results const useClientCache = new Map(); // Function to invalidate cache for a file const invalidateUseClientCache = (file) => { useClientCache.delete(file); }; const isUseClientModule = async (ctx, file, seen = new Set()) => { // Prevent infinite recursion if (seen.has(file)) return false; seen.add(file); try { // Check cache first if (useClientCache.has(file)) { return useClientCache.get(file); } // Read and check the file const content = (await pathExists(file)) ? await readFile(file, "utf-8") : ""; const hasUseClient = content.includes("'use client'") || content.includes('"use client"'); if (hasUseClient) { useClientCache.set(file, true); return true; } // Get the module from the module graph to find importers const module = ctx.server.moduleGraph.getModuleById(file); if (!module) { useClientCache.set(file, false); return false; } // Check all importers recursively for (const importer of module.importers) { if (await isUseClientModule(ctx, importer.url, seen)) { useClientCache.set(file, true); return true; } } useClientCache.set(file, false); return false; } catch (error) { useClientCache.set(file, false); return false; } }; export const miniflarePlugin = (givenOptions) => [ cloudflare(givenOptions), { name: "miniflare-plugin-hmr", async hotUpdate(ctx) { const environment = givenOptions.viteEnvironment?.name ?? "worker"; const entry = givenOptions.workerEntryPathname; if (!["client", environment].includes(this.environment.name)) { return; } // todo(justinvdm, 12 Dec 2024): Skip client references const modules = Array.from(ctx.server.environments[environment].moduleGraph.getModulesByFile(ctx.file) ?? []); const isWorkerUpdate = ctx.file === entry || modules.some((module) => hasEntryAsAncestor(module, entry)); // The worker doesnt need an update // => Short circuit HMR if (!isWorkerUpdate) { return []; } // The worker needs an update, but this is the client environment // => Notify for HMR update of any css files imported by in worker, that are also in the client module graph // Why: There may have been changes to css classes referenced, which might css modules to change if (this.environment.name === "client") { const cssModules = []; for (const [_, module] of ctx.server.environments[environment] .moduleGraph.idToModuleMap) { // todo(justinvdm, 13 Dec 2024): We check+update _all_ css files in worker module graph, // but it could just be a subset of css files that are actually affected, depending // on the importers and imports of the changed file. We should be smarter about this. if (module.file && module.file.endsWith(".css")) { const clientModules = ctx.server.environments.client.moduleGraph.getModulesByFile(module.file); if (clientModules) { cssModules.push(...clientModules.values()); } } } invalidateUseClientCache(ctx.file); return (await isUseClientModule(ctx, ctx.file)) ? [...ctx.modules, ...cssModules] : cssModules; } // The worker needs an update, and the hot check is for the worker environment // => Notify for custom RSC-based HMR update, then short circuit HMR if (isWorkerUpdate && this.environment.name === environment) { const shortName = getShortName(ctx.file, ctx.server.config.root); this.environment.logger.info(`${colors.green(`worker update`)} ${colors.dim(shortName)}`, { clear: true, timestamp: true, }); const m = ctx.server.environments.client.moduleGraph .getModulesByFile(resolve(givenOptions.rootDir, "src", "app", "style.css")) ?.values() .next().value; if (m) { ctx.server.environments.client.moduleGraph.invalidateModule(m, new Set(), ctx.timestamp, true); } ctx.server.environments.client.hot.send({ type: "custom", event: "rsc:update", data: { file: ctx.file, }, }); return []; } }, }, ];