-
Notifications
You must be signed in to change notification settings - Fork 10.4k
Prevent LayoutComponentBase properties from being trimmed. #30606
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
Conversation
// it appears unused. Statically reference it to ensure the setter is preserved. | ||
if (parameters.TryGetValue(BodyPropertyName, out RenderFragment? body)) | ||
{ | ||
Body = body; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a new side effect where Body is updated automatically when this is called, which I'm guessing is good/intentional?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
base.SetParametersAsync
effectively does this, but using reflection. In fact, we're actually assigning to this property twice but doing it this way means we're not introducing any new behavior to LayoutComponentBase
, and the assignment is statically visible to the trimmer which means it'll keep the setter if this method appears in code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah ok that makes sense then, do we still need to call base then? Does the base do anything more than set the body?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SetParametersAsync
handles the entire lifetime of the component (including calling OnInitialized
etc), so you do need to call it. Ideally we would want to avoid the reflection based assignment by doing something like this - https://docs.microsoft.com/en-us/aspnet/core/blazor/webassembly-performance-best-practices?view=aspnetcore-5.0#implement-setparametersasync-manually, but because this component is meant to be derived, we don't know if the derived type has additional parameters that need to be assigned.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool thanks for the explanation!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pranavkm To make the flow conceptually simpler, and avoid the double-write oddness, the pattern I've done in my own components when overriding SetParametersAsync
is this:
public override Task SetParametersAsync(ParameterView parameters)
{
// ... write all my own parameter properties ...
return base.SetParametersAsync(ParameterView.Empty);
}
I think this is the code pattern we'd generate if we did the source-generators-for-parameter-assignment work. Might be worth going with it here too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh wait the point you make about subclassing complicates this. We'd probably be OK in the case of a layout, since there's no normal way to pass parameters to layouts. But in principle people might do weird stuff and it would be better not to block them. I guess you should not pass ParameterView.Empty
to base
.
As for the more general case with other components and source generators, I'm not sure how we'll handle this. We could argue that subclasses should also override SetParametersAsync
to assign their own derived properties, but then they can't simply pass ParameterView.Empty
to the base.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We might need to make the Razor compiler know whether you're directly subclassing ComponentBase
or something else. If you are, it's safe to pass ParameterView.Empty
to the base, and you get the perf benefit. But if your component inherits something else, we don't know whether or not to do that, and you don't get the perf benefit.
Clearly that's not a great design. We might need something more complicated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the source generator, the way I had thought of it was we would code-generate some structure that would replace or feed in to WritersForType. We would simply replace the reflection discovery / invoke with compiled structures so we avoid some of the costs there.
src/Components/WebAssembly/testassets/WasmLinkerTest/WasmLinkerTest.csproj
Outdated
Show resolved
Hide resolved
@captainsafia any idea why there isn't an installer with your SDK changes as yet? The most recent installer is about a day old. |
They just fixed the bug in the installer repo that was causing this so should hopefully have SDK -> installer updates flowing soon. |
df3d769
to
d437e80
Compare
@captainsafia - https://dev.azure.com/dnceng/public/_build/results?buildId=1025923&view=ms.vss-test-web.build-test-results-tab&runId=31881838&resultId=125311&paneView=debug
|
@@ -57,6 +57,18 @@ private WebEventData(int browserRendererId, ulong eventHandlerId, EventFieldInfo | |||
|
|||
public EventArgs EventArgs { get; } | |||
|
|||
[DynamicDependency(JsonSerialized, typeof(ChangeEventArgs))] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I imagine the need to do this would go away once we have the JSON code generator. In the meanwhile, I ran the BasicTestApp locally and all of the scenarios worked. Working on getting tests sorted next.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm OK with this being here, especially if it's temporary, but it is strange.
It's not as if the developer manually creates some chain of calls that lead here. It's not really a dynamic dependency. In reality the framework hard-codes the idea that the event dispatch code paths are reachable, so it's very arbitrary that this particular method is where we put the rules about retaining ClipboardEventArgs
et al. It would be potentially difficult to find these attributes later if we wanted, since there's no clear logical deduction a developer could make to realise they would be here.
If there was an alternative to annotate the eventargs subclasses directly to say "never trim this" I think that would be a lot clearer. Don't know if there is such a mechanism like the following:
[NeverTrim] public class ClipboardEventArgs : EventArgs { ... }
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If there was an alternative to annotate the eventargs subclasses directly to say "never trim this" I think that would be a lot clearer.
AFAIK, there isn't a non-XML way to do this. That said, I was able to modify the code to avoid any trimmer specific by invoking the JSON serialization inline.
d437e80
to
07091da
Compare
The usage pattern for LayoutComponentBase does not really play well with static analysis and results in the property setter from being removed.
07091da
to
cd11dcb
Compare
@captainsafia I'm backing out the SDK change for now because it's failing with the error I linked to earlier. Could you update it once your diagnostics changes are in? |
Note that this SDK is busted for Blazor apps. We'll need to pick up another SDK, but the source changes should be the same.