Lack of flexibility for partials

Short description of issue

Partials update

Reproduction steps

Hello,

The partials feature in JS is an awesome addition to Liquid, it lacks some essential features to make it usable in a real-life theme.

The main problem is that in complex pages, we often have to keep some states (an accordion being open, the position of an image in a carousel, a current animation status…). However, the apply and refresh methods are replacing the whole DOM of a partial, without leaving the possibility to easily hook into it.

At Maestrooo, for our next theme, I’ve improved a lot how we handle rerendering of sections / blocks using a library called Morphlex (GitHub - yippee-fun/morphlex: Optimal DOM morphing, written in TypeScript. · GitHub). The main benefits of this library are:

  • It only changes the actual elements that has changed, so if 99% of the refreshed DOM stays the same and that only a span change, only this span is actually replaced. This helps a lot to preserve states, preventing connectedCallback/disconnectedCallback of custom elements to re-fire when not needed.

Additional info

  • It gives a lot of hooks, allowing us to control what happens when a DOM element is inserted/removed.

I think that Shopify should adopt a similar algorithm (or even re-use Morphlex under the hood) and give us hooks to restore states easily:

await partials.refresh(['product-grid', 'product-count'], {
  beforeNodeVisited: ({ fromNode, toNode} => {}),
  afterNodeVisited: ({ fromNode, toNote } => {}),
  beforeAttributeUpdated: ({ element, attributeName, newValue }) => {})
});

Morphlex has more hooks but from my experience in themes, those three hooks allows to achieve most of the use cases.

Let me know if you need any details :slight_smile:

What type of topic is this

Feature request

Hey @bakura10, thanks for the feedback and the examples.

Preserving that state and avoiding unnecessary custom-element reconnects are good reasons to give themes control over reconciliation.

We would prefer to support this without baking a particular morphing algorithm or library-specific hooks into the partials runtime. The direction we are exploring is making the existing Partials value returned by partials.fetch() iterable:

const updatedPartials = await partials.fetch('product-grid', 'product-count');

for (const { range, content } of updatedPartials) {
  reconcileRange(range, content); // theme defined function
}

Each iterable item would expose the partial’s name, a live Range covering its current content between the boundary markers, and a fresh DocumentFragment containing the incoming content.

reconcileRange here is theme-defined code, not another runtime API. You could adapt your preferred reconciler there and use its hooks directly, rather than us needing to replicate those hooks in the partials API.

The runtime would handle fetching and matching partials; your code would decide how to update the DOM. Existing partials.apply() and partials.refresh() usage would keep its current replacement and state-restoration behavior.

Would access to the DOM range and incoming fragment be enough to integrate your Morphlex setup, or is there anything else you’d need from the runtime?

Hi @Clauderic_Demers . That sounds like a perfect solution ;).

Ohh, it allows to achieve most of the use cases