fix(ssr): global attribute disparities#5247
Conversation
...xtures/attribute-global-html/as-component-prop/undeclared/modules/x/component/component.html
Show resolved
Hide resolved
...c/__tests__/fixtures/attribute-global-html/as-component-prop/without-@api-values/config.json
Show resolved
Hide resolved
| "entry": "x/component" | ||
| "entry": "x/component", | ||
| "ssrFiles": { | ||
| "error": "error-ssr.txt", |
There was a problem hiding this comment.
V1/V2 deviate, where V2 parent overrides the child value (as does CSR). V1 is incorrect.
packages/@lwc/engine-server/src/__tests__/fixtures/attribute-global-html/draggable/config.json
Show resolved
Hide resolved
packages/@lwc/engine-server/src/__tests__/fixtures/attribute-global-html/hidden/config.json
Show resolved
Hide resolved
packages/@lwc/engine-server/src/__tests__/fixtures/attribute-global-html/spellcheck/config.json
Show resolved
Hide resolved
| "entry": "x/component", | ||
| "ssrFiles": { | ||
| "error": "error-ssr.txt", | ||
| "expected": "expected-ssr.html" |
There was a problem hiding this comment.
V1/V2 deviate, where V2 matches CSR
...ixtures/known-boolean-attributes/default-def-html-attributes/static-on-component/config.json
Show resolved
Hide resolved
| // Avoid setting the following types of properties that should not be set: | ||
| // - Properties that are not public. | ||
| // - Properties that are not global. | ||
| // - Properties that are global but are internally overridden. |
There was a problem hiding this comment.
Does not seem correct. In CSR, child definitions do not override parent. We had tests for this, added more.
There was a problem hiding this comment.
If we had fixture tests for this, and it wasn't caught, we should add some karma tests to get the real browser behavior.
There was a problem hiding this comment.
yeah i'm adding some karma for these
|
/nucleus ignore --reason "Unrelated __lwc failure in webruntime" |
| // Avoid setting the following types of properties that should not be set: | ||
| // - Properties that are not public. | ||
| // - Properties that are not global. | ||
| // - Properties that are global but are internally overridden. |
There was a problem hiding this comment.
If we had fixture tests for this, and it wasn't caught, we should add some karma tests to get the real browser behavior.
| literalValue = shouldNormalize ? '0' : literalValue; | ||
| } | ||
| return b.property('init', key, b.literal(literalValue)); | ||
| } else if (value.type === 'Literal' && typeof value.value === 'boolean') { |
There was a problem hiding this comment.
What happens if tabindex is set to a boolean value? Can it be?
There was a problem hiding this comment.
Yes in the case of <div tabindex>. I don't know how that would be treated in the browser. Good thing to check.
There was a problem hiding this comment.
Yup, per the strange CSR logic, booleans are preserved, so nothing to do for this case here.
| // Global HTML "tabindex" attribute is specially massaged into a stringified number | ||
| // This follows the historical behavior in api.ts: | ||
| // https://github.com/salesforce/lwc/blob/f34a347/packages/%40lwc/engine-core/src/framework/api.ts#L193-L211 | ||
| const shouldNormalize = parseInt(literalValue) > 0; | ||
| literalValue = shouldNormalize ? '0' : literalValue; |
There was a problem hiding this comment.
| // Global HTML "tabindex" attribute is specially massaged into a stringified number | |
| // This follows the historical behavior in api.ts: | |
| // https://github.com/salesforce/lwc/blob/f34a347/packages/%40lwc/engine-core/src/framework/api.ts#L193-L211 | |
| const shouldNormalize = parseInt(literalValue) > 0; | |
| literalValue = shouldNormalize ? '0' : literalValue; | |
| literalValue = normalizeTabIndex(literalValue); |
If we've got the util, use it!
| * This follows the historical behavior in api.ts: | ||
| * https://github.com/salesforce/lwc/blob/f34a347/packages/%40lwc/engine-core/src/framework/api.ts#L193-L211 | ||
| */ | ||
| export function normalizeTabIndex(value: any): any { |
There was a problem hiding this comment.
Add some unit tests for this!
| * This follows the historical behavior in api.ts: | ||
| * https://github.com/salesforce/lwc/blob/f34a347/packages/%40lwc/engine-core/src/framework/api.ts#L193-L211 | ||
| */ | ||
| export function normalizeTabIndex(value: any): any { |
There was a problem hiding this comment.
| export function normalizeTabIndex(value: any): any { | |
| export function normalizeTabIndex(value: number): string | number { |
The original function was more broadly typed than necessary, since this is new we can be more restrictive.
Also, it's weird that this returns either the unmodified number or the string '0'. We should either always return a number or always return a string. (It's diverging from the original implementation, but not in a meaningful way.)
There was a problem hiding this comment.
Yes this is weird behavior. There's probably an actual reason for it though - I wouldn't necessarily assume that the divergence is unmeaningful.
There was a problem hiding this comment.
In the end, the attribute is going to end up a string attribute / numeric prop. This particular function is just an intermediate step in that. This particular function copies the business logic from engine-core, but the rest of the implementation (outside the function) is different, so I don't think it makes much of a difference whether the final cast to string/number happens in or out of the function.
There was a problem hiding this comment.
| export function normalizeTabIndex(value: any): any { | |
| export function normalizeTabIndex<T>(value: T): T | string { |
If you want to allow any type.
There was a problem hiding this comment.
I tried generic but it added issues with the comparison. I wanted to keep it exactly as the original to avoid any discrepancies
...erver/src/__tests__/fixtures/attribute-global-html/tabindex/modules/x/component/component.js
Show resolved
Hide resolved
divmain
left a comment
There was a problem hiding this comment.
Looks like there's a tiny bit more polish needed, but otherwise looks fantastic.
packages/@lwc/engine-server/src/__tests__/fixtures/attribute-global-html/hidden/config.json
Show resolved
Hide resolved
| @@ -0,0 +1,16 @@ | |||
| /* | |||
| * Copyright (c) 2020, salesforce.com, inc. | |||
There was a problem hiding this comment.
| * Copyright (c) 2020, salesforce.com, inc. | |
| * Copyright (c) 2025, salesforce.com, inc. |
| * This follows the historical behavior in api.ts: | ||
| * https://github.com/salesforce/lwc/blob/f34a347/packages/%40lwc/engine-core/src/framework/api.ts#L193-L211 | ||
| */ | ||
| export function normalizeTabIndex(value: any): any { |
There was a problem hiding this comment.
Yes this is weird behavior. There's probably an actual reason for it though - I wouldn't necessarily assume that the divergence is unmeaningful.
| export const expectedFailures = new Set([ | ||
| 'attribute-global-html/as-component-prop/undeclared/config.json', | ||
| 'attribute-global-html/as-component-prop/without-@api/config.json', | ||
| 'known-boolean-attributes/default-def-html-attributes/static-on-component/config.json', |
| literalValue = shouldNormalize ? '0' : literalValue; | ||
| } | ||
| return b.property('init', key, b.literal(literalValue)); | ||
| } else if (value.type === 'Literal' && typeof value.value === 'boolean') { |
There was a problem hiding this comment.
Yes in the case of <div tabindex>. I don't know how that would be treated in the browser. Good thing to check.
| if (name === 'class') { | ||
| cxt.import('normalizeClass'); | ||
| propValue = b.callExpression(b.identifier('normalizeClass'), [propValue]); | ||
| } else if (name.toLowerCase() === 'tabindex') { |
There was a problem hiding this comment.
nit: there is more than one name.toLowerCase() showing up in this function; probably could define nameLower variable.
|
/nucleus ignore --reason "Unrelated __lwc failure in webruntime" |
|
I added hydration coverage for global property precedence. As SSR V1 renders the wrong values, it causes hydration errors. I added a check here and an expectation for those failures. Here is the work item to review this and change the pattern if needed: W-17939029 |
Details
Does this pull request introduce a breaking change?
Does this pull request introduce an observable change?
GUS work item
W-17572462