Skip to content

Commit

Permalink
fix(hydrate): partially revert #5838 (#5876)
Browse files Browse the repository at this point in the history
  • Loading branch information
christian-bromann authored Jul 1, 2024
1 parent 42aa488 commit dfbc340
Show file tree
Hide file tree
Showing 9 changed files with 271 additions and 17 deletions.
16 changes: 5 additions & 11 deletions src/runtime/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,7 @@ export const registerStyle = (scopeId: string, cssText: string, allowCS: boolean
* @param mode an optional current mode
* @returns the scope ID for the component of interest
*/
export const addStyle = (
styleContainerNode: Element | Document | ShadowRoot,
cmpMeta: d.ComponentRuntimeMeta,
mode?: string,
) => {
const styleContainerDocument = styleContainerNode as Document;
const styleContainerShadowRoot = styleContainerNode as ShadowRoot;
export const addStyle = (styleContainerNode: any, cmpMeta: d.ComponentRuntimeMeta, mode?: string) => {
const scopeId = getScopeId(cmpMeta, mode);
const style = styles.get(scopeId);

Expand All @@ -66,7 +60,7 @@ export const addStyle = (

if (style) {
if (typeof style === 'string') {
styleContainerNode = styleContainerDocument.head || (styleContainerNode as HTMLElement);
styleContainerNode = styleContainerNode.head || (styleContainerNode as HTMLElement);
let appliedStyles = rootAppliedStyles.get(styleContainerNode);
let styleElm;
if (!appliedStyles) {
Expand All @@ -75,7 +69,7 @@ export const addStyle = (
if (!appliedStyles.has(scopeId)) {
if (
BUILD.hydrateClientSide &&
styleContainerShadowRoot.host &&
styleContainerNode.host &&
(styleElm = styleContainerNode.querySelector(`[${HYDRATED_STYLE_ID}="${scopeId}"]`))
) {
// This is only happening on native shadow-dom, do not needs CSS var shim
Expand Down Expand Up @@ -106,8 +100,8 @@ export const addStyle = (
appliedStyles.add(scopeId);
}
}
} else if (BUILD.constructableCSS && !styleContainerDocument.adoptedStyleSheets.includes(style)) {
styleContainerDocument.adoptedStyleSheets = [...styleContainerDocument.adoptedStyleSheets, style];
} else if (BUILD.constructableCSS && !styleContainerNode.adoptedStyleSheets.includes(style)) {
styleContainerNode.adoptedStyleSheets = [...styleContainerNode.adoptedStyleSheets, style];
}
}
return scopeId;
Expand Down
60 changes: 60 additions & 0 deletions test/end-to-end/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,16 @@ export namespace Components {
*/
"mode"?: any;
}
interface ScopedCarDetail {
"car": CarData;
}
/**
* Component that helps display a list of cars
*/
interface ScopedCarList {
"cars": CarData[];
"selected": CarData;
}
interface SlotCmp {
}
interface SlotCmpContainer {
Expand All @@ -130,6 +140,10 @@ export interface EventCmpCustomEvent<T> extends CustomEvent<T> {
detail: T;
target: HTMLEventCmpElement;
}
export interface ScopedCarListCustomEvent<T> extends CustomEvent<T> {
detail: T;
target: HTMLScopedCarListElement;
}
declare global {
interface HTMLAnotherCarDetailElement extends Components.AnotherCarDetail, HTMLStencilElement {
}
Expand Down Expand Up @@ -322,6 +336,32 @@ declare global {
prototype: HTMLPropCmpElement;
new (): HTMLPropCmpElement;
};
interface HTMLScopedCarDetailElement extends Components.ScopedCarDetail, HTMLStencilElement {
}
var HTMLScopedCarDetailElement: {
prototype: HTMLScopedCarDetailElement;
new (): HTMLScopedCarDetailElement;
};
interface HTMLScopedCarListElementEventMap {
"carSelected": CarData;
}
/**
* Component that helps display a list of cars
*/
interface HTMLScopedCarListElement extends Components.ScopedCarList, HTMLStencilElement {
addEventListener<K extends keyof HTMLScopedCarListElementEventMap>(type: K, listener: (this: HTMLScopedCarListElement, ev: ScopedCarListCustomEvent<HTMLScopedCarListElementEventMap[K]>) => any, options?: boolean | AddEventListenerOptions): void;
addEventListener<K extends keyof DocumentEventMap>(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
addEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
removeEventListener<K extends keyof HTMLScopedCarListElementEventMap>(type: K, listener: (this: HTMLScopedCarListElement, ev: ScopedCarListCustomEvent<HTMLScopedCarListElementEventMap[K]>) => any, options?: boolean | EventListenerOptions): void;
removeEventListener<K extends keyof DocumentEventMap>(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
removeEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
}
var HTMLScopedCarListElement: {
prototype: HTMLScopedCarListElement;
new (): HTMLScopedCarListElement;
};
interface HTMLSlotCmpElement extends Components.SlotCmp, HTMLStencilElement {
}
var HTMLSlotCmpElement: {
Expand Down Expand Up @@ -372,6 +412,8 @@ declare global {
"path-alias-cmp": HTMLPathAliasCmpElement;
"prerender-cmp": HTMLPrerenderCmpElement;
"prop-cmp": HTMLPropCmpElement;
"scoped-car-detail": HTMLScopedCarDetailElement;
"scoped-car-list": HTMLScopedCarListElement;
"slot-cmp": HTMLSlotCmpElement;
"slot-cmp-container": HTMLSlotCmpContainerElement;
"slot-parent-cmp": HTMLSlotParentCmpElement;
Expand Down Expand Up @@ -455,6 +497,17 @@ declare namespace LocalJSX {
*/
"mode"?: any;
}
interface ScopedCarDetail {
"car"?: CarData;
}
/**
* Component that helps display a list of cars
*/
interface ScopedCarList {
"cars"?: CarData[];
"onCarSelected"?: (event: ScopedCarListCustomEvent<CarData>) => void;
"selected"?: CarData;
}
interface SlotCmp {
}
interface SlotCmpContainer {
Expand Down Expand Up @@ -490,6 +543,8 @@ declare namespace LocalJSX {
"path-alias-cmp": PathAliasCmp;
"prerender-cmp": PrerenderCmp;
"prop-cmp": PropCmp;
"scoped-car-detail": ScopedCarDetail;
"scoped-car-list": ScopedCarList;
"slot-cmp": SlotCmp;
"slot-cmp-container": SlotCmpContainer;
"slot-parent-cmp": SlotParentCmp;
Expand Down Expand Up @@ -531,6 +586,11 @@ declare module "@stencil/core" {
"path-alias-cmp": LocalJSX.PathAliasCmp & JSXBase.HTMLAttributes<HTMLPathAliasCmpElement>;
"prerender-cmp": LocalJSX.PrerenderCmp & JSXBase.HTMLAttributes<HTMLPrerenderCmpElement>;
"prop-cmp": LocalJSX.PropCmp & JSXBase.HTMLAttributes<HTMLPropCmpElement>;
"scoped-car-detail": LocalJSX.ScopedCarDetail & JSXBase.HTMLAttributes<HTMLScopedCarDetailElement>;
/**
* Component that helps display a list of cars
*/
"scoped-car-list": LocalJSX.ScopedCarList & JSXBase.HTMLAttributes<HTMLScopedCarListElement>;
"slot-cmp": LocalJSX.SlotCmp & JSXBase.HTMLAttributes<HTMLSlotCmpElement>;
"slot-cmp-container": LocalJSX.SlotCmpContainer & JSXBase.HTMLAttributes<HTMLSlotCmpContainerElement>;
"slot-parent-cmp": LocalJSX.SlotParentCmp & JSXBase.HTMLAttributes<HTMLSlotParentCmpElement>;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`renderToString can render a scoped component within a shadow component 1`] = `"<car-list cars=\\"[{&quot;make&quot;:&quot;VW&quot;,&quot;model&quot;:&quot;Vento&quot;,&quot;year&quot;:2024},{&quot;make&quot;:&quot;VW&quot;,&quot;model&quot;:&quot;Beetle&quot;,&quot;year&quot;:2023}]\\" class=\\"sc-car-list-h\\" custom-hydrate-flag=\\"\\" s-id=\\"1\\"><template shadowrootmode=\\"open\\"><style sty-id=\\"sc-car-list\\">/*!@:host*/.sc-car-list-h{display:block;margin:10px;padding:10px;border:1px solid blue}/*!@ul*/ul.sc-car-list{display:block;margin:0;padding:0}/*!@li*/li.sc-car-list{list-style:none;margin:0;padding:20px}/*[email protected]*/.selected.sc-car-list{font-weight:bold;background:rgb(255, 255, 210)}</style><ul class=\\"sc-car-list\\" c-id=\\"1.0.0.0\\"><li class=\\"sc-car-list\\" c-id=\\"1.1.1.0\\"><car-detail class=\\"sc-car-list\\" custom-hydrate-flag=\\"\\" c-id=\\"1.2.2.0\\" s-id=\\"2\\"><!--r.2--><section c-id=\\"2.0.0.0\\"><!--t.2.1.1.0-->2024 VW Vento</section></car-detail></li><li class=\\"sc-car-list\\" c-id=\\"1.3.1.1\\"><car-detail class=\\"sc-car-list\\" custom-hydrate-flag=\\"\\" c-id=\\"1.4.2.0\\" s-id=\\"3\\"><!--r.3--><section c-id=\\"3.0.0.0\\"><!--t.3.1.1.0-->2023 VW Beetle</section></car-detail></li></ul></template><!--r.1--></car-list>"`;
exports[`renderToString can render a scoped component within a shadow component 1`] = `"<car-list cars=\\"[{&quot;make&quot;:&quot;VW&quot;,&quot;model&quot;:&quot;Vento&quot;,&quot;year&quot;:2024},{&quot;make&quot;:&quot;VW&quot;,&quot;model&quot;:&quot;Beetle&quot;,&quot;year&quot;:2023}]\\" class=\\"sc-car-list-h\\" custom-hydrate-flag=\\"\\" s-id=\\"1\\"><template shadowrootmode=\\"open\\"><style sty-id=\\"sc-car-list\\">/*!@:host*/.sc-car-list-h{display:block;margin:10px;padding:10px;border:1px solid blue}/*!@ul*/ul.sc-car-list{display:block;margin:0;padding:0}/*!@li*/li.sc-car-list{list-style:none;margin:0;padding:20px}/*[email protected]*/.selected.sc-car-list{font-weight:bold;background:rgb(255, 255, 210)}</style><ul class=\\"sc-car-list\\" c-id=\\"1.0.0.0\\"><li class=\\"sc-car-list\\" c-id=\\"1.1.1.0\\"><car-detail class=\\"sc-car-list\\" custom-hydrate-flag=\\"\\" c-id=\\"1.2.2.0\\" s-id=\\"2\\"><!--r.2--><section class=\\"sc-car-list\\" c-id=\\"2.0.0.0\\"><!--t.2.1.1.0-->2024 VW Vento</section></car-detail></li><li class=\\"sc-car-list\\" c-id=\\"1.3.1.1\\"><car-detail class=\\"sc-car-list\\" custom-hydrate-flag=\\"\\" c-id=\\"1.4.2.0\\" s-id=\\"3\\"><!--r.3--><section class=\\"sc-car-list\\" c-id=\\"3.0.0.0\\"><!--t.3.1.1.0-->2023 VW Beetle</section></car-detail></li></ul></template><!--r.1--></car-list>"`;

exports[`renderToString can render nested components 1`] = `"<another-car-list cars=\\"[{&quot;make&quot;:&quot;VW&quot;,&quot;model&quot;:&quot;Vento&quot;,&quot;year&quot;:2024},{&quot;make&quot;:&quot;VW&quot;,&quot;model&quot;:&quot;Beetle&quot;,&quot;year&quot;:2023}]\\" class=\\"sc-another-car-list-h\\" custom-hydrate-flag=\\"\\" s-id=\\"1\\"><template shadowrootmode=\\"open\\"><style sty-id=\\"sc-another-car-list\\">/*!@:host*/.sc-another-car-list-h{display:block;margin:10px;padding:10px;border:1px solid blue}/*!@ul*/ul.sc-another-car-list{display:block;margin:0;padding:0}/*!@li*/li.sc-another-car-list{list-style:none;margin:0;padding:20px}/*[email protected]*/.selected.sc-another-car-list{font-weight:bold;background:rgb(255, 255, 210)}</style><ul class=\\"sc-another-car-list\\" c-id=\\"1.0.0.0\\"><li class=\\"sc-another-car-list\\" c-id=\\"1.1.1.0\\"><another-car-detail class=\\"sc-another-car-list sc-another-car-detail-h\\" custom-hydrate-flag=\\"\\" c-id=\\"1.2.2.0\\" s-id=\\"2\\"><template shadowrootmode=\\"open\\"><style sty-id=\\"sc-another-car-detail\\">/*!@section*/section.sc-another-car-detail{color:green}</style><section class=\\"sc-another-car-detail\\" c-id=\\"2.0.0.0\\"><!--t.2.1.1.0-->2024 VW Vento</section></template><!--r.2--></another-car-detail></li><li class=\\"sc-another-car-list\\" c-id=\\"1.3.1.1\\"><another-car-detail class=\\"sc-another-car-list sc-another-car-detail-h\\" custom-hydrate-flag=\\"\\" c-id=\\"1.4.2.0\\" s-id=\\"3\\"><template shadowrootmode=\\"open\\"><style sty-id=\\"sc-another-car-detail\\">/*!@section*/section.sc-another-car-detail{color:green}</style><section class=\\"sc-another-car-detail\\" c-id=\\"3.0.0.0\\"><!--t.3.1.1.0-->2023 VW Beetle</section></template><!--r.3--></another-car-detail></li></ul></template><!--r.1--></another-car-list>"`;

Expand Down
46 changes: 46 additions & 0 deletions test/end-to-end/src/declarative-shadow-dom/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,52 @@
<!-- Auto Generated Below -->


## Overview

Component that helps display a list of cars

## Properties

| Property | Attribute | Description | Type | Default |
| ---------- | --------- | ----------- | ----------- | ----------- |
| `cars` | -- | | `CarData[]` | `undefined` |
| `selected` | -- | | `CarData` | `undefined` |


## Events

| Event | Description | Type |
| ------------- | ----------- | ---------------------- |
| `carSelected` | | `CustomEvent<CarData>` |


## Slots

| Slot | Description |
| ---------- | -------------------------------- |
| `"header"` | The slot for the header content. |


## Shadow Parts

| Part | Description |
| ------- | ------------------------------------------- |
| `"car"` | The shadow part to target to style the car. |


## Dependencies

### Depends on

- [another-car-detail](.)

### Graph
```mermaid
graph TD;
scoped-car-list --> another-car-detail
style scoped-car-list fill:#f9f,stroke:#333,stroke-width:4px
```

----------------------------------------------

*Built with [StencilJS](https://stenciljs.com/)*
24 changes: 24 additions & 0 deletions test/end-to-end/src/declarative-shadow-dom/scoped-car-detail.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Component, h, Prop } from '@stencil/core';

import { CarData } from '../car-list/car-data';

@Component({
tag: 'scoped-car-detail',
styleUrl: 'another-car-detail.css',
scoped: true,
})
export class CarDetail {
@Prop() car: CarData;

render() {
if (!this.car) {
return null;
}

return (
<section>
{this.car.year} {this.car.make} {this.car.model}
</section>
);
}
}
46 changes: 46 additions & 0 deletions test/end-to-end/src/declarative-shadow-dom/scoped-car-list.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Component, Event, EventEmitter, h, Prop } from '@stencil/core';

import { CarData } from '../car-list/car-data';

/**
* Component that helps display a list of cars
* @slot header - The slot for the header content.
* @part car - The shadow part to target to style the car.
*/
@Component({
tag: 'scoped-car-list',
styleUrl: 'another-car-list.css',
scoped: true,
})
export class CarList {
@Prop() cars: CarData[];
@Prop({ mutable: true }) selected: CarData;
@Event() carSelected: EventEmitter<CarData>;

componentWillLoad() {
return new Promise((resolve) => setTimeout(resolve, 20));
}

selectCar(car: CarData) {
this.selected = car;
this.carSelected.emit(car);
}

render() {
if (!Array.isArray(this.cars)) {
return null;
}

return (
<ul>
{this.cars.map((car) => {
return (
<li class={car === this.selected ? 'selected' : ''}>
<another-car-detail car={car}></another-car-detail>
</li>
);
})}
</ul>
);
}
}
8 changes: 4 additions & 4 deletions test/end-to-end/src/declarative-shadow-dom/test.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,18 +141,18 @@ describe('renderToString', () => {
});
expect(html).toMatchSnapshot();
expect(html).toContain(
'<car-detail class="sc-car-list" custom-hydrate-flag="" c-id="1.2.2.0" s-id="2"><!--r.2--><section c-id="2.0.0.0"><!--t.2.1.1.0-->2024 VW Vento</section></car-detail>',
'<car-detail class="sc-car-list" custom-hydrate-flag="" c-id="1.2.2.0" s-id="2"><!--r.2--><section class="sc-car-list" c-id="2.0.0.0"><!--t.2.1.1.0-->2024 VW Vento</section></car-detail>',
);
expect(html).toContain(
'<car-detail class="sc-car-list" custom-hydrate-flag="" c-id="1.4.2.0" s-id="3"><!--r.3--><section c-id="3.0.0.0"><!--t.3.1.1.0-->2023 VW Beetle</section></car-detail>',
'<car-detail class="sc-car-list" custom-hydrate-flag="" c-id="1.4.2.0" s-id="3"><!--r.3--><section class="sc-car-list" c-id="3.0.0.0"><!--t.3.1.1.0-->2023 VW Beetle</section></car-detail>',
);
});

it('can render a scoped component within a shadow component (sync)', async () => {
const input = `<car-list cars=${JSON.stringify([vento, beetle])}></car-list>`;
const expectedResults = [
'<car-detail class="sc-car-list" custom-hydrate-flag="" c-id="1.2.2.0" s-id="2"><!--r.2--><section c-id="2.0.0.0"><!--t.2.1.1.0-->2024 VW Vento</section></car-detail>',
'<car-detail class="sc-car-list" custom-hydrate-flag="" c-id="1.4.2.0" s-id="3"><!--r.3--><section c-id="3.0.0.0"><!--t.3.1.1.0-->2023 VW Beetle</section></car-detail>',
'<car-detail class="sc-car-list" custom-hydrate-flag="" c-id="1.2.2.0" s-id="2"><!--r.2--><section class="sc-car-list" c-id="2.0.0.0"><!--t.2.1.1.0-->2024 VW Vento</section></car-detail>',
'<car-detail class="sc-car-list" custom-hydrate-flag="" c-id="1.4.2.0" s-id="3"><!--r.3--><section class="sc-car-list" c-id="3.0.0.0"><!--t.3.1.1.0-->2023 VW Beetle</section></car-detail>',
] as const;
const opts = {
serializeShadowRoot: true,
Expand Down
84 changes: 84 additions & 0 deletions test/end-to-end/src/miscellaneous/renderToString.e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { CarData } from '../car-list/car-data';

const vento = new CarData('VW', 'Vento', 2024);
const beetle = new CarData('VW', 'Beetle', 2023);

// @ts-ignore may not be existing when project hasn't been built
type HydrateModule = typeof import('../../hydrate');
let renderToString: HydrateModule['renderToString'];

describe('renderToString', () => {
beforeAll(async () => {
// @ts-ignore may not be existing when project hasn't been built
const mod = await import('../../hydrate');
renderToString = mod.renderToString;
});

it('allows to hydrate whole HTML page', async () => {
const { html } = await renderToString(
`<html>
<head>
<link rel="stylesheet" href="whatever.css" >
</head>
<body>
<div class="__next">
<main>
<car-list cars=${JSON.stringify([vento, beetle])}></car-list>
</main>
</div>
<script type="module">
import { defineCustomElements } from "./static/loader/index.js";
defineCustomElements().catch(console.error);
</script>
</body>
</html>`,
{ fullDocument: true, serializeShadowRoot: false },
);
/**
* starts with a DocType and HTML tag
*/
expect(html.startsWith('<!doctype html><html ')).toBeTruthy();
/**
* renders hydration styles and custom link tag within the head tag
*/
expect(html).toContain(
'selected.sc-car-list{font-weight:bold;background:rgb(255, 255, 210)}</style><link rel="stylesheet" href="whatever.css"> </head> <body>',
);
});

it('allows to hydrate whole HTML page with using a scoped component', async () => {
const { html } = await renderToString(
`<html>
<head>
<link rel="stylesheet" href="whatever.css" >
</head>
<body>
<div class="__next">
<main>
<scoped-car-list cars=${JSON.stringify([vento, beetle])}></scoped-car-list>
</main>
</div>
<script type="module">
import { defineCustomElements } from "./static/loader/index.js";
defineCustomElements().catch(console.error);
</script>
</body>
</html>`,
{ fullDocument: true, serializeShadowRoot: false },
);
/**
* starts with a DocType and HTML tag
*/
expect(html.startsWith('<!doctype html><html ')).toBeTruthy();
/**
* renders hydration styles and custom link tag within the head tag
*/
expect(html).toContain(
'.selected.sc-scoped-car-list{font-weight:bold;background:rgb(255, 255, 210)}</style><style sty-id="sc-another-car-detail">/*!@section*/section.sc-another-car-detail{color:green}</style><link rel="stylesheet" href="whatever.css"> </head> <body> <div class="__next"> <main> <scoped-car-list',
);
});
});
Loading

0 comments on commit dfbc340

Please sign in to comment.