Skip to content

Commit

Permalink
get rid of hydration errors
Browse files Browse the repository at this point in the history
  • Loading branch information
christian-bromann committed Jul 3, 2024
1 parent d2c19b9 commit f3a59a8
Show file tree
Hide file tree
Showing 9 changed files with 326 additions and 140 deletions.
4 changes: 2 additions & 2 deletions packages/example-project/component-library/hydrate/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,10 +227,10 @@ export interface HydrateStaticData {
content: string;
}
export declare function streamToString(html: string | any, option?: SerializeDocumentOptions): Readable;
export declare function renderToString(html: string | any, options: SerializeDocumentOptions | undefined, asStream: true): Readable;
export declare function renderToString(html: string | any, options?: SerializeDocumentOptions): Promise<HydrateResults>;
export declare function hydrateDocument(doc: any | string, options: HydrateDocumentOptions | undefined, asStream?: boolean): Readable;
export declare function renderToString(html: string | any, options: SerializeDocumentOptions | undefined, asStream: true): Readable;
export declare function hydrateDocument(doc: any | string, options?: HydrateDocumentOptions): Promise<HydrateResults>;
export declare function hydrateDocument(doc: any | string, options: HydrateDocumentOptions | undefined, asStream?: boolean): Readable;
export declare function serializeDocumentToString(doc: Document, opts: HydrateFactoryOptions): string;

export {};
12 changes: 5 additions & 7 deletions packages/example-project/component-library/hydrate/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ const NAMESPACE = 'component-library';
const BUILD = /* component-library */ { allRenderFn: true, appendChildSlotFix: false, asyncLoading: true, attachStyles: true, cloneNodeFix: false, cmpDidLoad: false, cmpDidRender: false, cmpDidUnload: false, cmpDidUpdate: false, cmpShouldUpdate: false, cmpWillLoad: true, cmpWillRender: false, cmpWillUpdate: false, connectedCallback: true, constructableCSS: false, cssAnnotations: true, devTools: false, disconnectedCallback: true, element: false, event: true, experimentalScopedSlotChanges: false, experimentalSlotFixes: false, formAssociated: false, hasRenderFn: true, hostListener: false, hostListenerTarget: false, hostListenerTargetBody: false, hostListenerTargetDocument: false, hostListenerTargetParent: false, hostListenerTargetWindow: false, hotModuleReplacement: false, hydrateClientSide: true, hydrateServerSide: true, hydratedAttribute: false, hydratedClass: true, hydratedSelectorName: "hydrated", invisiblePrehydration: true, isDebug: false, isDev: false, isTesting: false, lazyLoad: true, lifecycle: true, lifecycleDOMEvents: false, member: true, method: true, mode: false, observeAttribute: true, profile: false, prop: true, propBoolean: true, propMutable: true, propNumber: true, propString: true, reflect: true, scoped: true, scopedSlotTextContentFix: false, scriptDataOpts: false, shadowDelegatesFocus: false, shadowDom: true, shadowDomShim: true, slot: true, slotChildNodesFix: false, slotRelocation: true, state: true, style: true, svg: true, taskQueue: true, updatable: true, vdomAttribute: true, vdomClass: true, vdomFunctional: true, vdomKey: true, vdomListener: true, vdomPropOrAttr: true, vdomRef: true, vdomRender: true, vdomStyle: true, vdomText: true, vdomXlink: true, watchCallback: true };

