Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"format-check": "prettier --check --cache .",
"test": "vitest",
"test-unit": "vitest --project unit --project unit-jsdom",
"test-e2e": "node scripts/build.js vue -f global -d && vitest --project e2e",
"test-e2e": "node scripts/build.js vue -f global+esm-browser-vapor -d && vitest --project e2e",
"test-e2e-vapor": "pnpm run prepare-e2e-vapor && vitest --project e2e-vapor",
"prepare-e2e-vapor": "node scripts/build.js -f cjs+esm-bundler+esm-bundler-runtime && pnpm run -C packages-private/vapor-e2e-test build",
"test-dts": "run-s build-dts test-dts-only",
Expand Down
128 changes: 82 additions & 46 deletions packages/runtime-core/src/apiAsyncComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
type ComponentInternalInstance,
type ComponentOptions,
type ConcreteComponent,
type GenericComponent,
type GenericComponentInstance,
currentInstance,
getComponentName,
Expand Down Expand Up @@ -68,37 +69,14 @@ export function defineAsyncComponent<
__asyncLoader: load,

__asyncHydrate(el, instance, hydrate) {
let patched = false
;(instance.bu || (instance.bu = [])).push(() => (patched = true))
const performHydrate = () => {
// skip hydration if the component has been patched
if (patched) {
if (__DEV__) {
const resolvedComp = getResolvedComp()
warn(
`Skipping lazy hydration for component '${getComponentName(resolvedComp!) || resolvedComp!.__file}': ` +
`it was updated before lazy hydration performed.`,
)
}
return
}
hydrate()
}
const doHydrate = hydrateStrategy
? () => {
const teardown = hydrateStrategy(performHydrate, cb =>
forEachElement(el, cb),
)
if (teardown) {
;(instance.bum || (instance.bum = [])).push(teardown)
}
}
: performHydrate
if (getResolvedComp()) {
doHydrate()
} else {
load().then(() => !instance.isUnmounted && doHydrate())
}
performAsyncHydrate(
el,
instance,
hydrate,
getResolvedComp,
load,
hydrateStrategy,
)
},

get __asyncResolved() {
Expand Down Expand Up @@ -130,19 +108,7 @@ export function defineAsyncComponent<
(__FEATURE_SUSPENSE__ && suspensible && instance.suspense) ||
(__SSR__ && isInSSRComponentSetup)
) {
return load()
.then(comp => {
return () => createInnerComp(comp, instance)
})
.catch(err => {
onError(err)
return () =>
errorComponent
? createVNode(errorComponent as ConcreteComponent, {
error: err,
})
: null
})
return loadInnerComponent(instance, load, onError, errorComponent)
}

const { loaded, error, delayed } = useAsyncComponentState(
Expand Down Expand Up @@ -185,10 +151,10 @@ export function defineAsyncComponent<
}) as T
}

