From: Eduardo San Martin Morote Date: Mon, 28 Sep 2020 13:57:48 +0000 (+0200) Subject: feat(ssr): support ssr X-Git-Tag: v2.0.0-alpha.3~8 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=59709e0851db66d337054e3aab0db987fab20f9d;p=thirdparty%2Fvuejs%2Fpinia.git feat(ssr): support ssr --- diff --git a/__tests__/ssr.spec.ts b/__tests__/ssr.spec.ts new file mode 100644 index 00000000..3a7e017c --- /dev/null +++ b/__tests__/ssr.spec.ts @@ -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( + `
${ssrInterpolate(ctx.user.name)}: ${ssrInterpolate( + ctx.cart.items + )}
` + ) + }, + 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(` + "
default: [ + { + "name": "one", + "amount": 1 + } + ]
" + `) + expect(await renderToString(a2)).toMatchInlineSnapshot(` + "
default: [ + { + "name": "one", + "amount": 1 + } + ]
" + `) + }) + + 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(` + "
a1: [ + { + "name": "one", + "amount": 1 + } + ]
" + `) + expect(await renderToString(a2)).toMatchInlineSnapshot(` + "
a2: [ + { + "name": "one", + "amount": 1 + } + ]
" + `) + }) +}) diff --git a/src/rootStore.ts b/src/rootStore.ts index 49160052..ca87c566 100644 --- a/src/rootStore.ts +++ b/src/rootStore.ts @@ -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 + store GenericStore>( + useStore: F + ): ReturnType +} + +export const piniaSymbol = (__DEV__ + ? Symbol('pinia') + : Symbol()) as InjectionKey + +export function createPinia(): Pinia { + const pinia: Pinia = { install(app: App) { + app.provide(piniaSymbol, pinia) + // TODO: strip out if no need for setClientApp(app) }, + store GenericStore>( + useStore: F + ): ReturnType { + const store = useStore(pinia) as ReturnType + return store + }, } + + return pinia } /** diff --git a/src/store.ts b/src/store.ts index 868c0849..bc5b4ca1 100644 --- a/src/store.ts +++ b/src/store.ts @@ -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 { + return function useStore(reqKey?: object | null): Store { + // avoid injecting if `useStore` when not possible + reqKey = reqKey || (getCurrentInstance() && inject(piniaSymbol)) if (reqKey) setActiveReq(reqKey) const req = getActiveReq() let stores = storesMap.get(req)