Skip to content

Commit c1e6838

Browse files
johnjenkinsJohn Jenkins
and
John Jenkins
authoredJan 28, 2025··
fix(ssr): exponential perf slow down (#6128)
* fix(ssr): exponential perf slow down * chore: tidying --------- Co-authored-by: John Jenkins <john.jenkins@nanoporetech.com>
1 parent f7ecec3 commit c1e6838

File tree

4 files changed

+71
-25
lines changed

4 files changed

+71
-25
lines changed
 

‎src/hydrate/platform/proxy-host-element.ts

+28-21
Original file line numberDiff line numberDiff line change
@@ -97,27 +97,32 @@ export function proxyHostElement(elm: d.HostElement, cstr: d.ComponentConstructo
9797
enumerable: true,
9898
});
9999

100-
// instance
101-
Object.defineProperty((cstr as any).prototype, memberName, {
102-
get: function (this: any) {
103-
if (origGetter && attrPropVal === undefined && !getValue(this, memberName)) {
104-
// if the initial value comes from an instance getter
105-
// the element will never have the value set. So let's do that now.
106-
setValue(this, memberName, origGetter.apply(this), cmpMeta);
107-
}
108-
109-
// if we have a parsed value from an attribute / or userland prop use that first.
110-
// otherwise if we have a getter already applied, use that.
111-
const ref = getHostRef(this);
112-
return ref.$instanceValues$?.get(memberName) !== undefined
113-
? ref.$instanceValues$?.get(memberName)
114-
: origGetter
115-
? origGetter.apply(this)
116-
: getValue(this, memberName);
117-
},
118-
configurable: true,
119-
enumerable: true,
120-
});
100+
if (!(cstr as any).prototype.__stencilAugmented) {
101+
// instance prototype
102+
Object.defineProperty((cstr as any).prototype, memberName, {
103+
get: function (this: any) {
104+
const ref = getHostRef(this);
105+
// incoming value from a attr / prop?
106+
const attrPropVal = ref.$instanceValues$?.get(memberName);
107+
108+
if (origGetter && attrPropVal === undefined && !getValue(this, memberName)) {
109+
// if the initial value comes from an instance getter
110+
// the element will never have the value set. So let's do that now.
111+
setValue(this, memberName, origGetter.apply(this), cmpMeta);
112+
}
113+
114+
// if we have a parsed value from an attribute / or userland prop use that first.
115+
// otherwise if we have a getter already applied, use that.
116+
return attrPropVal !== undefined
117+
? attrPropVal
118+
: origGetter
119+
? origGetter.apply(this)
120+
: getValue(this, memberName);
121+
},
122+
configurable: true,
123+
enumerable: true,
124+
});
125+
}
121126
} else if (memberFlags & MEMBER_FLAGS.Method) {
122127
Object.defineProperty(elm, memberName, {
123128
value(this: d.HostElement, ...args: any[]) {
@@ -131,6 +136,8 @@ export function proxyHostElement(elm: d.HostElement, cstr: d.ComponentConstructo
131136
});
132137
}
133138
});
139+
// instance prototype should only be processed once
140+
(cstr as any).prototype.__stencilAugmented = true;
134141
}
135142
}
136143

‎src/runtime/proxy-component.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,11 @@ export const proxyComponent = (
3030
const prototype = (Cstr as any).prototype;
3131

3232
if (BUILD.isTesting) {
33-
if (prototype.done) {
33+
if (prototype.__stencilAugmented) {
3434
// @ts-expect-error - we don't want to re-augment the prototype. This happens during spec tests.
3535
return;
3636
}
37-
prototype.done = true;
37+
prototype.__stencilAugmented = true;
3838
}
3939

4040
/**

‎test/wdio/ssr-hydration/cmp.test.tsx

+27
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,31 @@ describe('ssr-shadow-cmp', () => {
5656

5757
document.querySelector('#stage')?.remove();
5858
});
59+
60+
it('checks perf when loading lots of the same component', async () => {
61+
performance.mark('start');
62+
63+
const { html } = await renderToString(
64+
Array(50)
65+
.fill(0)
66+
.map((_, i) => `<ssr-shadow-cmp>Value ${i}</ssr-shadow-cmp>`)
67+
.join(''),
68+
{
69+
fullDocument: true,
70+
serializeShadowRoot: true,
71+
constrainTimeouts: false,
72+
},
73+
);
74+
const stage = document.createElement('div');
75+
stage.setAttribute('id', 'stage');
76+
stage.setHTMLUnsafe(html);
77+
document.body.appendChild(stage);
78+
79+
performance.mark('end');
80+
const renderTime = performance.measure('render', 'start', 'end').duration;
81+
82+
await expect(renderTime).toBeLessThan(100);
83+
84+
document.querySelector('#stage')?.remove();
85+
});
5986
});

‎test/wdio/ssr-hydration/cmp.tsx

+14-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,25 @@
1-
import { Component, h } from '@stencil/core';
1+
import { Component, h, Prop } from '@stencil/core';
22

33
@Component({
44
tag: 'ssr-shadow-cmp',
55
shadow: true,
66
})
77
export class SsrShadowCmp {
8+
@Prop() value: string;
9+
@Prop() label: string;
10+
@Prop() selected: boolean;
11+
@Prop() disabled: boolean;
12+
813
render() {
914
return (
10-
<div>
15+
<div
16+
class={{
17+
option: true,
18+
'option--selected': this.selected,
19+
'option--disabled': this.disabled,
20+
'option--novalue': !this.value,
21+
}}
22+
>
1123
<slot />
1224
</div>
1325
);

0 commit comments

Comments
 (0)
Please sign in to comment.