11import { renderToString } from "preact-render-to-string" ;
2- import { ComponentChildren , ComponentType , Fragment , h , options } from "preact" ;
2+ import {
3+ Component ,
4+ ComponentChildren ,
5+ ComponentType ,
6+ Fragment ,
7+ h ,
8+ Options as PreactOptions ,
9+ options as preactOptions ,
10+ VNode ,
11+ } from "preact" ;
312import {
413 AppModule ,
514 ErrorPage ,
@@ -20,6 +29,16 @@ import { assetHashingHook } from "../runtime/utils.ts";
2029import { htmlEscapeJsonString } from "./htmlescape.ts" ;
2130import { serialize } from "./serializer.ts" ;
2231
32+ // These hooks are long stable, but when we originally added them we
33+ // weren't sure if they should be public.
34+ export interface AdvancedPreactOptions extends PreactOptions {
35+ /** Attach a hook that is invoked after a tree was mounted or was updated. */
36+ __c ?( vnode : VNode , commitQueue : Component [ ] ) : void ;
37+ /** Attach a hook that is invoked before a vnode has rendered. */
38+ __r ?( vnode : VNode ) : void ;
39+ }
40+ const options = preactOptions as AdvancedPreactOptions ;
41+
2342export interface RenderOptions < Data > {
2443 route : Route < Data > | UnknownPage | ErrorPage ;
2544 islands : Island [ ] ;
@@ -450,18 +469,51 @@ function wrapWithMarker(vnode: ComponentChildren, markerText: string) {
450469const ISLANDS : Island [ ] = [ ] ;
451470const ENCOUNTERED_ISLANDS : Set < Island > = new Set ( [ ] ) ;
452471let ISLAND_PROPS : unknown [ ] = [ ] ;
472+
473+ // Keep track of which component rendered which vnode. This allows us
474+ // to detect when an island is rendered within another instead of being
475+ // passed as children.
476+ let ownerStack : VNode [ ] = [ ] ;
477+ const islandOwners = new Map < VNode , VNode > ( ) ;
478+
453479const originalHook = options . vnode ;
454480let ignoreNext = false ;
455481options . vnode = ( vnode ) => {
456482 assetHashingHook ( vnode ) ;
457483 const originalType = vnode . type as ComponentType < unknown > ;
484+
485+ // Use a labelled statement that allows ous to break out of it
486+ // whilst still continuing execution. We still want to call previous
487+ // `options.vnode` hooks if there were any, otherwise we'd break
488+ // the change for other plugins hooking into Preact.
489+ patchIslands:
458490 if ( typeof vnode . type === "function" ) {
459491 const island = ISLANDS . find ( ( island ) => island . component === originalType ) ;
460492 if ( island ) {
493+ const hasOwners = ownerStack . length > 0 ;
494+ if ( hasOwners ) {
495+ const prevOwner = ownerStack [ ownerStack . length - 1 ] ;
496+ islandOwners . set ( vnode , prevOwner ) ;
497+ }
498+
499+ // Check if we already patched this component
461500 if ( ignoreNext ) {
462501 ignoreNext = false ;
463- return ;
502+ break patchIslands;
503+ }
504+
505+ // Check if an island is rendered inside another island, not just
506+ // passed as a child. Example:
507+ // function Island() {}
508+ // return <OtherIsland />
509+ // }
510+ if ( hasOwners ) {
511+ const prevOwner = ownerStack [ ownerStack . length - 1 ] ;
512+ if ( islandOwners . has ( prevOwner ) ) {
513+ break patchIslands;
514+ }
464515 }
516+
465517 ENCOUNTERED_ISLANDS . add ( island ) ;
466518 vnode . type = ( props ) => {
467519 ignoreNext = true ;
@@ -488,3 +540,30 @@ options.vnode = (vnode) => {
488540 }
489541 if ( originalHook ) originalHook ( vnode ) ;
490542} ;
543+
544+ // Keep track of owners
545+ const oldDiffed = options . diffed ;
546+ const oldRender = options . __r ;
547+ const oldCommit = options . __c ;
548+ options . __r = ( vnode ) => {
549+ if (
550+ typeof vnode . type === "function" &&
551+ vnode . type !== Fragment
552+ ) {
553+ ownerStack . push ( vnode ) ;
554+ }
555+ oldRender ?.( vnode ) ;
556+ } ;
557+ options . diffed = ( vnode ) => {
558+ if ( typeof vnode . type === "function" ) {
559+ if ( vnode . type !== Fragment ) {
560+ ownerStack . pop ( ) ;
561+ }
562+ }
563+ oldDiffed ?.( vnode ) ;
564+ } ;
565+ options . __c = ( vnode , queue ) => {
566+ oldCommit ?.( vnode , queue ) ;
567+ ownerStack = [ ] ;
568+ islandOwners . clear ( ) ;
569+ } ;
0 commit comments