@adobe-commerce/elsie 2.1.0-beta.2 → 2.1.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # @adobe-commerce/elsie
2
2
 
3
+ ## 2.1.0-beta.4
4
+
5
+ ### Patch Changes
6
+
7
+ - 8fc53e5: Fix `Slot` silently dropping
8
+ `replaceWith`/`appendChild`/`prependChild`/
9
+ `appendSibling`/`prependSibling`/`remove` calls made from a `slot` callback
10
+ that doesn't `return`/`await` its own async work (e.g. a non-`async` callback
11
+ that calls an `async` helper as a bare statement). Previously, nothing was
12
+ left to trigger the render that would have picked up the queued method once
13
+ the slot's normal init-triggered render pass had already completed. `Slot` now
14
+ detects that case and flushes the pending update itself. Existing callers that
15
+ already `return`/`await` their `slot` callback are unaffected.
16
+
17
+ See `docs/slot-dom-graft-race.md` for the full trace.
18
+
19
+ ## 2.1.0-beta.3
20
+
21
+ ### Patch Changes
22
+
23
+ - 3bfb136: Fix an intermittent `insertBefore` crash in AEM Assets image slots
24
+ (`tryRenderAemAssetsImage`/`makeAemAssetsImageSlot`) that surfaced after the
25
+ preact 10.29 bump. The image render into its container was never awaited
26
+ before handing the container to `Slot`'s `replaceWith`, so it could be grafted
27
+ into the DOM before it had any content. See `docs/slot-dom-graft-race.md` for
28
+ the full trace.
29
+
3
30
  ## 2.1.0-beta.2
4
31
 
5
32
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe-commerce/elsie",
3
- "version": "2.1.0-beta.2",
3
+ "version": "2.1.0-beta.4",
4
4
  "license": "SEE LICENSE IN LICENSE.md",
5
5
  "description": "Domain Package SDK",
6
6
  "engines": {
@@ -238,7 +238,7 @@ export function tryGenerateAemAssetsOptimizedUrl(
238
238
 
239
239
  /** Creates a slot that renders an AEM Assets image. */
240
240
  export function makeAemAssetsImageSlot(config: AemAssetsImageSlotConfig) {
241
- return (ctx: RenderContext) => {
241
+ return async (ctx: RenderContext): Promise<void> => {
242
242
  const { wrapper, alias, params, imageProps } = config;
243
243
 
244
244
  if (!imageProps.src) {
@@ -274,17 +274,25 @@ export function makeAemAssetsImageSlot(config: AemAssetsImageSlotConfig) {
274
274
  params: imageComponentParams,
275
275
  };
276
276
 
277
- UI.render(Image, imageComponentProps)(container);
277
+ // Wait for the Image (and any of its own nested slots) to finish
278
+ // rendering into `container` before handing it to `ctx.replaceWith`.
279
+ // Slot grafts `container` into the DOM via an imperative `ref` callback
280
+ // that Preact's own diffing doesn't track (see docs/slot-dom-graft-race.md)
281
+ // -- handing it over before it has content means it gets grafted empty,
282
+ // then mutated again later outside Preact's bookkeeping, which is what
283
+ // was corrupting Preact's internal DOM pointers and causing sporadic
284
+ // `insertBefore` crashes after the preact 10.29 bump.
285
+ await UI.render(Image, imageComponentProps)(container);
278
286
  ctx.replaceWith(container);
279
287
  };
280
288
  }
281
289
 
282
- export function tryRenderAemAssetsImage(
290
+ export async function tryRenderAemAssetsImage(
283
291
  ctx: RenderContext,
284
292
  config: AemAssetsImageSlotConfig,
285
- ): void {
293
+ ): Promise<void> {
286
294
  // Renders an equivalent of the default image.
287
- function renderDefaultImage(): void {
295
+ async function renderDefaultImage(): Promise<void> {
288
296
  const container = config.wrapper ?? document.createElement('div');
289
297
  const { imageProps, params } = config;
290
298
  const imageComponentProps: ImageProps = {
@@ -293,7 +301,8 @@ export function tryRenderAemAssetsImage(
293
301
  height: params.height,
294
302
  };
295
303
 
296
- UI.render(Image, imageComponentProps)(container);
304
+ // See the comment in `makeAemAssetsImageSlot` above.
305
+ await UI.render(Image, imageComponentProps)(container);
297
306
  ctx.replaceWith(container);
298
307
  }
299
308
 
@@ -301,7 +310,7 @@ export function tryRenderAemAssetsImage(
301
310
 
302
311
  if (!assetsEnabled) {
303
312
  // No-op, render the default image.
304
- renderDefaultImage();
313
+ await renderDefaultImage();
305
314
  return;
306
315
  }
307
316
 
@@ -315,9 +324,9 @@ export function tryRenderAemAssetsImage(
315
324
 
316
325
  if (!isAemAssetsUrl(assetsUrl)) {
317
326
  // Not an AEM Assets URL, so render the default image.
318
- renderDefaultImage();
327
+ await renderDefaultImage();
319
328
  return;
320
329
  }
321
330
 
322
- makeAemAssetsImageSlot(config)(ctx);
331
+ await makeAemAssetsImageSlot(config)(ctx);
323
332
  }
package/src/lib/slot.tsx CHANGED
@@ -146,9 +146,21 @@ export function useSlot<K, V extends HTMLElement>(
146
146
  });
147
147
  };
148
148
 
149
+ const handleLifeCycleRenderRef = useRef<() => Promise<void>>();
150
+
149
151
  const _registerMethod = useCallback((cb: Function) => {
150
152
  if (typeof cb === 'function') {
151
153
  methodsRef.current.push(cb);
154
+
155
+ // If the slot already finished its normal init-triggered render pass
156
+ // (status is back to 'ready'), nothing else will flush this method.
157
+ // This happens when an async `slot` callback doesn't `return`/`await`
158
+ // its own async work before calling replaceWith/appendChild/etc. --
159
+ // `handleLifeCycleInit` moves on before this method gets registered.
160
+ // Flush it ourselves rather than silently dropping it.
161
+ if (status.current === 'ready') {
162
+ handleLifeCycleRenderRef.current?.();
163
+ }
152
164
  } else {
153
165
  console.warn('Skipped: Invalid _registerMethod', cb);
154
166
  }
@@ -337,6 +349,8 @@ export function useSlot<K, V extends HTMLElement>(
337
349
  status.current = 'ready';
338
350
  }, [children, context, name, props, render, state]);
339
351
 
352
+ handleLifeCycleRenderRef.current = handleLifeCycleRender;
353
+
340
354
  // Initialization
341
355
  const handleLifeCycleInit = useCallback(async () => {
342
356
  if (!callback) return;