diff --git a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts b/packages/compiler-core/__tests__/transforms/transformElement.spec.ts index bf3510a052d..e4ab776a764 100644 --- a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts +++ b/packages/compiler-core/__tests__/transforms/transformElement.spec.ts @@ -99,6 +99,16 @@ describe('compiler: element transform', () => { expect(node.tag).toBe(`$setup["Example"]`) }) + test('resolve component from setup bindings & component', () => { + const { root, node } = parseWithElementTransform(`<Example/>`, { + bindingMetadata: { + Example: BindingTypes.SETUP_CONST, + }, + }) + expect(root.helpers).not.toContain(RESOLVE_COMPONENT) + expect(node.tag).toBe(`_resolveSetupReturned("Example", $setup)`) + }) + test('resolve component from setup bindings (inline)', () => { const { root, node } = parseWithElementTransform(`<Example/>`, { inline: true, diff --git a/packages/compiler-core/src/ast.ts b/packages/compiler-core/src/ast.ts index 8116f532b79..2247f2c758b 100644 --- a/packages/compiler-core/src/ast.ts +++ b/packages/compiler-core/src/ast.ts @@ -9,6 +9,7 @@ import { OPEN_BLOCK, type RENDER_LIST, type RENDER_SLOT, + RESOLVE_SETUP_RETURNED, WITH_DIRECTIVES, type WITH_MEMO, } from './runtimeHelpers' @@ -865,6 +866,10 @@ export function getVNodeBlockHelper( return ssr || isComponent ? CREATE_BLOCK : CREATE_ELEMENT_BLOCK } +export function getSetupReturnedHelper() { + return RESOLVE_SETUP_RETURNED +} + export function convertToBlock( node: VNodeCall, { helper, removeHelper, inSSR }: TransformContext, diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts index 6b6f24b3a30..909fbf42c94 100644 --- a/packages/compiler-core/src/codegen.ts +++ b/packages/compiler-core/src/codegen.ts @@ -24,6 +24,7 @@ import { type TemplateLiteral, type TextNode, type VNodeCall, + getSetupReturnedHelper, getVNodeBlockHelper, getVNodeHelper, locStub, @@ -318,6 +319,8 @@ export function generate( if (!__BROWSER__ && options.bindingMetadata && !options.inline) { // binding optimization args args.push('$props', '$setup', '$data', '$options') + // Add helper 'getSetupReturnedHelper' for $setup + context.helper(getSetupReturnedHelper()) } const signature = !__BROWSER__ && options.isTS diff --git a/packages/compiler-core/src/runtimeHelpers.ts b/packages/compiler-core/src/runtimeHelpers.ts index 7cf3757b249..3eff4592b6e 100644 --- a/packages/compiler-core/src/runtimeHelpers.ts +++ b/packages/compiler-core/src/runtimeHelpers.ts @@ -26,6 +26,9 @@ export const CREATE_STATIC: unique symbol = Symbol( export const RESOLVE_COMPONENT: unique symbol = Symbol( __DEV__ ? `resolveComponent` : ``, ) +export const RESOLVE_SETUP_RETURNED = Symbol( + __DEV__ ? `resolveSetupReturned` : ``, +) export const RESOLVE_DYNAMIC_COMPONENT: unique symbol = Symbol( __DEV__ ? `resolveDynamicComponent` : ``, ) @@ -98,6 +101,7 @@ export const helperNameMap: Record<symbol, string> = { [CREATE_TEXT]: `createTextVNode`, [CREATE_STATIC]: `createStaticVNode`, [RESOLVE_COMPONENT]: `resolveComponent`, + [RESOLVE_SETUP_RETURNED]: `resolveSetupReturned`, [RESOLVE_DYNAMIC_COMPONENT]: `resolveDynamicComponent`, [RESOLVE_DIRECTIVE]: `resolveDirective`, [RESOLVE_FILTER]: `resolveFilter`, diff --git a/packages/compiler-core/src/transforms/transformElement.ts b/packages/compiler-core/src/transforms/transformElement.ts index c917436ea91..471aac038f6 100644 --- a/packages/compiler-core/src/transforms/transformElement.ts +++ b/packages/compiler-core/src/transforms/transformElement.ts @@ -21,6 +21,7 @@ import { createObjectProperty, createSimpleExpression, createVNodeCall, + getSetupReturnedHelper, } from '../ast' import { PatchFlags, @@ -344,10 +345,13 @@ function resolveSetupReference(name: string, context: TransformContext) { checkType(BindingTypes.SETUP_REACTIVE_CONST) || checkType(BindingTypes.LITERAL_CONST) if (fromConst) { + const helper = context.helperString return context.inline ? // in inline mode, const setup bindings (e.g. imports) can be used as-is fromConst - : `$setup[${JSON.stringify(fromConst)}]` + : `${helper(getSetupReturnedHelper())}(${JSON.stringify( + fromConst, + )}, $setup)` } const fromMaybeRef = diff --git a/packages/runtime-core/src/helpers/resolveAssets.ts b/packages/runtime-core/src/helpers/resolveAssets.ts index aa6532c2811..a6dd122e5af 100644 --- a/packages/runtime-core/src/helpers/resolveAssets.ts +++ b/packages/runtime-core/src/helpers/resolveAssets.ts @@ -6,7 +6,7 @@ import { } from '../component' import { currentRenderingInstance } from '../componentRenderContext' import type { Directive } from '../directives' -import { camelize, capitalize, isString } from '@vue/shared' +import { camelize, capitalize, isLateTag, isString } from '@vue/shared' import { warn } from '../warning' import type { VNodeTypes } from '../vnode' @@ -106,18 +106,26 @@ function resolveAsset( resolve(instance[type] || (Component as ComponentOptions)[type], name) || // global registration resolve(instance.appContext[type], name) - if (!res && maybeSelfReference) { // fallback to implicit self-reference return Component } - if (__DEV__ && warnMissing && !res) { - const extra = - type === COMPONENTS - ? `\nIf this is a native custom element, make sure to exclude it from ` + + if ( + __DEV__ && + warnMissing && + ((!res && !isLateTag(name)) || (res && isLateTag(name))) + ) { + let extra = '' + if (type === COMPONENTS) { + if (isLateTag(name)) { + extra = `\nplease do not use built-in tag names as component names.` + } else { + extra = + `\nIf this is a native custom element, make sure to exclude it from ` + `component resolution via compilerOptions.isCustomElement.` - : `` + } + } warn(`Failed to resolve ${type.slice(0, -1)}: ${name}${extra}`) } @@ -138,3 +146,16 @@ function resolve(registry: Record<string, any> | undefined, name: string) { registry[capitalize(camelize(name))]) ) } + +/** + * @private + */ +export function resolveSetupReturned(name: string, setupReturn: any) { + if (!setupReturn) return name + const returnValue = setupReturn[name] + if (returnValue && returnValue.__file && isLateTag(name as string)) { + const extra = `\nplease do not use built-in tag names as component names.` + warn(`Failed to resolve component: ${name},${extra}`) + } + return returnValue +} 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<number> = 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 diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index 7f716b5f4e8..15c915c8919 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -144,6 +144,7 @@ export { resolveComponent, resolveDirective, resolveDynamicComponent, + resolveSetupReturned, } from './helpers/resolveAssets' // For integration with runtime compiler export { registerRuntimeCompiler, isRuntimeOnly } from './component' diff --git a/packages/shared/src/domTagConfig.ts b/packages/shared/src/domTagConfig.ts index 7f9d198e569..9084909c4b1 100644 --- a/packages/shared/src/domTagConfig.ts +++ b/packages/shared/src/domTagConfig.ts @@ -14,6 +14,8 @@ const HTML_TAGS = 'option,output,progress,select,textarea,details,dialog,menu,' + 'summary,template,blockquote,iframe,tfoot' +const LATE_ADDED_TAGS = 'search' + // https://developer.mozilla.org/en-US/docs/Web/SVG/Element const SVG_TAGS = 'svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,' + @@ -62,3 +64,5 @@ export const isMathMLTag: (key: string) => boolean = */ export const isVoidTag: (key: string) => boolean = /*@__PURE__*/ makeMap(VOID_TAGS) + +export const isLateTag = /*#__PURE__*/ makeMap(LATE_ADDED_TAGS)