Skip to content

perf: improve AutoFrame style mirroring performance and fix deduplication#1658

Open
richardszegh wants to merge 4 commits into
puckeditor:mainfrom
richardszegh:main
Open

perf: improve AutoFrame style mirroring performance and fix deduplication#1658
richardszegh wants to merge 4 commits into
puckeditor:mainfrom
richardszegh:main

Conversation

@richardszegh

@richardszegh richardszegh commented May 5, 2026

Copy link
Copy Markdown

Closes #1659

Disclaimer

Since I am pretty new to Puck's source code, I used a fair bit of AI help with the code. Any constructive feedback is welcome.

Our problem

In our use case, we are loading Webpack federated modules (micro-frontends) as Puck blocks. The fed. mods. are styled via PandaCSS.

Puck is using an iframe to render the drop zones/slots in the editor by default. Since iframes can't inherit any styles from the parent window Puck uses a MutationObserver to mirror every style tag from the parent into the iframe. This in combination with all the duplicated styles created by Panda CSS from our fedmods results in a ~30-45s long task blocking the main thread.

With these changes, we are no longer experiencing the issue. Drag'n'dropping blocks is now responsive, and does not block the main thread.

Description

AutoFrame's CopyHostStyles component was using object-hash to hash the full outerHTML of each style node for deduplication. This is O(n) on the CSS string length and was causing significant main thread blocking when a large number of style nodes were present in the parent document.

Additionally, the MutationObserver callback was firing one independent deferred task per added/removed node, meaning a burst of style injections would queue many separate tasks rather than being handled in a single pass. There was also a bug where removeEl deleted the hash entry but never spliced the elements array, causing stale entries to accumulate indefinitely.

Changes made

  • Replaced object-hash with an inline djb2 hash function. <link> elements are keyed by href, <style> elements by their innerHTML hash plus serialised attributes. This avoids full node serialisation while maintaining correctness for cases where two <style> tags share identical CSS but differ in attributes
  • Deduplication now happens before mirrorEl() is called, so duplicate nodes are rejected without any DOM cloning.
  • MutationObserver callbacks are batched: added/removed nodes are collected into pending arrays and flushed in a single deferred pass, preventing bursts of style injections from queuing one main-thread task per node.
  • Style nodes are also deduplicated during initial collection, not just during subsequent mutations.
  • Fixed removeEl to splice the elements array after removal, preventing stale entries that could cause lookupEl to return incorrect results for re-added nodes and unbounded array growth over the lifetime of the observer.
  • Removed object-hash as a dependency.

How to test

  • Open the editor with iframe enabled (default).
  • Add a component that injects a large number of <style> tags into the parent document (e.g. via a CSS-in-JS library or atomic CSS framework).
  • Drag the component into the canvas and confirm it is added without any noticeable delay or main thread freeze.
  • Confirm styles are correctly mirrored into the iframe and the component renders as expected.

Richard Szegh added 4 commits May 5, 2026 11:48
…ver in AutoFrame

- Replace object-hash with a lightweight djb2 hash function to avoid
  O(n) serialisation of large CSS strings on every style node mutation
- Key style elements by href (link) or innerHTML hash (style) instead
  of full outerHTML, enabling early deduplication before any DOM cloning
- Batch MutationObserver callbacks via a single deferred flush to prevent
  per-node main-thread tasks during bulk style injection from federated modules
- Deduplicate style nodes during initial collection to skip duplicate
  PandaCSS atomic class blocks injected by multiple federated modules
- Fix removeEl not splicing the elements array, causing unbounded growth
No longer used after replacing with inline djb2 hash in AutoFrame.
Two <style> tags with identical CSS but different attributes (e.g. nonce)
would previously be incorrectly deduplicated. Include attributes in the
hash key to restore full correctness while keeping the fast djb2 approach.
@vercel

vercel Bot commented May 5, 2026

Copy link
Copy Markdown

Someone is attempting to deploy a commit to the Puck Team on Vercel.

A member of the Team first needs to authorize it.

@richardszegh richardszegh marked this pull request as ready for review May 5, 2026 14:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AutoFrame style mirroring causes main thread freeze when many style nodes are present

1 participant