Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(hydrate): partially revert #5838 #5876

Merged
merged 1 commit into from
Jul 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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/)*
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