]> git.ipfire.org Git - thirdparty/vuejs/pinia.git/commitdiff
feat(ssr): support ssr
authorEduardo San Martin Morote <posva13@gmail.com>
Mon, 28 Sep 2020 13:57:48 +0000 (15:57 +0200)
committerEduardo San Martin Morote <posva13@gmail.com>
Mon, 28 Sep 2020 13:57:48 +0000 (15:57 +0200)
__tests__/ssr.spec.ts [new file with mode: 0644]
src/rootStore.ts
src/store.ts

diff --git a/__tests__/ssr.spec.ts b/__tests__/ssr.spec.ts
new file mode 100644 (file)
index 0000000..3a7e017
--- /dev/null
@@ -0,0 +1,83 @@
+/**
+ * @jest-environment node
+ */
+import { createPinia } from '../src'
+import { useCartStore } from './pinia/stores/cart'
+import { createSSRApp, inject } from 'vue'
+import { renderToString, ssrInterpolate } from '@vue/server-renderer'
+import { useUserStore } from './pinia/stores/user'
+
+describe('SSR', () => {
+  const App = {
+    ssrRender(ctx: any, push: any, parent: any) {
+      push(
+        `<div>${ssrInterpolate(ctx.user.name)}: ${ssrInterpolate(
+          ctx.cart.items
+        )}</div>`
+      )
+    },
+    setup() {
+      const cart = useCartStore()
+      const user = useUserStore()
+      user.name = inject('name', 'default')
+      cart.addItem('one')
+      return { cart, user }
+    },
+  }
+
+  function createMyApp() {
+    const app = createSSRApp(App)
+    const pinia = createPinia()
+    app.use(pinia)
+    // const rootEl = document.createElement('div')
+    // document.body.appendChild(rootEl)
+
+    return { app }
+  }
+
+  it('keeps apps separated', async () => {
+    const { app: a1 } = createMyApp()
+    const { app: a2 } = createMyApp()
+
+    expect(await renderToString(a1)).toMatchInlineSnapshot(`
+      "<div>default: [
+        {
+          &quot;name&quot;: &quot;one&quot;,
+          &quot;amount&quot;: 1
+        }
+      ]</div>"
+    `)
+    expect(await renderToString(a2)).toMatchInlineSnapshot(`
+      "<div>default: [
+        {
+          &quot;name&quot;: &quot;one&quot;,
+          &quot;amount&quot;: 1
+        }
+      ]</div>"
+    `)
+  })
+
+  it('can use a different store', async () => {
+    const { app: a1 } = createMyApp()
+    const { app: a2 } = createMyApp()
+    a1.provide('name', 'a1')
+    a2.provide('name', 'a2')
+
+    expect(await renderToString(a1)).toMatchInlineSnapshot(`
+      "<div>a1: [
+        {
+          &quot;name&quot;: &quot;one&quot;,
+          &quot;amount&quot;: 1
+        }
+      ]</div>"
+    `)
+    expect(await renderToString(a2)).toMatchInlineSnapshot(`
+      "<div>a2: [
+        {
+          &quot;name&quot;: &quot;one&quot;,
+          &quot;amount&quot;: 1
+        }
+      ]</div>"
+    `)
+  })
+})
index 49160052c765f47556411dbbfb41e5b79613d211..ca87c566bbea7dda0ad0fb8dca81672572c1f20e 100644 (file)
@@ -1,4 +1,4 @@
-import { App } from 'vue'
+import { App, InjectionKey, Plugin } from 'vue'
 import { NonNullObject, StateTree, GenericStore } from './types'
 
 /**
@@ -64,12 +64,33 @@ export let clientApp: App | undefined
 export const setClientApp = (app: App) => (clientApp = app)
 export const getClientApp = () => clientApp
 
-export function createPinia() {
-  return {
+export interface Pinia {
+  install: Exclude<Plugin['install'], undefined>
+  store<F extends (req?: NonNullObject) => GenericStore>(
+    useStore: F
+  ): ReturnType<F>
+}
+
+export const piniaSymbol = (__DEV__
+  ? Symbol('pinia')
+  : Symbol()) as InjectionKey<Pinia>
+
+export function createPinia(): Pinia {
+  const pinia: Pinia = {
     install(app: App) {
+      app.provide(piniaSymbol, pinia)
+      // TODO: strip out if no need for
       setClientApp(app)
     },
+    store<F extends (req?: NonNullObject) => GenericStore>(
+      useStore: F
+    ): ReturnType<F> {
+      const store = useStore(pinia) as ReturnType<F>
+      return store
+    },
   }
+
+  return pinia
 }
 
 /**
index 868c0849496bb8043334aedfd52bcd1e029d3b49..bc5b4ca16c82898a7791747e87ee5c09f7c8e7b2 100644 (file)
@@ -1,4 +1,12 @@
-import { ref, watch, computed, Ref, reactive } from 'vue'
+import {
+  ref,
+  watch,
+  computed,
+  Ref,
+  reactive,
+  inject,
+  getCurrentInstance,
+} from 'vue'
 import {
   StateTree,
   StoreWithState,
@@ -16,6 +24,7 @@ import {
   storesMap,
   getInitialState,
   getClientApp,
+  piniaSymbol,
 } from './rootStore'
 import { addDevtools } from './devtools'
 
@@ -193,7 +202,9 @@ export function createStore<
 }) {
   const { id, state, getters, actions } = options
 
-  return function useStore(reqKey?: object): Store<Id, S, G, A> {
+  return function useStore(reqKey?: object | null): Store<Id, S, G, A> {
+    // avoid injecting if `useStore` when not possible
+    reqKey = reqKey || (getCurrentInstance() && inject(piniaSymbol))
     if (reqKey) setActiveReq(reqKey)
     const req = getActiveReq()
     let stores = storesMap.get(req)