refactor(ssr): make hydration logic tree-shakeable
This commit is contained in:
parent
112d8f7d86
commit
167f8241bd
@ -91,7 +91,7 @@ export type CreateAppFunction<HostElement> = (
|
|||||||
|
|
||||||
export function createAppAPI<HostNode, HostElement>(
|
export function createAppAPI<HostNode, HostElement>(
|
||||||
render: RootRenderFunction<HostNode, HostElement>,
|
render: RootRenderFunction<HostNode, HostElement>,
|
||||||
hydrate: (vnode: VNode, container: Element) => void
|
hydrate?: (vnode: VNode, container: Element) => void
|
||||||
): CreateAppFunction<HostElement> {
|
): CreateAppFunction<HostElement> {
|
||||||
return function createApp(rootComponent: Component, rootProps = null) {
|
return function createApp(rootComponent: Component, rootProps = null) {
|
||||||
if (rootProps != null && !isObject(rootProps)) {
|
if (rootProps != null && !isObject(rootProps)) {
|
||||||
@ -200,7 +200,7 @@ export function createAppAPI<HostNode, HostElement>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isHydrate) {
|
if (isHydrate && hydrate) {
|
||||||
hydrate(vnode, rootContainer as any)
|
hydrate(vnode, rootContainer as any)
|
||||||
} else {
|
} else {
|
||||||
render(vnode, rootContainer)
|
render(vnode, rootContainer)
|
||||||
|
@ -15,9 +15,11 @@ import { warn } from './warning'
|
|||||||
import { PatchFlags, isReservedProp, isOn } from '@vue/shared'
|
import { PatchFlags, isReservedProp, isOn } from '@vue/shared'
|
||||||
|
|
||||||
// Note: hydration is DOM-specific
|
// Note: hydration is DOM-specific
|
||||||
// but we have to place it in core due to tight coupling with core - splitting
|
// But we have to place it in core due to tight coupling with core - splitting
|
||||||
// it out creates a ton of unnecessary complexity.
|
// it out creates a ton of unnecessary complexity.
|
||||||
export function createHydrateFn(
|
// Hydration also depends on some renderer internal logic which needs to be
|
||||||
|
// passed in via arguments.
|
||||||
|
export function createHydrationFunctions(
|
||||||
mountComponent: any, // TODO
|
mountComponent: any, // TODO
|
||||||
patchProp: any // TODO
|
patchProp: any // TODO
|
||||||
) {
|
) {
|
||||||
|
@ -65,7 +65,7 @@ export { useCSSModule } from './helpers/useCssModule'
|
|||||||
// Internal API ----------------------------------------------------------------
|
// Internal API ----------------------------------------------------------------
|
||||||
|
|
||||||
// For custom renderers
|
// For custom renderers
|
||||||
export { createRenderer } from './renderer'
|
export { createRenderer, createHydrationRenderer } from './renderer'
|
||||||
export { warn } from './warning'
|
export { warn } from './warning'
|
||||||
export {
|
export {
|
||||||
handleError,
|
handleError,
|
||||||
|
@ -62,7 +62,7 @@ import {
|
|||||||
import { ErrorCodes, callWithErrorHandling } from './errorHandling'
|
import { ErrorCodes, callWithErrorHandling } from './errorHandling'
|
||||||
import { KeepAliveSink, isKeepAlive } from './components/KeepAlive'
|
import { KeepAliveSink, isKeepAlive } from './components/KeepAlive'
|
||||||
import { registerHMR, unregisterHMR } from './hmr'
|
import { registerHMR, unregisterHMR } from './hmr'
|
||||||
import { createHydrateFn } from './hydration'
|
import { createHydrationFunctions } from './hydration'
|
||||||
|
|
||||||
const __HMR__ = __BUNDLER__ && __DEV__
|
const __HMR__ = __BUNDLER__ && __DEV__
|
||||||
|
|
||||||
@ -186,6 +186,32 @@ export function createRenderer<
|
|||||||
HostNode extends object = any,
|
HostNode extends object = any,
|
||||||
HostElement extends HostNode = any
|
HostElement extends HostNode = any
|
||||||
>(options: RendererOptions<HostNode, HostElement>) {
|
>(options: RendererOptions<HostNode, HostElement>) {
|
||||||
|
const res = baseCreateRenderer(options)
|
||||||
|
return res as typeof res & {
|
||||||
|
hydrate: undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Separate API for creating hydration-enabled renderer.
|
||||||
|
// Hydration logic is only used when calling this function, making it
|
||||||
|
// tree-shakable.
|
||||||
|
export function createHydrationRenderer<
|
||||||
|
HostNode extends object = any,
|
||||||
|
HostElement extends HostNode = any
|
||||||
|
>(options: RendererOptions<HostNode, HostElement>) {
|
||||||
|
const res = baseCreateRenderer(options, createHydrationFunctions)
|
||||||
|
return res as typeof res & {
|
||||||
|
hydrate: ReturnType<typeof createHydrationFunctions>[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function baseCreateRenderer<
|
||||||
|
HostNode extends object = any,
|
||||||
|
HostElement extends HostNode = any
|
||||||
|
>(
|
||||||
|
options: RendererOptions<HostNode, HostElement>,
|
||||||
|
createHydrationFns?: typeof createHydrationFunctions
|
||||||
|
) {
|
||||||
type HostVNode = VNode<HostNode, HostElement>
|
type HostVNode = VNode<HostNode, HostElement>
|
||||||
type HostVNodeChildren = VNodeArrayChildren<HostNode, HostElement>
|
type HostVNodeChildren = VNodeArrayChildren<HostNode, HostElement>
|
||||||
type HostSuspenseBoundary = SuspenseBoundary<HostNode, HostElement>
|
type HostSuspenseBoundary = SuspenseBoundary<HostNode, HostElement>
|
||||||
@ -215,6 +241,12 @@ export function createRenderer<
|
|||||||
options
|
options
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let hydrate: ReturnType<typeof createHydrationFunctions>[0] | undefined
|
||||||
|
let hydrateNode: ReturnType<typeof createHydrationFunctions>[1] | undefined
|
||||||
|
if (createHydrationFns) {
|
||||||
|
;[hydrate, hydrateNode] = createHydrationFns(mountComponent, hostPatchProp)
|
||||||
|
}
|
||||||
|
|
||||||
function patch(
|
function patch(
|
||||||
n1: HostVNode | null, // null means this is a mount
|
n1: HostVNode | null, // null means this is a mount
|
||||||
n2: HostVNode,
|
n2: HostVNode,
|
||||||
@ -1054,7 +1086,7 @@ export function createRenderer<
|
|||||||
if (instance.bm !== null) {
|
if (instance.bm !== null) {
|
||||||
invokeHooks(instance.bm)
|
invokeHooks(instance.bm)
|
||||||
}
|
}
|
||||||
if (initialVNode.el) {
|
if (initialVNode.el && hydrateNode) {
|
||||||
// vnode has adopted host node - perform hydration instead of mount.
|
// vnode has adopted host node - perform hydration instead of mount.
|
||||||
hydrateNode(initialVNode.el as Node, subTree, instance)
|
hydrateNode(initialVNode.el as Node, subTree, instance)
|
||||||
} else {
|
} else {
|
||||||
@ -1823,8 +1855,6 @@ export function createRenderer<
|
|||||||
container._vnode = vnode
|
container._vnode = vnode
|
||||||
}
|
}
|
||||||
|
|
||||||
const [hydrate, hydrateNode] = createHydrateFn(mountComponent, hostPatchProp)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
render,
|
render,
|
||||||
hydrate,
|
hydrate,
|
||||||
|
@ -1,49 +1,62 @@
|
|||||||
import {
|
import {
|
||||||
createRenderer,
|
createRenderer,
|
||||||
|
createHydrationRenderer,
|
||||||
warn,
|
warn,
|
||||||
RootRenderFunction,
|
RootRenderFunction,
|
||||||
CreateAppFunction
|
CreateAppFunction,
|
||||||
|
VNode,
|
||||||
|
App
|
||||||
} from '@vue/runtime-core'
|
} from '@vue/runtime-core'
|
||||||
import { nodeOps } from './nodeOps'
|
import { nodeOps } from './nodeOps'
|
||||||
import { patchProp } from './patchProp'
|
import { patchProp } from './patchProp'
|
||||||
// Importing from the compiler, will be tree-shaken in prod
|
// Importing from the compiler, will be tree-shaken in prod
|
||||||
import { isFunction, isString, isHTMLTag, isSVGTag } from '@vue/shared'
|
import { isFunction, isString, isHTMLTag, isSVGTag } from '@vue/shared'
|
||||||
|
|
||||||
const {
|
const rendererOptions = {
|
||||||
render: baseRender,
|
|
||||||
hydrate: baseHydrate,
|
|
||||||
createApp: baseCreateApp
|
|
||||||
} = createRenderer({
|
|
||||||
patchProp,
|
patchProp,
|
||||||
...nodeOps
|
...nodeOps
|
||||||
})
|
}
|
||||||
|
|
||||||
|
// lazy create the renderer - this makes core renderer logic tree-shakable
|
||||||
|
// in case the user only imports reactivity utilities from Vue.
|
||||||
|
let renderer:
|
||||||
|
| ReturnType<typeof createRenderer>
|
||||||
|
| ReturnType<typeof createHydrationRenderer>
|
||||||
|
|
||||||
|
let enabledHydration = false
|
||||||
|
|
||||||
|
function ensureRenderer() {
|
||||||
|
return renderer || (renderer = createRenderer(rendererOptions))
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureHydrationRenderer() {
|
||||||
|
renderer = enabledHydration
|
||||||
|
? renderer
|
||||||
|
: createHydrationRenderer(rendererOptions)
|
||||||
|
enabledHydration = true
|
||||||
|
return renderer as ReturnType<typeof createHydrationRenderer>
|
||||||
|
}
|
||||||
|
|
||||||
// use explicit type casts here to avoid import() calls in rolled-up d.ts
|
// use explicit type casts here to avoid import() calls in rolled-up d.ts
|
||||||
export const render = baseRender as RootRenderFunction<Node, Element>
|
export const render = ((...args) => {
|
||||||
export const hydrate = baseHydrate as RootRenderFunction<Node, Element>
|
ensureRenderer().render(...args)
|
||||||
|
}) as RootRenderFunction<Node, Element>
|
||||||
|
|
||||||
export const createApp: CreateAppFunction<Element> = (...args) => {
|
export const hydrate = ((...args) => {
|
||||||
const app = baseCreateApp(...args)
|
ensureHydrationRenderer().hydrate(...args)
|
||||||
|
}) as (vnode: VNode, container: Element) => void
|
||||||
|
|
||||||
|
export const createApp = ((...args) => {
|
||||||
|
const app = ensureRenderer().createApp(...args)
|
||||||
|
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
// Inject `isNativeTag`
|
injectNativeTagCheck(app)
|
||||||
// this is used for component name validation (dev only)
|
|
||||||
Object.defineProperty(app.config, 'isNativeTag', {
|
|
||||||
value: (tag: string) => isHTMLTag(tag) || isSVGTag(tag),
|
|
||||||
writable: false
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { mount } = app
|
const { mount } = app
|
||||||
app.mount = (container: Element | string): any => {
|
app.mount = (containerOrSelector: Element | string): any => {
|
||||||
if (isString(container)) {
|
const container = normalizeContainer(containerOrSelector)
|
||||||
container = document.querySelector(container)!
|
if (!container) return
|
||||||
if (!container) {
|
|
||||||
__DEV__ &&
|
|
||||||
warn(`Failed to mount app: mount target selector returned null.`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const component = app._component
|
const component = app._component
|
||||||
if (
|
if (
|
||||||
__RUNTIME_COMPILE__ &&
|
__RUNTIME_COMPILE__ &&
|
||||||
@ -53,15 +66,50 @@ export const createApp: CreateAppFunction<Element> = (...args) => {
|
|||||||
) {
|
) {
|
||||||
component.template = container.innerHTML
|
component.template = container.innerHTML
|
||||||
}
|
}
|
||||||
const isHydrate = container.hasAttribute('data-server-rendered')
|
// clear content before mounting
|
||||||
if (!isHydrate) {
|
container.innerHTML = ''
|
||||||
// clear content before mounting
|
return mount(container)
|
||||||
container.innerHTML = ''
|
|
||||||
}
|
|
||||||
return mount(container, isHydrate)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
}) as CreateAppFunction<Element>
|
||||||
|
|
||||||
|
export const createSSRApp = ((...args) => {
|
||||||
|
const app = ensureHydrationRenderer().createApp(...args)
|
||||||
|
|
||||||
|
if (__DEV__) {
|
||||||
|
injectNativeTagCheck(app)
|
||||||
|
}
|
||||||
|
|
||||||
|
const { mount } = app
|
||||||
|
app.mount = (containerOrSelector: Element | string): any => {
|
||||||
|
const container = normalizeContainer(containerOrSelector)
|
||||||
|
if (container) {
|
||||||
|
return mount(container, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return app
|
||||||
|
}) as CreateAppFunction<Element>
|
||||||
|
|
||||||
|
function injectNativeTagCheck(app: App) {
|
||||||
|
// Inject `isNativeTag`
|
||||||
|
// this is used for component name validation (dev only)
|
||||||
|
Object.defineProperty(app.config, 'isNativeTag', {
|
||||||
|
value: (tag: string) => isHTMLTag(tag) || isSVGTag(tag),
|
||||||
|
writable: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeContainer(container: Element | string): Element | null {
|
||||||
|
if (isString(container)) {
|
||||||
|
const res = document.querySelector(container)
|
||||||
|
if (__DEV__ && !res) {
|
||||||
|
warn(`Failed to mount app: mount target selector returned null.`)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
return container
|
||||||
}
|
}
|
||||||
|
|
||||||
// DOM-only runtime directive helpers
|
// DOM-only runtime directive helpers
|
||||||
|
Loading…
Reference in New Issue
Block a user