diff --git a/examples/vue-pinia/components/PersistedCounter.vue b/examples/vue-pinia/components/PersistedCounter.vue new file mode 100644 index 00000000..c24e0d85 --- /dev/null +++ b/examples/vue-pinia/components/PersistedCounter.vue @@ -0,0 +1,9 @@ + + + diff --git a/examples/vue-pinia/package.json b/examples/vue-pinia/package.json index 1269ebaa..73b66c24 100644 --- a/examples/vue-pinia/package.json +++ b/examples/vue-pinia/package.json @@ -8,6 +8,7 @@ "dependencies": { "@vitejs/plugin-vue": "^6.0.1", "pinia": "^3.0.1", + "pinia-plugin-persistedstate": "^4.7.1", "vike": "^0.4.247", "vike-vue": "^0.9.6", "vike-vue-pinia": "^0.2.3", diff --git a/examples/vue-pinia/pages/+onCreatePinia.client.ts b/examples/vue-pinia/pages/+onCreatePinia.client.ts new file mode 100644 index 00000000..9622c5c1 --- /dev/null +++ b/examples/vue-pinia/pages/+onCreatePinia.client.ts @@ -0,0 +1,6 @@ +import type { PageContext } from 'vike/types' +import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' + +export function onCreatePinia(pageContext: PageContext) { + pageContext.pinia!.use(piniaPluginPersistedstate) +} diff --git a/examples/vue-pinia/pages/about/+Page.vue b/examples/vue-pinia/pages/about/+Page.vue index 3e7c3e28..c21eb11b 100644 --- a/examples/vue-pinia/pages/about/+Page.vue +++ b/examples/vue-pinia/pages/about/+Page.vue @@ -2,8 +2,19 @@

About

Example of using Pinia.

+

Example of using Pinia with state persistence.

+

This counter persists across page reloads (stored in localStorage)

+ +
Loading...
diff --git a/examples/vue-pinia/stores/usePersistedCounterStore.ts b/examples/vue-pinia/stores/usePersistedCounterStore.ts new file mode 100644 index 00000000..99ac700b --- /dev/null +++ b/examples/vue-pinia/stores/usePersistedCounterStore.ts @@ -0,0 +1,17 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' + +export const usePersistedCounterStore = defineStore( + 'persistedCounter', + () => { + const count = ref(0) + + const increment = () => count.value++ + + return { count, increment } + }, + { + // Persist state to localStorage - counter persists across page reloads + persist: true, + }, +) diff --git a/packages/vike-vue-pinia/integration/+config.ts b/packages/vike-vue-pinia/integration/+config.ts index 26d90a44..70249e43 100644 --- a/packages/vike-vue-pinia/integration/+config.ts +++ b/packages/vike-vue-pinia/integration/+config.ts @@ -13,6 +13,12 @@ const config = { onCreateApp: 'import:vike-vue-pinia/__internal/integration/onCreateApp:onCreateApp', onAfterRenderHtml: 'import:vike-vue-pinia/__internal/integration/onAfterRenderHtml:onAfterRenderHtml', onCreatePageContext: 'import:vike-vue-pinia/__internal/integration/onCreatePageContext:onCreatePageContext', + meta: { + onCreatePinia: { + env: { client: true, server: true }, + cumulative: true, + }, + }, } satisfies Config declare global { @@ -24,5 +30,16 @@ declare global { interface GlobalContext { pinia?: Pinia } + interface Config { + /** + * Hook called after creating the Pinia instance. + * + * Use this to register Pinia plugins. + */ + onCreatePinia?: (pageContext: PageContext) => void | Promise + } + interface ConfigResolved { + onCreatePinia?: Array<(pageContext: PageContext) => void | Promise> + } } } diff --git a/packages/vike-vue-pinia/integration/createPiniaPlus.ts b/packages/vike-vue-pinia/integration/createPiniaPlus.ts new file mode 100644 index 00000000..eda43e36 --- /dev/null +++ b/packages/vike-vue-pinia/integration/createPiniaPlus.ts @@ -0,0 +1,24 @@ +export { createPiniaPlus } + +import { createPinia } from 'pinia' +import type { PageContext } from 'vike/types' + +// Call createPinia() and +onCreatePinia hooks +async function createPiniaPlus(pageContext: PageContext, useGloablContext?: boolean) { + const pinia = createPinia() + + if (useGloablContext) { + // Implicitly sets pageContext.pinia (because pageContext inherits all globalContext properties as fallback) + pageContext.globalContext.pinia = pinia + } else { + pageContext.pinia = pinia + } + + // Call +onCreatePinia hooks + const { onCreatePinia } = pageContext.config + if (onCreatePinia) { + await Promise.all(onCreatePinia.map((hook) => hook(pageContext))) + } + + return pinia +} diff --git a/packages/vike-vue-pinia/integration/onCreateApp.ts b/packages/vike-vue-pinia/integration/onCreateApp.ts index 36f4b801..4bed0e45 100644 --- a/packages/vike-vue-pinia/integration/onCreateApp.ts +++ b/packages/vike-vue-pinia/integration/onCreateApp.ts @@ -1,14 +1,14 @@ export { onCreateApp } import type { PageContext } from 'vike/types' -import { createPinia } from 'pinia' +import { createPiniaPlus } from './createPiniaPlus' -function onCreateApp(pageContext: PageContext) { +async function onCreateApp(pageContext: PageContext) { const { app } = pageContext if (!app) return if (pageContext.isClientSide) { - const pinia = createPinia() + const pinia = await createPiniaPlus(pageContext, true) const { _piniaInitialState } = pageContext if (_piniaInitialState) { pinia.state.value = { @@ -18,7 +18,6 @@ function onCreateApp(pageContext: PageContext) { ...pinia.state.value, } } - pageContext.globalContext.pinia = pinia } app.use(pageContext.globalContext.pinia ?? pageContext.pinia!) diff --git a/packages/vike-vue-pinia/integration/onCreatePageContext.server.ts b/packages/vike-vue-pinia/integration/onCreatePageContext.server.ts index 9a0eb5b4..2cad2b2b 100644 --- a/packages/vike-vue-pinia/integration/onCreatePageContext.server.ts +++ b/packages/vike-vue-pinia/integration/onCreatePageContext.server.ts @@ -1,8 +1,8 @@ export { onCreatePageContext } import type { PageContextServer } from 'vike/types' -import { createPinia } from 'pinia' +import { createPiniaPlus } from './createPiniaPlus' -function onCreatePageContext(pageContext: PageContextServer) { - pageContext.pinia = createPinia() +async function onCreatePageContext(pageContext: PageContextServer) { + await createPiniaPlus(pageContext) } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8ee42f54..71d9fd58 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -97,6 +97,9 @@ importers: pinia: specifier: ^3.0.1 version: 3.0.1(typescript@5.9.2)(vue@3.5.13(typescript@5.9.2)) + pinia-plugin-persistedstate: + specifier: ^4.7.1 + version: 4.7.1(pinia@3.0.1(typescript@5.9.2)(vue@3.5.13(typescript@5.9.2))) vike: specifier: ^0.4.247 version: 0.4.247(vite@7.1.5) @@ -1135,6 +1138,9 @@ packages: resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==} engines: {node: '>=6'} + defu@6.1.4: + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -1510,6 +1516,20 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + pinia-plugin-persistedstate@4.7.1: + resolution: {integrity: sha512-WHOqh2esDlR3eAaknPbqXrkkj0D24h8shrDPqysgCFR6ghqP/fpFfJmMPJp0gETHsvrh9YNNg6dQfo2OEtDnIQ==} + peerDependencies: + '@nuxt/kit': '>=3.0.0' + '@pinia/nuxt': '>=0.10.0' + pinia: '>=3.0.0' + peerDependenciesMeta: + '@nuxt/kit': + optional: true + '@pinia/nuxt': + optional: true + pinia: + optional: true + pinia@3.0.1: resolution: {integrity: sha512-WXglsDzztOTH6IfcJ99ltYZin2mY8XZCXujkYWVIJlBjqsP6ST7zw+Aarh63E1cDVYeyUcPCxPHzJpEOmzB6Wg==} peerDependencies: @@ -2666,6 +2686,8 @@ snapshots: dependencies: type-detect: 4.1.0 + defu@6.1.4: {} + delayed-stream@1.0.0: {} dot-prop@5.3.0: @@ -3030,6 +3052,12 @@ snapshots: picomatch@4.0.3: {} + pinia-plugin-persistedstate@4.7.1(pinia@3.0.1(typescript@5.9.2)(vue@3.5.13(typescript@5.9.2))): + dependencies: + defu: 6.1.4 + optionalDependencies: + pinia: 3.0.1(typescript@5.9.2)(vue@3.5.13(typescript@5.9.2)) + pinia@3.0.1(typescript@5.9.2)(vue@3.5.13(typescript@5.9.2)): dependencies: '@vue/devtools-api': 7.7.2