Skip to content

Unguarded customElements.define #18

Description

@timkpaine

We've hit this a few times over the years with perspective.

Affected versions

  • regular-layout 0.6.1

What happens

Unguarded customElements.define throws when a second bundle inlines the engine. The engine registers its three elements at module import. If two independently-built bundles on the same page each inline regular-layout (a common bundler outcome — e.g. Perspective's viewer bundles its own copy), whichever bundle loads second throws NotSupportedError from customElements.define at import time, and that entire bundle dies — not just the layout registration, everything downstream of the import.

Root cause

src/extensions.ts lines 17–19 run unconditionally at import:

customElements.define("regular-layout", RegularLayout);
customElements.define("regular-layout-frame", RegularLayoutFrame);
customElements.define("regular-layout-tab", RegularLayoutTab);

The CustomElementRegistry is a page-global; a second define for an already-registered name throws NotSupportedError, and because these calls are top-level module side effects, the throw aborts evaluation of the whole importing bundle.

Reproduction

Build two separate bundles that each import "regular-layout" (or import anything that inlines it) and load both on one page with <script> tags. The second script throws NotSupportedError: the name "regular-layout" has already been used with this registry and none of its remaining top-level code runs.

Downstream impact

Perspective 5.x's @perspective-dev/viewer pins and inlines regular-layout "=0.6.1" in its CDN bundle (its dist/cdn/perspective-viewer.js contains all three customElements.define("regular-layout*") calls). Any page combining that bundle with our own regular-layout-based bundle dies.

Suggested fix

Guard each registration:

if (!customElements.get("regular-layout")) {
    customElements.define("regular-layout", RegularLayout);
}

(and likewise for the other two), or move registration into an exported defineElements() so bundle authors decide when/whether to register. The guard is the smaller change and matches what most component libraries do for exactly this dual-bundle case. However, as we've discussed in various channels over the years, it raises the question/possibility of having incompatible versions racing to register first.

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

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions