]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
refactor(ssr): make hydration logic tree-shakeable
authorEvan You <yyx990803@gmail.com>
Fri, 14 Feb 2020 06:30:08 +0000 (01:30 -0500)
committerEvan You <yyx990803@gmail.com>
Fri, 14 Feb 2020 06:30:08 +0000 (01:30 -0500)
packages/runtime-core/src/apiCreateApp.ts
packages/runtime-core/src/hydration.ts
packages/runtime-core/src/index.ts
packages/runtime-core/src/renderer.ts
packages/runtime-dom/src/index.ts

index e8888487f75e2cf1e078efbf2f3ae5cffbf6724c..ed5ce0950c5a0c133cf04d9796f46c44c3f3ac98 100644 (file)
@@ -91,7 +91,7 @@ export type CreateAppFunction<HostElement> = (
 
 export function createAppAPI<HostNode, HostElement>(
   render: RootRenderFunction<HostNode, HostElement>,
-  hydrate: (vnode: VNode, container: Element) => void
+  hydrate?: (vnode: VNode, container: Element) => void
 ): CreateAppFunction<HostElement> {
   return function createApp(rootComponent: Component, rootProps = null) {
     if (rootProps != null && !isObject(rootProps)) {
@@ -200,7 +200,7 @@ export function createAppAPI<HostNode, HostElement>(
             }
           }
 
-          if (isHydrate) {
+          if (isHydrate && hydrate) {
             hydrate(vnode, rootContainer as any)
           } else {
             render(vnode, rootContainer)
index ee6a810cae8a772f2a2f81428d74b8c04975b99b..3318001f1997c4f89244dfaac014a806b231db0d 100644 (file)
@@ -15,9 +15,11 @@ import { warn } from './warning'
 import { PatchFlags, isReservedProp, isOn } from '@vue/shared'
 
 // 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.
-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
   patchProp: any // TODO
 ) {
index 6a74f56939aba996c45b1d76473b58c09b948e9f..09a3ac2db0d6c346e6fb035914ebc93452eeb344 100644 (file)
@@ -65,7 +65,7 @@ export { useCSSModule } from './helpers/useCssModule'
 // Internal API ----------------------------------------------------------------
 
 // For custom renderers
-export { createRenderer } from './renderer'
+export { createRenderer, createHydrationRenderer } from './renderer'
 export { warn } from './warning'
 export {
   handleError,
index afbb289870546ca9bc8d752f11ab8fff99e977ba..ab8a29d45527b65b70be3607829ae75c62025753 100644 (file)
@@ -62,7 +62,7 @@ import {
 import { ErrorCodes, callWithErrorHandling } from './errorHandling'
 import { KeepAliveSink, isKeepAlive } from './components/KeepAlive'
 import { registerHMR, unregisterHMR } from './hmr'
-import { createHydrateFn } from './hydration'
+import { createHydrationFunctions } from './hydration'
 
 const __HMR__ = __BUNDLER__ && __DEV__
 
@@ -186,6 +186,32 @@ export function createRenderer<
   HostNode extends object = any,
   HostElement extends HostNode = any
 >(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 HostVNodeChildren = VNodeArrayChildren<HostNode, HostElement>
   type HostSuspenseBoundary = SuspenseBoundary<HostNode, HostElement>
@@ -215,6 +241,12 @@ export function createRenderer<
     options
   }
 
+  let hydrate: ReturnType<typeof createHydrationFunctions>[0] | undefined
+  let hydrateNode: ReturnType<typeof createHydrationFunctions>[1] | undefined
+  if (createHydrationFns) {
+    ;[hydrate, hydrateNode] = createHydrationFns(mountComponent, hostPatchProp)
+  }
+
   function patch(
     n1: HostVNode | null, // null means this is a mount
     n2: HostVNode,
@@ -1054,7 +1086,7 @@ export function createRenderer<
         if (instance.bm !== null) {
           invokeHooks(instance.bm)
         }
-        if (initialVNode.el) {
+        if (initialVNode.el && hydrateNode) {
           // vnode has adopted host node - perform hydration instead of mount.
           hydrateNode(initialVNode.el as Node, subTree, instance)
         } else {
@@ -1823,8 +1855,6 @@ export function createRenderer<
     container._vnode = vnode
   }
 
-  const [hydrate, hydrateNode] = createHydrateFn(mountComponent, hostPatchProp)
-
   return {
     render,
     hydrate,
index 102a8a7384fac28cf09921e5ee51b4b1146435b3..cda9c50231e90acfb693d27207eb01562fd48681 100644 (file)
@@ -1,49 +1,62 @@
 import {
   createRenderer,
+  createHydrationRenderer,
   warn,
   RootRenderFunction,
-  CreateAppFunction
+  CreateAppFunction,
+  VNode,
+  App
 } from '@vue/runtime-core'
 import { nodeOps } from './nodeOps'
 import { patchProp } from './patchProp'
 // Importing from the compiler, will be tree-shaken in prod
 import { isFunction, isString, isHTMLTag, isSVGTag } from '@vue/shared'
 
-const {
-  render: baseRender,
-  hydrate: baseHydrate,
-  createApp: baseCreateApp
-} = createRenderer({
+const rendererOptions = {
   patchProp,
   ...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
-export const render = baseRender as RootRenderFunction<Node, Element>
-export const hydrate = baseHydrate as RootRenderFunction<Node, Element>
+export const render = ((...args) => {
+  ensureRenderer().render(...args)
+}) as RootRenderFunction<Node, Element>
+
+export const hydrate = ((...args) => {
+  ensureHydrationRenderer().hydrate(...args)
+}) as (vnode: VNode, container: Element) => void
 
-export const createApp: CreateAppFunction<Element> = (...args) => {
-  const app = baseCreateApp(...args)
+export const createApp = ((...args) => {
+  const app = ensureRenderer().createApp(...args)
 
   if (__DEV__) {
-    // 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
-    })
+    injectNativeTagCheck(app)
   }
 
   const { mount } = app
-  app.mount = (container: Element | string): any => {
-    if (isString(container)) {
-      container = document.querySelector(container)!
-      if (!container) {
-        __DEV__ &&
-          warn(`Failed to mount app: mount target selector returned null.`)
-        return
-      }
-    }
+  app.mount = (containerOrSelector: Element | string): any => {
+    const container = normalizeContainer(containerOrSelector)
+    if (!container) return
     const component = app._component
     if (
       __RUNTIME_COMPILE__ &&
@@ -53,15 +66,50 @@ export const createApp: CreateAppFunction<Element> = (...args) => {
     ) {
       component.template = container.innerHTML
     }
-    const isHydrate = container.hasAttribute('data-server-rendered')
-    if (!isHydrate) {
-      // clear content before mounting
-      container.innerHTML = ''
+    // clear content before mounting
+    container.innerHTML = ''
+    return mount(container)
+  }
+
+  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 mount(container, isHydrate)
   }
 
   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