XAML server: Native AOT is feasible with no functional cost; work is out-of-proc project load
Los mantenedores suelen responder en 8 días
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 5/5
- Tiempo estimado
- Más de una semana
- Aptitud para principiantes
- 32/100
- Tipo de issue
- Nueva funcionalidad
- Claridad
- Bastante claro
- Estado de actividad
- Activo
- Stack tecnológico
- csharp, typescript
- Área
- developer-experience, devtools, tooling
Línea de trabajo
Comienza con WinUiXaml.Workspace/RoslynProjectWorkspace.cs y la ruta existente del observador de archivos de C# en XamlLanguageServer.cs y XamlProjectResolver.cs. Añade cobertura de fixtures para ObservableProperty y RelayCommand; después, valida la obtención fuera de proceso de CscCommandLineArgs y el helper de actualización del generador frente a las comprobaciones de características existentes para Stage == Full. La tarea estará terminada cuando la ruta nativa conserve los miembros generados, los diagnósticos, la visualización de símbolos y el comportamiento de actualización sin Roslyn Workspaces ni MSBuildLocator.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
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.
- Lenguaje dominante
- TypeScript
- Estrellas
- 14
- Forks
- 4
- Merge medio
- 7 d 28 min
- PR fusionados (30 d)
- 10
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Tiene una plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de microsoft/WinAppVSCE
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
microsoft/WinAppVSCE#320 ·
Los mantenedores suelen responder en 8 días
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
microsoft/WinAppVSCE#262 ·
Los mantenedores suelen responder en 8 días
-
[Feature]: Update WinUI templates to include launch.json + VSCE RecommendationPosiblemente ocupada @chiaramooney la tomó hace 10 días. Abiertoenhancement
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
microsoft/WinAppVSCE#219 · 1 asignado ·
Los mantenedores suelen responder en 8 días
-
documentation
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
microsoft/WinAppVSCE#160 ·
Los mantenedores suelen responder en 8 días
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
microsoft/WinAppVSCE#159 ·
Los mantenedores suelen responder en 8 días
Todos los issues de microsoft/WinAppVSCE
Issues similares
-
e2e-failure ready-to-code
Dificultad 2/5 1-3 horas Aptitud para principiantes 79/100
redhat-developer/rhdh-plugin-export-overlays#4580 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
devdotfast/whiteboard#1415 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
area/sessions comp/desktop desktop/sessions P2 type/bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 84/100
NousResearch/hermes-agent#145383 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 74/100
Los mantenedores suelen responder en 1 día
-
enhancement p2 setup
Dificultad 1/5 Menos de una hora Aptitud para principiantes 88/100
vercel/eve#5419 · 1 comentario ·
Los mantenedores suelen responder en 1 día