vite-plugin-react-server
Version:
Vite plugin for React Server Components (RSC)
1,002 lines (1,000 loc) • 147 kB
JavaScript
/**
* vite-plugin-react-server
* Copyright (c) Nico Brinkkemper
* MIT License
*/
import { workerData, parentPort } from 'node:worker_threads';
import { PassThrough } from 'node:stream';
import { createLogger } from 'vite';
import { join, relative } from 'node:path';
import { createRscWorkerLoader } from './createRscWorkerLoader.js';
import { handleRscRender } from './handleRscRender.server.js';
import { setMaxListenersOnPort } from '../../stream/setMaxListeners.js';
import { toError } from '../../error/toError.js';
import { decodeReply } from 'react-server-dom-esm/server';
import { createHandlers } from './handlers.js';
import { addModuleId, addCssFileContent, hmrState, clearAllCachedComponents, cssFiles, isModuleInvalidated, hasCachedComponent, getCachedComponent, clearCachedComponent, cacheComponent } from './state.server.js';
import { getRunner, getRpc } from './runnerInstance.js';
import { processInlineCssForState, combineCssFiles } from '../../helpers/createUnifiedCssProcessor.js';
import { routeToURL } from '../../utils/routeToURL.js';
import { DEFAULT_CONFIG } from '../../config/defaults.js';
import { resolvePageAndProps } from '../../helpers/resolvePageAndProps.js';
import { resolveComponent } from '../../helpers/resolveComponent.js';
import { handleError } from '../../error/handleError.js';
import { Root } from '../../components/root.js';
import { workerUserOptions } from './workerUserOptions.js';
import { hydrateUserOptions } from '../../helpers/hydrateUserOptions.js';
import { React } from '../../vendor/vendor.server.js';
import { sendMessage } from '../sendMessage.js';
import { createModuleResolutionMetrics } from '../../metrics/createModuleResolutionMetrics.js';
const logger = createLogger(workerData.resolvedConfig?.logLevel ?? "info");
const verbose = workerData.userOptions.verbose ?? false;
process.on("uncaughtException", (error) => {
if (verbose) {
logger?.error(`[RSC-WORKER] Uncaught exception: ${error.message}`, {
error
});
}
if (parentPort) {
parentPort.postMessage({
type: "ERROR",
id: "uncaught-exception",
error: {
message: error.message,
stack: error.stack,
name: error.name
},
context: "Uncaught Exception in RSC Worker"
});
}
process.exit(1);
});
const renderStates = /* @__PURE__ */ new Map();
let fallbackUserOptions = null;
function getUserOptions(renderId) {
if (renderId && renderStates.has(renderId)) {
const renderState = renderStates.get(renderId);
if (renderState.initialized) {
return renderState.userOptions;
}
}
if (fallbackUserOptions) {
return fallbackUserOptions;
}
const userOptionsResult = hydrateUserOptions(workerData.userOptions);
if (userOptionsResult.type === "error") {
throw userOptionsResult.error;
}
fallbackUserOptions = userOptionsResult.userOptions;
if (verbose) {
logger?.info(
`[FALLBACK] Using fallback userOptions for renderId: ${renderId || "none"}`
);
logger?.info(
`[FALLBACK] fallbackUserOptions.build: ${JSON.stringify(
fallbackUserOptions.build
)}`
);
}
return fallbackUserOptions;
}
function setRenderUserOptions(renderId, userOptions) {
renderStates.set(renderId, {
userOptions,
initialized: true
});
}
function cleanupRenderState(renderId) {
renderStates.delete(renderId);
}
const activeRenders = /* @__PURE__ */ new Map();
const activeStreamsByRoute = /* @__PURE__ */ new Map();
const headlessStreamElements = /* @__PURE__ */ new Map();
const headlessStreamErrors = /* @__PURE__ */ new Map();
function cleanupRender(id) {
const rscStream = activeRenders.get(id);
if (rscStream) {
rscStream.destroy();
activeRenders.delete(id);
for (const [route, streamId] of activeStreamsByRoute.entries()) {
if (streamId === id) {
activeStreamsByRoute.delete(route);
break;
}
}
}
cleanupRenderState(id);
}
function clearHeadlessError(route) {
headlessStreamErrors.delete(route);
}
async function loadComponentsWithCache(options) {
const {
pagePath,
propsPath,
rootPath,
htmlPath,
pageExportName = "Page",
propsExportName = "props",
rootExportName = "Root",
htmlExportName = "Html",
url,
loader,
verbose: verbose2,
logger: logger2,
panicThreshold = "none",
resolvedPageProps
} = options;
const normalizeUrlForCache = (urlStr) => {
if (urlStr.includes(".")) return urlStr;
return urlStr.endsWith("/") ? urlStr : urlStr + "/";
};
const normalizedUrl = normalizeUrlForCache(url);
let PageComponent;
let pageProps = resolvedPageProps;
let RootComponent;
let HtmlComponent;
if (verbose2) {
logger2?.info(
`[loadComponentsWithCache] pagePath=${pagePath}, propsPath=${propsPath}, url=${url}`
);
}
if (pagePath) {
if (verbose2) {
logger2?.info(
`[loadComponentsWithCache] Loading page and props for pagePath=${pagePath}`
);
}
const pageId = `${pagePath}#${pageExportName}`;
const isPageInvalidated = isModuleInvalidated(pagePath);
let needToLoadProps = false;
if (hasCachedComponent(pageId) && !isPageInvalidated) {
PageComponent = getCachedComponent(pageId);
if (verbose2) {
logger2?.info(
`[rsc-worker] Using cached Page component from: ${pagePath}`
);
}
if (propsPath && !resolvedPageProps) {
const propsId = `${propsPath}#${propsExportName}@${normalizedUrl}`;
if (hasCachedComponent(propsId)) {
clearCachedComponent(propsId);
}
needToLoadProps = true;
} else if (resolvedPageProps) {
if (verbose2) {
logger2?.info(
`[rsc-worker] Using pre-resolved pageProps from main thread: ${Object.keys(resolvedPageProps).length} keys`
);
}
}
} else {
if (isPageInvalidated && hasCachedComponent(pageId)) {
clearCachedComponent(pageId);
if (verbose2) {
logger2?.info(
`[rsc-worker] Cleared invalidated Page component cache: ${pagePath}`
);
}
}
if (propsPath && isPageInvalidated) {
const propsId = `${propsPath}#${propsExportName}@${normalizedUrl}`;
if (hasCachedComponent(propsId)) {
clearCachedComponent(propsId);
}
}
try {
const pageAndPropsResult = await resolvePageAndProps({
pagePath,
propsPath,
pageExportName,
propsExportName,
url: normalizedUrl,
loader,
verbose: verbose2 || false,
logger: logger2
});
if (verbose2) {
logger2?.info(
`[loadComponentsWithCache] pageAndPropsResult for ${pagePath}:`,
pageAndPropsResult
);
}
if (pageAndPropsResult.type === "success") {
PageComponent = pageAndPropsResult.PageComponent;
if (!resolvedPageProps) {
pageProps = pageAndPropsResult.pageProps;
}
cacheComponent(pageId, PageComponent);
if (verbose2) {
logger2?.info(
`[rsc-worker] Loaded and cached PageComponent from: ${pagePath}`
);
if (propsPath) {
logger2?.info(`[rsc-worker] Loaded fresh pageProps from: ${propsPath}`);
logger2?.info(`[rsc-worker] Loaded pageProps:`, pageProps);
}
}
} else {
const pageError = pageAndPropsResult.error ?? new Error(
`[rsc-worker] Failed to load page module from ${pagePath}`
);
const panicError = handleError({
error: pageError,
logger: logger2,
panicThreshold,
critical: true,
context: `rsc-worker: load page from ${pagePath}`,
log: true
});
throw panicError ?? pageError;
}
} catch (error) {
const panicError = handleError({
error,
logger: logger2,
panicThreshold,
critical: true,
log: true
});
throw panicError ?? error;
}
}
if (needToLoadProps && propsPath) {
if (verbose2) {
logger2?.info(
`[rsc-worker] Loading props separately for URL: ${url} (Page was cached)`
);
}
try {
const pageAndPropsResult = await resolvePageAndProps({
pagePath,
propsPath,
pageExportName,
propsExportName,
url: normalizedUrl,
loader,
verbose: verbose2 || false,
logger: logger2
});
if (pageAndPropsResult.type === "success") {
pageProps = pageAndPropsResult.pageProps;
if (verbose2) {
logger2?.info(
`[rsc-worker] Loaded fresh pageProps for URL: ${url}`
);
}
} else {
if (verbose2) {
logger2?.warn(
`[rsc-worker] Failed to load props for URL ${url}: ${pageAndPropsResult.error?.message}`
);
}
pageProps = {};
}
} catch (error) {
if (verbose2) {
logger2?.error(
`[rsc-worker] Error loading props for URL ${url}`,
{ error }
);
}
pageProps = {};
}
}
}
if (rootPath) {
const rootId = `${rootPath}#${rootExportName}`;
const isRootInvalidated = isModuleInvalidated(rootPath);
if (hasCachedComponent(rootId) && !isRootInvalidated) {
RootComponent = getCachedComponent(rootId);
if (verbose2) {
logger2?.info(
`[rsc-worker] Using cached Root component from: ${rootPath}`
);
}
} else {
if (isRootInvalidated && hasCachedComponent(rootId)) {
clearCachedComponent(rootId);
if (verbose2) {
logger2?.info(
`[rsc-worker] Cleared invalidated Root component cache: ${rootPath}`
);
}
}
const rootResult = await resolveComponent({
componentPath: rootPath,
exportName: rootExportName,
loader
});
if (rootResult.type === "success") {
RootComponent = rootResult.component;
cacheComponent(rootId, RootComponent);
if (verbose2) {
logger2?.info(
`[rsc-worker] Loaded and cached Root component from: ${rootPath}`
);
logger2?.info(
`[rsc-worker] Root component type: ${typeof RootComponent}, isSymbol: ${typeof RootComponent === "symbol"}, keys: ${RootComponent ? Object.keys(RootComponent) : "null"}`
);
}
} else {
const rootError = rootResult.error ?? new Error(
`[rsc-worker] Failed to load Root component from ${rootPath}`
);
const panicError = handleError({
error: rootError,
logger: logger2,
panicThreshold,
critical: true,
log: true
});
throw panicError ?? rootError;
}
}
} else {
RootComponent = Root;
if (verbose2) {
logger2?.info(`[rsc-worker] Using built-in default Root component`);
}
}
if (verbose2) {
logger2?.info(`[rsc-worker] Html component resolution: htmlPath='${htmlPath}' (type: ${typeof htmlPath})`);
}
if (htmlPath === "") {
HtmlComponent = React.Fragment;
if (verbose2) {
logger2?.info(`[rsc-worker] Using headless Html component (empty string)`);
}
} else if (htmlPath === void 0) {
if (verbose2) {
logger2?.info(`[rsc-worker] htmlPath is undefined, using default Html component`);
}
try {
const { Html } = await import('../../components/html.js');
HtmlComponent = Html;
if (verbose2) {
logger2?.info(`[rsc-worker] Successfully loaded default Html component`);
}
} catch (error) {
logger2?.warn(
`[rsc-worker] Error loading default Html component: ${error instanceof Error ? error.message : String(error)}`
);
HtmlComponent = React.Fragment;
}
} else if (htmlPath) {
if (verbose2) {
logger2?.info(`[rsc-worker] Attempting to load custom Html component from: ${htmlPath}`);
}
const htmlId = `${htmlPath}#${htmlExportName}`;
const isHtmlInvalidated = isModuleInvalidated(htmlPath);
if (hasCachedComponent(htmlId) && !isHtmlInvalidated) {
HtmlComponent = getCachedComponent(htmlId);
if (verbose2) {
logger2?.info(
`[rsc-worker] Using cached Html component from: ${htmlPath}`
);
}
} else {
if (isHtmlInvalidated && hasCachedComponent(htmlId)) {
clearCachedComponent(htmlId);
if (verbose2) {
logger2?.info(
`[rsc-worker] Cleared invalidated Html component cache: ${htmlPath}`
);
}
}
if (verbose2) {
logger2?.info(`[rsc-worker] Component not cached, calling resolveComponent with path: ${htmlPath}, exportName: ${htmlExportName}`);
}
const htmlResult = await resolveComponent({
componentPath: htmlPath,
exportName: htmlExportName,
loader
});
if (htmlResult.type === "success") {
HtmlComponent = htmlResult.component;
cacheComponent(htmlId, HtmlComponent);
if (verbose2) {
logger2?.info(
`[rsc-worker] Loaded and cached Html component from: ${htmlPath}`
);
}
} else {
if (verbose2) {
logger2?.warn(
`[rsc-worker] Failed to load Html component from ${htmlPath}: ${htmlResult.error?.message || "Unknown error"}`
);
logger2?.warn(`[rsc-worker] Html resolution error details:`, htmlResult.error);
}
HtmlComponent = React.Fragment;
}
}
} else {
try {
const { Html } = await import('../../components/html.js');
HtmlComponent = Html;
if (verbose2) {
logger2?.info(`[rsc-worker] Using default Html component`);
}
} catch (error) {
logger2?.warn(
`[rsc-worker] Error loading default Html component: ${error}`
);
}
}
return { PageComponent, pageProps, RootComponent, HtmlComponent };
}
let storedFromWorker;
let storedToWorker;
async function messageHandler(msg) {
if (msg.type === "INIT") {
storedFromWorker = msg.dataPort;
storedToWorker = msg.controlPort;
if (storedFromWorker) {
setMaxListenersOnPort(storedFromWorker, 500);
}
if (storedToWorker) {
setMaxListenersOnPort(storedToWorker, 500);
}
}
const effectiveHandlers = createHandlers(storedFromWorker, storedToWorker);
try {
if (verbose) {
logger.info(
`[rsc-worker] Received message: ${msg.type} for id: ${msg.id}`
);
}
switch (msg.type) {
case "INIT":
const currentStreamId = msg.options.id || msg.id;
const renderUserOptions = workerUserOptions(msg.options);
setRenderUserOptions(currentStreamId, renderUserOptions);
if (verbose) {
logger?.info(
`[INIT] Set render state for renderId: ${currentStreamId}`
);
logger?.info(
`[INIT] renderUserOptions.build: ${JSON.stringify(
renderUserOptions.build
)}`
);
}
const userOptions = renderUserOptions;
cleanupRender(currentStreamId);
const isHeadless = msg.options.htmlPath === "";
const rscVariant = isHeadless ? "rsc-headless" : "rsc-full";
if (verbose) {
logger.info(
`[rsc-worker] Processing ${rscVariant} render for route: ${msg.options.route} with ID: ${currentStreamId}`
);
}
const rscStream = msg.rscStream || new PassThrough();
activeRenders.set(currentStreamId, rscStream);
const moduleResolutionStartTime = performance.now();
const projectRoot = msg.options.projectRoot || userOptions.projectRoot || workerData.userOptions?.projectRoot || process.cwd();
const buildConfig = {
server: msg.options.build?.server || userOptions.build?.server || DEFAULT_CONFIG.BUILD.server,
client: msg.options.build?.client || userOptions.build?.client || DEFAULT_CONFIG.BUILD.client,
static: msg.options.build?.static || userOptions.build?.static || DEFAULT_CONFIG.BUILD.static,
outDir: msg.options.build?.outDir || userOptions.build?.outDir || DEFAULT_CONFIG.BUILD.outDir,
assetsDir: msg.options.build?.assetsDir || userOptions.build?.assetsDir || DEFAULT_CONFIG.BUILD.assetsDir,
rscOutputPath: msg.options.build?.rscOutputPath || userOptions.build?.rscOutputPath || DEFAULT_CONFIG.BUILD.rscOutputPath,
htmlOutputPath: msg.options.build?.htmlOutputPath || userOptions.build?.htmlOutputPath || DEFAULT_CONFIG.BUILD.htmlOutputPath,
preserveModulesRoot: msg.options.build?.preserveModulesRoot || userOptions.build?.preserveModulesRoot || DEFAULT_CONFIG.BUILD.preserveModulesRoot
};
const serverManifest = workerData.serverManifest || msg.options.manifest || {};
if (verbose) {
logger?.info(
`[rsc-worker:${msg.options.route}]
msg.options.build: ${JSON.stringify(msg.options.build)}
msg.options.route
userOptions.build: ${JSON.stringify(userOptions.build)}
final buildConfig: ${JSON.stringify(buildConfig)}`
);
}
const loader = createRscWorkerLoader({
verbose: msg.options.verbose ?? workerData.userOptions.verbose,
logger,
projectRoot,
manifest: serverManifest,
build: buildConfig
// bundle: workerData.bundle || {},
});
const url = msg.options.url || routeToURL(
msg.options.route,
userOptions.moduleBaseURL,
userOptions.build?.rscOutputPath ?? DEFAULT_CONFIG.BUILD.rscOutputPath
);
if (verbose) {
logger?.info(`[rsc-worker:messageHandler] Component paths for route ${msg.options.route}:`);
logger?.info(` pagePath: ${msg.options.pagePath}`);
logger?.info(` propsPath: ${msg.options.propsPath}`);
logger?.info(` rootPath: ${msg.options.rootPath}`);
logger?.info(` htmlPath: ${msg.options.htmlPath}`);
}
const { PageComponent, pageProps, RootComponent, HtmlComponent } = await loadComponentsWithCache({
pagePath: msg.options.pagePath,
propsPath: msg.options.propsPath,
rootPath: msg.options.rootPath,
htmlPath: msg.options.htmlPath,
pageExportName: msg.options.pageExportName ?? userOptions.pageExportName,
propsExportName: msg.options.propsExportName ?? userOptions.propsExportName,
rootExportName: msg.options.rootExportName ?? userOptions.rootExportName,
htmlExportName: msg.options.htmlExportName ?? userOptions.htmlExportName,
url,
loader,
verbose,
logger,
panicThreshold: msg.options.panicThreshold,
resolvedPageProps: msg.options.resolvedPageProps
// Pre-resolved from main thread
});
if (verbose) {
logger.info(
`[rsc-worker] Loaded components for route ${msg.options.route}:`
);
logger.info(`[rsc-worker] - PageComponent: ${typeof PageComponent}`);
logger.info(`[rsc-worker] - pageProps: ${JSON.stringify(pageProps)}`);
logger.info(`[rsc-worker] - RootComponent: ${typeof RootComponent}`);
logger.info(`[rsc-worker] - HtmlComponent: ${typeof HtmlComponent}`);
}
const moduleResolutionTime = performance.now() - moduleResolutionStartTime;
if (effectiveHandlers.onMetrics) {
const moduleResolutionMetric = createModuleResolutionMetrics({
route: msg.options.route,
workerType: "rsc",
resolutionTime: moduleResolutionTime,
fromMainThread: false,
fromRscWorker: true,
fromHtmlWorker: false,
description: `Module resolution for route ${msg.options.route}`
});
effectiveHandlers.onMetrics(msg.id, moduleResolutionMetric);
}
const rpc = getRpc();
if (rpc && msg.options.pagePath) {
try {
const cssFileUserOptions = getUserOptions();
const collected = await rpc("collectCss", [msg.options.pagePath, projectRoot]) || [];
for (const { id, code } of collected) {
addCssFileContent(id, code, cssFileUserOptions);
}
if (verbose) {
logger?.info(
`[rsc-worker] runner CSS bridge: collected ${collected.length} file(s) for ${msg.options.pagePath}`
);
}
} catch (err) {
if (verbose) {
logger?.warn(
`[rsc-worker] runner CSS bridge failed: ${String(err)}`
);
}
}
}
const messageCssFiles = new Map(msg.options.cssFiles || []);
processInlineCssForState(
messageCssFiles,
addCssFileContent,
userOptions
);
const combinedCssFiles = combineCssFiles(cssFiles, messageCssFiles);
const handlerOptions = {
route: msg.options.route,
url,
pageProps,
PageComponent,
RootComponent,
HtmlComponent,
cssFiles: combinedCssFiles,
globalCss: msg.options.globalCss ?? /* @__PURE__ */ new Map(),
manifest: msg.options.manifest || {},
projectRoot: userOptions.projectRoot || workerData.userOptions?.projectRoot || process.cwd(),
moduleBase: userOptions.moduleBase || workerData.userOptions?.moduleBase || DEFAULT_CONFIG.MODULE_BASE,
moduleBasePath: userOptions.moduleBasePath || workerData.userOptions?.moduleBasePath || DEFAULT_CONFIG.MODULE_BASE_PATH,
moduleBaseURL: userOptions.moduleBaseURL || workerData.userOptions?.moduleBaseURL || DEFAULT_CONFIG.MODULE_BASE_URL,
moduleRootPath: userOptions.moduleRootPath || workerData.userOptions?.moduleRootPath,
verbose: msg.options.verbose || verbose,
logger,
panicThreshold: msg.options.panicThreshold || "none",
rscTimeout: msg.options.rscTimeout,
serverPipeableStreamOptions: (() => {
const options = msg.options.serverPipeableStreamOptions || workerData.userOptions?.serverPipeableStreamOptions;
if (verbose) {
logger.info(
`[rsc-worker] ${isHeadless ? "Headless" : "Full"} stream serverPipeableStreamOptions: ${JSON.stringify(
options
)}`
);
}
return options;
})()
};
let result;
const headlessError = headlessStreamErrors.get(msg.options.route);
const shouldUseFallback = headlessError && !isHeadless;
if (isHeadless) {
headlessStreamErrors.delete(msg.options.route);
}
const shouldReuseElements = !isHeadless && msg.options.reuseHeadlessStreamId;
let reusableElements = null;
if (shouldReuseElements && msg.options.reuseHeadlessStreamId) {
reusableElements = headlessStreamElements.get(
msg.options.reuseHeadlessStreamId
);
if (verbose) {
if (reusableElements) {
logger.info(
`[rsc-worker] Found reusable elements from headless stream ${msg.options.reuseHeadlessStreamId}`
);
} else if (msg.options.reuseHeadlessStreamId) {
logger.info(
`[rsc-worker] No reusable elements found for headless stream ${msg.options.reuseHeadlessStreamId}`
);
}
}
}
const isStaticGeneration = msg.options.build && (msg.options.build.outDir || msg.options.build.static);
if (verbose) {
logger.info(
`[rsc-worker] Route: ${msg.options.route}, isHeadless: ${isHeadless}, isStaticGeneration: ${isStaticGeneration}`
);
logger.info(
`[rsc-worker] msg.options.build: ${JSON.stringify(
msg.options.build
)}`
);
}
if (shouldUseFallback) {
if (verbose) {
logger.info(
`[rsc-worker] Using fallback components due to previous error: ${headlessError.message}`
);
}
const fallbackHandlerOptions = {
...handlerOptions,
PageComponent: React.Fragment
// Keep the original HtmlComponent to ensure bootstrapScripts are rendered
};
result = handleRscRender(
{
...fallbackHandlerOptions,
id: currentStreamId,
reuseHeadlessStreamId: msg.options.reuseHeadlessStreamId
},
effectiveHandlers,
void 0,
headlessStreamElements,
headlessStreamErrors
);
} else {
try {
if (isHeadless) {
if (verbose) {
logger.info(
`[rsc-worker] Creating headless RSC stream for route ${msg.options.route} (no HTML wrapper)`
);
}
const headlessHandlerOptions = {
...handlerOptions,
HtmlComponent: React.Fragment
// Headless RSC - no HTML wrapper
};
result = handleRscRender(
{
...headlessHandlerOptions,
id: currentStreamId,
reuseHeadlessStreamId: msg.options.reuseHeadlessStreamId
},
effectiveHandlers,
void 0,
headlessStreamElements,
headlessStreamErrors
);
} else {
if (shouldReuseElements && reusableElements) {
if (verbose) {
logger.info(
`[rsc-worker] Creating full RSC stream with reused elements from headless stream for route ${msg.options.route}`
);
}
const reuseHandlerOptions = {
...handlerOptions,
PageComponent: () => reusableElements
// Reuse the headless stream's elements
};
result = handleRscRender(
{
...reuseHandlerOptions,
id: currentStreamId,
reuseHeadlessStreamId: msg.options.reuseHeadlessStreamId
},
effectiveHandlers,
void 0,
headlessStreamElements,
headlessStreamErrors
);
} else {
if (verbose) {
logger.info(
`[rsc-worker] Creating full RSC stream for route ${msg.options.route} (no element reuse)`
);
}
result = handleRscRender(
{
...handlerOptions,
id: currentStreamId,
reuseHeadlessStreamId: msg.options.reuseHeadlessStreamId
},
effectiveHandlers,
void 0,
headlessStreamElements,
headlessStreamErrors
);
}
}
} catch (error) {
if (verbose) {
logger.warn(
`[rsc-worker] Original PageComponent failed during creation for route ${msg.options.route}: ${error}`
);
}
const fallbackHandlerOptions = {
...handlerOptions,
PageComponent: React.Fragment
// Keep the original HtmlComponent to ensure bootstrapScripts are rendered
};
result = handleRscRender(
{
...fallbackHandlerOptions,
id: currentStreamId,
reuseHeadlessStreamId: msg.options.reuseHeadlessStreamId
},
effectiveHandlers,
void 0,
headlessStreamElements,
headlessStreamErrors
);
}
}
if (verbose) {
logger.info(
`[rsc-worker] Processing single-use stream ${currentStreamId} for route: ${msg.options.route}`
);
}
clearHeadlessError(msg.options.route);
return result;
case "RESOLVE_COMPONENTS": {
const resolutionStartTime = performance.now();
const resolveUserOptions = getUserOptions(msg.id);
try {
if (verbose) {
logger.info(
`[rsc-worker] Resolving components for route: ${msg.route}`
);
logger.info(`[rsc-worker] pagePath: ${msg.pagePath}`);
logger.info(`[rsc-worker] propsPath: ${msg.propsPath}`);
logger.info(`[rsc-worker] rootPath: ${msg.rootPath}`);
logger.info(`[rsc-worker] htmlPath: ${msg.htmlPath}`);
}
if (verbose) {
logger?.info(
`[rsc-worker:${msg.route}] pagePath=${msg.pagePath}, propsPath=${msg.propsPath}`
);
}
const buildConfig2 = {
server: resolveUserOptions.build?.server || "server",
client: resolveUserOptions.build?.client || "client",
static: resolveUserOptions.build?.static || "static",
outDir: resolveUserOptions.build?.outDir || "dist"
};
if (verbose) {
logger?.info(
`[RESOLVE_COMPONENTS] Using outDir: ${buildConfig2.outDir}`
);
logger?.info(
`[RESOLVE_COMPONENTS] renderId: ${msg.id}, hasRenderState: ${renderStates.has(msg.id)}`
);
if (renderStates.has(msg.id)) {
const renderState = renderStates.get(msg.id);
logger?.info(
`[RESOLVE_COMPONENTS] renderState.initialized: ${renderState.initialized}`
);
logger?.info(
`[RESOLVE_COMPONENTS] renderState.userOptions.build: ${JSON.stringify(
renderState.userOptions.build
)}`
);
}
}
const loader2 = createRscWorkerLoader({
verbose,
logger,
projectRoot: workerData.userOptions?.projectRoot,
manifest: workerData.serverManifest || {},
build: buildConfig2
});
await loadComponentsWithCache({
pagePath: msg.pagePath,
propsPath: msg.propsPath,
rootPath: msg.rootPath,
htmlPath: msg.htmlPath,
pageExportName: msg.pageExportName || "default",
propsExportName: msg.propsExportName || "props",
rootExportName: msg.rootExportName || "Root",
htmlExportName: msg.htmlExportName || "Html",
url: `/${msg.route}`,
// Simple URL for component resolution
loader: loader2,
verbose,
logger
});
const resolutionTime = performance.now() - resolutionStartTime;
if (verbose) {
logger.info(
`[rsc-worker] Components resolved for route: ${msg.route} in ${resolutionTime.toFixed(2)}ms`
);
}
const response = {
type: "COMPONENTS_RESOLVED",
id: msg.id,
route: msg.route,
resolutionTime
};
parentPort?.postMessage(response);
} catch (error) {
logger.error(
`[rsc-worker] Failed to resolve components for route ${msg.route}: ${error}`
);
effectiveHandlers.onError(msg.id, toError(error), {
route: msg.route,
context: `Component resolution failed for route ${msg.route}`
});
}
return;
}
case "SERVER_ACTION": {
try {
const serverActionUserOptions = getUserOptions();
const [filePath2, exportName] = msg.id.split("#");
if (!filePath2 || !exportName) {
throw new Error(
`Invalid server action ID format: ${msg.id}. Expected format: "path/to/file.ts#exportName"`
);
}
const actionPath = filePath2.startsWith(
serverActionUserOptions.moduleBasePath
) ? filePath2.slice(serverActionUserOptions.moduleBasePath.length) : filePath2;
const fullPath = join(
workerData.userOptions?.projectRoot,
actionPath
);
const module = await import(fullPath);
const action = module[exportName];
if (typeof action !== "function") {
throw new Error(`Server action not found: ${msg.id}`);
}
let decodedArgs = msg.args;
if (msg.args.length === 1 && typeof msg.args[0] === "string") {
try {
const moduleBasePath = serverActionUserOptions.moduleBasePath ?? "/";
decodedArgs = await decodeReply(msg.args[0], moduleBasePath);
if (verbose) {
logger?.info(`[rsc-worker] Decoded server action args: ${JSON.stringify(decodedArgs)}`);
}
} catch {
if (verbose) {
logger?.info(`[rsc-worker] Using raw server action args`);
}
}
}
const result2 = await action(...decodedArgs);
effectiveHandlers.onServerActionResponse?.(msg.id, result2);
} catch (error) {
const errorMessage = toError(error).message;
effectiveHandlers.onServerActionResponse?.(
msg.id,
void 0,
errorMessage
);
}
return;
}
case "INITIALIZED_REACT_LOADER":
case "INITIALIZED_CSS_LOADER":
case "INITIALIZED_ENV_LOADER":
return;
case "HMR_UPDATE":
const filePath = msg.path || msg.id;
const hmrProjectRoot = workerData.userOptions?.projectRoot || process.cwd();
const normalizedPath = filePath.startsWith(hmrProjectRoot) ? relative(hmrProjectRoot, filePath) : filePath;
hmrState.set(normalizedPath, {
timestamp: msg.timestamp || Date.now(),
invalidated: true,
routes: msg.routes || []
});
if (verbose) {
logger?.info(
`[rsc-worker] HMR_UPDATE: Invalidated module ${normalizedPath} (from ${filePath})`
);
}
if (headlessStreamElements.size > 0) {
if (verbose) {
logger?.info(
`[rsc-worker] HMR_UPDATE: Clearing ${headlessStreamElements.size} cached headless stream elements`
);
}
headlessStreamElements.clear();
}
clearAllCachedComponents();
if (verbose) {
logger?.info(
`[rsc-worker] HMR_UPDATE: Cleared all cached components from temporaryReferences`
);
}
const runner = getRunner();
if (runner) {
runner.clearCache();
if (verbose) {
logger?.info(
`[rsc-worker] HMR_UPDATE: runner cache cleared (trigger: ${filePath})`
);
}
}
headlessStreamErrors.clear();
effectiveHandlers.onHmrUpdate(normalizedPath, msg.routes || []);
return;
case "ABORT":
return;
case "HMR_CLEANUP":
hmrState.delete(msg.id);
effectiveHandlers.onHmrAccept(msg.id, msg.routes || []);
return;
case "CSS_FILE":
if (msg.id) {
const cssFileUserOptions = getUserOptions();
addCssFileContent(msg.id, msg.content, cssFileUserOptions);
}
effectiveHandlers.onCssFile?.(msg.id, msg.content);
return;
case "SERVER_MODULE":
addModuleId(msg.id, msg.url);
effectiveHandlers.onServerModule?.(msg.id, msg.url, msg.source);
return;
case "MODULE_REQUEST": {
const { id, path } = msg;
try {
const module = await import(join(workerData.userOptions?.projectRoot, path));
effectiveHandlers.onServerModule?.(id, path, module);
} catch (error) {
effectiveHandlers.onError(id, toError(error));
}
return;
}
case "SHUTDOWN": {
effectiveHandlers.onShutdown?.(msg.id);
if (parentPort) {
sendMessage(
{
type: "SHUTDOWN_COMPLETE",
id: msg.id
},
parentPort
);
}
return;
}
default: {
logger.info(`Unknown message: ${msg.type}`);
return;
}
}
} catch (error) {
effectiveHandlers.onError("worker/rsc", toError(error));
effectiveHandlers.onEnd?.("worker/rsc");
effectiveHandlers.onShutdown?.("*");
}
}
export { messageHandler };
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWVzc2FnZUhhbmRsZXIuc2VydmVyLmpzIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9wbHVnaW4vd29ya2VyL3JzYy9tZXNzYWdlSGFuZGxlci5zZXJ2ZXIudHN4Il0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IHBhcmVudFBvcnQsIHdvcmtlckRhdGEgfSBmcm9tIFwibm9kZTp3b3JrZXJfdGhyZWFkc1wiO1xuaW1wb3J0IHsgUGFzc1Rocm91Z2ggfSBmcm9tIFwibm9kZTpzdHJlYW1cIjtcbmltcG9ydCB7IGNyZWF0ZUxvZ2dlciB9IGZyb20gXCJ2aXRlXCI7XG5pbXBvcnQgeyBqb2luLCByZWxhdGl2ZSB9IGZyb20gXCJub2RlOnBhdGhcIjtcbmltcG9ydCB7IGNyZWF0ZVJzY1dvcmtlckxvYWRlciB9IGZyb20gXCIuL2NyZWF0ZVJzY1dvcmtlckxvYWRlci5qc1wiO1xuaW1wb3J0IHsgaGFuZGxlUnNjUmVuZGVyIH0gZnJvbSBcIi4vaGFuZGxlUnNjUmVuZGVyLnNlcnZlci5qc1wiO1xuaW1wb3J0IHsgc2V0TWF4TGlzdGVuZXJzT25Qb3J0IH0gZnJvbSBcIi4uLy4uL3N0cmVhbS9zZXRNYXhMaXN0ZW5lcnMuanNcIjtcbmltcG9ydCB0eXBlIHtcbiAgUnNjV29ya2VySW5wdXRNZXNzYWdlLFxuICBDb21wb25lbnRzUmVzb2x2ZWRNZXNzYWdlLFxufSBmcm9tIFwiLi90eXBlcy5qc1wiO1xuaW1wb3J0IHsgdG9FcnJvciB9IGZyb20gXCIuLi8uLi9lcnJvci90b0Vycm9yLmpzXCI7XG4vLyBJbXBvcnQgZGVjb2RlUmVwbHkgZm9yIHNlcnZlciBhY3Rpb24gYXJndW1lbnQgZGVjb2RpbmdcbmltcG9ydCB7IGRlY29kZVJlcGx5IH0gZnJvbSBcInJlYWN0LXNlcnZlci1kb20tZXNtL3NlcnZlclwiO1xuXG5pbXBvcnQgeyBjcmVhdGVIYW5kbGVycyB9IGZyb20gXCIuL2hhbmRsZXJzLmpzXCI7XG5pbXBvcnQge1xuICBhZGRDc3NGaWxlQ29udGVudCxcbiAgYWRkTW9kdWxlSWQsXG4gIGNzc0ZpbGVzLFxuICBobXJTdGF0ZSxcbiAgY2FjaGVDb21wb25lbnQsXG4gIGdldENhY2hlZENvbXBvbmVudCxcbiAgaGFzQ2FjaGVkQ29tcG9uZW50LFxuICBjbGVhckNhY2hlZENvbXBvbmVudCxcbiAgaXNNb2R1bGVJbnZhbGlkYXRlZCxcbiAgY2xlYXJBbGxDYWNoZWRDb21wb25lbnRzLFxufSBmcm9tIFwiLi9zdGF0ZS5zZXJ2ZXIuanNcIjtcbmltcG9ydCB7IGdldFJ1bm5lciwgZ2V0UnBjIH0gZnJvbSBcIi4vcnVubmVySW5zdGFuY2UuanNcIjtcbmltcG9ydCB7XG4gIGNvbWJpbmVDc3NGaWxlcyxcbiAgcHJvY2Vzc0lubGluZUNzc0ZvclN0YXRlLFxufSBmcm9tIFwiLi4vLi4vaGVscGVycy9jcmVhdGVVbmlmaWVkQ3NzUHJvY2Vzc29yLmpzXCI7XG5pbXBvcnQgeyByb3V0ZVRvVVJMIH0gZnJvbSBcIi4uLy4uL3V0aWxzL3JvdXRlVG9VUkwuanNcIjtcbmltcG9ydCB7IERFRkFVTFRfQ09ORklHIH0gZnJvbSBcIi4uLy4uL2NvbmZpZy9kZWZhdWx0cy5qc1wiO1xuaW1wb3J0IHsgcmVzb2x2ZVBhZ2VBbmRQcm9wcyB9IGZyb20gXCIuLi8uLi9oZWxwZXJzL3Jlc29sdmVQYWdlQW5kUHJvcHMuanNcIjtcbmltcG9ydCB7IHJlc29sdmVDb21wb25lbnQgfSBmcm9tIFwiLi4vLi4vaGVscGVycy9yZXNvbHZlQ29tcG9uZW50LmpzXCI7XG5pbXBvcnQgeyBoYW5kbGVFcnJvciB9IGZyb20gXCIuLi8uLi9lcnJvci9oYW5kbGVFcnJvci5qc1wiO1xuaW1wb3J0IHR5cGUgeyBQYW5pY1RocmVzaG9sZCB9IGZyb20gXCIuLi8uLi90eXBlcy5qc1wiO1xuaW1wb3J0IHsgUm9vdCBhcyBEZWZhdWx0Um9vdCB9IGZyb20gXCIuLi8uLi9jb21wb25lbnRzL3Jvb3QuanNcIjtcbmltcG9ydCB7IHdvcmtlclVzZXJPcHRpb25zIH0gZnJvbSBcIi4vd29ya2VyVXNlck9wdGlvbnMuanNcIjtcbmltcG9ydCB7IGh5ZHJhdGVVc2VyT3B0aW9ucyB9IGZyb20gXCIuLi8uLi9oZWxwZXJzL2h5ZHJhdGVVc2VyT3B0aW9ucy5qc1wiO1xuaW1wb3J0IHsgUmVhY3QgfSBmcm9tIFwiLi4vLi4vdmVuZG9yL3ZlbmRvci5zZXJ2ZXIuanNcIjtcbmltcG9ydCB7IHNlbmRNZXNzYWdlIH0gZnJvbSBcIi4uL3NlbmRNZXNzYWdlLmpzXCI7XG5pbXBvcnQgeyBjcmVhdGVNb2R1bGVSZXNvbHV0aW9uTWV0cmljcyB9IGZyb20gXCIuLi8uLi9tZXRyaWNzL2NyZWF0ZU1vZHVsZVJlc29sdXRpb25NZXRyaWNzLmpzXCI7XG5cbmNvbnN0IGxvZ2dlciA9IGNyZWF0ZUxvZ2dlcih3b3JrZXJEYXRhLnJlc29sdmVkQ29uZmlnPy5sb2dMZXZlbCA/PyBcImluZm9cIik7XG5jb25zdCB2ZXJib3NlID0gd29ya2VyRGF0YS51c2VyT3B0aW9ucy52ZXJib3NlID8/IGZhbHNlO1xuXG4vLyBBZGQgdW5jYXVnaHQgZXhjZXB0aW9uIGhhbmRsZXIgdG8gY2F0Y2ggUmVhY3QgcmVuZGVyaW5nIGVycm9yc1xucHJvY2Vzcy5vbihcInVuY2F1Z2h0RXhjZXB0aW9uXCIsIChlcnJvcikgPT4ge1xuICBpZiAodmVyYm9zZSkge1xuICAgIGxvZ2dlcj8uZXJyb3IoYFtSU0MtV09SS0VSXSBVbmNhdWdodCBleGNlcHRpb246ICR7ZXJyb3IubWVzc2FnZX1gLCB7XG4gICAgICBlcnJvcixcbiAgICB9KTtcbiAgfVxuXG4gIC8vIFNlbmQgZXJyb3IgdGhyb3VnaCBjb250cm9sIHBvcnQgaWYgd2UgaGF2ZSBhY3RpdmUgaGFuZGxlcnNcbiAgaWYgKHBhcmVudFBvcnQpIHtcbiAgICBwYXJlbnRQb3J0LnBvc3RNZXNzYWdlKHtcbiAgICAgIHR5cGU6IFwiRVJST1JcIixcbiAgICAgIGlkOiBcInVuY2F1Z2h0LWV4Y2VwdGlvblwiLFxuICAgICAgZXJyb3I6IHtcbiAgICAgICAgbWVzc2FnZTogZXJyb3IubWVzc2FnZSxcbiAgICAgICAgc3RhY2s6IGVycm9yLnN0YWNrLFxuICAgICAgICBuYW1lOiBlcnJvci5uYW1lLFxuICAgICAgfSxcbiAgICAgIGNvbnRleHQ6IFwiVW5jYXVnaHQgRXhjZXB0aW9uIGluIFJTQyBXb3JrZXJcIixcbiAgICB9KTtcbiAgfVxuXG4gIC8vIEV4aXQgdGhlIHdvcmtlciB3aXRoIGVycm9yIGNvZGVcbiAgcHJvY2Vzcy5leGl0KDEpO1xufSk7XG5cbi8vIFJlbmRlciBzdGF0ZSB0byB0cmFjayB1c2VyIG9wdGlvbnMgcGVyIHJlbmRlciBJRFxuY29uc3QgcmVuZGVyU3RhdGVzID0gbmV3IE1hcDxcbiAgc3RyaW5nLFxuICB7XG4gICAgdXNlck9wdGlvbnM6IGFueTtcbiAgICBpbml0aWFsaXplZDogYm9vbGVhbjtcbiAgfVxuPigpO1xuXG4vLyBDYWNoZSBmb3IgZmFsbGJhY2sgdXNlciBvcHRpb25zIHRvIGF2b2lkIHJlLWh5ZHJhdGluZ1xubGV0IGZhbGxiYWNrVXNlck9wdGlvbnM6IGFueSA9IG51bGw7XG5cbi8vIEhlbHBlciBmdW5jdGlvbiB0byBnZXQgdXNlciBvcHRpb25zIGZvciBhIHNwZWNpZmljIHJlbmRlclxuZnVuY3Rpb24gZ2V0VXNlck9wdGlvbnMocmVuZGVySWQ/OiBzdHJpbmcpIHtcbiAgLy8gSWYgd2UgaGF2ZSBhIHJlbmRlciBJRCBhbmQgaXQncyBpbml0aWFsaXplZCwgdXNlIHRoZSByZW5kZXItc3BlY2lmaWMgb3B0aW9uc1xuICBpZiAocmVuZGVySWQgJiYgcmVuZGVyU3RhdGVzLmhhcyhyZW5kZXJJZCkpIHtcbiAgICBjb25zdCByZW5kZXJTdGF0ZSA9IHJlbmRlclN0YXRlcy5nZXQocmVuZGVySWQpITtcbiAgICBpZiAocmVuZGVyU3RhdGUuaW5pdGlhbGl6ZWQpIHtcbiAgICAgIHJldHVybiByZW5kZXJTdGF0ZS51c2VyT3B0aW9ucztcbiAgICB9XG4gIH1cblxuICAvLyBVc2UgY2FjaGVkIGZhbGxiYWNrIGlmIGF2YWlsYWJsZVxuICBpZiAoZmFsbGJhY2tVc2VyT3B0aW9ucykge1xuICAgIHJldHVybiBmYWxsYmFja1VzZXJPcHRpb25zO1xuICB9XG5cbiAgLy8gRmFsbGJhY2sgdG8gd29ya2VyRGF0YSB1c2VyT3B0aW9ucyBpZiBubyByZW5kZXItc3BlY2lmaWMgb3B0aW9ucyBhcmUgYXZhaWxhYmxlXG4gIGNvbnN0IHVzZXJPcHRpb25zUmVzdWx0ID0gaHlkcmF0ZVVzZXJPcHRpb25zKHdvcmtlckRhdGEudXNlck9wdGlvbnMpO1xuICBpZiAodXNlck9wdGlvbnNSZXN1bHQudHlwZSA9PT0gXCJlcnJvclwiKSB7XG4gICAgdGhyb3cgdXNlck9wdGlvbnNSZXN1bHQuZXJyb3I7XG4gIH1cbiAgLy8gQ2FjaGUgdGhlIHJlc3VsdFxuICBmYWxsYmFja1VzZXJPcHRpb25zID0gdXNlck9wdGlvbnNSZXN1bHQudXNlck9wdGlvbnM7XG4gIGlmICh2ZXJib3NlKSB7XG4gICAgbG9nZ2VyPy5pbmZvKFxuICAgICAgYFtGQUxMQkFDS10gVXNpbmcgZmFsbGJhY2sgdXNlck9wdGlvbnMgZm9yIHJlbmRlcklkOiAke1xuICAgICAgICByZW5kZXJJZCB8fCBcIm5vbmVcIlxuICAgICAgfWBcbiAgICApO1xuICAgIGxvZ2dlcj8uaW5mbyhcbiAgICAgIGBbRkFMTEJBQ0tdIGZhbGxiYWNrVXNlck9wdGlvbnMuYnVpbGQ6ICR7SlNPTi5zdHJpbmdpZnkoXG4gICAgICAgIGZhbGxiYWNrVXNlck9wdGlvbnMuYnVpbGRcbiAgICAgICl9YFxuICAgICk7XG4gIH1cbiAgcmV0dXJuIGZhbGxiYWNrVXNlck9wdGlvbnM7XG59XG5cbi8vIEhlbHBlciBmdW5jdGlvbiB0byBzZXQgdXNlciBvcHRpb25zIGZvciBhIHNwZWNpZmljIHJlbmRlclxuZnVuY3Rpb24gc2V0UmVuZGVyVXNlck9wdGlvbnMocmVuZGVySWQ6IHN0cmluZywgdXNlck9wdGlvbnM6IGFueSkge1xuICByZW5kZXJTdGF0ZXMuc2V0KHJlbmRlcklkLCB7XG4gICAgdXNlck9wdGlvbnMsXG4gICAgaW5pdGlhbGl6ZWQ6IHRydWUsXG4gIH0pO1xufVxuXG4vLyBIZWxwZXIgZnVuY3Rpb24gdG8gY2xlYW51cCByZW5kZXIgc3RhdGVcbmZ1bmN0aW9uIGNsZWFudXBSZW5kZXJTdGF0ZShyZW5kZXJJZDogc3RyaW5nKSB7XG4gIHJlbmRlclN0YXRlcy5kZWxldGUocmVuZGVySWQpO1xufVxuXG4vLyBUcmFjayBhY3RpdmUgcmVuZGVycyAtIHN0b3JlIHRoZSBSU0Mgc3RyZWFtIGZvciBlYWNoIHVuaXF1ZSByZW5kZXIgKGlkKVxuY29uc3QgYWN0aXZlUmVuZGVycyA9IG5ldyBNYXA8c3RyaW5nLCBQYXNzVGhyb3VnaD4oKTtcblxuLy8gR2xvYmFsIHZhcmlhYmxlIHRvIHN0b3JlIHRoZSBjb3JyZWN0IGJ1aWxkIGNvbmZpZ3VyYXRpb24gZnJvbSBJTklUIGNhc2Vcbi8vIGxldCBjb3JyZWN0QnVpbGRDb25maWc6IGFueSA9IG51bGw7XG5cbi8vIFRyYWNrIGFjdGl2ZSBzdHJlYW1zIGJ5IHJvdXRlIGZvciByZXVzZVxuY29uc3QgYWN0aXZlU3RyZWFtc0J5Um91dGUgPSBuZXcgTWFwPHN0cmluZywgc3RyaW5nPigpOyAvLyByb3V0ZSAtPiBzdHJlYW1JZFxuXG4vLyBUcmFjayBoZWFkbGVzcyBzdHJlYW0gZWxlbWVudHMgYnkgc3RyZWFtIElEIGZvciByZXVzZSBpbiBmdWxsIHN0cmVhbXNcbmNvbnN0IGhlYWRsZXNzU3RyZWFtRWxlbWVudHMgPSBuZXcgTWFwPHN0cmluZywgYW55PigpOyAvLyBzdHJlYW1JZCAtPiBSZWFjdCBlbGVtZW50c1xuXG4vLyBUcmFjayBoZWFkbGVzcyBzdHJlYW0gZXJyb3JzIGJ5IHJvdXRlIGZvciBmdWxsIHN0cmVhbSBlcnJvciBwcm9wYWdhdGlvblxuY29uc3QgaGVhZGxlc3NTdHJlYW1FcnJvcnMgPSBuZXcgTWFwPHN0cmluZywgRXJyb3I+KCk7IC8vIHJvdXRlIC0+IGVycm9yXG5cblxuZnVuY3Rpb24gY2xlYW51cFJlbmRlcihpZDogc3RyaW5nKSB7XG4gIGNvbnN0IHJzY1N0cmVhbSA9IGFjdGl2ZVJlbmRlcnMuZ2V0KGlkKTtcbiAgaWYgKHJzY1N0cmVhbSkge1xuICAgIHJzY1N0cmVhbS5kZXN0cm95KCk7XG4gICAgYWN0aXZlUmVuZGVycy5kZWxldGUoaWQpO1xuXG4gICAgLy8gQ2xlYW4gdXAgcm91dGUgbWFwcGluZ1xuICAgIGZvciAoY29uc3QgW3JvdXRlLCBzdHJlYW1JZF0gb2YgYWN0aXZlU3RyZWFtc0J5Um91dGUuZW50cmllcygpKSB7XG4gICAgICBpZiAoc3RyZWFtSWQgPT09IGlkKSB7XG4gICAgICAgIGFjdGl2ZVN0cmVhbXNCeVJvdXRlLmRlbGV0ZShyb3V0ZSk7XG4gICAgICAgIGJyZWFrO1xuICAgICAgfVxuICAgIH1cblxuICAgIC8vIENsZWFuIHVwIHJldXNhYmxlIGVsZW1lbnRzIG9ubHkgaWYgd2Uga25vdyB3aGljaCBoZWFkbGVzcyBzdHJlYW0gdGhpcyBmdWxsIHN0cmVhbSBjb25zdW1lZFxuICAgIC8vIFRoZSBjYWxsZXIgc2hvdWxkIHRlbGwgdXMgd2hpY2ggaGVhZGxlc3Mgc3RyZWFtIHRvIGNsZWFuIHVwLCBub3QgaW5mZXIgZnJvbSBJRCBwYXR0ZXJuc1xuICAgIC8vIE5vdGU6IEVsZW1lbnQgY2xlYW51cCBzaG91bGQgYmUgaGFuZGxlZCBleHBsaWNpdGx5IGJ5IHRoZSBzdHJlYW0gcmV1c2Ugc3lzdGVtXG4gIH1cblxuICAvLyBDbGVhbiB1cCByZW5kZXIgc3RhdGVcbiAgY2xlYW51cFJlbmRlclN0YXRlKGlkKTtcbn1cblxuZnVuY3Rpb24gY2xlYXJIZWFkbGVzc0Vycm9yKHJvdXRlOiBzdHJpbmcpIHtcbiAgaGVhZGxlc3NTdHJlYW1FcnJvcnMuZGVsZXRlKHJvdXRlKTtcbn1cblxuLyoqXG4gKiBIZWxwZXIgZnVuY3Rpb24gdG8gbG9hZCBjb21wb25lbnRzIHdpdGggY2FjaGluZ1xuICovXG5hc3luYyBmdW5jdGlvbiBsb2FkQ29tcG9uZW50c1dpdGhDYWNoZShvcHRpb25zOiB7XG4gIHBhZ2VQYXRoPzogc3RyaW5nO1xuICBwcm9wc1BhdGg/OiBzdHJpbmc7XG4gIHJvb3RQYXRoPzogc3RyaW5nO1xuICBodG1sUGF0aD86IHN0cmluZztcbiAgcGFnZUV4cG9ydE5hbWU/OiBzdHJpbmc7XG4gIHByb3BzRXhwb3J0TmFtZT86IHN0cmluZztcbiAgcm9vdEV4cG9ydE5hbWU/OiBzdHJpbmc7XG4gIGh0bWxFeHBvcnROYW1lPzogc3RyaW5nO1xuICB1cmw6IHN0cmluZztcbiAgbG9hZGVyOiBhbnk7XG4gIHZlcmJvc2U/OiBib29sZWFuO1xuICBsb2dnZXI/OiBhbnk7XG4gIHBhbmljVGhyZXNob2xkPzogUGFuaWNUaHJlc2hvbGQ7XG4gIHJlc29sdmVkUGFnZVByb3BzPzogUmVjb3JkPHN0cmluZywgdW5rbm93bj47ICAvLyBQcmUtcmVzb2x2ZWQgcHJvcHMgZnJvbSBtYWluIHRocmVhZFxufSkge1xuICBjb25zdCB7XG4gICAgcGFnZVBhdGgsXG4gICAgcHJvcHNQYXRoLFxuICAgIHJvb3RQYXRoLFxuICAgIGh0bWxQYXRoLFxuICAgIHBhZ2VFeHBvcnROYW1lID0gXCJQYWdlXCIsXG4gICAgcHJvcHNFeHBvcnROYW1lID0gXCJwcm9wc1wiLFxuICAgIHJvb3RFeHBvcnROYW1lID0gXCJSb290XCIsXG4gICAgaHRtbEV4cG9ydE5hbWUgPSBcIkh0bWxcIixcbiAgICB1cmwsXG4gICAgbG9hZGVyLFxuICAgIHZlcmJvc2UsXG4gICAgbG9nZ2VyLFxuICAgIHBhbmljVGhyZXNob2xkID0gXCJub25lXCIsXG4gICAgcmVzb2x2ZWRQYWdlUHJvcHMsXG4gIH0gPSBvcHRpb25zO1xuICBcbiAgLy8gTm9ybWFsaXplIFVSTCBmb3IgY2FjaGUga2V5IC0gZW5zdXJlIHRyYWlsaW5nIHNsYXNoIGZvciBmb2xkZXIgcm91dGVzXG4gIC8vIFRoaXMgZW5zdXJlcyAvOG1tYy9sZXZlbHMgYW5kIC84bW1jL2xldmVscy8gdXNlIHRoZSBzYW1lIGNhY2hlIGtleVxuICBjb25zdCBub3JtYWxpemVVcmxGb3JDYWNoZSA9ICh1cmxTdHI6IHN0cmluZyk6IHN0cmluZyA9PiB7XG4gICAgLy8gRG9uJ3QgYWRkIHRyYWlsaW5nIHNsYXNoIHRvIHBhdGhzIHdpdGggZmlsZSBleHRlbnNpb25zXG4gICAgaWYgKHVybFN0ci5pbmNsdWRlcygnLicpKSByZXR1cm4gdXJsU3RyO1xuICAgIHJldHVybiB1cmxTdHIuZW5kc1dpdGgoJy8nKSA/IHVybFN0ciA6IHVybFN0ciArICcvJztcbiAgfTtcbiAgY29uc3Qgbm9ybWFsaXplZFVybCA9IG5vcm1hbGl6ZVVybEZvckNhY2hlKHVybCk7XG5cbiAgbGV0IFBhZ2VDb21wb25lbnQ6IGFueTtcbiAgbGV0IHBhZ2VQcm9wczogYW55ID0gcmVzb2x2ZWRQYWdlUHJvcHM7ICAvLyBVc2UgcHJlLXJlc29sdmVkIHByb3BzIGlmIGF2YWlsYWJsZVxuICBsZXQgUm9vdENvbXBvbmVudDogYW55O1xuICBsZXQgSHRtbENvbXBvbmVudDogYW55O1xuXG4gIC8vIExvYWQgcGFnZSBhbmQgcHJvcHMgdXNpbmcgdGhlIHVuaWZpZWQgaGVscGVyXG4gIGlmICh2ZXJib3NlKSB7XG4gICAgbG9nZ2VyPy5pbmZvKFxuICAgICAgYFtsb2FkQ29tcG9uZW50c1dpdGhDYWNoZV0gcGFnZVBhdGg9JHtwYWdlUGF0aH0sIHByb3BzUGF0aD0ke3Byb3BzUGF0aH0sIHVybD0ke3VybH1gXG4gICAgKTtcbiAgfVxuICBpZiAocGFnZVBhdGgpIHtcbiAgICBpZiAodmVyYm9zZSkge1xuICAgICAgbG9nZ2VyPy5pbmZvKFxuICAgICAgICBgW2xvYWRDb21wb25lbnRzV2l0aENhY2hlXSBMb2FkaW5nIHBhZ2UgYW5kIHByb3BzIGZvciBwYWdlUGF0aD0ke3BhZ2VQYXRofWBcbiAgICAgICk7XG4gICAgfVxuICAgIFxuICAgIC8vIENSSVRJQ0FMOiBDaGVjayBpZiBQYWdlIG1vZHVsZSBpcyBpbnZhbGlkYXRlZCBiZWZvcmUgdXNpbmcgY2FjaGVcbiAgICBjb25zdCBwYWdlSWQgPSBgJHtwYWdlUGF0aH0jJHtwYWdlRXhwb3J0TmFtZX1gO1xuICAgIGNvbnN0IGlzUGFnZUludmFsaWRhdGVkID0gaXNNb2R1bGVJbnZhbGlkYXRlZChwYWdlUGF0aCk7XG4gICAgXG4gICAgLy8gVHJhY2sgaWYgd2UgbmVlZCB0byBsb2FkIHByb3BzIHNlcGFyYXRlbHkgKHdoZW4gUGFnZSBpcyBjYWNoZWQgYnV0IHByb3BzIGZvciB0aGlzIFVSTCBhcmVuJ3QpXG4gICAgbGV0IG5lZWRUb0xvYWRQcm9wcyA9IGZhbHNlO1xuICAgIFxuICAgIC8vIENoZWNrIGNhY2hlIGZpcnN0LCBidXQgb25seSBpZiBub3QgaW52YWxpZGF0ZWQuXG4gICAgLy9cbiAgICAvLyBiZC01eHUgKDIwMjYtMDQtMzApOiB3ZSBjYWNoZSB0aGUgUGFnZSBjb21wb25lbnQgKGEgc3RhYmxlIGZ1bmN0aW9uIGZyb21cbiAgICAvLyB0aGUgc291cmNlIGZpbGUpIGJ1dCBOT1QgdGhlIHByb3BzIHJlc3VsdC4gUHJvcHMgZnVuY3Rpb25zIHJlYWQgbXV0YWJsZVxuICAgIC8vIHNlcnZlciBzdGF0ZSDigJQgREIgcm93cywgaW4tbWVtb3J5IHN0b3JlcywgcmVxdWVzdC1zY29wZWQgZGF0YSDigJQgYW5kIGFcbiAgICAvLyBjcm9zcy1yZXF1ZXN0IGNhY2hlIHNpbGVudGx5IHNlcnZlcyBzdGFsZSBkYXRhIGFmdGVyIGEgc2VydmVyIGFjdGlvblxuICAgIC8vIG11dGF0ZXMgdGhhdCBzdGF0ZS4gVGhlIHByZS1iZC01eHUgY29kZSBrZXllZCBwYWdlUHJvcHMgYnkgVVJMIGFuZCBvbmx5XG4gICAgLy8gaW52YWxpZGF0ZWQgb24gZmlsZSBjaGFuZ2UsIHNvIGUuZy4gZGVsZXRpbmcgYSB0b2RvIHZpYSBhIHNlcnZlciBhY3Rpb25cbiAgICAvLyB3YXMgY29ycmVjdGx5IHBlcnNpc3RlZCB0byB0aGUgREIgYnV0IHRoZSBuZXh0IC90b2Rvcy9pbmRleC5yc2Mgc3RpbGxcbiAgICAvLyByZXR1cm5lZCB0aGUgcHJlLWRlbGV0ZSBjYWNoZWQgcHJvcHMuIGRldjpyc2MgZGlkbid0IGdvIHRocm91Z2ggdGhpc1xuICAgIC8vIHdvcmtlciBwYXRoIHNvIGl0IGRpZG4ndCByZXBybyB0aGVyZS4gQWx3YXlzIHJlbG9hZCBwcm9wcyBpbiBkZXYuXG4gICAgaWYgKGhhc0NhY2hlZENvbXBvbmVudChwYWdlSWQpICYmICFpc1BhZ2VJbnZhbGlkYXRlZCkge1xuICAgICAgUGFnZUNvbXBvbmVudCA9IGdldENhY2hlZENvbXBvbmVudChwYWdlSWQpO1xuICAgICAgaWYgKHZlcmJvc2UpIHtcbiAgICAgICAgbG9nZ2VyPy5pbmZvKFxuICAgICAgICAgIGBbcnNjLXdvcmtlcl0gVXNpbmcgY2FjaGVkIFBhZ2UgY29tcG9uZW50IGZyb206ICR7cGFnZVBhdGh9YFxuICAgICAgICApO1xuICAgICAgfVxuXG4gICAgICBpZiAocHJvcHNQYXRoICYmICFyZXNvbHZlZFBhZ2VQcm9wcykge1xuICAgICAgICAvLyBQcm9wcyBhcmUgaW50ZW50aW9uYWxseSBOT1QgY2FjaGVkIGFjcm9zcyByZXF1ZXN0cyDigJQgc2VlIGNvbW1lbnRcbiAgICAgICAgLy8gYWJvdmUuIERyb3AgYW55IHByZS1leGlzdGluZyBjYWNoZSBlbnRyeSBmcm9tIG9sZGVyIHZlcnNpb25zLCB0aGVuXG4gICAgICAgIC8vIGZhbGwgdGhyb3VnaCB0byB0aGUgc2VwYXJhdGUtbG9hZCBwYXRoIGJl