/*
Stencil Hydrate Platform v4.19.0 | MIT Licensed | https://stenciljs.com
Stencil Hydrate Platform v4.19.2 | MIT Licensed | https://stenciljs.com
*/
var __defProp = Object.defineProperty;
var __export = (target, all) => {
Expand Down Expand Up @@ -577,21 +577,19 @@ var registerStyle = (scopeId2, cssText, allowCS) => {
};
var addStyle = (styleContainerNode, cmpMeta, mode) => {
var _a;
const styleContainerDocument = styleContainerNode;
const styleContainerShadowRoot = styleContainerNode;
const scopeId2 = getScopeId(cmpMeta);
const style = styles.get(scopeId2);
styleContainerNode = styleContainerNode.nodeType === 11 /* DocumentFragment */ ? styleContainerNode : doc;
if (style) {
if (typeof style === "string") {
styleContainerNode = styleContainerDocument.head || styleContainerNode;
styleContainerNode = styleContainerNode.head || styleContainerNode;
let appliedStyles = rootAppliedStyles.get(styleContainerNode);
let styleElm;
if (!appliedStyles) {
rootAppliedStyles.set(styleContainerNode, appliedStyles = /* @__PURE__ */ new Set());
}
if (!appliedStyles.has(scopeId2)) {
if (styleContainerShadowRoot.host && (styleElm = styleContainerNode.querySelector(`[${HYDRATED_STYLE_ID}="${scopeId2}"]`))) {
if (styleContainerNode.host && (styleElm = styleContainerNode.querySelector(`[${HYDRATED_STYLE_ID}="${scopeId2}"]`))) {
styleElm.innerHTML = style;
} else {
styleElm = doc.createElement("style");
Expand Down Expand Up @@ -1997,7 +1995,7 @@ function hydrateApp(win2, opts, results, afterHydrate, resolve) {
if (isValidComponent(elm, opts) && results.hydratedCount < opts.maxHydrateCount) {
if (!connectedElements.has(elm) && shouldHydrate(elm)) {
connectedElements.add(elm);
return hydrateComponent(win2, results, elm.nodeName, elm, waitingElements);
return hydrateComponent.call(elm, win2, results, elm.nodeName, elm, waitingElements);
}
}
return resolved2;
Expand Down Expand Up @@ -3338,7 +3336,7 @@ exports.hydrateApp = hydrateApp;
}

/*
Stencil Hydrate Runner v4.19.0 | MIT Licensed | https://stenciljs.com
Stencil Hydrate Runner v4.19.2 | MIT Licensed | https://stenciljs.com
*/
var __defProp = Object.defineProperty;
var __export = (target, all) => {
Expand Down
12 changes: 5 additions & 7 deletions packages/example-project/component-library/hydrate/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ const NAMESPACE = 'component-library';
const BUILD = /* component-library */ { allRenderFn: true, appendChildSlotFix: false, asyncLoading: true, attachStyles: true, cloneNodeFix: false, cmpDidLoad: false, cmpDidRender: false, cmpDidUnload: false, cmpDidUpdate: false, cmpShouldUpdate: false, cmpWillLoad: true, cmpWillRender: false, cmpWillUpdate: false, connectedCallback: true, constructableCSS: false, cssAnnotations: true, devTools: false, disconnectedCallback: true, element: false, event: true, experimentalScopedSlotChanges: false, experimentalSlotFixes: false, formAssociated: false, hasRenderFn: true, hostListener: false, hostListenerTarget: false, hostListenerTargetBody: false, hostListenerTargetDocument: false, hostListenerTargetParent: false, hostListenerTargetWindow: false, hotModuleReplacement: false, hydrateClientSide: true, hydrateServerSide: true, hydratedAttribute: false, hydratedClass: true, hydratedSelectorName: "hydrated", invisiblePrehydration: true, isDebug: false, isDev: false, isTesting: false, lazyLoad: true, lifecycle: true, lifecycleDOMEvents: false, member: true, method: true, mode: false, observeAttribute: true, profile: false, prop: true, propBoolean: true, propMutable: true, propNumber: true, propString: true, reflect: true, scoped: true, scopedSlotTextContentFix: false, scriptDataOpts: false, shadowDelegatesFocus: false, shadowDom: true, shadowDomShim: true, slot: true, slotChildNodesFix: false, slotRelocation: true, state: true, style: true, svg: true, taskQueue: true, updatable: true, vdomAttribute: true, vdomClass: true, vdomFunctional: true, vdomKey: true, vdomListener: true, vdomPropOrAttr: true, vdomRef: true, vdomRender: true, vdomStyle: true, vdomText: true, vdomXlink: true, watchCallback: true };

/*
Stencil Hydrate Platform v4.19.0 | MIT Licensed | https://stenciljs.com
Stencil Hydrate Platform v4.19.2 | MIT Licensed | https://stenciljs.com
*/
var __defProp = Object.defineProperty;
var __export = (target, all) => {
Expand Down Expand Up @@ -573,21 +573,19 @@ var registerStyle = (scopeId2, cssText, allowCS) => {
};
var addStyle = (styleContainerNode, cmpMeta, mode) => {
var _a;
const styleContainerDocument = styleContainerNode;
const styleContainerShadowRoot = styleContainerNode;
const scopeId2 = getScopeId(cmpMeta);
const style = styles.get(scopeId2);
styleContainerNode = styleContainerNode.nodeType === 11 /* DocumentFragment */ ? styleContainerNode : doc;
if (style) {
if (typeof style === "string") {
styleContainerNode = styleContainerDocument.head || styleContainerNode;
styleContainerNode = styleContainerNode.head || styleContainerNode;
let appliedStyles = rootAppliedStyles.get(styleContainerNode);
let styleElm;
if (!appliedStyles) {
rootAppliedStyles.set(styleContainerNode, appliedStyles = /* @__PURE__ */ new Set());
}
if (!appliedStyles.has(scopeId2)) {
if (styleContainerShadowRoot.host && (styleElm = styleContainerNode.querySelector(`[${HYDRATED_STYLE_ID}="${scopeId2}"]`))) {
if (styleContainerNode.host && (styleElm = styleContainerNode.querySelector(`[${HYDRATED_STYLE_ID}="${scopeId2}"]`))) {
styleElm.innerHTML = style;
} else {
styleElm = doc.createElement("style");
Expand Down Expand Up @@ -1993,7 +1991,7 @@ function hydrateApp(win2, opts, results, afterHydrate, resolve) {
if (isValidComponent(elm, opts) && results.hydratedCount < opts.maxHydrateCount) {
if (!connectedElements.has(elm) && shouldHydrate(elm)) {
connectedElements.add(elm);
return hydrateComponent(win2, results, elm.nodeName, elm, waitingElements);
return hydrateComponent.call(elm, win2, results, elm.nodeName, elm, waitingElements);
}
}
return resolved2;
Expand Down Expand Up @@ -3334,7 +3332,7 @@ exports.hydrateApp = hydrateApp;
}

/*
Stencil Hydrate Runner v4.19.0 | MIT Licensed | https://stenciljs.com
Stencil Hydrate Runner v4.19.2 | MIT Licensed | https://stenciljs.com
*/
var __defProp = Object.defineProperty;
var __export = (target, all) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/example-project/component-library/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
},
"devDependencies": {
"@stencil/angular-output-target": "workspace:*",
"@stencil/core": "^4.19.0",
"@stencil/core": "^4.19.2",
"@stencil/react-output-target": "workspace:*",
"@stencil/vue-output-target": "workspace:*",
"@types/puppeteer": "2.0.1",
Expand Down
17 changes: 9 additions & 8 deletions packages/example-project/next-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,24 @@
"prettier": "prettier --write --print-width 80 \"*.mjs\" \"*.json\" \"src/**/*.tsx\" \"*.ts\""
},
"dependencies": {
"html-react-parser": "^5.1.10",
"next": "14.1.4",
"react": "^18",
"react-dom": "^18",
"next": "14.1.4"
"react-dom": "^18"
},
"devDependencies": {
"typescript": "^5",
"@stencil/react-output-target": "workspace:*",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"autoprefixer": "^10.0.1",
"postcss": "^8",
"tailwindcss": "^3.3.0",
"component-library": "workspace:*",
"component-library-react": "workspace:*",
"eslint": "^8",
"eslint-config-next": "14.1.4",
"@stencil/react-output-target": "workspace:*",
"component-library-react": "workspace:*",
"component-library": "workspace:*",
"postcss": "^8",
"tailwindcss": "^3.3.0",
"typescript": "^5",
"unplugin-stencil": "file:/Users/christian.bromann/Sites/Ionic/projects/unplugin-stencil"
},
"volta": {
Expand Down
29 changes: 29 additions & 0 deletions packages/example-project/next-app/src/app/Button/Button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use client';

import { useState } from 'react';

import { MyButton } from '../components';

function Button() {
const [inputEvent, setInputEvent] = useState<number>(0);
// return <MyButton>Hello</MyButton>
return (
<>
<MyButton
suppressHydrationWarning
onClick={(e) => {
e.preventDefault();
e.stopPropagation()
setInputEvent(inputEvent + 1)
}}
>
Click me
</MyButton>
<div>
<p>Input Event: {inputEvent}</p>
</div>
</>
);
}

export default Button;
109 changes: 72 additions & 37 deletions packages/example-project/next-app/src/app/components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

/* eslint-disable */

import dynamic from 'next/dynamic'
import type { EventName } from '@stencil/react-output-target/runtime';
import { createComponent } from '@stencil/react-output-target/runtime';
import {
Expand Down Expand Up @@ -53,24 +53,59 @@ import {
defineCustomElement as defineMyRange,
} from 'component-library/components/my-range.js';
import React from 'react';
import parse from 'html-react-parser'

type MyButtonEvents = {
onMyFocus: EventName<CustomEvent<void>>;
onMyBlur: EventName<CustomEvent<void>>;
};

export const MyButton = createComponent<MyButtonElement, MyButtonEvents>({
tagName: 'my-button',
elementClass: MyButtonElement,
react: React,
events: {
onMyFocus: 'myFocus',
onMyBlur: 'myBlur',
} as MyButtonEvents,
defineCustomElement: defineMyButton,
// @ts-expect-error
suppressHydrationErrors: true,
});
function createServerSideComponent (tagName: string) {
return async ({ children, ...props }: React.PropsWithChildren<{}>) => {
let stringProps = ''
for (const [key, value] of Object.entries(props)) {
if (key === 'children' || (typeof value !== 'string' && typeof value !== 'number' && typeof value !== 'boolean')) {
continue
}
stringProps += ` ${key}="${value}"`
}

const { html } = await renderToString(`<${tagName} ${stringProps.trim()}>${children}</${tagName}>`, {
fullDocument: false,
serializeShadowRoot: true
});

if (!html) {
throw new Error('No HTML returned from renderToString')
}
const StencilElement = () => parse(html, {
transform(reactNode, domNode) {
if ('name' in domNode && domNode.name === tagName) {
const { children, ...props } = (reactNode as any).props
const __html = html.slice(html.indexOf('<template '), -`</${domNode.name}>`.length)
const CustomTag = `${tagName}`
return <CustomTag {...props} dangerouslySetInnerHTML={{ __html: __html }}></CustomTag>
}
},
})
return <StencilElement />
}
}

export const MyButton = process.browser
? createComponent<MyButtonElement, MyButtonEvents>({
tagName: 'my-button',
elementClass: MyButtonElement,
react: React,
events: {
onMyFocus: 'myFocus',
onMyBlur: 'myBlur',
} as MyButtonEvents,
defineCustomElement: defineMyButton,
// @ts-expect-error
suppressHydrationErrors: true,
})
: createServerSideComponent('my-button')

type MyCheckboxEvents = {
onMyChange: EventName<MyCheckboxCustomEvent<CheckboxChangeEventDetail>>;
Expand Down Expand Up @@ -107,31 +142,31 @@ export type MyInputEvents = {
onMyFocus: EventName<CustomEvent<void>>;
};

function HTMLComment({ comment }: { comment: string }) {
const html = `<!-- ${comment} -->`;
const callback = (instance: HTMLScriptElement) => {
if (instance) {
instance.replaceWith(html);
}
};
return (<script ref={callback} />);
}
const MyInputClient = createComponent<MyInputElement, MyInputEvents>({
tagName: 'my-input',
elementClass: MyInputElement,
react: React,
events: {
onMyInput: 'myInput',
onMyChange: 'myChange',
onMyBlur: 'myBlur',
onMyFocus: 'myFocus',
} as MyInputEvents,
defineCustomElement: defineMyInput,
});

export const MyInput = process.browser
? createComponent<MyInputElement, MyInputEvents>({
tagName: 'my-input',
elementClass: MyInputElement,
react: React,
events: {
onMyInput: 'myInput',
onMyChange: 'myChange',
onMyBlur: 'myBlur',
onMyFocus: 'myFocus',
} as MyInputEvents,
defineCustomElement: defineMyInput,
})
: async (props: React.PropsWithChildren<{}>) => {
const { html } = await renderToString('<my-input placeholder="haha"></my-input>');
const templateTag = html.slice(html.indexOf('<body>') + '<body>'.length, -('</body></html>'.length));
// @ts-expect-error
return <my-input suppressHydrationErrors>
{/* @ts-expect-error */}
<template shadowrootmode="open" dangerouslySetInnerHTML={{
__html: templateTag
}} />
{/* @ts-expect-error */}
</my-input>
}
? MyInputClient
: createServerSideComponent('my-input')

type MyPopoverEvents = {
onMyPopoverDidPresent: EventName<CustomEvent<void>>;
Expand Down
7 changes: 6 additions & 1 deletion packages/example-project/next-app/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import Input from "./Input/Input";
import Button from "./Button/Button";

export default function Home() {
return <Input />;
return <>
{/* <Input />
and */}
<Button>Hello</Button>
</>;
}
Loading

0 comments on commit f3a59a8

Please sign in to comment.