XAML server: Native AOT is feasible with no functional cost; work is out-of-proc project load
Maintainer thường phản hồi trong vòng 8 ngày
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 5/5
- Thời gian dự kiến
- Hơn một tuần
- Mức phù hợp với người mới
- 32/100
- Loại issue
- Tính năng
- Độ rõ ràng
- Khá rõ ràng
- Mức độ hoạt động
- Sôi nổi
- Công nghệ
- csharp, typescript
- Lĩnh vực
- developer-experience, devtools, tooling
Hướng nghiên cứu
Bắt đầu với WinUiXaml.Workspace/RoslynProjectWorkspace.cs và đường dẫn file-watcher C# hiện có trong XamlLanguageServer.cs và XamlProjectResolver.cs. Thêm phạm vi kiểm thử fixture cho ObservableProperty và RelayCommand, sau đó xác thực việc lấy CscCommandLineArgs ngoài tiến trình và helper làm mới generator dựa trên các kiểm tra tính năng hiện có cho Stage == Full. Hoàn thành khi đường dẫn native vẫn giữ được các member được sinh, diagnostics, hiển thị symbol và hành vi làm mới mà không cần Roslyn Workspaces hoặc MSBuildLocator.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Status: revised three times. Filed as a closed negative ("infeasible, do not retry") — wrong. Revised to "feasible, but costs
{x:Bind}accuracy against source-generated members" — also wrong. Revised to "feasible, cost is generated-member staleness" — also wrong. This body reflects the current, measured position: no accuracy cost, no freshness cost.
Summary
Native AOT compilation of the WinUI XAML language server is feasible, with no known blocker and no functional cost.
The only cost is engineering: rebuilding the project-load path as an out-of-process design-time build. Source-generated members ([ObservableProperty], [RelayCommand]) remain fully supported, refresh on the same trigger as today, and do so ~5x faster than the current path.
A 33.5 MB win-arm64 native binary referencing only Microsoft.CodeAnalysis.CSharp — no Roslyn Workspaces, no Microsoft.Build.*, no MSBuildLocator — passes the full Stage == Full feature set.
Why the original "infeasible" verdict was wrong
Two blockers were observed and reported as individually fatal:
MefHostServices.DefaultHostthrowsCompositionFailedExceptionunder AOT (trimming breaks MEF type identity)MSBuildLocatorneeds a runtime assembly loader, which AOT doesn't have
Both observations are accurate. The error was assuming both are unavoidable.
MEF only loads through Microsoft.CodeAnalysis.Workspaces, and that dependency is confined to a single file — WinUiXaml.Workspace/RoslynProjectWorkspace.cs. Everything downstream consumes only Compilation:
XamlProjectResolvercalls exactly one Workspaces member:GetCompilationAsync()(XamlProjectResolver.cs:137).GetFrameworkCompilation()already hand-builds a rawCSharpCompilation.Create.- No
SymbolFinder,Formatter,SimplifierorRenameranywhere. - The
TextDocumentused across the LSP layer is the server's own type, not Roslyn's.
Drop Workspaces and both the MEF warnings and the CompositionFailedException disappear. The claim that this would require "reimplementing project/document/solution tracking" was wrong — nothing in the server consumes that surface.
AOT-safe MSBuild acquisition
Process.Start is AOT-safe, so MSBuild runs as a child process:
dotnet msbuild <proj> -t:Rebuild -p:ProvideCommandLineArgs=true \
-p:SkipCompilerExecution=true -p:DesignTimeBuild=true -getItem:CscCommandLineArgs
On the smoke fixture this returns 276 args — 209 /reference: + 28 sources, including every XamlCompiler-generated .g.cs.
Source-generated members: fully supported, and faster than today
Two successive revisions of this issue got this wrong — first claiming {x:Bind} against [ObservableProperty] would break, then claiming it would be stale. Neither holds.
What freshness actually is today
Measured against the current framework-dependent server, not assumed:
documentSelectoris XAML-only (xamlLanguageService.ts:678-681), andonDidChangeTextDocumentearly-returns unlesslanguageId === "xaml"(line 352-356).- C# therefore reaches the server only through the file watcher
**/*.{cs,csproj,xaml,props,targets}(line 673) — i.e. on save. Unsaved C# buffers are already invisible. - On a
.csevent,DidChangeWatchedFilesAsyncmarks itstructural(XamlLanguageServer.cs:536) and calls_resolver.Invalidate, which evicts the cache entry (XamlProjectResolver.cs:232). The next resolve is a fullMSBuildWorkspace.OpenProjectAsync.
Today is not incremental. The bar to match is: on save, ~2.5 s.
PHASE 1 (restore only, no build ever run): load 10672 ms
Greeting: True RefreshCommand: True
PHASE 2 (saved .cs edit adds [ObservableProperty] int ItemCount, NO build):
reload 2486 ms ItemCount: True
VERDICT: in-proc generators run -> today = "on save, no build needed"
Matching it out-of-process
A full incremental dotnet build -p:EmitCompilerGeneratedFiles=true after touching one .cs takes 4,347 ms — it works, but pays for the XAML compiler and every other target.
A helper that runs only CSharpGeneratorDriver, fed the cached CscCommandLineArgs:
refs=210 analyzers=11 sources=29 incremental=18 legacy=1 generators, 4 generated files
Greeting: True ItemCount: True RefreshCommand: True
cold 2581 ms (generator phase 2057)
warm 512 / 517 / 514 ms (generator phase 257-316)
| Path | Latency on saved .cs edit |
|---|---|
Today (MSBuildWorkspace full reload) |
2,486 ms |
| Out-of-proc full design-time build | 4,347 ms |
| Out-of-proc generator-only helper | ~515 ms warm |
~5x faster than the current path, on the identical trigger, with identical accuracy.
The invalidation plumbing already exists
DidChangeWatchedFilesAsync already distinguishes the two classes:
.cs→ csc args unchanged; re-run generators only.csproj/.props/.targets/project.assets.json→ re-acquireCscCommandLineArgs, then re-run
Nothing new is needed to know when to refresh. Only what to call changes.
Measured result — Workspaces-free native binary
refs=209 srcs=28 / parsed trees=28 / metadata refs=209
[PASS] metadata symbol Page -> Microsoft.UI.Xaml.Controls.Page
[PASS] source symbol SmokePage -> SmokeFixture.SmokePage
[PASS] base chain crosses to metadata -> Page UserControl Control FrameworkElement UIElement DependencyObject Object
[PASS] x:Name generated fields -> Scroller,Title,BoundText,AttachedProbe,ScratchInput,GoButton,Repeater
[PASS] GoButton field type binds -> Microsoft.UI.Xaml.Controls.Button
[PASS] code-behind handler + location -> SmokePage.xaml.cs:27
[PASS] no compile errors (semantic model sound) -> 0 errors
RESULT: ALL CHECKS PASSED
The incremental path an LSP actually stresses was also verified: ReplaceSyntaxTree picks up newly typed members, the original compilation stays immutable (so the existing caching model is sound), and diagnostics and symbol display work natively.
Roslyn's compiler layer is empirically AOT-clean
Dropping Workspaces takes unique analyzer warnings 144 → 20, across only three sites:
| Site | Nature |
|---|---|
UICultureUtilities |
localized diagnostic text; TryGet* pattern, degrades to English |
PooledDelegates |
IL2091, annotation-only |
RoslynLazyInitializer |
IL2091, annotation-only |
Zero MEF, MSBuild or Workspaces entries. The decisive detail is what is absent — no IL3050, no IL2055/IL2060 — meaning nothing reachable requires runtime code generation.
⚠️ Roslyn is not formally supported on Native AOT. This is an empirical result for the paths this server exercises and must be re-run on Roslyn version bumps.
The extension's SDK acquisition is already AOT-safe
Worth stating explicitly, because it means the out-of-proc precedent already ships:
- The .NET host is resolved in TypeScript —
dotnetInstallTool.tsvia the ms-dotnettoolsdotnet.findPathcommand. dotnetRuntime.tssetsDOTNET_ROOT/DOTNET_HOST_PATHon the child environment (lines 24-25).- The SDK is already invoked as a child process —
runDotnetRestore,xamlLanguageService.ts:848.
None of that runs inside the .NET server. The only in-process SDK assembly loading is MSBuildLocator in MsBuildRegistrar.cs (lines 40-62) — exactly what an AOT migration deletes. Acquisition is already fine; only consumption isn't.
Revised verdict
No known blocker, and no functional cost. The remaining work is:
- Rebuilding the project-load path as an out-of-proc design-time build (scope: one file,
RoslynProjectWorkspace.cs, plus a generator-refresh helper)
⚠️ Gate before attempting: add fixture coverage for source-generated members and wire EmitCompilerGeneratedFiles / the generator-driver helper. There is currently no ObservableProperty, CommunityToolkit.Mvvm or [RelayCommand] usage anywhere in this repository (verified repo-wide), so no existing test would catch a regression in this path. This gate now guards a migration, not a tradeoff.
Note that the existing self-contained publish already delivers the main user-facing benefit (no .NET SDK required), so the case for AOT rests on startup time and memory footprint — plus, per the table above, a materially faster C#-edit refresh.
Reproduction traps worth recording
- Do not pass
-p:PublishAot=trueon the command line — it propagates toWinUiXaml.Xaml(netstandard2.0) and fails withNETSDK1207. Set it in the.csproj; project-file properties don't propagate. - Console output hard-wraps and truncates warnings. Use
"/flp:logfile=aot.log;verbosity=normal". MSB3073 ... link.exe ... exited with code 123is not a host/target mismatch. The ILC targets shell out tovswhere.exe; when it isn't onPATH, the error text is concatenated into$(_Linker). PrependC:\Program Files (x86)\Microsoft Visual Studio\InstallertoPATHinside avcvarsall.batshell.PublishAot=trueimplicitly setsJsonSerializerIsReflectionEnabledByDefault=false. A reflection-based serializer dies immediately withInvalidOperationException: Reflection-based serialization has been disabled for this application.-getItem:ReferencePathreturns pre-target state (empty). Use-getItem:CscCommandLineArgs.- Generated files are absent from
CscCommandLineArgs; add the directory explicitly, and keepCompilerGeneratedFilesOutputPathunderobj/to avoid the SDK's implicit**/*.csglob re-ingesting it and duplicating every declaration. -p:SkipCompilerExecution=true(howCscCommandLineArgsis harvested) means csc never runs, so that invocation produces no generator output. Harvesting args and refreshing generated members are two separate calls.Assembly.GetTypes()over the analyzer list throwsReflectionTypeLoadExceptionon*.CodeFixers.dll(it referencesMicrosoft.CodeAnalysis.Workspaces). Catch it and usertle.Types.Where(t => t != null), or the helper dies with0xE0434352before running a single generator.- Namespace collision makes the Workspaces surface look pervasive when it is one file. The server defines its own
WinUiXaml.Workspacenamespace, plus its ownTextDocumentandXamlFormattertypes. Grepping forWorkspace/TextDocument/Formatterreturns 44 hits across 36 files and suggests a deep Roslyn Workspaces dependency. This misreading is what produced the original incorrect "infeasible" verdict. Narrow the search instead:
That returns 6 lines in a single file,using Microsoft.CodeAnalysis.MSBuild | MSBuildWorkspace | MefHostServices | SymbolFinder | Simplifier | Renamer | Microsoft.CodeAnalysis.FormattingWinUiXaml.Workspace/RoslynProjectWorkspace.cs.
Prerequisite work, already done
Commit aa4baab on chiaramooney-native-aot-compatibility routes all LSP traffic through a source-generated JsonSerializerContext, taking our own trim/AOT warnings 12 → 0 with no behavior change. It is a hard prerequisite for any AOT build (see the reflection-serialization trap above) but stands on its own merits, and it caught a genuine latent bug: Command.Arguments (an object[] of code-action argument records) was unregistered and would have thrown NotSupportedException at runtime.
Tradeoff for a reviewer: the strictness cuts both ways — a future result type reaching the wire without a [JsonSerializable] entry now throws rather than quietly working.
Not attached to #50, which is in active review.
- Ngôn ngữ chính
- TypeScript
- Star
- 14
- Fork
- 4
- Merge trung bình
- 6 ngày 14 giờ
- Pull request đã merge (30 ngày)
- 8
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của microsoft/WinAppVSCE
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
microsoft/WinAppVSCE#320 ·
Maintainer thường phản hồi trong vòng 8 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
microsoft/WinAppVSCE#262 ·
Maintainer thường phản hồi trong vòng 8 ngày
-
[Feature]: Update WinUI templates to include launch.json + VSCE RecommendationCó thể đã có người làm @chiaramooney đã nhận 13 ngày trước. Đang mởenhancement
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
microsoft/WinAppVSCE#219 · 1 người được giao ·
Maintainer thường phản hồi trong vòng 8 ngày
-
documentation
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
microsoft/WinAppVSCE#160 ·
Maintainer thường phản hồi trong vòng 8 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
microsoft/WinAppVSCE#159 ·
Maintainer thường phản hồi trong vòng 8 ngày
Tất cả issue của microsoft/WinAppVSCE
Issue tương tự
-
area:desktop area:ui bug platform:macos
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 67/100
anthropics/claude-code#106183 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 1/5 1-3 giờ Mức phù hợp với người mới 76/100
Maintainer thường phản hồi trong vòng 1 ngày
-
feature:App-Tour feature:Chat ready Web
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 62/100
digitalfabrik/integreat-app#4523 · 2 reaction ·
Maintainer thường phản hồi trong vòng 3 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Required Lexical field accepts two empty paragraphsCó thể đã có người làm @lazerg đã nhận hôm nay. Đang mởBug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
payloadcms/payload#19222 ·
Maintainer thường phản hồi trong vòng 1 ngày