Skip to content

Deduplicate shared dependencies across static component assets #365

Description

@mohamedmansour

Summary

Static component assets emitted by PR #360 are intentionally self-contained: each requested root embeds its full conservative dependency closure into one ESM module. This is correct and simple, but it duplicates shared component template/style bytes across multiple root assets.

We should investigate an ESM asset graph or shared-chunk strategy that deduplicates common dependencies while preserving the current runtime behavior.

Motivation

If multiple lazy roots depend on the same design-system components, each .webui.js asset repeats the same template metadata, CSS importmap entries, and condition functions. Runtime registration dedups via getTemplate(tag) / templatesAlreadyRegistered, but the browser still downloads and parses duplicated bytes.

This can become significant when:

  • Many lazy roots share common shell/card/button/list components.
  • Component templates include non-trivial JSON metadata and condition closures.
  • Assets are CDN cached but users navigate through multiple lazy roots in one session.

Current behavior

Given roots settings-dialog and mail-thread, if both depend on shared-button, each emitted root asset contains shared-button in its components list and embeds its template payload. ESM module-graph dedup cannot help because the dependency is embedded as object literal data rather than imported as a shared module.

Correctness is fine: the loader avoids duplicate registration when templates are already present. The issue is network/parse byte duplication.

Proposed directions

Evaluate one or more of these designs:

  1. One module per component

    • Emit <tag>.webui.js for every component in the closure.
    • Root assets import dependency component modules.
    • Browser ESM caching deduplicates shared dependencies.
  2. Common shared asset chunks

    • Compute overlap across requested roots at build time.
    • Emit root assets plus one or more shared chunks.
    • Keep root manifest ergonomic while avoiding repeated shared payloads.
  3. Bundler-integrated virtual modules

    • Let a Vite/webpack plugin map component tags to virtual modules.
    • Use the bundler's existing chunking/dedup logic.

Constraints

  • Do not regress the simple CLI case: one root should still be easy to load with defineComponentAssets().
  • Preserve current asset payload semantics and versioning, or introduce a versioned format upgrade.
  • Keep CSS-module importmap dedup against window.__webui.styles and already-injected asset styles.
  • Avoid adding .webui.js preload tags by default; only CSS assets should participate in CSS preload/link behavior.
  • Maintain deterministic output names and content hashes.

Acceptance criteria

  • Shared component payload bytes are downloaded once when two lazy roots share a dependency.
  • Runtime still skips imports/registration for templates already present from SSR or prior assets.
  • Tests cover two roots with a shared child component and verify no duplicate shared module fetch when using the new loader shape.
  • A migration or compatibility story exists for current version 1 self-contained assets.

Related: PR #360 review discussion.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestjavascriptPull requests that update javascript coderustPull requests that update rust code
    No fields configured for Feature.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions