Skip to content

feat(cli): Enable PublishTrimmed for Cli.Embedded (Phase 2 of #40) #50

@kirkone

Description

@kirkone

Goal

Enable PublishTrimmed=true for Cli.Embedded to reduce the standalone binary size.
This is Phase 2 of #40 — the static-link build is done, but actual trimming was never enabled.

Baseline

  • Current Cli.Embedded single-file size: 46.5 MB (Brotli compressed)
  • Expected after trimming: 25-35 MB (~30-40% reduction)

Investigation Results

A test build with PublishTrimmed=true + TrimMode=partial revealed 45 trim issues — all in our own code:

Category Count API
JsonSerializer without source-gen 27 IL2026
LINQ Expression building 8 IL2026 (Expression.Property)
MakeGenericMethod 1 IL2060

Good news: Scriban, NetVips, Spectre.Console, System.CommandLine, Microsoft.Extensions.* are all trim-compatible. No third-party blockers.

Tasks

Phase 1: Foundation

  • Add EnableTrimAnalyzer=true to Core, Features/Generate, Features/Packages (will surface remaining issues)

Phase 2: JSON Source Generators (27 occurrences)

Replace untyped JsonSerializer.Serialize/Deserialize with [JsonSerializable] source-gen contexts.

Already done (5 contexts): PackageIndex, ThemeJsonConfig, NuGetSearch, Statistics, OneDrive, Calendar.

Remaining:

  • Core/Services/GlobalConfigManager (Load + Save)
  • Core/Themes/LocalThemeProvider
  • Features/Generate/Services/ManifestService (Load + Save)
  • Features/Generate/Services/RenderService.ResolveSingleDataSourceAsync
  • Features/Generate/Commands/ConfigImageCommand.LoadThemeDefaults
  • Other RefreshCommand JSON paths (some still untyped)

Phase 3: LINQ Expression Builder (8 occurrences)

  • Analyze Features/Generate/Filtering/FilterExpressionBuilderExpression.Property(expr, name) is fundamentally trim-unsafe (uses reflection at runtime)
  • Options: annotate with [DynamicallyAccessedMembers], restructure to use typed accessors, or replace with source-generated filter compiler

Phase 4: MakeGenericMethod (1 occurrence)

  • Features/Generate/Commands/CreatePageCommand.ExtractValues — annotate with DynamicallyAccessedMembersAttribute or restructure

Phase 5: Activate Trimming

  • Set PublishTrimmed=true + TrimMode=partial in Cli.Embedded.csproj
  • Smoke-test all commands (generate, theme, plugin, project, config, source, …)
  • Test interactive menu
  • Test theme extract
  • Test image processing pipeline end-to-end
  • Measure final size

Phase 6: Pipeline

  • Add PublishTrimmed to release.yml Standalone publish step
  • Add PublishTrimmed to ci.yml smoke-test (catches regressions early)
  • Update release notes with size info

References

Strategy

Work in small, testable steps — each phase should land as a separate PR. Do NOT enable PublishTrimmed until all analyzer issues are resolved, otherwise we trade compile-time errors for runtime crashes.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions