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

feat(hydrate): support object serialization for hydrated components #6208

Open
wants to merge 33 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
ed1e2a3
feat(hydrate): support object serialization for hydrated components
christian-bromann Mar 18, 2025
90ea840
prettier
christian-bromann Mar 18, 2025
adbef04
serialize parameters into string
christian-bromann Mar 18, 2025
e85c9c0
don't serialize primitives
christian-bromann Mar 18, 2025
b502f45
implement hydration part
christian-bromann Mar 18, 2025
0f4dc33
prettier
christian-bromann Mar 18, 2025
559edd8
add more unit tests
christian-bromann Mar 18, 2025
ddf403c
prettier
christian-bromann Mar 18, 2025
90a4cdd
revert some unneeded changes
christian-bromann Mar 18, 2025
93cf58d
better identify if value is serializable
christian-bromann Mar 18, 2025
1f52a61
properly case unknown attributes
christian-bromann Mar 18, 2025
ce86124
fix tests
christian-bromann Mar 18, 2025
243eca6
reorganisation
christian-bromann Mar 19, 2025
1fb43e9
fix unit tests
christian-bromann Mar 19, 2025
e30ea71
fix analysis tests
christian-bromann Mar 19, 2025
d6dbdf6
e2e fixes
christian-bromann Mar 19, 2025
2980c71
prettier
christian-bromann Mar 19, 2025
32615f9
progress
christian-bromann Mar 19, 2025
390fe79
skip some tests
christian-bromann Mar 19, 2025
1aa936e
prettier
christian-bromann Mar 19, 2025
fe1603b
reorg
christian-bromann Mar 19, 2025
bbc4382
fix test
christian-bromann Mar 19, 2025
03ac2d5
fix tests
christian-bromann Mar 19, 2025
188dcda
only deserialize if hydrateClientSide is set
christian-bromann Mar 19, 2025
c565edc
improve comment
christian-bromann Mar 19, 2025
655f070
more test improvements
christian-bromann Mar 19, 2025
302a004
prettier
christian-bromann Mar 19, 2025
7b108e2
revert some tests that cause test errors
christian-bromann Mar 19, 2025
a91c9a5
Revert "revert some tests that cause test errors"
christian-bromann Mar 19, 2025
3cf72e9
chore: fix tests
Mar 19, 2025
2418b84
Merge branch 'cb/prop-serialization' of github.com:stenciljs/core int…
Mar 19, 2025
603c6c3
chore: lint
Mar 19, 2025
0e5a479
chore: fix e2e tests
Mar 20, 2025
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
Prev Previous commit
Next Next commit
implement hydration part
  • Loading branch information
