diff --git a/.changeset/lazy-rats-beam.md b/.changeset/lazy-rats-beam.md new file mode 100644 index 000000000000..66acfe33f265 --- /dev/null +++ b/.changeset/lazy-rats-beam.md @@ -0,0 +1,5 @@ +--- +"astro": patch +--- + +Improves performance for frequent use of small components. diff --git a/packages/astro/src/core/render-context.ts b/packages/astro/src/core/render-context.ts index eb05df6f5db6..5cfc8ef2ede3 100644 --- a/packages/astro/src/core/render-context.ts +++ b/packages/astro/src/core/render-context.ts @@ -242,12 +242,57 @@ export class RenderContext { return result; } + #astroPagePartial?: Omit; + /** + * The Astro global is sourced in 3 different phases: + * - **Static**: `.generator` and `.glob` is printed by the compiler, instantiated once per process per astro file + * - **Page-level**: `.request`, `.cookies`, `.locals` etc. These remain the same for the duration of the request. + * - **Component-level**: `.props`, `.slots`, and `.self` are unique to each _use_ of each component. + * + * The page level partial is used as the prototype of the user-visible `Astro` global object, which is instantiated once per use of a component. + */ createAstro( result: SSRResult, - astroGlobalPartial: AstroGlobalPartial, + astroStaticPartial: AstroGlobalPartial, props: Record, slotValues: Record | null ): AstroGlobal { + // Create page partial with static partial so they can be cached together. + const astroPagePartial = (this.#astroPagePartial ??= this.createAstroPagePartial( + result, + astroStaticPartial + )); + // Create component-level partials. `Astro.self` is added by the compiler. + const astroComponentPartial = { props, self: null }; + + // Create final object. `Astro.slots` will be lazily created. + const Astro: Omit = Object.assign( + Object.create(astroPagePartial), + astroComponentPartial + ); + + // Handle `Astro.slots` + let _slots: AstroGlobal['slots']; + Object.defineProperty(Astro, 'slots', { + get: () => { + if (!_slots) { + _slots = new Slots( + result, + slotValues, + this.pipeline.logger + ) as unknown as AstroGlobal['slots']; + } + return _slots; + }, + }); + + return Astro as AstroGlobal; + } + + createAstroPagePartial( + result: SSRResult, + astroStaticPartial: AstroGlobalPartial + ): Omit { const renderContext = this; const { cookies, locals, params, pipeline, request, url } = this; const { response } = result; @@ -260,12 +305,10 @@ export class RenderContext { } return new Response(null, { status, headers: { Location: path } }); }; - const slots = new Slots(result, slotValues, pipeline.logger) as unknown as AstroGlobal['slots']; - // `Astro.self` is added by the compiler - const astroGlobalCombined: Omit = { - generator: astroGlobalPartial.generator, - glob: astroGlobalPartial.glob, + return { + generator: astroStaticPartial.generator, + glob: astroStaticPartial.glob, cookies, get clientAddress() { return renderContext.clientAddress(); @@ -280,17 +323,13 @@ export class RenderContext { get preferredLocaleList() { return renderContext.computePreferredLocaleList(); }, - props, locals, redirect, request, response, - slots, site: pipeline.site, url, }; - - return astroGlobalCombined as AstroGlobal; } clientAddress() {