Skip to content

Reduce duplicated WinRT interface implementation code in generated JS bindings #182

Description

@lei9444

Summary

Reduce avoidable output-size overhead from the WinRT interface implementation support introduced in #148, particularly repeated helpers emitted into every supported interface module.

The growth is largely an expected cost of the new functionality, not evidence of accidental full WinUI generation. This issue tracks generation efficiency rather than a correctness regression, and does not propose removing interface implementation support.

Motivation

A same-metadata comparison of v0.1.0-preview.21 and main at bcd0e832af18947051ee5177650392a5f3336ee4, following electron-on-windows-gallery's generation configuration, produced:

Output preview.21 main Increase
JavaScript .js 4,263,692 B 5,243,826 B 980,134 B
TypeScript .d.ts 926,199 B 1,177,338 B 251,139 B
.dynwinrt-js-types 309,039 B 395,431 B 86,392 B
.mjs and .json 288,183 B 292,560 B 4,377 B
Total 5,787,113 B 7,109,155 B 1,322,042 B (+22.8%)

These are uncompressed file lengths, not compressed package size, allocated disk space, runtime memory, or native-addon size. The initial observation was approximately 2 MB; this controlled configuration reproduced 1.32 MB (1.26 MiB), not the full 2 MB. File count increased only from 1,312 to 1,324.

Directly identifiable newly emitted implementation blocks account for approximately 1.04 MB:

Content Measured bytes
Identical helpers copied into 111 supported interface modules 295,815
Per-interface plans and delegate adapters 132,772
Supported implementation factory/dispatch methods 348,895
Throwing implementation stubs in 131 unsupported interfaces 59,369
Implementation-related TypeScript declaration blocks 205,363

The helper block is 2,665 bytes per interface and contains __implementationCheck, Field, Array, Reference, Sync, and Handler. Keeping one shared copy would eliminate approximately 293 KB of repeated text before accounting for shared-module and import overhead. The per-interface plans and dispatch code represent additional functionality and cannot simply be deduplicated the same way.

Applications that only call existing Windows APIs still receive the implementation support in their generated interface modules. Other changes also contribute: six additional IMap JS/declaration pairs occupy 70,746 bytes, and the type manifest and imports grow. These figures overlap the implementation-block accounting and should not be added as independent categories.

Relevant source:

Component

dynwinrt-codegen (code generator)

Basic Example

Reproduction configuration:

  • WinAppSDK 2.2.0, using its component metadata: Foundation 2.1.0, AI 2.2.3, Widgets 2.0.5, and MachineLearning 2.1.70.
  • Windows SDK CPP 10.0.28000.1839 metadata for references.
  • Follow the WinApp CLI 0.5.0 policy: Windows SDK CPP, InteractiveExperiences 2.0.15, and WinUI 2.2.1 are reference-only; WebView2 is excluded. Use public metadata paths, not private runtime metadata.
  • Bulk-generate the 32 public non-UI WinAppSDK metadata files using generate --winmd-list <emit-list> --ref-list <ref-list> --output <output>.
  • Into the same output, cherry-pick Windows.ApplicationModel.LimitedAccessFeatures, Windows.Storage.StorageFile, and Windows.Graphics.Imaging.BitmapDecoder, keeping the same metadata available as references.
  • Repeat with each generator in a separate fresh output directory, then sum file lengths and compare relative paths.

For example, IStorageFolderQueryOperations.js grew from 21,078 to 46,334 bytes, and ILanguageModel2.js from 27,517 to 50,559 bytes. Most growth is inside existing interface files.

Potential implementation directions, not yet a decided API design:

  • Extract the common implementation helpers into one generated internal support module.
  • Consider demand-driven helper emission and a more compact representation for unsupported-interface errors without weakening fail-closed behavior.
  • Evaluate an optional call-only generation mode or separate implementation entrypoints for consumers that do not implement WinRT interfaces, while preserving existing behavior by default.

Preserve CJS/ESM behavior, custom runtime import names, lazy loading/import-cycle behavior, synchronous handler validation, conversion contracts, and ownership/release semantics. Measure output-size changes using a fixed metadata fixture; do not trade correctness checks for size.

Open Questions

  • Should common helpers live in a generated internal module or in a versioned runtime subpath?
  • Is a separate implementation entrypoint or explicit call-only option worthwhile beyond helper deduplication?
  • How can unsupported-interface diagnostics remain explicit without repeating long error strings and declarations?
  • What stable metadata fixture and size budget should guard against future unintentional generated-output growth?

No production code was changed during this investigation.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions