Skip to content

Commit b8aa270

Browse files
committed
lazy-define: skip observe() work when nothing is pending
core.ts calls observe() for every shadow-root controller on connect. Once all lazy definitions resolve (or when lazyDefine is never used), there is nothing to watch for — the browser upgrades any already-defined element's instances natively — so observe() can return immediately instead of creating/scanning/ disconnecting a MutationObserver per connect.
1 parent 570e7e4 commit b8aa270

2 files changed

Lines changed: 24 additions & 1 deletion

File tree

src/lazy-define.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,14 @@ export function lazyDefine(tagNameOrObj: string | Record<string, () => void>, si
112112
}
113113

114114
export function observe(target: ElementLike): void {
115+
// Nothing is waiting to be defined. Any tags that already resolved are now
116+
// real custom elements, so the browser upgrades their instances natively and
117+
// there is nothing for us to watch for. This keeps controllers that never use
118+
// lazyDefine — and those connecting after every definition has resolved —
119+
// completely free of observer cost (core.ts calls observe() for every
120+
// shadow-root controller on connect).
121+
if (!dynamicElements.size) return
122+
115123
elementLoader ||= new MutationObserver(mutations => {
116124
if (!dynamicElements.size) return
117125
for (const mutation of mutations) {

test/lazy-define.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,20 @@ describe('lazyDefine', () => {
166166

167167
expect(onSecond).to.be.callCount(1)
168168
})
169+
170+
it('does no work when observe() is called with no pending definitions', async () => {
171+
// Drain any pending state from prior expectations.
172+
await animationFrame()
173+
174+
const rafSpy = spy(window, 'requestAnimationFrame')
175+
// core.ts calls observe() for every shadow-root controller on connect; it
176+
// must be free when nothing is waiting to be lazily defined.
177+
observe(document)
178+
const scheduled = rafSpy.callCount
179+
rafSpy.restore()
180+
181+
expect(scheduled).to.equal(0)
182+
})
169183
})
170184

171185
describe('race condition prevention', () => {
@@ -240,12 +254,13 @@ describe('lazyDefine', () => {
240254
const el = await fixture(html`<div></div>`)
241255
const shadowRoot = el.attachShadow({mode: 'open'})
242256

257+
lazyDefine('redundant-test-element', onDefine)
258+
243259
// Observe the same shadow root multiple times
244260
observe(shadowRoot)
245261
observe(shadowRoot)
246262
observe(shadowRoot)
247263

248-
lazyDefine('redundant-test-element', onDefine)
249264
// eslint-disable-next-line github/unescaped-html-literal
250265
shadowRoot.innerHTML = '<redundant-test-element></redundant-test-element>'
251266

0 commit comments

Comments
 (0)