rwsdk
Version:
Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime
232 lines (231 loc) • 10.8 kB
JavaScript
import MagicString from "magic-string";
import path from "path";
import { readFile } from "fs/promises";
import debug from "debug";
import { normalizeModulePath } from "../lib/normalizeModulePath.mjs";
import { pathExists } from "fs-extra";
import { stat } from "fs/promises";
import { getSrcPaths } from "../lib/getSrcPaths.js";
import { hasDirective } from "./hasDirective.mjs";
export const findFilesContainingDirective = async ({ projectRootDir, files, directive, debugNamespace, }) => {
const log = debug(debugNamespace);
log("Starting search for '%s' files in projectRootDir=%s", directive, projectRootDir);
const filesToScan = await getSrcPaths(projectRootDir);
log("Found %d files to scan for '%s' directive", filesToScan.length, directive);
for (const file of filesToScan) {
try {
const stats = await stat(file);
if (!stats.isFile()) {
process.env.VERBOSE && log("Skipping %s (not a file)", file);
continue;
}
process.env.VERBOSE && log("Scanning file: %s", file);
const content = await readFile(file, "utf-8");
if (hasDirective(content, directive)) {
const normalizedPath = normalizeModulePath(file, projectRootDir);
log("Found '%s' directive in file: %s -> %s", directive, file, normalizedPath);
files.add(normalizedPath);
}
}
catch (error) {
console.error(`Error reading file ${file}:`, error);
}
}
log("Completed scan. Found %d %s files total", files.size, directive);
process.env.VERBOSE &&
log("Found files for %s: %j", directive, Array.from(files));
};
const resolveOptimizedDep = async (projectRootDir, id, environment, debugNamespace) => {
const log = debug(debugNamespace);
try {
const depsDir = environment === "client" ? "deps" : `deps_${environment}`;
const nodeModulesDepsDirPath = path.join("node_modules", ".vite", depsDir);
const depsDirPath = path.join(projectRootDir, nodeModulesDepsDirPath);
const manifestPath = path.join(depsDirPath, "_metadata.json");
log("Checking for manifest at: %s", manifestPath);
const manifestExists = await pathExists(manifestPath);
if (!manifestExists) {
log("Manifest not found at %s", manifestPath);
return undefined;
}
const manifestContent = await readFile(manifestPath, "utf-8");
const manifest = JSON.parse(manifestContent);
if (manifest.optimized && manifest.optimized[id]) {
const optimizedFile = manifest.optimized[id].file;
const optimizedPath = path.join("/", nodeModulesDepsDirPath, optimizedFile);
log("Found optimized dependency: filePath=%s, optimizedPath=%s", id, optimizedPath);
return optimizedPath;
}
process.env.VERBOSE &&
log("File not found in optimized dependencies: id=%s", id);
return undefined;
}
catch (error) {
process.env.VERBOSE &&
log("Error resolving optimized dependency for id=%s: %s", id, error);
return undefined;
}
};
const addOptimizedDepsEntries = async ({ projectRootDir, directive, environment, debugNamespace, files, }) => {
const log = debug(debugNamespace);
try {
const depsDir = environment === "client" ? "deps" : `deps_${environment}`;
const depsDirPath = path.join(projectRootDir, "node_modules", ".vite", depsDir);
const manifestPath = path.join(depsDirPath, "_metadata.json");
process.env.VERBOSE && log("Checking for manifest at: %s", manifestPath);
const manifestExists = await pathExists(manifestPath);
if (!manifestExists) {
process.env.VERBOSE && log("Manifest not found at %s", manifestPath);
return;
}
const manifestContent = await readFile(manifestPath, "utf-8");
const manifest = JSON.parse(manifestContent);
for (const entryId of Object.keys(manifest.optimized)) {
if (entryId.startsWith("/node_modules/")) {
const srcPath = manifest.optimized[entryId].src;
const resolvedSrcPath = path.resolve(projectRootDir, "node_modules", ".vite", "deps", srcPath);
let contents;
try {
contents = await readFile(resolvedSrcPath, "utf-8");
}
catch (error) {
process.env.VERBOSE &&
log("Error reading file %s: %s", resolvedSrcPath, error);
continue;
}
if (hasDirective(contents, directive)) {
log("Adding optimized entry to files: %s", entryId);
files.add(entryId);
}
else {
log("Skipping optimized entry %s because it does not contain the '%s' directive", entryId, directive);
}
}
}
}
catch (error) {
process.env.VERBOSE &&
log("Error adding optimized deps entries: %s", error);
}
};
export const createDirectiveLookupPlugin = async ({ projectRootDir, files, config, }) => {
const debugNamespace = `rwsdk:vite:${config.pluginName}`;
const log = debug(debugNamespace);
let isDev = false;
log("Initializing %s plugin with projectRootDir=%s", config.pluginName, projectRootDir);
await findFilesContainingDirective({
projectRootDir,
files,
directive: config.directive,
debugNamespace,
});
let devServer;
return {
name: `rwsdk:${config.pluginName}`,
config(_, { command, isPreview }) {
isDev = !isPreview && command === "serve";
log("Development mode: %s", isDev);
},
configureServer(server) {
devServer = server;
},
async configEnvironment(env, viteConfig) {
log("Configuring environment: env=%s", env);
// Add optimized deps entries that match our pattern
await addOptimizedDepsEntries({
projectRootDir,
files,
directive: config.directive,
environment: env,
debugNamespace,
});
viteConfig.optimizeDeps ??= {};
viteConfig.optimizeDeps.esbuildOptions ??= {};
viteConfig.optimizeDeps.esbuildOptions.plugins ??= [];
viteConfig.optimizeDeps.esbuildOptions.plugins.push({
name: `rwsdk:${config.pluginName}`,
setup(build) {
log("Setting up esbuild plugin for %s", config.virtualModuleName);
// Handle both direct virtual module name and /@id/ prefixed version
const escapedVirtualModuleName = config.virtualModuleName.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&");
const escapedPrefixedModuleName = `/@id/${config.virtualModuleName}`.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&");
build.onResolve({
filter: new RegExp(`^(${escapedVirtualModuleName}|${escapedPrefixedModuleName})\.js$`),
}, () => {
process.env.VERBOSE &&
log("Esbuild onResolve: marking %s as external", config.virtualModuleName);
return {
path: `${config.virtualModuleName}.js`,
external: true,
};
});
},
});
const shouldOptimizeForEnv = !config.optimizeForEnvironments ||
config.optimizeForEnvironments.includes(env);
if (shouldOptimizeForEnv) {
log("Applying optimizeDeps and aliasing for environment: %s", env);
viteConfig.optimizeDeps.include ??= [];
for (const file of files) {
const actualFilePath = path.join(projectRootDir, file);
process.env.VERBOSE &&
log("Adding to optimizeDeps.entries: %s", actualFilePath);
const entries = Array.isArray(viteConfig.optimizeDeps.entries)
? viteConfig.optimizeDeps.entries
: [].concat(viteConfig.optimizeDeps.entries ?? []);
viteConfig.optimizeDeps.entries = entries;
entries.push(actualFilePath);
}
log("Environment configuration complete for env=%s", env);
}
else {
log("Skipping optimizeDeps and aliasing for environment: %s", env);
}
},
resolveId(source) {
process.env.VERBOSE && log("Resolving id=%s", source);
if (source === `${config.virtualModuleName}.js`) {
log("Resolving %s module", config.virtualModuleName);
// context(justinvdm, 16 Jun 2025): Include .js extension
// so it goes through vite processing chain
return source;
}
process.env.VERBOSE && log("No resolution for id=%s", source);
},
async load(id) {
process.env.VERBOSE && log("Loading id=%s", id);
if (id === config.virtualModuleName + ".js") {
log("Loading %s module with %d files", config.virtualModuleName, files.size);
const environment = this.environment?.name || "client";
log("Current environment: %s, isDev: %s", environment, isDev);
const optimizedDeps = {};
if (isDev && devServer) {
for (const file of files) {
const resolvedPath = await resolveOptimizedDep(projectRootDir, file, environment, debugNamespace);
if (resolvedPath) {
optimizedDeps[file] = resolvedPath;
}
}
}
const s = new MagicString(`
export const ${config.exportName} = {
${Array.from(files)
.map((file) => `
"${file}": () => import("${optimizedDeps[file] ?? file}"),
`)
.join("")}
};
`);
const code = s.toString();
const map = s.generateMap();
log("Generated virtual module code length: %d", code.length);
process.env.VERBOSE && log("Generated virtual module code: %s", code);
return {
code,
map,
};
}
process.env.VERBOSE && log("No load handling for id=%s", id);
},
};
};