Skip to content

Commit

Permalink
fix(hydrate): support server side rendering of components with listen…
Browse files Browse the repository at this point in the history
…er (#5877)
  • Loading branch information
christian-bromann authored Jul 2, 2024
1 parent bb2e04f commit 2c5b7f8
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 4 deletions.
3 changes: 2 additions & 1 deletion src/hydrate/platform/hydrate-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export function hydrateApp(

// add it to our Set so we know it's already being connected
connectedElements.add(elm);
return hydrateComponent(win, results, elm.nodeName, elm, waitingElements);
return hydrateComponent.call(elm, win, results, elm.nodeName, elm, waitingElements);
}
}

Expand Down Expand Up @@ -163,6 +163,7 @@ export function hydrateApp(
}

async function hydrateComponent(
this: HTMLElement,
win: Window & typeof globalThis,
results: d.HydrateResults,
tagName: string,
Expand Down
13 changes: 13 additions & 0 deletions test/end-to-end/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ export namespace Components {
}
interface DomVisible {
}
interface DsdListenCmp {
}
interface ElementCmp {
}
interface EmptyCmp {
Expand Down Expand Up @@ -257,6 +259,12 @@ declare global {
prototype: HTMLDomVisibleElement;
new (): HTMLDomVisibleElement;
};
interface HTMLDsdListenCmpElement extends Components.DsdListenCmp, HTMLStencilElement {
}
var HTMLDsdListenCmpElement: {
prototype: HTMLDsdListenCmpElement;
new (): HTMLDsdListenCmpElement;
};
interface HTMLElementCmpElement extends Components.ElementCmp, HTMLStencilElement {
}
var HTMLElementCmpElement: {
Expand Down Expand Up @@ -401,6 +409,7 @@ declare global {
"dom-api": HTMLDomApiElement;
"dom-interaction": HTMLDomInteractionElement;
"dom-visible": HTMLDomVisibleElement;
"dsd-listen-cmp": HTMLDsdListenCmpElement;
"element-cmp": HTMLElementCmpElement;
"empty-cmp": HTMLEmptyCmpElement;
"empty-cmp-shadow": HTMLEmptyCmpShadowElement;
Expand Down Expand Up @@ -464,6 +473,8 @@ declare namespace LocalJSX {
}
interface DomVisible {
}
interface DsdListenCmp {
}
interface ElementCmp {
}
interface EmptyCmp {
Expand Down Expand Up @@ -532,6 +543,7 @@ declare namespace LocalJSX {
"dom-api": DomApi;
"dom-interaction": DomInteraction;
"dom-visible": DomVisible;
"dsd-listen-cmp": DsdListenCmp;
"element-cmp": ElementCmp;
"empty-cmp": EmptyCmp;
"empty-cmp-shadow": EmptyCmpShadow;
Expand Down Expand Up @@ -575,6 +587,7 @@ declare module "@stencil/core" {
"dom-api": LocalJSX.DomApi & JSXBase.HTMLAttributes<HTMLDomApiElement>;
"dom-interaction": LocalJSX.DomInteraction & JSXBase.HTMLAttributes<HTMLDomInteractionElement>;
"dom-visible": LocalJSX.DomVisible & JSXBase.HTMLAttributes<HTMLDomVisibleElement>;
"dsd-listen-cmp": LocalJSX.DsdListenCmp & JSXBase.HTMLAttributes<HTMLDsdListenCmpElement>;
"element-cmp": LocalJSX.ElementCmp & JSXBase.HTMLAttributes<HTMLElementCmpElement>;
"empty-cmp": LocalJSX.EmptyCmp & JSXBase.HTMLAttributes<HTMLEmptyCmpElement>;
"empty-cmp-shadow": LocalJSX.EmptyCmpShadow & JSXBase.HTMLAttributes<HTMLEmptyCmpShadowElement>;
Expand Down
3 changes: 3 additions & 0 deletions test/end-to-end/src/declarative-shadow-dom/dsd-listen-cmp.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
:host {
display: block;
}
25 changes: 25 additions & 0 deletions test/end-to-end/src/declarative-shadow-dom/dsd-listen-cmp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Component, Element, h, Host, Listen } from '@stencil/core';

@Component({
tag: 'dsd-listen-cmp',
styleUrl: 'dsd-listen-cmp.css',
shadow: true,
})
export class MyWhateverComponent {
@Element() hostElement: HTMLSlotElement;
private slotRef: HTMLSlotElement;

@Listen('keydown', { capture: true }) // Crashes, incorrect binding in hydrate index.js
handleKeyPress(e: CustomEvent): void {
e.stopPropagation();
console.log(this.slotRef);
}

render() {
return (
<Host>
<slot ref={(el: HTMLSlotElement) => (this.slotRef = el)}></slot>
</Host>
);
}
}
48 changes: 48 additions & 0 deletions test/end-to-end/src/declarative-shadow-dom/test.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,4 +205,52 @@ describe('renderToString', () => {
const button = await page.find('cmp-server-vs-client');
expect(button.shadowRoot.querySelector('div')).toEqualText('Server vs Client? Winner: Client');
});

it('can hydrate components with event listeners', async () => {
const { html } = await renderToString(
`
<dsd-listen-cmp>Hello World</dsd-listen-cmp>
<car-list cars=${JSON.stringify([vento, beetle])}></car-list>
`,
{
serializeShadowRoot: true,
fullDocument: false,
},
);

/**
* renders the component with listener with proper vdom annotation, e.g.
* ```html
* <dsd-listen-cmp class="sc-dsd-listen-cmp-h" custom-hydrate-flag="" s-id="1">
* <template shadowrootmode="open">
* <style sty-id="sc-dsd-listen-cmp">
* .sc-dsd-listen-cmp-h{display:block}
* </style>
* <slot c-id="1.0.0.0" class="sc-dsd-listen-cmp"></slot>
* </template>
* <!--r.1-->
* Hello World
* </dsd-listen-cmp>
* ```
*/

expect(html).toContain(
`<dsd-listen-cmp class=\"sc-dsd-listen-cmp-h\" custom-hydrate-flag=\"\" s-id=\"1\"><template shadowrootmode=\"open\"><style sty-id=\"sc-dsd-listen-cmp\">/*!@:host*/.sc-dsd-listen-cmp-h{display:block}</style><slot class=\"sc-dsd-listen-cmp\" c-id=\"1.0.0.0\"></slot></template><!--r.1-->Hello World</dsd-listen-cmp>`,
);

/**
* renders second component with proper vdom annotation, e.g.:
* ```html
* <car-detail c-id="2.4.2.0" class="sc-car-list" custom-hydrate-flag="" s-id="4">
* <!--r.4-->
* <section c-id="4.0.0.0" class="sc-car-list">
* <!--t.4.1.1.0-->
* 2023 VW Beetle
* </section>
* </car-detail>
*/
expect(html).toContain(
`<car-detail class=\"sc-car-list\" custom-hydrate-flag=\"\" c-id=\"2.4.2.0\" s-id=\"4\"><!--r.4--><section class=\"sc-car-list\" c-id=\"4.0.0.0\"><!--t.4.1.1.0-->2023 VW Beetle</section></car-detail>`,
);
});
});
8 changes: 5 additions & 3 deletions test/end-to-end/src/miscellaneous/test.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import { type E2EPage, newE2EPage } from '@stencil/core/testing';

let page: E2EPage;

function checkSorted(arr: string[]) {
return arr.every((value, index, array) => index === 0 || value >= array[index - 1]);
}

describe('do not throw page already closed if page was defined in before(All) hook', () => {
beforeAll(async () => {
page = await newE2EPage();
Expand Down Expand Up @@ -38,8 +42,6 @@ describe('sorts hydrated component styles', () => {
.split('\n')
.map((c) => c.slice(0, c.indexOf('{')))
.find((c) => c.includes('app-root'));
expect(classSelector).toBe(
'another-car-detail,another-car-list,app-root,build-data,car-detail,car-list,cmp-a,cmp-b,cmp-c,cmp-dsd,cmp-server-vs-client,dom-api,dom-interaction,dom-visible,element-cmp,empty-cmp,empty-cmp-shadow,env-data,event-cmp,import-assets,listen-cmp,method-cmp,path-alias-cmp,prerender-cmp,prop-cmp,scoped-car-detail,scoped-car-list,slot-cmp,slot-cmp-container,slot-parent-cmp,state-cmp',
);
expect(checkSorted(classSelector.split(','))).toBeTruthy();
});
});

0 comments on commit 2c5b7f8

Please sign in to comment.