UNPKG

vite-plugin-react-server

Version:
869 lines (784 loc) 31.4 kB
import type { PreRenderedAsset, PreRenderedChunk } from "rollup"; import type { StreamPluginOptions, ResolvedUserOptions, PageName, PropsName, HtmlName, RootName, } from "../types.js"; import { BASE_PATTERNS, DEFAULT_CONFIG, DEFAULT_LOADER_CONFIG, } from "./defaults.js"; import { join, resolve } from "node:path"; import { pluginRoot } from "../root.js"; import { createInputNormalizer } from "../helpers/inputNormalizer.js"; import { resolveDirectiveMatcher } from "./resolveDirectiveMatcher.js"; import { resolveAllowedDirectives } from "./resolveAllowedDirectives.js"; import { resolveRegExp } from "./resolveRegExp.js"; import type { LoaderConfig } from "../loader/types.js"; import { handleError } from "../error/handleError.js"; import { createLogger, type Logger } from "vite"; import { getCondition } from "./getCondition.js"; import { getNodeEnv } from "./getNodeEnv.js"; import { stashUserOptions, getStashedUserOptions, getEnvironmentId } from "./stashedOptionsState.js"; import { readFileSync, existsSync } from "node:fs"; import { createRollupLikeHash } from "./createRollupLikeHash.js"; export type ResolveOptionsReturn = | { type: "success"; userOptions: ResolvedUserOptions; error?: never; } | { type: "error"; error: unknown; userOptions?: never }; export type ResolveOptionsFn = ( options: StreamPluginOptions, forceResolve?: boolean, logger?: Logger ) => ResolveOptionsReturn; // /** // * Ensures a path ends with .js extension // */ // const addExtension = (path: string, extension: string = "js") => { // if (path.endsWith(`.${extension}`)) return path; // if (path.endsWith("/.")) return path.slice(0, -2) + "." + extension; // if (path.endsWith(".")) return path + "." + extension; // return path + "." + extension; // }; /** * Handles search query parameters in file paths */ const handleSearchQuery = (path: string) => { const searchQuery = path.split("?")[1]; if (!searchQuery) return path; const folder = path.split("/").slice(0, -1).join("/"); const filename = path.split("/").pop(); const fileNameExtIndex = filename?.lastIndexOf("."); const fileNameWithoutExt = filename?.slice(0, fileNameExtIndex); const extension = filename?.slice(fileNameExtIndex); return `${folder}/${fileNameWithoutExt}.${searchQuery}.${extension}`; }; /** * Registers a path with an optional pattern matcher and extension. * If a pattern matches and the path doesn't end with the extension, the extension is appended. * * @param path - The path to register * @param pattern - Optional pattern matcher function that returns true if the path matches * @param ext - Optional extension to append if pattern matches and path doesn't already end with it */ const registerPath = (path: string, pattern?: RegExp, ext?: string) => { // If we have a pattern and it doesn't match, or we have an extension and the path doesn't end with it, append the extension if ((pattern && !pattern.test(path)) || (ext && !path.endsWith(ext))) { return path + ext; } return path; }; // ============================================================================ // Main Options Resolver // ============================================================================ let originalOptions: any | null = null; /** * Resolves the user options for the plugin. * * @param options - The user options to resolve. * @returns The resolved options. */ export const resolveOptions: ResolveOptionsFn = function _resolveOptions( options = {} as StreamPluginOptions, forceResolve = false, logger = createLogger(!options.verbose ? "error" : "info", { prefix: "vite:plugin-react-server/config#resolveOptions", }) ) { if (!forceResolve && originalOptions == null) { originalOptions = options; } else if (originalOptions != null && options !== originalOptions) { if (options.verbose) { logger.info("options changed, forcing re-resolve"); } forceResolve = true; } // Force re-resolve if projectRoot changed if (originalOptions != null && originalOptions.projectRoot !== options.projectRoot) { if (options.verbose) { logger.info(`projectRoot changed from ${originalOptions.projectRoot} to ${options.projectRoot}, forcing re-resolve`); } forceResolve = true; } const panicThreshold = typeof options.panicThreshold === "string" ? options.panicThreshold : DEFAULT_CONFIG.PANIC_THRESHOLD; // since panicThreshold affects the behavior of the plugin, we need to re-resolve the options if it changes const envId = getEnvironmentId(getCondition(), process.env.NODE_ENV ?? "production"); // Return stashed options if available const stashedOptions = getStashedUserOptions(envId); if (stashedOptions && !forceResolve) { return { type: "success", userOptions: stashedOptions as never, }; } const loaderMode = options.loader?.mode ?? undefined; // Module path configuration const moduleBase = typeof options.moduleBase === "string" ? options.moduleBase : DEFAULT_CONFIG.MODULE_BASE; // Basic configuration - use the first projectRoot that was provided, or fall back to current options const projectRoot = options.projectRoot ?? originalOptions?.projectRoot ?? process.cwd(); if (options.verbose && options.projectRoot != originalOptions?.projectRoot) { logger.info(`[resolveOptions] new projectRoot: ${projectRoot}`); } // Build options const preserveModulesRoot = options.build?.preserveModulesRoot ?? DEFAULT_CONFIG.BUILD.preserveModulesRoot; // Get current mode to check if we're in development const currentMode = getNodeEnv(process.env.NODE_ENV); const isDevelopment = currentMode === "development"; // 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) // CRITICAL: In development mode, NEVER strip moduleBase (src/) because Vite serves from source locations const preserveModulesRootString = !isDevelopment && preserveModulesRoot === false ? moduleBase // Strip src/ from output paths (production only) : undefined; // Keep src/ in output paths const client = typeof options.build?.client === "string" ? options.build.client : DEFAULT_CONFIG.BUILD.client; const outDir = typeof options.build?.outDir === "string" ? options.build.outDir : DEFAULT_CONFIG.BUILD.outDir; // Use defaults for now - these will be updated later when we have the config with proper prefix const moduleBasePath = typeof options.moduleBasePath === "string" ? options.moduleBasePath : DEFAULT_CONFIG.MODULE_BASE_PATH; const moduleBaseURL = typeof options.moduleBaseURL === "string" ? options.moduleBaseURL : DEFAULT_CONFIG.MODULE_BASE_URL; const moduleRootPath = typeof options.moduleRootPath === "string" ? options.moduleRootPath : join(projectRoot, outDir, client); const publicOrigin = typeof options.publicOrigin === "string" ? options.publicOrigin : DEFAULT_CONFIG.PUBLIC_ORIGIN; const rscWorkerPath = typeof options.rscWorkerPath === "string" ? resolve(projectRoot, options.rscWorkerPath) : resolve(pluginRoot, DEFAULT_CONFIG.RSC_WORKER_PATH); const htmlWorkerPath = typeof options.htmlWorkerPath === "string" ? resolve(projectRoot, options.htmlWorkerPath) : resolve(pluginRoot, DEFAULT_CONFIG.HTML_WORKER_PATH); const loaderPath = typeof options.loaderPath === "string" ? resolve(projectRoot, options.loaderPath) : resolve(pluginRoot, DEFAULT_CONFIG.LOADER_PATH); const jsExtension = typeof options.build?.jsExtension === "string" ? options.build.jsExtension : DEFAULT_CONFIG.BUILD.jsExtension; const cssExtension = typeof options.build?.cssExtension === "string" ? options.build.cssExtension : DEFAULT_CONFIG.BUILD.cssExtension; const cssModuleExtension = typeof options.build?.cssModuleExtension === "string" ? options.build.cssModuleExtension : DEFAULT_CONFIG.BUILD.cssModuleExtension; const htmlExtension = typeof options.build?.htmlExtension === "string" ? options.build.htmlExtension : DEFAULT_CONFIG.BUILD.htmlExtension; const jsonExtension = typeof options.build?.jsonExtension === "string" ? options.build.jsonExtension : DEFAULT_CONFIG.BUILD.jsonExtension; const rscExtension = typeof options.build?.rscExtension === "string" ? options.build.rscExtension : DEFAULT_CONFIG.BUILD.rscExtension; const rscOutputPath = typeof options.build?.rscOutputPath === "string" ? options.build.rscOutputPath : DEFAULT_CONFIG.BUILD.rscOutputPath; const htmlOutputPath = typeof options.build?.htmlOutputPath === "string" ? options.build.htmlOutputPath : DEFAULT_CONFIG.BUILD.htmlOutputPath; const modulePattern = resolveRegExp( options.autoDiscover?.modulePattern, DEFAULT_CONFIG.AUTO_DISCOVER.modulePattern ); const jsonPattern = resolveRegExp( options.autoDiscover?.jsonPattern, DEFAULT_CONFIG.AUTO_DISCOVER.jsonPattern ); const cssPattern = resolveRegExp( options.autoDiscover?.cssPattern, DEFAULT_CONFIG.AUTO_DISCOVER.cssPattern ); const htmlPattern = resolveRegExp( options.autoDiscover?.htmlPattern, DEFAULT_CONFIG.AUTO_DISCOVER.htmlPattern ); const rscPattern = resolveRegExp( options.autoDiscover?.rscPattern, DEFAULT_CONFIG.AUTO_DISCOVER.rscPattern ); const clientPattern = resolveRegExp( options.autoDiscover?.clientPattern, DEFAULT_CONFIG.AUTO_DISCOVER.clientPattern ); const serverPattern = resolveRegExp( options.autoDiscover?.serverPattern, DEFAULT_CONFIG.AUTO_DISCOVER.serverPattern ); const nodePattern = resolveRegExp( options.autoDiscover?.nodePattern, DEFAULT_CONFIG.AUTO_DISCOVER.nodeOnly ); const propsPattern = resolveRegExp( options.autoDiscover?.propsPattern, DEFAULT_CONFIG.AUTO_DISCOVER.propsPattern ); const pagePattern = resolveRegExp( options.autoDiscover?.pagePattern, DEFAULT_CONFIG.AUTO_DISCOVER.pagePattern ); const cssModulePattern = resolveRegExp( options.autoDiscover?.cssModulePattern, DEFAULT_CONFIG.AUTO_DISCOVER.cssModulePattern ); const vendorPattern = resolveRegExp( options.autoDiscover?.vendorPattern, DEFAULT_CONFIG.AUTO_DISCOVER.vendorPattern ); const virtualPattern = resolveRegExp( options.autoDiscover?.virtualPattern, DEFAULT_CONFIG.AUTO_DISCOVER.virtualPattern ); const dotPattern = resolveRegExp( options.autoDiscover?.dotPattern, BASE_PATTERNS.DOT_FILES ); /** Loader options */ const serverDirective = resolveRegExp( options.loader?.serverDirective, DEFAULT_LOADER_CONFIG.serverDirective ); const clientDirective = resolveRegExp( options.loader?.clientDirective, DEFAULT_LOADER_CONFIG.clientDirective ); const isServerFunctionCode = resolveDirectiveMatcher( serverDirective, (code: string, moduleId?: string) => code.match(serverDirective) != null || (typeof moduleId === "string" && serverPattern.test(moduleId)) || false ); const isClientComponentCode = resolveDirectiveMatcher( clientDirective, (code: string, moduleId?: string) => code.match(clientDirective) != null || (typeof moduleId === "string" && clientPattern.test(moduleId)) || false ); const isClientComponentByCode = resolveDirectiveMatcher( clientDirective, (code: string) => code.match(clientDirective) != null || false ); const isClientComponentByName = (moduleId: string, _transformedModuleId?: string) => (typeof moduleId === "string" && clientPattern.test(moduleId)) || false; const hashOption = options.build?.hash ?? DEFAULT_CONFIG.BUILD.hash; // User-facing hash function that can take source content or filename const hash = (input: string | null, _ssr: boolean, sourceContent?: string) => { if (!input) return ""; if (new RegExp(BASE_PATTERNS.EXT.NODE).test(input)) { return input; } // CRITICAL: Never hash node_modules files - Vite/Rollup handles those if (input.includes("node_modules")) { return input; } // CRITICAL: Never hash virtual modules (_virtual or matching virtualPattern) - Vite handles those const virtualPattern = resolveRegExp( options.autoDiscover?.virtualPattern, DEFAULT_CONFIG.AUTO_DISCOVER.virtualPattern ); if (input.includes("_virtual") || (virtualPattern && virtualPattern.test(input))) { return input; } // Check if hashing is disabled if (hashOption === "false") { return input; } // Skip outputs that are addressed by canonical path rather than via the // build manifest, so renaming them would break their callers: // - htmlPattern / rscPattern: SSG outputs at fixed route URLs // - pagePattern / propsPattern / serverPattern: SSR entry modules the // runtime imports by literal path (see resolvePageAndProps), not // browser-served assets so caching isn't a concern either // Everything else falls through to content-based hashing so prod deploys // bust browser/CDN caches. Cross-environment hash consistency is provided // by handleSsrEntryName / handleSsrAssetName in resolveUserConfig.ts // (which feed sourceContent through to this function) and the // augmentChunkHash plugin. if ( htmlPattern.test(input) || rscPattern.test(input) || pagePattern.test(input) || propsPattern.test(input) || serverPattern.test(input) ) { return input; } // Determine what to hash based on available input let contentToHash: string; if (sourceContent) { // Use provided source content (preferred) contentToHash = sourceContent; } else { // Try to read source file content try { const sourcePath = resolve(projectRoot, input); if (existsSync(sourcePath)) { contentToHash = readFileSync(sourcePath, 'utf-8'); // Debug logging if (options.verbose) { logger.info(`[hash] Reading source: ${sourcePath} (${contentToHash.length} chars)`); } } else { // Fallback to filename contentToHash = input; if (options.verbose) { logger.warn(`[hash] File not found: ${sourcePath}, using filename: ${input}`); } } } catch (error) { // Fallback to filename contentToHash = input; if (options.verbose) { logger.warn(`[hash] Error reading ${input}: ${error}`); } } } // Generate hash using Rollup-like algorithm const hashCharacters = typeof hashOption === 'object' && hashOption?.format === 'hex' ? 'hex' : 'base36'; const contentHash = createRollupLikeHash(contentToHash, hashCharacters); // Apply naming logic const extensionIndex = input.lastIndexOf("."); if (extensionIndex !== -1) { const extension = input.slice(extensionIndex); const filename = input.slice(0, extensionIndex); return filename + "-" + contentHash + extension; } else { return input + "-" + contentHash; } }; // Output path resolution const getOutputPath = (n: string | null, isAsset: boolean = false) => { if (!n) return ""; let path = handleSearchQuery(n); path = path.startsWith(moduleBase + moduleBasePath) ? path.slice(moduleBase.length + moduleBasePath.length) : path; // For assets that are not modules, preserve the original extension // Only apply module transformations if the file actually matches module patterns if (isAsset) { // Check if this is a module file (JS/TS/JSX/TSX) - if so, apply module transformations const isModuleFile = modulePattern.test(path) || clientPattern.test(path) || serverPattern.test(path) || propsPattern.test(path) || pagePattern.test(path); // If it's not a module file, preserve the original extension if (!isModuleFile) { return path; } } if (vendorPattern.test(path)) return registerPath(path, vendorPattern, jsExtension); if (cssModulePattern.test(path)) return registerPath(path, cssModulePattern, cssExtension); if (cssPattern.test(path)) return registerPath(path, cssPattern, cssExtension); if (clientPattern.test(path)) return registerPath(path, clientPattern, jsExtension); if (htmlPattern.test(path)) return registerPath(path, htmlPattern, htmlExtension); if (jsonPattern.test(path)) return registerPath(path, jsonPattern, jsonExtension); if (propsPattern.test(path)) return registerPath(path, propsPattern, jsExtension); if (pagePattern.test(path)) return registerPath(path, pagePattern, jsExtension); if (serverPattern.test(path)) return registerPath(path, serverPattern, jsExtension); if (modulePattern.test(path)) return registerPath(path, modulePattern, jsExtension); // Fallback: only apply module pattern if we're sure it's a module // For assets, we've already returned above, so this is safe return registerPath(path, modulePattern, jsExtension); }; const normalizer = options.normalizer ?? createInputNormalizer({ root: projectRoot, preserveModulesRoot: preserveModulesRootString, removeExtension: true, moduleBasePath, moduleBaseURL, }); // File naming functions - defined as regular functions to avoid closure issues function entryFile(n: PreRenderedChunk, ssr: boolean, sourceContent?: string): string { const normalizedName = normalizer(n.name)[0]; let outputPath = getOutputPath(normalizedName); // When preserveModulesRoot is true, preserve the src/ prefix in output paths if (preserveModulesRoot && n.name.startsWith("src/")) { // If the normalized name doesn't have src/, add it back if (!outputPath.startsWith("src/")) { // Handle the case where outputPath starts with a slash if (outputPath.startsWith("/")) { outputPath = "src" + outputPath; } else { outputPath = "src/" + outputPath; } } } return hash(outputPath, ssr, sourceContent); } function chunkFile(n: PreRenderedChunk, ssr: boolean, sourceContent?: string): string { const normalizedName = normalizer(n.name)[0]; let outputPath = getOutputPath(normalizedName); // When preserveModulesRoot is true, preserve the src/ prefix in output paths if (preserveModulesRoot && n.name.startsWith("src/")) { // If the normalized name doesn't have src/, add it back if (!outputPath.startsWith("src/")) { // Handle the case where outputPath starts with a slash if (outputPath.startsWith("/")) { outputPath = "src" + outputPath; } else { outputPath = "src/" + outputPath; } } } return hash(outputPath, ssr, sourceContent); } function assetFile(n: PreRenderedAsset, _ssr: boolean = false): string { // Rollup may report the same asset under several `names`; dedupe so we // don't emit invalid comma-joined filenames like `Foo.eot,Foo.eot`. When // multiple distinct names survive they all reference the same emitted // file, so use the first as the canonical output path. const uniqueNames = Array.from(new Set(n.names)); let firstName = uniqueNames[0]; // Clean up asset paths by removing the moduleBase from within assets directory // Transform: assets/src/page/file.css -> assets/page/file.css const assetsDir = build.assetsDir || "assets"; if (firstName.startsWith(assetsDir + "/" + moduleBase + "/")) { firstName = assetsDir + "/" + firstName.slice((assetsDir + "/" + moduleBase + "/").length); } // Handle moduleBasePath removal else if (moduleBasePath && firstName.startsWith(moduleBasePath)) { firstName = firstName.slice(moduleBasePath.length); } else if ( moduleBaseURL && moduleBaseURL !== "/" && moduleBaseURL !== "" && firstName.startsWith(moduleBaseURL) ) { firstName = firstName.slice(moduleBaseURL.length); } // Handle direct moduleBase removal else if (firstName.startsWith(moduleBase + "/")) { firstName = firstName.slice(moduleBase.length + 1); } // For CSS files, ensure they go into the assets directory if (firstName.endsWith(".css")) { // Add assets directory prefix if not already present if (!firstName.startsWith(assetsDir + "/")) { firstName = assetsDir + "/" + firstName; } return hash(firstName, false); } // For other assets, apply the extension mapping if needed // Pass isAsset=true to preserve extensions for non-module assets return hash(getOutputPath(firstName, true), false); } /** * pages * assetsDir * client * server * static * api * outDir * hash * preserveModulesRoot * rscOutputPath * htmlOutputPath * entryFile * chunkFile * assetFile * extensionMap * moduleExtension * jsExtension * cssExtension * htmlExtension * jsonExtension * rscExtension * cssModuleExtension * nodeExtension */ const build = { pages: options.build?.pages ?? DEFAULT_CONFIG.BUILD.pages, assetsDir: options.build?.assetsDir ?? DEFAULT_CONFIG.BUILD.assetsDir, client: options.build?.client ?? DEFAULT_CONFIG.BUILD.client, server: options.build?.server ?? DEFAULT_CONFIG.BUILD.server, static: options.build?.static ?? DEFAULT_CONFIG.BUILD.static, api: options.build?.api ?? DEFAULT_CONFIG.BUILD.api, preserveModulesRoot: options.build?.preserveModulesRoot ?? DEFAULT_CONFIG.BUILD.preserveModulesRoot, outDir: options.build?.outDir ?? DEFAULT_CONFIG.BUILD.outDir, hash: options.build?.hash ?? DEFAULT_CONFIG.BUILD.hash, extensionMap: { // File extensions first (more specific patterns should come first) [BASE_PATTERNS.EXT.CSS]: cssExtension, [BASE_PATTERNS.EXT.JSON]: jsonExtension, [BASE_PATTERNS.EXT.HTML]: htmlExtension, [BASE_PATTERNS.EXT.RSC]: rscExtension, // Special case for .node files [BASE_PATTERNS.EXT.NODE]: BASE_PATTERNS.EXT.NODE + (options.build?.jsExtension ?? DEFAULT_CONFIG.BUILD.jsExtension), // General module pattern last (less specific) [BASE_PATTERNS.MODULE]: jsExtension, ...options.build?.extensionMap, }, entryFile, chunkFile, assetFile, rscOutputPath: rscOutputPath, htmlOutputPath: htmlOutputPath, moduleExtension: jsExtension, jsExtension: jsExtension, cssExtension: cssExtension, htmlExtension: htmlExtension, jsonExtension: jsonExtension, rscExtension: rscExtension, cssModuleExtension: cssModuleExtension, nodeExtension: DEFAULT_CONFIG.BUILD.nodeExtension, useRscWorker: options.build?.useRscWorker ?? DEFAULT_CONFIG.BUILD.useRscWorker, useHtmlWorker: options.build?.useHtmlWorker ?? // Force useHtmlWorker to true when build.pages is explicitly configured, regardless of default logic (options.build?.pages && (Array.isArray(options.build.pages) || typeof options.build.pages === 'function')) ? true : DEFAULT_CONFIG.BUILD.useHtmlWorker, renderMode: options.build?.renderMode ?? "parallel", batchSize: options.build?.batchSize ?? 8, } satisfies ResolvedUserOptions["build"]; // Development configuration const dev = { useHtmlWorker: options.dev?.useHtmlWorker ?? DEFAULT_CONFIG.DEV.useHtmlWorker, useRscWorker: options.dev?.useRscWorker ?? DEFAULT_CONFIG.DEV.useRscWorker, } satisfies ResolvedUserOptions["dev"]; // Auto-discovery configuration const autoDiscover = { clientEntry: options.autoDiscover?.clientEntry ?? DEFAULT_CONFIG.AUTO_DISCOVER.clientEntry, serverEntry: options.autoDiscover?.serverEntry ?? DEFAULT_CONFIG.AUTO_DISCOVER.serverEntry, cssEntry: options.autoDiscover?.cssEntry ?? DEFAULT_CONFIG.AUTO_DISCOVER.cssEntry, jsonEntry: options.autoDiscover?.jsonEntry ?? DEFAULT_CONFIG.AUTO_DISCOVER.jsonEntry, htmlEntry: options.autoDiscover?.htmlEntry ?? DEFAULT_CONFIG.AUTO_DISCOVER.htmlEntry, modulePattern, jsonPattern, cssPattern, htmlPattern, rscPattern, clientPattern, serverPattern, nodePattern, propsPattern, pagePattern, cssModulePattern, vendorPattern, virtualPattern, dotPattern, } satisfies ResolvedUserOptions["autoDiscover"]; const allowedDirectives = resolveAllowedDirectives( options.loader?.allowedDirectives ?? DEFAULT_LOADER_CONFIG.allowedDirectives ); // Create loader configuration const loader = loaderMode ? ({ serverDirective: resolveRegExp( options.loader?.serverDirective, DEFAULT_LOADER_CONFIG.serverDirective ), clientDirective: resolveRegExp( options.loader?.clientDirective, DEFAULT_LOADER_CONFIG.clientDirective ), allowedDirectives: allowedDirectives, getDirectiveType: options.loader?.getDirectiveType ?? options.loader?.allowedDirectives ? (directive: string) => { if (options.loader?.allowedDirectives) { if (Array.isArray(options.loader?.allowedDirectives)) { return options.loader?.allowedDirectives.includes(directive) ? directive === "use server" ? "server" : "client" : undefined; } else { const config = options.loader?.allowedDirectives[directive]; return config ? directive === "use server" ? "server" : "client" : undefined; } } return undefined; } : DEFAULT_LOADER_CONFIG.getDirectiveType, mode: loaderMode, importServerPath: options.loader?.importServerPath ?? DEFAULT_CONFIG.RSC_LOADER[loaderMode].importServerPath, importClientPath: options.loader?.importClientPath ?? DEFAULT_CONFIG.RSC_LOADER[loaderMode].importClientPath, registerClientReferenceName: options.loader?.registerClientReferenceName ?? DEFAULT_CONFIG.RSC_LOADER[loaderMode].registerClientReferenceName, registerServerReferenceName: options.loader?.registerServerReferenceName ?? DEFAULT_CONFIG.RSC_LOADER[loaderMode].registerServerReferenceName, isServerFunctionCode, isClientComponentCode, isClientComponentByCode, isClientComponentByName, parse: options?.loader?.parse ?? DEFAULT_LOADER_CONFIG.parse, moduleID: options?.loader?.moduleID ?? options?.moduleID, } as Required<LoaderConfig>) : undefined; const pipeableStreamOptions = options.pipeableStreamOptions ? options.pipeableStreamOptions : {}; // Return resolved options try { const userOptions: ResolvedUserOptions = { projectRoot, moduleBase, moduleBasePath, moduleBaseURL, moduleRootPath, publicOrigin, build: build, dev: dev, verbose: options.verbose ?? DEFAULT_CONFIG.VERBOSE, availableEnvironments: (options as any).availableEnvironments, strategy: (options as any).strategy, onMetrics: typeof options.onMetrics === "function" ? options.onMetrics : DEFAULT_CONFIG.ON_METRICS, onEvent: typeof options.onEvent === "function" ? options.onEvent : DEFAULT_CONFIG.ON_EVENT, Page: options.Page, props: options.props, Html: options.Html ?? DEFAULT_CONFIG.HTML, Root: options.Root ?? DEFAULT_CONFIG.ROOT, components: options.components, normalizer: normalizer, moduleID: options.moduleID, // if not provided, will be created when config hook is called pageExportName: options.pageExportName ?? (DEFAULT_CONFIG.PAGE_EXPORT_NAME as PageName), propsExportName: options.propsExportName ?? (DEFAULT_CONFIG.PROPS_EXPORT_NAME as PropsName), htmlExportName: options.htmlExportName ?? (DEFAULT_CONFIG.HTML_EXPORT_NAME as HtmlName), rootExportName: options.rootExportName ?? (DEFAULT_CONFIG.ROOT_EXPORT_NAME as RootName), css: { inlineCss: options.css?.inlineCss ?? DEFAULT_CONFIG.CSS.inlineCss, inlineThreshold: options.css?.inlineThreshold ?? DEFAULT_CONFIG.CSS.inlineThreshold, inlinePatterns: options.css?.inlinePatterns ?? DEFAULT_CONFIG.CSS.inlinePatterns, linkPatterns: options.css?.linkPatterns ?? DEFAULT_CONFIG.CSS.linkPatterns, }, htmlWorkerPath: htmlWorkerPath, rscWorkerPath: rscWorkerPath, loaderPath: loaderPath, reactLoaderPath: options.reactLoaderPath ?? DEFAULT_CONFIG.REACT_LOADER_PATH, cssLoaderPath: options.cssLoaderPath ?? DEFAULT_CONFIG.CSS_LOADER_PATH, envLoaderPath: options.envLoaderPath ?? DEFAULT_CONFIG.ENV_LOADER_PATH, clientEntry: options.clientEntry ?? DEFAULT_CONFIG.CLIENT_ENTRY, serverEntry: options.serverEntry ?? DEFAULT_CONFIG.SERVER_ENTRY, clientPackages: (options as { clientPackages?: readonly string[] }).clientPackages, autoDiscover: autoDiscover, loader: loader, pipeableStreamOptions, rscTimeout: typeof options.rscTimeout === "number" ? options.rscTimeout : DEFAULT_CONFIG.RSC_TIMEOUT, htmlTimeout: typeof options.htmlTimeout === "number" ? options.htmlTimeout : DEFAULT_CONFIG.HTML_TIMEOUT, htmlWorkerStartupTimeout: typeof options.htmlWorkerStartupTimeout === "number" ? options.htmlWorkerStartupTimeout : DEFAULT_CONFIG.HTML_WORKER_STARTUP_TIMEOUT, rscWorkerStartupTimeout: typeof options.rscWorkerStartupTimeout === "number" ? options.rscWorkerStartupTimeout : DEFAULT_CONFIG.RSC_WORKER_STARTUP_TIMEOUT, fileWriteTimeout: typeof options.fileWriteTimeout === "number" ? options.fileWriteTimeout : DEFAULT_CONFIG.FILE_WRITE_TIMEOUT, workerShutdownTimeout: typeof options.workerShutdownTimeout === "number" ? options.workerShutdownTimeout : DEFAULT_CONFIG.WORKER_SHUTDOWN_TIMEOUT, panicThreshold: panicThreshold, }; // Stash the resolved options stashUserOptions(envId, userOptions); return { type: "success", userOptions, }; } catch (error) { return { type: "error", error: handleError({ error, logger: logger, panicThreshold, context: "config(resolveOptions)", }), }; } };