From e075dfad5c7649c6045e3711687ec888e7aa1a39 Mon Sep 17 00:00:00 2001 From: Michael Brevard Date: Wed, 18 Sep 2024 10:44:23 +0300 Subject: [PATCH 001/305] perf(hydration): avoid observer if element is in viewport (#11639) --- .../runtime-core/src/hydrationStrategies.ts | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/packages/runtime-core/src/hydrationStrategies.ts b/packages/runtime-core/src/hydrationStrategies.ts index 51200fc1c34..791ca9e5254 100644 --- a/packages/runtime-core/src/hydrationStrategies.ts +++ b/packages/runtime-core/src/hydrationStrategies.ts @@ -26,6 +26,16 @@ export const hydrateOnIdle: HydrationStrategyFactory = return () => cancelIdleCallback(id) } +function elementIsVisibleInViewport(el: Element) { + const { top, left, bottom, right } = el.getBoundingClientRect() + // eslint-disable-next-line no-restricted-globals + const { innerHeight, innerWidth } = window + return ( + ((top > 0 && top < innerHeight) || (bottom > 0 && bottom < innerHeight)) && + ((left > 0 && left < innerWidth) || (right > 0 && right < innerWidth)) + ) +} + export const hydrateOnVisible: HydrationStrategyFactory< IntersectionObserverInit > = opts => (hydrate, forEach) => { @@ -37,7 +47,14 @@ export const hydrateOnVisible: HydrationStrategyFactory< break } }, opts) - forEach(el => ob.observe(el)) + forEach(el => { + if (elementIsVisibleInViewport(el)) { + hydrate() + ob.disconnect() + return false + } + ob.observe(el) + }) return () => ob.disconnect() } @@ -85,14 +102,20 @@ export const hydrateOnInteraction: HydrationStrategyFactory< return teardown } -export function forEachElement(node: Node, cb: (el: Element) => void): void { +export function forEachElement( + node: Node, + cb: (el: Element) => void | false, +): void { // fragment if (isComment(node) && node.data === '[') { let depth = 1 let next = node.nextSibling while (next) { if (next.nodeType === DOMNodeTypes.ELEMENT) { - cb(next as Element) + const result = cb(next as Element) + if (result === false) { + break + } } else if (isComment(next)) { if (next.data === ']') { if (--depth === 0) break From 7257e6a34200409b3fc347d3bb807e11e2785974 Mon Sep 17 00:00:00 2001 From: linzhe <40790268+linzhe141@users.noreply.github.com> Date: Fri, 20 Sep 2024 16:41:58 +0800 Subject: [PATCH 002/305] fix(hydration): avoid observing non-Element node (#11954) close #11952 --- packages/runtime-core/src/hydrationStrategies.ts | 1 + .../vue/__tests__/e2e/hydration-strat-visible.html | 8 +++++++- .../vue/__tests__/e2e/hydrationStrategies.spec.ts | 11 +++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/packages/runtime-core/src/hydrationStrategies.ts b/packages/runtime-core/src/hydrationStrategies.ts index 791ca9e5254..bb98ea93c20 100644 --- a/packages/runtime-core/src/hydrationStrategies.ts +++ b/packages/runtime-core/src/hydrationStrategies.ts @@ -48,6 +48,7 @@ export const hydrateOnVisible: HydrationStrategyFactory< } }, opts) forEach(el => { + if (!(el instanceof Element)) return if (elementIsVisibleInViewport(el)) { hydrate() ob.disconnect() diff --git a/packages/vue/__tests__/e2e/hydration-strat-visible.html b/packages/vue/__tests__/e2e/hydration-strat-visible.html index 7b3b5ddf35d..489222f8606 100644 --- a/packages/vue/__tests__/e2e/hydration-strat-visible.html +++ b/packages/vue/__tests__/e2e/hydration-strat-visible.html @@ -11,9 +11,12 @@ + + `, + { inlineTemplate: true }, + ) + expect(content).toMatch(`_isRef(count) ? (count).value = $event : null`) + assertCode(content) + }) + test('v-model should not generate ref assignment code for non-setup bindings', () => { const { content } = compile( ` + `, + { isProd: true }, + ) + assertCode(content) + expect(content).toMatch(`const foo = __props.value<123>`) + }) + test('aliasing', () => { const { content, bindings } = compile(` + `) + assertCode(content) + expect(content).toMatch(`z: { type: Number, required: true }`) + expect(content).toMatch(`y: { type: String, required: true }`) + expect(content).toMatch(`x: { type: Number, required: false }`) + expect(bindings).toStrictEqual({ + x: BindingTypes.PROPS, + y: BindingTypes.PROPS, + z: BindingTypes.PROPS, + }) + }) + + test('w/ intersection type', () => { + const { content, bindings } = compile(` + + `) + assertCode(content) + expect(content).toMatch(`y: { type: String, required: true }`) + expect(content).toMatch(`x: { type: Number, required: false }`) + expect(bindings).toStrictEqual({ + x: BindingTypes.PROPS, + y: BindingTypes.PROPS, + }) + }) + test('w/ exported interface', () => { const { content, bindings } = compile(` + `) + assertCode(content) + expect(content).toMatch(`console.log(__props.value)`) + }) + test('defineProps/defineEmits in multi-variable declaration (full removal)', () => { const { content } = compile(` diff --git a/packages/vue/__tests__/e2e/hydrationStrategies.spec.ts b/packages/vue/__tests__/e2e/hydrationStrategies.spec.ts index 69934d9591e..d792edf1960 100644 --- a/packages/vue/__tests__/e2e/hydrationStrategies.spec.ts +++ b/packages/vue/__tests__/e2e/hydrationStrategies.spec.ts @@ -86,6 +86,36 @@ describe('async component hydration strategies', () => { await assertHydrationSuccess() }) + // #13255 + test('media query (patched before hydration)', async () => { + const spy = vi.fn() + const currentPage = page() + currentPage.on('pageerror', spy) + + const warn: any[] = [] + currentPage.on('console', e => warn.push(e.text())) + + await goToCase('media') + await page().waitForFunction(() => window.isRootMounted) + expect(await page().evaluate(() => window.isHydrated)).toBe(false) + + // patch + await page().evaluate(() => (window.show.value = false)) + await click('button') + expect(await text('button')).toBe('1') + + // resize + await page().setViewport({ width: 400, height: 600 }) + await page().waitForFunction(() => window.isHydrated) + await assertHydrationSuccess('2') + + expect(spy).toBeCalledTimes(0) + currentPage.off('pageerror', spy) + expect( + warn.some(w => w.includes('Skipping lazy hydration for component')), + ).toBe(true) + }) + test('interaction', async () => { await goToCase('interaction') await page().waitForFunction(() => window.isRootMounted) From d37a2ac59d904ac0e3257ba552b6c04920a363f0 Mon Sep 17 00:00:00 2001 From: edison Date: Fri, 16 May 2025 08:22:37 +0800 Subject: [PATCH 270/305] fix(compiler-core): ensure mapping is added only if node source is available (#13285) close #13261 close vitejs/vite-plugin-vue#368 --- packages/compiler-core/src/codegen.ts | 6 ++-- .../__tests__/compileTemplate.spec.ts | 29 +++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts index 70116cfb61a..6b4559fabb2 100644 --- a/packages/compiler-core/src/codegen.ts +++ b/packages/compiler-core/src/codegen.ts @@ -188,7 +188,9 @@ function createCodegenContext( name = content } } - addMapping(node.loc.start, name) + if (node.loc.source) { + addMapping(node.loc.start, name) + } } if (newlineIndex === NewlineType.Unknown) { // multiple newlines, full iteration @@ -225,7 +227,7 @@ function createCodegenContext( context.column = code.length - newlineIndex } } - if (node && node.loc !== locStub) { + if (node && node.loc !== locStub && node.loc.source) { addMapping(node.loc.end) } } diff --git a/packages/compiler-sfc/__tests__/compileTemplate.spec.ts b/packages/compiler-sfc/__tests__/compileTemplate.spec.ts index 2ea1eb9d378..22623299a02 100644 --- a/packages/compiler-sfc/__tests__/compileTemplate.spec.ts +++ b/packages/compiler-sfc/__tests__/compileTemplate.spec.ts @@ -157,6 +157,35 @@ test('source map', () => { ).toMatchObject(getPositionInCode(template.content, `foobar`)) }) +test('source map: v-if generated comment should not have original position', () => { + const template = parse( + ` + + `, + { filename: 'example.vue', sourceMap: true }, + ).descriptor.template! + + const { code, map } = compile({ + filename: 'example.vue', + source: template.content, + }) + + expect(map!.sources).toEqual([`example.vue`]) + expect(map!.sourcesContent).toEqual([template.content]) + + const consumer = new SourceMapConsumer(map as RawSourceMap) + const commentNode = code.match(/_createCommentVNode\("v-if", true\)/) + expect(commentNode).not.toBeNull() + const commentPosition = getPositionInCode(code, commentNode![0]) + const originalPosition = consumer.originalPositionFor(commentPosition) + // the comment node should not be mapped to the original source + expect(originalPosition.column).toBeNull() + expect(originalPosition.line).toBeNull() + expect(originalPosition.source).toBeNull() +}) + test('should work w/ AST from descriptor', () => { const source = `