]> git.ipfire.org Git - thirdparty/vuejs/pinia.git/commitdiff
feat(nuxt): do not serialize skipHydrate properties
authorBalázs Kaufmann <bkaufmann23@gmail.com>
Thu, 17 Oct 2024 03:03:05 +0000 (05:03 +0200)
committerGitHub <noreply@github.com>
Thu, 17 Oct 2024 03:03:05 +0000 (12:03 +0900)
Co-authored-by: Eduardo San Martin Morote <posva13@gmail.com>
12 files changed:
packages/nuxt/__tests__/nuxt.spec.ts
packages/nuxt/playground/app.vue
packages/nuxt/playground/nuxt.config.ts
packages/nuxt/playground/pages/index.vue [new file with mode: 0644]
packages/nuxt/playground/pages/skip-hydrate.vue [new file with mode: 0644]
packages/nuxt/playground/stores/with-skip-hydrate.ts [new file with mode: 0644]
packages/nuxt/playground/tsconfig.json [new file with mode: 0644]
packages/nuxt/src/module.ts
packages/nuxt/src/runtime/payload-plugin.ts [new file with mode: 0644]
packages/nuxt/src/runtime/plugin.vue3.ts
packages/pinia/src/index.ts
packages/pinia/src/store.ts

index 51f7cf5a832b0f4a8e6606260273d6fa80b96488..563e94b43219ae08ef63957e4de4cd8c851131cd 100644 (file)
@@ -33,4 +33,10 @@ describe('works with nuxt', async () => {
     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')
+  })
 })
index 450e65a2d31c12802c361a54ff36aa9ba8086d17..8f62b8bf92189a33f41b1fcd2036d78e8fe68937 100644 (file)
@@ -1,21 +1,3 @@
-<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>
index 973b5d95a3b93adea555813b994005e0ff01ff21..1431c79015a8c2877851873f4820e55afe3e764e 100644 (file)
@@ -3,9 +3,11 @@ import { defineNuxtConfig } from 'nuxt/config'
 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: {
@@ -19,4 +21,6 @@ export default defineNuxtConfig({
       __TEST__: false,
     },
   },
+
+  compatibilityDate: '2024-09-26',
 })
diff --git a/packages/nuxt/playground/pages/index.vue b/packages/nuxt/playground/pages/index.vue
new file mode 100644 (file)
index 0000000..450e65a
--- /dev/null
@@ -0,0 +1,21 @@
+<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>
diff --git a/packages/nuxt/playground/pages/skip-hydrate.vue b/packages/nuxt/playground/pages/skip-hydrate.vue
new file mode 100644 (file)
index 0000000..c3ad2aa
--- /dev/null
@@ -0,0 +1,13 @@
+<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>
diff --git a/packages/nuxt/playground/stores/with-skip-hydrate.ts b/packages/nuxt/playground/stores/with-skip-hydrate.ts
new file mode 100644 (file)
index 0000000..3cbbc31
--- /dev/null
@@ -0,0 +1,16 @@
+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)
+  )
+}
diff --git a/packages/nuxt/playground/tsconfig.json b/packages/nuxt/playground/tsconfig.json
new file mode 100644 (file)
index 0000000..4b34df1
--- /dev/null
@@ -0,0 +1,3 @@
+{
+  "extends": "./.nuxt/tsconfig.json"
+}
index b528bad4f6e438ab5d23fe0e8fff20f741d7c061..08148ad940356767246e5973190e0fc0693c2d0b 100644 (file)
@@ -11,6 +11,7 @@ import {
   addImportsDir,
 } from '@nuxt/kit'
 import type { NuxtModule } from '@nuxt/schema'
+import { fileURLToPath } from 'node:url'
 
 export interface ModuleOptions {
   /**
@@ -44,7 +45,9 @@ const module: NuxtModule<ModuleOptions> = defineNuxtModule<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 (
@@ -58,7 +61,7 @@ const module: NuxtModule<ModuleOptions> = defineNuxtModule<ModuleOptions>({
     }
 
     // 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 =
@@ -76,14 +79,15 @@ const module: NuxtModule<ModuleOptions> = defineNuxtModule<ModuleOptions>({
     // 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' },
@@ -93,12 +97,12 @@ const module: NuxtModule<ModuleOptions> = defineNuxtModule<ModuleOptions>({
 
     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))
       }
     }
   },
diff --git a/packages/nuxt/src/runtime/payload-plugin.ts b/packages/nuxt/src/runtime/payload-plugin.ts
new file mode 100644 (file)
index 0000000..8563543
--- /dev/null
@@ -0,0 +1,20 @@
+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
index f15db3645773f5ca10b3d09380a67ebba4dd635d..1e101d066ab5d9a99793089f54c226c3217d0712 100644 (file)
@@ -1,6 +1,7 @@
 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',
@@ -10,7 +11,7 @@ const plugin: Plugin<{ pinia: Pinia }> = defineNuxtPlugin({
     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
     }
index d06aedc4fafe899e4991dcad2dccd5bd1df2ca49..be7b83475b6b2e906b898192e7c5263d1a00bae0 100644 (file)
@@ -11,7 +11,7 @@ export type {
   PiniaPluginContext,
 } from './rootStore'
 
-export { defineStore, skipHydrate } from './store'
+export { defineStore, skipHydrate, shouldHydrate } from './store'
 export type {
   StoreActions,
   StoreGetters,
index 3bd239bdfb1ceb79caa6567f351573353b343b4f..16ae7aa4a17cbfb092b28f7b68d2bb8fe657314f 100644 (file)
@@ -139,7 +139,7 @@ export function skipHydrate<T = any>(obj: T): T {
  * @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)