function createInnerComp(
export function createInnerComp(
comp: ConcreteComponent,
parent: ComponentInternalInstance,
) {
): VNode {
const { ref, props, children, ce } = parent.vnode
const vnode = createVNode(comp, props, children)
// ensure inner component inherits the async wrapper's ref owner
Expand Down Expand Up @@ -311,3 +277,73 @@ export const useAsyncComponentState = (

return { loaded, error, delayed }
}

/**
* shared between core and vapor
* @internal
*/
export function loadInnerComponent(
instance: ComponentInternalInstance,
load: () => Promise<any>,
onError: (err: Error) => void,
errorComponent: ConcreteComponent | undefined,
): Promise<() => VNode | null> {
return load()
.then(comp => {
return () => createInnerComp(comp, instance)
})
.catch(err => {
onError(err)
return () =>
errorComponent
? createVNode(errorComponent as ConcreteComponent, {
error: err,
})
: null
})
}

/**
* shared between core and vapor
* @internal
*/
export function performAsyncHydrate(
el: Element,
instance: GenericComponentInstance,
hydrate: () => void,
getResolvedComp: () => GenericComponent | undefined,
load: () => Promise<GenericComponent>,
hydrateStrategy: HydrationStrategy | undefined,
): void {
let patched = false
;(instance.bu || (instance.bu = [])).push(() => (patched = true))
const performHydrate = () => {
// skip hydration if the component has been patched
if (patched) {
if (__DEV__) {
const resolvedComp = getResolvedComp()! as GenericComponent
warn(
`Skipping lazy hydration for component '${getComponentName(resolvedComp) || resolvedComp.__file}': ` +
`it was updated before lazy hydration performed.`,
)
}
return
}
hydrate()
}
const doHydrate = hydrateStrategy
? () => {
const teardown = hydrateStrategy(performHydrate, cb =>
forEachElement(el, cb),
)
if (teardown) {
;(instance.bum || (instance.bum = [])).push(teardown)
}
}
: performHydrate
if (getResolvedComp()) {
doHydrate()
} else {
load().then(() => !instance.isUnmounted && doHydrate())
}
}
21 changes: 21 additions & 0 deletions packages/runtime-core/src/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,27 @@ export interface ComponentInternalOptions {
__name?: string
}

export interface AsyncComponentInternalOptions<
R = ConcreteComponent,
I = ComponentInternalInstance,
> {
/**
* marker for AsyncComponentWrapper
* @internal
*/
__asyncLoader?: () => Promise<R>
/**
* the inner component resolved by the AsyncComponentWrapper
* @internal
*/
__asyncResolved?: R
/**
* Exposed for lazy hydration
* @internal
*/
__asyncHydrate?: (el: Element, instance: I, hydrate: () => void) => void
}

export interface FunctionalComponent<
P = {},
E extends EmitsOptions | Record<string, any[]> = {},
Expand Down
23 changes: 2 additions & 21 deletions packages/runtime-core/src/componentOptions.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {
type AsyncComponentInternalOptions,
type Component,
type ComponentInternalInstance,
type ComponentInternalOptions,
type ConcreteComponent,
type Data,
type InternalRenderFunction,
type SetupContext,
Expand Down Expand Up @@ -127,6 +127,7 @@ export interface ComponentOptionsBase<
Provide extends ComponentProvideOptions = ComponentProvideOptions,
> extends LegacyOptions<Props, D, C, M, Mixin, Extends, I, II, Provide>,
ComponentInternalOptions,
AsyncComponentInternalOptions,
ComponentCustomOptions {
setup?: (
this: void,
Expand Down Expand Up @@ -190,26 +191,6 @@ export interface ComponentOptionsBase<
*/
__ssrInlineRender?: boolean

/**
* marker for AsyncComponentWrapper
* @internal
*/
__asyncLoader?: () => Promise<ConcreteComponent>
/**
* the inner component resolved by the AsyncComponentWrapper
* @internal
*/
__asyncResolved?: ConcreteComponent
/**
* Exposed for lazy hydration
* @internal
*/
__asyncHydrate?: (
el: Element,
instance: ComponentInternalInstance,
hydrate: () => void,
) => void

// Type differentiators ------------------------------------------------------

// Note these are internal but need to be exposed in d.ts for type inference
Expand Down
6 changes: 4 additions & 2 deletions packages/runtime-core/src/hydrationStrategies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,10 @@ export const hydrateOnInteraction: HydrationStrategyFactory<
hasHydrated = true
teardown()
hydrate()
// replay event
e.target!.dispatchEvent(new (e.constructor as any)(e.type, e))
// replay event if the event is not delegated
if (!(`$evt${e.type}` in e.target!)) {
e.target!.dispatchEvent(new (e.constructor as any)(e.type, e))
}
}
}
const teardown = () => {
Expand Down
11 changes: 10 additions & 1 deletion packages/runtime-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ export type {
GlobalDirectives,
ComponentInstance,
ComponentCustomElementInterface,
AsyncComponentInternalOptions,
} from './component'
export type {
DefineComponent,
Expand Down Expand Up @@ -535,7 +536,12 @@ export { queueJob, flushOnAppMount } from './scheduler'
/**
* @internal
*/
export { expose, nextUid, validateComponentName } from './component'
export {
expose,
nextUid,
validateComponentName,
isInSSRComponentSetup,
} from './component'
/**
* @internal
*/
Expand Down Expand Up @@ -595,6 +601,9 @@ export {
createAsyncComponentContext,
useAsyncComponentState,
isAsyncWrapper,
performAsyncHydrate,
loadInnerComponent,
createInnerComp,
} from './apiAsyncComponent'
/**
* @internal
Expand Down
Loading
Loading