diff --git a/src/compiler/output-targets/dist-hydrate-script/hydrate-factory-closure.ts b/src/compiler/output-targets/dist-hydrate-script/hydrate-factory-closure.ts index 2b60a094402..1ec8f9472cc 100644 --- a/src/compiler/output-targets/dist-hydrate-script/hydrate-factory-closure.ts +++ b/src/compiler/output-targets/dist-hydrate-script/hydrate-factory-closure.ts @@ -139,11 +139,21 @@ export function hydrateFactory($stencilWindow, $stencilHydrateOpts, $stencilHydr ${HYDRATE_APP_CLOSURE_START} `; +/** + * The closure wraps the entire platform (runtime, vdom and every component + * class) so it lexically captures the per-call \`window\`/\`document\`. + * Re-executing it on every \`hydrateApp\` call is a significant fixed cost per + * render, so the evaluated closure is cached on the window object and reused + * whenever the same window is passed in again (e.g. via \`reuseWindow\`). + */ export const HYDRATE_FACTORY_OUTRO = ` /*hydrateAppClosure end*/ - hydrateApp(window, $stencilHydrateOpts, $stencilHydrateResults, $stencilAfterHydrate, $stencilHydrateResolve); + return hydrateApp; } - hydrateAppClosure($stencilWindow); + if (!$stencilWindow.__stencilHydrateApp) { + $stencilWindow.__stencilHydrateApp = hydrateAppClosure($stencilWindow); + } + $stencilWindow.__stencilHydrateApp($stencilWindow, $stencilHydrateOpts, $stencilHydrateResults, $stencilAfterHydrate, $stencilHydrateResolve); } `; diff --git a/src/declarations/stencil-public-compiler.ts b/src/declarations/stencil-public-compiler.ts index 45b9d9fb198..39d8e52da40 100644 --- a/src/declarations/stencil-public-compiler.ts +++ b/src/declarations/stencil-public-compiler.ts @@ -947,6 +947,21 @@ export interface HydrateDocumentOptions { * Sets `document.referrer` */ referrer?: string; + /** + * Reuse a process-global mock window (one per `serializeShadowRoot` mode) across + * `renderToString()`/`hydrateDocument()` calls that receive an HTML string. This + * avoids re-executing the entire hydrate platform closure (runtime, vdom and all + * component definitions) on every call, which is the dominant fixed cost when + * rendering many small fragments (e.g. one `renderToString` call per component + * instance, as the framework output targets do). + * + * The reused window is not concurrency safe, so renders are serialized through + * an internal queue. Intended for rendering HTML fragments + * (`fullDocument: false`); one known output difference is that hydration + * annotation counters (e.g. ``) become unique across the process + * instead of restarting at 1 per call. Defaults to `false`. + */ + reuseWindow?: boolean; /** * Removes every `