UNPKG

vite-plugin-react-server

Version:
233 lines (232 loc) 11.5 kB
import { join } from "node:path"; import { createLogger } from "vite"; import { getEnvValue, setEnvValue } from "../env/getEnvKey.js"; import { DEFAULT_CONFIG } from "../config/defaults.js"; // Cache for resolved environment configs to avoid recomputation const stashedEnvironmentConfig = {}; let originalConfig = null; /** * Resolve environment-specific configuration for the Environment API. * This is a sophisticated version that follows the same patterns as resolveUserConfig * but adapted for Vite's Environment API requirements. */ export const resolveEnvironmentConfig = function _resolveEnvironmentConfig({ condition, config, userOptions, autoDiscoveredFiles, ssr = undefined, forceResolve = false, }) { // Handle config changes and caching if (!forceResolve && originalConfig == null) { originalConfig = config; } else if (originalConfig != null && config !== originalConfig) { forceResolve = true; } // Determine SSR mode with fallbacks ssr = typeof ssr === "boolean" ? ssr : typeof config.build?.ssr === "boolean" ? config.build?.ssr : condition === "react-server" ? true : false; if (condition === "react-server" && !ssr) { const logger = config.customLogger ?? createLogger(); logger.warn("react-server build should be ssr, but it was manually set to false. This may not work as expected."); } // Determine environment-specific directory const envDir = condition === "react-client" && ssr ? userOptions.build.client : condition === "react-client" ? userOptions.build.static : userOptions.build.server; const envId = `${envDir}${ssr ? "-ssr" : ""}`; // Check cache first if (stashedEnvironmentConfig[envId] && !forceResolve) { return { type: "success", environmentConfig: stashedEnvironmentConfig[envId], }; } try { // Get environment variables (env vars take precedence over config) const vitePrefix = config.envPrefix ?? DEFAULT_CONFIG.ENV_PREFIX; const primaryPrefix = typeof vitePrefix === "string" ? vitePrefix : vitePrefix[0]; const envBaseUrl = getEnvValue("BASE_URL", primaryPrefix); const effectiveModuleBaseURL = envBaseUrl != null && envBaseUrl !== "" ? envBaseUrl : userOptions.moduleBaseURL; const envPublicOrigin = getEnvValue("PUBLIC_ORIGIN", primaryPrefix); const effectivePublicOrigin = envPublicOrigin != null ? envPublicOrigin : userOptions.publicOrigin; // Set process.env values to ensure they're available for server-side code if (!getEnvValue("BASE_URL", primaryPrefix)) { setEnvValue("BASE_URL", effectiveModuleBaseURL, primaryPrefix); } if (!getEnvValue("PUBLIC_ORIGIN", primaryPrefix)) { setEnvValue("PUBLIC_ORIGIN", effectivePublicOrigin, primaryPrefix); } // Determine inputs based on condition and SSR with better normalization let inputs; if (condition === "react-client") { if (ssr) { // For SSR builds, exclude HTML files and use only client inputs inputs = Object.fromEntries(Object.entries(autoDiscoveredFiles.clientInputs).filter(([, value]) => !value.endsWith(".html") && !value.endsWith(".htm"))); } else { // For static builds, use static inputs (which can include HTML) inputs = autoDiscoveredFiles.staticInputs; } } else { // For server builds, use server inputs (no HTML files) inputs = autoDiscoveredFiles.serverInputs; } // Normalize inputs for this environment (following resolveUserConfig pattern) const normalizedInputs = Object.fromEntries(Object.entries(inputs).map(([key, value]) => [ key, value.slice(Number(value.startsWith("/"))), ])); // Handle user-defined output options (following resolveUserConfig pattern) const userDefinedOutput = config.build?.rollupOptions?.output; const hasOtherOutput = Array.isArray(userDefinedOutput) && userDefinedOutput.length > 1; const hasValidOutput = userDefinedOutput && !hasOtherOutput; const hasObjectOutput = userDefinedOutput && !hasOtherOutput && typeof userDefinedOutput === "object" && userDefinedOutput !== null; const userDefinedAssetFileNames = hasObjectOutput ? "assetFileNames" in userDefinedOutput ? userDefinedOutput.assetFileNames : undefined : // find the other asset file names hasOtherOutput ? userDefinedOutput.find((o) => o?.assetFileNames) ?.assetFileNames : undefined; const userDefinedChunkFileNames = hasValidOutput ? "chunkFileNames" in userDefinedOutput ? userDefinedOutput.chunkFileNames : undefined : undefined; const userDefinedEntryFileNames = hasValidOutput ? "entryFileNames" in userDefinedOutput ? userDefinedOutput.entryFileNames : undefined : undefined; // Rollup's preserveModulesRoot works in reverse of what you'd expect: // - When user wants preservation (true): pass undefined to Rollup (don't strip anything) // - When user wants stripping (false): pass moduleBase to Rollup (strip this path) const preserveModulesRootString = userOptions.build.preserveModulesRoot === false ? userOptions.moduleBase // Strip src/ from output paths : undefined; // Keep src/ in output paths // Basic rollup options - the file naming will be handled by resolveUserConfig mapping const rollupOptions = { input: normalizedInputs, output: { format: "esm", exports: "named", preserveModules: true, // Required for preserveModulesRoot to work preserveModulesRoot: preserveModulesRootString, // Note: entryFileNames, chunkFileNames, assetFileNames will be overridden by createEnvironmentPlugin mapping entryFileNames: userDefinedEntryFileNames, chunkFileNames: userDefinedChunkFileNames, assetFileNames: userDefinedAssetFileNames, }, }; if (condition === "react-client") { // Client environment configuration const clientEnvironmentConfig = { outDir: join(userOptions.build.outDir, envDir), assetsDir: userOptions.build.assetsDir, emptyOutDir: config.build?.emptyOutDir ?? true, copyPublicDir: typeof config.build?.copyPublicDir === "boolean" ? config.build?.copyPublicDir : !ssr, target: config.build?.target ?? "esnext", minify: config.build?.minify ?? true, manifest: config.build?.manifest ?? `.vite/manifest.json`, ssrManifest: config.build?.ssrManifest ?? false, ssrEmitAssets: config.build?.ssrEmitAssets ?? true, cssCodeSplit: typeof config.build?.cssCodeSplit === "boolean" ? config.build?.cssCodeSplit : true, modulePreload: config.build?.modulePreload ?? false, rollupOptions, }; stashedEnvironmentConfig[envId] = clientEnvironmentConfig; return { type: "success", environmentConfig: clientEnvironmentConfig, }; } else { // Server environment configuration const serverBuildEnvironmentOptions = { outDir: join(userOptions.build.outDir, envDir), assetsDir: userOptions.build.assetsDir, emptyOutDir: config.build?.emptyOutDir ?? true, copyPublicDir: typeof config.build?.copyPublicDir === "boolean" ? config.build?.copyPublicDir : false, target: config.build?.target ?? "esnext", // Use esnext for pure ESM - no helpers needed minify: config.build?.minify ?? true, manifest: config.build?.manifest ?? `.vite/manifest.json`, ssrManifest: config.build?.ssrManifest ?? false, ssrEmitAssets: typeof config.build?.ssrEmitAssets === "boolean" ? config.build?.ssrEmitAssets : true, cssCodeSplit: typeof config.build?.cssCodeSplit === "boolean" ? config.build?.cssCodeSplit : true, modulePreload: config.build?.modulePreload ?? false, rollupOptions: { ...config.build?.rollupOptions, input: normalizedInputs, output: { format: "esm", exports: "named", preserveModules: true, // Required for preserveModulesRoot to work preserveModulesRoot: preserveModulesRootString, // Note: entryFileNames, chunkFileNames, assetFileNames will be overridden by createEnvironmentPlugin mapping entryFileNames: userDefinedEntryFileNames, chunkFileNames: userDefinedChunkFileNames, assetFileNames: userDefinedAssetFileNames, }, preserveEntrySignatures: config.build?.rollupOptions?.preserveEntrySignatures ?? "strict", external: config.build?.rollupOptions?.external ?? [ "react", "react/jsx-runtime", "react/jsx-dev-runtime", "react-dom", "react-server-dom-esm/server.node", ], context: "module", plugins: [ ...(Array.isArray(config.build?.rollupOptions?.plugins) ? config.build.rollupOptions.plugins : config.build?.rollupOptions?.plugins ? [config.build.rollupOptions.plugins] : []), { name: "react-server-conditions", buildStart() { // Ensure react-server condition is available during server builds if (condition === "react-server") { // process.env.NODE_OPTIONS = (process.env.NODE_OPTIONS || "") + " --conditions react-server"; } }, }, ], }, }; stashedEnvironmentConfig[envId] = serverBuildEnvironmentOptions; return { type: "success", environmentConfig: serverBuildEnvironmentOptions, }; } } catch (error) { return { type: "error", error, }; } };