From 25a0d4a65ff8d5c9dec85dd454c9355421e88db6 Mon Sep 17 00:00:00 2001 From: Evan You Date: Fri, 24 Jan 2020 11:39:52 -0500 Subject: [PATCH] wip(ssr): reduce reactivity overhead during ssr --- packages/reactivity/src/computed.ts | 4 ++++ packages/reactivity/src/reactive.ts | 14 ++++++++++---- packages/reactivity/src/ref.ts | 10 +++++++++- packages/runtime-core/src/apiLifecycle.ts | 13 +++++++++---- packages/runtime-core/src/apiOptions.ts | 4 ++-- packages/runtime-core/src/apiWatch.ts | 8 ++++++-- packages/runtime-core/src/component.ts | 18 ++++++++++-------- 7 files changed, 50 insertions(+), 21 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index 1f1d8ea43d..4e2df4f85c 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -56,6 +56,10 @@ export function computed( // expose effect so computed can be stopped effect: runner, get value() { + if (__SSR__) { + return getter() + } + if (dirty) { value = runner() dirty = false diff --git a/packages/reactivity/src/reactive.ts b/packages/reactivity/src/reactive.ts index 091214481d..256c91e6b4 100644 --- a/packages/reactivity/src/reactive.ts +++ b/packages/reactivity/src/reactive.ts @@ -1,4 +1,4 @@ -import { isObject, toRawType } from '@vue/shared' +import { isObject, toRawType, EMPTY_OBJ } from '@vue/shared' import { mutableHandlers, readonlyHandlers, @@ -117,9 +117,15 @@ function createReactiveObject( if (!canObserve(target)) { return target } - const handlers = collectionTypes.has(target.constructor) - ? collectionHandlers - : baseHandlers + const handlers = __SSR__ + ? // disable reactivity in SSR. + // NOTE: a potential caveat here is isReactive check may return different + // values on nested values on client/server. This should be very rare but + // we should keep an eye on this. + EMPTY_OBJ + : collectionTypes.has(target.constructor) + ? collectionHandlers + : baseHandlers observed = new Proxy(target, handlers) toProxy.set(target, observed) toRaw.set(observed, target) diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index 5b99194b2e..fccff406fb 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -36,6 +36,14 @@ export function ref(raw?: unknown) { return raw } raw = convert(raw) + + if (__SSR__) { + return { + _isRef: true, + value: raw + } + } + const r = { _isRef: true, get value() { @@ -58,7 +66,7 @@ export function ref(raw?: unknown) { export function toRefs( object: T ): { [K in keyof T]: Ref } { - if (__DEV__ && !isReactive(object)) { + if (__DEV__ && !__SSR__ && !isReactive(object)) { console.warn(`toRefs() expects a reactive object but received a plain one.`) } const ret: any = {} diff --git a/packages/runtime-core/src/apiLifecycle.ts b/packages/runtime-core/src/apiLifecycle.ts index 1bab9ca957..0b0c25e2f9 100644 --- a/packages/runtime-core/src/apiLifecycle.ts +++ b/packages/runtime-core/src/apiLifecycle.ts @@ -65,7 +65,8 @@ export function injectHook( export const createHook = any>( lifecycle: LifecycleHooks ) => (hook: T, target: ComponentInternalInstance | null = currentInstance) => - injectHook(lifecycle, hook, target) + // post-create lifecycle registrations are noops during SSR + !__SSR__ && injectHook(lifecycle, hook, target) export const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT) export const onMounted = createHook(LifecycleHooks.MOUNTED) @@ -87,6 +88,10 @@ export type ErrorCapturedHook = ( instance: ComponentPublicInstance | null, info: string ) => boolean | void -export const onErrorCaptured = createHook( - LifecycleHooks.ERROR_CAPTURED -) + +export const onErrorCaptured = ( + hook: ErrorCapturedHook, + target: ComponentInternalInstance | null = currentInstance +) => { + injectHook(LifecycleHooks.ERROR_CAPTURED, hook, target) +} diff --git a/packages/runtime-core/src/apiOptions.ts b/packages/runtime-core/src/apiOptions.ts index 2cccb32c6b..9eb3009b25 100644 --- a/packages/runtime-core/src/apiOptions.ts +++ b/packages/runtime-core/src/apiOptions.ts @@ -218,7 +218,7 @@ export function applyOptions( ) { const renderContext = instance.renderContext === EMPTY_OBJ - ? (instance.renderContext = reactive({})) + ? (instance.renderContext = __SSR__ ? {} : reactive({})) : instance.renderContext const ctx = instance.proxy! const { @@ -285,7 +285,7 @@ export function applyOptions( checkDuplicateProperties!(OptionTypes.DATA, key) } } - instance.data = reactive(data) + instance.data = __SSR__ ? data : reactive(data) } else { // existing data: this is a mixin or extends. extend(instance.data, data) diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts index 4760344c4a..000f48edb1 100644 --- a/packages/runtime-core/src/apiWatch.ts +++ b/packages/runtime-core/src/apiWatch.ts @@ -13,7 +13,8 @@ import { isArray, isFunction, isString, - hasChanged + hasChanged, + NOOP } from '@vue/shared' import { recordEffect } from './apiReactivity' import { @@ -85,7 +86,10 @@ export function watch( cbOrOptions?: WatchCallback | WatchOptions, options?: WatchOptions ): StopHandle { - if (isFunction(cbOrOptions)) { + if (__SSR__ && !(options && options.flush === 'sync')) { + // during SSR, non-sync watchers never fire. + return NOOP + } else if (isFunction(cbOrOptions)) { // effect callback as 2nd argument - this is a source watcher return doWatch(effectOrSource, cbOrOptions, options) } else { diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index 77f7678737..8117d68d7c 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -288,7 +288,6 @@ function setupStatefulComponent( instance: ComponentInternalInstance, parentSuspense: SuspenseBoundary | null ) { - let setupResult const Component = instance.type as ComponentOptions if (__DEV__) { @@ -315,7 +314,9 @@ function setupStatefulComponent( // 2. create props proxy // the propsProxy is a reactive AND readonly proxy to the actual props. // it will be updated in resolveProps() on updates before render - const propsProxy = (instance.propsProxy = shallowReadonly(instance.props)) + const propsProxy = (instance.propsProxy = __SSR__ + ? instance.props + : shallowReadonly(instance.props)) // 3. call setup() const { setup } = Component if (setup) { @@ -324,7 +325,7 @@ function setupStatefulComponent( currentInstance = instance currentSuspense = parentSuspense - setupResult = callWithErrorHandling( + const setupResult = callWithErrorHandling( setup, instance, ErrorCodes.SETUP_FUNCTION, @@ -334,7 +335,10 @@ function setupStatefulComponent( currentSuspense = null if (isPromise(setupResult)) { - if (__FEATURE_SUSPENSE__) { + if (__SSR__) { + // return the promise so server-renderer can wait on it + return setupResult + } else if (__FEATURE_SUSPENSE__) { // async setup returned Promise. // bail here and wait for re-entry. instance.asyncDep = setupResult @@ -350,8 +354,6 @@ function setupStatefulComponent( } else { finishComponentSetup(instance, parentSuspense) } - - return setupResult } export function handleSetupResult( @@ -371,7 +373,7 @@ export function handleSetupResult( } // setup returned bindings. // assuming a render function compiled from template is present. - instance.renderContext = reactive(setupResult) + instance.renderContext = __SSR__ ? setupResult : reactive(setupResult) } else if (__DEV__ && setupResult !== undefined) { warn( `setup() should return an object. Received: ${ @@ -449,7 +451,7 @@ function finishComponentSetup( } if (instance.renderContext === EMPTY_OBJ) { - instance.renderContext = reactive({}) + instance.renderContext = __SSR__ ? {} : reactive({}) } } -- 2.47.3