const html = await $fetch('/')
expect(html).toContain('Count: 101')
})
+
+ it('drops state that is marked with skipHydrate', async () => {
+ const html = await $fetch('/skip-hydrate')
+ expect(html).not.toContain('I should not be serialized or hydrated')
+ expect(html).toContain('skipHydrate-wrapped state is correct')
+ })
})
-<script lang="ts" setup>
-// import {useCounter }from '~/stores/counter'
-
-const counter = useCounter()
-
-useTestStore()
-useSomeStoreStore()
-
-// await useAsyncData('counter', () => counter.asyncIncrement().then(() => true))
-
-if (import.meta.server) {
- counter.increment()
-}
-</script>
-
<template>
- <div>
- <p>Count: {{ counter.$state.count }}</p>
- <button @click="counter.increment()">+</button>
- </div>
+ <NuxtPage />
</template>
import piniaModule from '../src/module'
export default defineNuxtConfig({
+ devtools: { enabled: true },
alias: {
pinia: fileURLToPath(new URL('../../pinia/src/index.ts', import.meta.url)),
},
+
modules: [piniaModule],
pinia: {
__TEST__: false,
},
},
+
+ compatibilityDate: '2024-09-26',
})
--- /dev/null
+<script lang="ts" setup>
+// import {useCounter }from '~/stores/counter'
+
+const counter = useCounter()
+
+useTestStore()
+useSomeStoreStore()
+
+// await useAsyncData('counter', () => counter.asyncIncrement().then(() => true))
+
+if (import.meta.server) {
+ counter.increment()
+}
+</script>
+
+<template>
+ <div>
+ <p>Count: {{ counter.$state.count }}</p>
+ <button @click="counter.increment()">+</button>
+ </div>
+</template>
--- /dev/null
+<script lang="ts" setup>
+const store = useWithSkipHydrateStore()
+const skipHydrateState = computed(() => {
+ return store.skipped?.text === 'I should not be serialized or hydrated'
+ ? 'skipHydrate-wrapped state is correct'
+ : 'skipHydrate-wrapped state is incorrect'
+})
+</script>
+
+<template>
+ <h2>skipHydrate() test</h2>
+ <p>{{ skipHydrateState }}</p>
+</template>
--- /dev/null
+import { skipHydrate } from 'pinia'
+
+export const useWithSkipHydrateStore = defineStore('with-skip-hydrate', () => {
+ const skipped = skipHydrate(
+ ref({
+ text: 'I should not be serialized or hydrated',
+ })
+ )
+ return { skipped }
+})
+
+if (import.meta.hot) {
+ import.meta.hot.accept(
+ acceptHMRUpdate(useWithSkipHydrateStore, import.meta.hot)
+ )
+}
--- /dev/null
+{
+ "extends": "./.nuxt/tsconfig.json"
+}
addImportsDir,
} from '@nuxt/kit'
import type { NuxtModule } from '@nuxt/schema'
+import { fileURLToPath } from 'node:url'
export interface ModuleOptions {
/**
disableVuex: true,
},
setup(options, nuxt) {
- const resolver = createResolver(import.meta.url)
+ // configure transpilation
+ const { resolve } = createResolver(import.meta.url)
+ const runtimeDir = fileURLToPath(new URL('./runtime', import.meta.url))
// Disable default Vuex store (Nuxt v2.10+ only)
if (
}
// Transpile runtime
- nuxt.options.build.transpile.push(resolver.resolve('./runtime'))
+ nuxt.options.build.transpile.push(resolve(runtimeDir))
// Make sure we use the mjs build for pinia
nuxt.options.alias.pinia =
// https://github.com/nuxt/framework/issues/9130
nuxt.hook('modules:done', () => {
if (isNuxt2()) {
- addPlugin(resolver.resolve('./runtime/plugin.vue2'))
+ addPlugin(resolve(runtimeDir, 'plugin.vue2'))
} else {
- addPlugin(resolver.resolve('./runtime/plugin.vue3'))
+ addPlugin(resolve(runtimeDir, 'plugin.vue3'))
+ addPlugin(resolve(runtimeDir, 'payload-plugin'))
}
})
// Add auto imports
- const composables = resolver.resolve('./runtime/composables')
+ const composables = resolve(runtimeDir, 'composables')
addImports([
{ from: composables, name: 'defineStore' },
{ from: composables, name: 'acceptHMRUpdate' },
if (!options.storesDirs) {
// resolve it against the src dir which is the root by default
- options.storesDirs = [resolver.resolve(nuxt.options.srcDir, 'stores')]
+ options.storesDirs = [resolve(nuxt.options.srcDir, 'stores')]
}
if (options.storesDirs) {
for (const storeDir of options.storesDirs) {
- addImportsDir(resolver.resolve(nuxt.options.rootDir, storeDir))
+ addImportsDir(resolve(nuxt.options.rootDir, storeDir))
}
}
},
--- /dev/null
+import {
+ definePayloadPlugin,
+ definePayloadReducer,
+ definePayloadReviver,
+} from '#imports'
+import { shouldHydrate } from 'pinia'
+
+/**
+ * Removes properties marked with `skipHydrate()` to avoid sending unused data to the client.
+ */
+const payloadPlugin = definePayloadPlugin(() => {
+ definePayloadReducer(
+ 'skipHydrate',
+ // We need to return something truthy to be treated as a match
+ (data: unknown) => !shouldHydrate(data) && 1
+ )
+ definePayloadReviver('skipHydrate', (_data: 1) => undefined)
+})
+
+export default payloadPlugin
import { createPinia, setActivePinia } from 'pinia'
import type { Pinia } from 'pinia'
import { defineNuxtPlugin, type Plugin } from '#app'
+import { toRaw } from 'vue'
const plugin: Plugin<{ pinia: Pinia }> = defineNuxtPlugin({
name: 'pinia',
setActivePinia(pinia)
if (import.meta.server) {
- nuxtApp.payload.pinia = pinia.state.value
+ nuxtApp.payload.pinia = toRaw(pinia.state.value)
} else if (nuxtApp.payload && nuxtApp.payload.pinia) {
pinia.state.value = nuxtApp.payload.pinia as any
}
PiniaPluginContext,
} from './rootStore'
-export { defineStore, skipHydrate } from './store'
+export { defineStore, skipHydrate, shouldHydrate } from './store'
export type {
StoreActions,
StoreGetters,
* @param obj - target variable
* @returns true if `obj` should be hydrated
*/
-function shouldHydrate(obj: any) {
+export function shouldHydrate(obj: any) {
return isVue2
? /* istanbul ignore next */ !skipHydrateMap.has(obj)
: !isPlainObject(obj) || !obj.hasOwnProperty(skipHydrateSymbol)