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

perf: static-optimize xlink:href #4346

Merged
merged 5 commits into from
Jul 3, 2024
Merged

Conversation

nolanlawson
Copy link
Collaborator

Details

Partially addresses #4280

Adds support in the static content optimization for xlink:href and href when used in an SVG <use>.

Also adds some tests for uncovered test cases (e.g. boolean-true: <use xlink:href>).

Does this pull request introduce a breaking change?

  • 😮‍💨 No, it does not introduce a breaking change.

Does this pull request introduce an observable change?

  • 🤞 No, it does not introduce an observable change.

@nolanlawson nolanlawson requested a review from a team as a code owner July 1, 2024 23:10
const value =
isStringLiteral(attrValue) && !isFragmentOnlyUrl(attrValue.value)
? t.literal(attrValue.value)
: codeGen.genScopedFragId(expression);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a little kooky to me – basically we only add the api_scoped_frag_id() function when the value is a non-literal or starts with a #:

// dynamic
api_scoped_frag_id($cmp.foo)

// static, starts with #
api_scoped_frag_id('#yolo')

// static, does not start with #
'/foo.svg'

We could just run everything through the api_scoped_frag_id function, since it already checks to see if the value starts with a #. But I guess this is an optimization for static values that don't start with #. So I preserved the existing behavior.

@@ -744,9 +748,13 @@ export default class CodeGen {
// TODO [#3658]: `disableSyntheticShadowSupport` should also disable this dynamic behavior
const isIdOrIdRef =
(name === 'id' || isIdReferencingAttribute(name)) &&
(isExpression(value) || isStringLiteral(value));
!isBooleanLiteral(value);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only three possibilities here are 1) expression, 2) string literal, 3) boolean literal.

Copy link
Member

@jmsjtu jmsjtu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had two questions but otherwise, LGTM!

@@ -9,32 +9,22 @@ import {
sanitizeAttribute,
} from "lwc";
const $fragment1 = parseFragment`<a class="test${0}" data-foo="datafoo" aria-hidden="h" role="presentation" href="/foo" title="test" tabindex="-1"${2}></a>`;
const $fragment2 = parseFragment`<table bgcolor="x"${3}></table>`;
const $fragment3 = parseFragment`<div${"c0"} aria-hidden="hidden"${2}></div>`;
const $fragment2 = parseFragment`<svg class="cubano${0}" focusable="true"${2}><use${"a1:xlink:href"}${3}/></svg>`;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nolanlawson - The assumption in buildSerializeExpressionFn is that the first : in an attribute is the delimiter to split the part tokens.

Do you think it would be worth adding a comment in the buildSerializeExpressionFn that the attribute name can contain a : in it as well?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done! af1b3f4

const $fragment1 = parseSVGFragment`<defs${3}><circle${"a1:id"} r="10" cx="10" cy="10" fill="black"${3}/><circle${"a2:id"} r="10" cx="14" cy="14" fill="red"${3}/></defs>`;
const stc0 = {
attrs: {
width: "100px",
height: "100px",
viewport: "0 0 100 100",
},
key: 0,
svg: true,
};
const $fragment1 = parseFragment`<svg width="100px" height="100px" viewport="0 0 100 100"${3}><defs${3}><circle${"a2:id"} r="10" cx="10" cy="10" fill="black"${3}/><circle${"a3:id"} r="10" cx="14" cy="14" fill="red"${3}/></defs><use${"a4:href"}${3}/><use${"a5:xlink:href"}${3}/></svg>`;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will using parseSVGFragment vs parseFragment cause any observable changes?

I'm not sure if I'm interpreting this correctly but it looks like with parseSVGFragment the content that's returned is just the <defs> whereas with parseFragment the content will be returned with a wrapping <svg> tag.

If it does, will it break any existing tests?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is expected. For the <svg> element itself, we always do parseFragment, not parseSVGFragment, due to this logic:

const parseMethod =
element.name !== 'svg' && element.namespace === SVG_NAMESPACE
? PARSE_SVG_FRAGMENT_METHOD_NAME
: PARSE_FRAGMENT_METHOD_NAME;

The reason this changed in the above fixture is because we are static-optimizing the whole <svg> rather than the <defs> now.

I think the reason we did it this way is because <svg> automatically forces the HTML parser to use the SVG namespace, but for the stuff inside, we have to explicitly explain that it's in the SVG namespace.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's a CodePen to demonstrate. Note that the <circle> only works when parsed within the <svg>, not by itself.

@nolanlawson nolanlawson requested a review from jmsjtu July 2, 2024 22:40
this,
// `addLegacySanitizationHook` is true because `isCustomRendererHookRequired`
// being false is a precondition for static nodes.
true
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI @jmsjtu I added this bit here. I realized that due to the isCustomRendererHookRequired check here, this is always true:

nodeIsStaticSafe =
isBaseElement(node) &&
!isCustomRendererHookRequired(node, state) &&
isStaticNode(node, state.config.apiVersion);

@nolanlawson nolanlawson merged commit a92c69e into master Jul 3, 2024
11 checks passed
@nolanlawson nolanlawson deleted the nolan/optimize-xlink-href branch July 3, 2024 17:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants