diff --git a/src/lazy-define.ts b/src/lazy-define.ts index 016b886..93243ef 100644 --- a/src/lazy-define.ts +++ b/src/lazy-define.ts @@ -57,26 +57,32 @@ const strategies: Record = { type ElementLike = Element | Document | ShadowRoot -const timers = new WeakMap() +const pendingElements = new Set() +let scanTimer: number | null = null + function scan(element: ElementLike) { - cancelAnimationFrame(timers.get(element) || 0) - timers.set( - element, - requestAnimationFrame(() => { + pendingElements.add(element) + if (scanTimer != null) return + scanTimer = requestAnimationFrame(() => { + scanTimer = null + if (!dynamicElements.size) { + pendingElements.clear() + return + } + for (const el of pendingElements) { for (const tagName of dynamicElements.keys()) { - const child: Element | null = - element instanceof Element && element.matches(tagName) ? element : element.querySelector(tagName) + const child: Element | null = el instanceof Element && el.matches(tagName) ? el : el.querySelector(tagName) if (customElements.get(tagName) || child) { const strategyName = (child?.getAttribute('data-load-on') || 'ready') as keyof typeof strategies const strategy = strategyName in strategies ? strategies[strategyName] : strategies.ready // eslint-disable-next-line github/no-then for (const cb of dynamicElements.get(tagName) || []) strategy(tagName).then(cb) dynamicElements.delete(tagName) - timers.delete(element) } } - }) - ) + } + pendingElements.clear() + }) } let elementLoader: MutationObserver diff --git a/test/lazy-define.ts b/test/lazy-define.ts index b2517d6..5e49aba 100644 --- a/test/lazy-define.ts +++ b/test/lazy-define.ts @@ -68,6 +68,38 @@ describe('lazyDefine', () => { expect(onDefine3).to.have.callCount(1) }) + it('coalesces multiple added elements into a single rAF callback', async () => { + const onDefine = spy() + lazyDefine('coalesce-test-element', onDefine) + + const rafSpy = spy(window, 'requestAnimationFrame') + const callsBefore = rafSpy.callCount + + await fixture(html` +
+ + + + + + + + + + +
+ `) + + await animationFrame() + + const rafCallsFromScan = rafSpy.callCount - callsBefore + rafSpy.restore() + + // Should use at most a few rAF calls, not one per element + expect(rafCallsFromScan).to.be.lessThan(5) + expect(onDefine).to.be.callCount(1) + }) + it('lazy loads elements in shadow roots', async () => { const onDefine = spy() lazyDefine('nested-shadow-element', onDefine)