Repository: vuejs/composition-api (ts)

Issue:
`isReactive`/`reactive` doesn't work in SSR (regression in beta 14)
`reactive` doesn't work on SSR. In a vanilla Nuxt project using just `@vue/composition-api`, `isReactive(reactive({}))` prints `false` on server-side but `true` on client-side.

**Reproduction**: https://codesandbox.io/s/sweet-dubinsky-ilbt1?file=/pages/index.vue

**Linked issue**: https://github.com/nuxt-community/composition-api/issues/244

Pull request:
fix: `reactive` in SSR
close #537

Patch:
diff --git a/src/apis/index.ts b/src/apis/index.ts
index 6c47069..5d0bda2 100644
--- a/src/apis/index.ts
+++ b/src/apis/index.ts
@@ -1,4 +1,26 @@
-export * from './state'
+export {
+  isReactive,
+  isRef,
+  isRaw,
+  markRaw,
+  reactive,
+  ref,
+  customRef,
+  Ref,
+  set,
+  shallowReactive,
+  shallowRef,
+  toRaw,
+  toRef,
+  toRefs,
+  triggerRef,
+  unref,
+  UnwrapRef,
+  isReadonly,
+  shallowReadonly,
+  proxyRefs,
+  ShallowUnwrapRef,
+} from '../reactivity'
 export * from './lifecycle'
 export * from './watch'
 export * from './computed'
diff --git a/src/apis/state.ts b/src/apis/state.ts
deleted file mode 100644
index 4c1e70b..0000000
--- a/src/apis/state.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-export {
-  isReactive,
-  isRef,
-  markRaw,
-  reactive,
-  ref,
-  customRef,
-  Ref,
-  set,
-  shallowReactive,
-  shallowRef,
-  toRaw,
-  toRef,
-  toRefs,
-  triggerRef,
-  unref,
-  UnwrapRef,
-  isReadonly,
-  shallowReadonly,
-  proxyRefs,
-  ShallowUnwrapRef,
-} from '../reactivity'
diff --git a/src/reactivity/reactive.ts b/src/reactivity/reactive.ts
index 8196a58..585f304 100644
--- a/src/reactivity/reactive.ts
+++ b/src/reactivity/reactive.ts
@@ -1,6 +1,6 @@
 import { AnyObject } from '../types/basic'
 import { getRegisteredVueOrDefault } from '../runtimeContext'
-import { isPlainObject, def, warn } from '../utils'
+import { isPlainObject, def, warn, hasOwn } from '../utils'
 import { isComponentInstance, defineComponentInstance } from '../utils/helper'
 import { RefKey } from '../utils/symbols'
 import { isRef, UnwrapRef } from './ref'
@@ -111,6 +111,11 @@ function observe<T>(obj: T): T {
     observed = vm._data.$$state
   }
 
+  // in SSR, there is no __ob__. Mock for reactivity check
+  if (!hasOwn(observed, '__ob__')) {
+    def(observed, '__ob__', {})
+  }
+
   return observed
 }
 
@@ -154,7 +159,7 @@ export function shallowReactive(obj: any): any {
       configurable: true,
       get: function getterHandler() {
         const value = getter ? getter.call(obj) : val
-        ob.dep.depend()
+        ob.dep?.depend()
         return value
       },
       set: function setterHandler(newVal) {
@@ -164,7 +169,7 @@ export function shallowReactive(obj: any): any {
         } else {
           val = newVal
         }
-        ob.dep.notify()
+        ob.dep?.notify()
       },
     })
   }


Test patch:
diff --git a/test/helpers/wait-for-update.js b/test/helpers/wait-for-update.js
index f426afd..df8334d 100644
--- a/test/helpers/wait-for-update.js
+++ b/test/helpers/wait-for-update.js
@@ -13,7 +13,7 @@ const Vue = require('vue')
 // })
 // .then(done)
 
-window.waitForUpdate = (initialCb) => {
+const waitForUpdate = (initialCb) => {
   let end
   const queue = initialCb ? [initialCb] : []
 
@@ -69,7 +69,11 @@ window.waitForUpdate = (initialCb) => {
   return chainer
 }
 
-exports.waitForUpdate = window.waitForUpdate
+if (typeof window !== 'undefined') {
+  window.waitForUpdate = waitForUpdate
+}
+
+exports.waitForUpdate = waitForUpdate
 
 function timeout(n) {
   return (next) => setTimeout(next, n)
diff --git a/test/ssr/serverPrefetch.spec.js b/test/ssr/serverPrefetch.spec.js
index 9673053..578c786 100644
--- a/test/ssr/serverPrefetch.spec.js
+++ b/test/ssr/serverPrefetch.spec.js
@@ -4,8 +4,8 @@ const {
   ref,
   onServerPrefetch,
   getCurrentInstance,
-  provide,
-  inject,
+  isReactive,
+  reactive,
 } = require('../../src')
 
 function fetch(result) {
diff --git a/test/ssr/ssrReactive.spec.ts b/test/ssr/ssrReactive.spec.ts
new file mode 100644
index 0000000..befa01c
--- /dev/null
+++ b/test/ssr/ssrReactive.spec.ts
@@ -0,0 +1,47 @@
+/**
+ * @jest-environment node
+ */
+
+import Vue from '../vue'
+import { isReactive, reactive, ref, isRaw, isRef, shallowRef } from '../../src'
+import { createRenderer } from 'vue-server-renderer'
+
+describe('SSR Reactive', () => {
+  beforeEach(() => {
+    process.env.VUE_ENV = 'server'
+  })
+
+  it('should in SSR context', async () => {
+    expect(typeof window).toBe('undefined')
+    expect((Vue.observable({}) as any).__ob__).toBeUndefined()
+  })
+
+  it('should render', async () => {
+    const app = new Vue({
+      setup() {
+        return {
+          count: ref(42),
+        }
+      },
+      render(this: any, h) {
+        return h('div', this.count)
+      },
+    })
+
+    const serverRenderer = createRenderer()
+    const html = await serverRenderer.renderToString(app)
+    expect(html).toBe('<div data-server-rendered="true">42</div>')
+  })
+
+  it('reactive + isReactive', async () => {
+    const state = reactive({})
+    expect(isReactive(state)).toBe(true)
+    expect(isRaw(state)).toBe(false)
+  })
+
+  it('shallowRef + isRef', async () => {
+    const state = shallowRef({})
+    expect(isRef(state)).toBe(true)
+    expect(isRaw(state)).toBe(false)
+  })
+})