christian-bromann committed Mar 18, 2025
commit b502f450ca49dc5214279b821ba604042aa2df76
2 changes: 1 addition & 1 deletion src/hydrate/runner/serialize.ts
Original file line number Diff line number Diff line change
@@ -46,7 +46,7 @@ export function serializeProperty(value: unknown) {
/**
* If the value is a primitive type, return it as is.
*/
if (SERIALIZABLE_TYPES.includes(typeof value)) {
if (SERIALIZABLE_TYPES.includes(typeof value) && value !== Infinity && value !== -Infinity && !isNaN(value as number)) {
return value
}

41 changes: 40 additions & 1 deletion src/runtime/client-hydrate.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { BUILD } from '@app-data';
import { plt, win } from '@platform';
import { CMP_FLAGS } from '@utils';
import { CMP_FLAGS, MEMBER_FLAGS } from '@utils';

import type * as d from '../declarations';
import { deserializeProperty, SERIALIZED_PREFIX } from '../hydrate/runner/serialize';
import { patchSlottedNode } from './dom-extras';
import { createTime } from './profile';
import {
@@ -53,6 +54,44 @@ export const initializeClientHydrate = (
const vnode: d.VNode = newVNode(tagName, null);
vnode.$elm$ = hostElm;

const attributes: Record<string, unknown> = {};
const members = Object.entries(hostRef.$cmpMeta$.$members$ || {});
members.forEach(([memberName, [memberFlags, metaAttributeName]]) => {
if (!(memberFlags & MEMBER_FLAGS.Prop)) {
return
}
const attributeName = metaAttributeName || memberName;
let attrValue = hostElm.getAttribute(attributeName);

/**
* Allow hydrate parameters that contain a simple object, e.g.
* ```ts
* import { renderToString } from 'component-library/hydrate';
* await renderToString(`<car-detail car=${JSON.stringify({ year: 1234 })}></car-detail>`);
* ```
*/
if (
(attrValue?.startsWith('{') && attrValue.endsWith('}')) ||
(attrValue?.startsWith('[') && attrValue.endsWith(']'))
) {
try {
attrValue = JSON.parse(attrValue);
} catch (e) {
/* ignore */
}
}
/**
* Allow hydrate parameters that contain a complex non-serialized values.
*/
else if (attrValue?.startsWith(SERIALIZED_PREFIX)) {
attrValue = deserializeProperty(attrValue);
}

attributes[attributeName] = attrValue;
hostRef.$instanceValues$.set(attributeName, attrValue);
});
vnode.$attrs$ = attributes;

let scopeId: string;
if (BUILD.scoped) {
const cmpMeta = hostRef.$cmpMeta$;
12 changes: 11 additions & 1 deletion src/runtime/vdom/vdom-render.ts
Original file line number Diff line number Diff line change
@@ -991,7 +991,17 @@ export const renderVdom = (hostRef: d.HostRef, renderFnResults: d.VNode | d.VNod
// 'dummy' Host node (well, an empty vnode) since `renderVdom` assumes
// implicitly that the top-level vdom node is 1) an only child and 2)
// contains attrs that need to be set on the host element.
const rootVnode = isHost(renderFnResults) ? renderFnResults : h(null, null, renderFnResults as any);
const isHostElem = isHost(renderFnResults)
const rootVnode = isHostElem ? renderFnResults : h(null, null, renderFnResults as any);

/**
* Make sure to propagate attributes set on the host element to the root vnode.
* This is needed as otherwise the root vnode will not have any attributes set on it
* which causes issues when rendering the node during hydration.
*/
if (isInitialLoad && !isHostElem && hostRef.$vnode$) {
rootVnode.$attrs$ = hostRef.$vnode$.$attrs$ || {};
}

hostTagName = hostElm.tagName;

17 changes: 13 additions & 4 deletions test/wdio/complex-properties/cmp.test.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/// <reference types="../dist/components.d.ts" />
import { render } from '@wdio/browser-runner/stencil';
import { $, expect } from '@wdio/globals';

@@ -25,10 +26,6 @@ describe('complex-properties', () => {
const { html } = await renderToString(template, {
fullDocument: false,
});
const stage = document.createElement('div');
stage.setAttribute('id', 'stage');
stage.setHTMLUnsafe(html);
document.body.appendChild(stage);

render({ html, components: [] });
await expect($('complex-properties')).toHaveText(
@@ -43,4 +40,16 @@ describe('complex-properties', () => {
].join('\n'),
);
});

it('can change a complex property and see it updated correctly', async () => {
const elm = document.querySelector('complex-properties') as HTMLComplexPropertiesElement;
elm.foo = { bar: '456', loo: [4, 5, 6], qux: { quux: Symbol('new quux') } };
await expect(elm).toHaveText(
expect.stringContaining([
`this.foo.bar: 456`,
`this.foo.loo: 4, 5, 6`,
`this.foo.qux: symbol`,
].join('\n')),
);
});
});
2 changes: 1 addition & 1 deletion test/wdio/package.json
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@
"version": "0.0.0",
"scripts": {
"build": "run-s build.no-external-runtime build.test-sibling build.main build.global-script build.prerender build.invisible-prehydration build.es2022",
"build.main": "node ../../bin/stencil build --debug --es5",
"build.main": "node ../../bin/stencil build --debug --es5 && cp src/components.d.ts dist/components.d.ts",
"build.es2022": "node ../../bin/stencil build --debug --config stencil.config-es2022.ts",
"build.global-script": "node ../../bin/stencil build --debug --es5 --config global-script.stencil.config.ts",
"build.test-sibling": "cd test-sibling && npm run build",
Loading