diff --git a/examples/full/pages/star-wars/index/+Page.vue b/examples/full/pages/star-wars/index/+Page.vue index 109d2368..0a8730fd 100644 --- a/examples/full/pages/star-wars/index/+Page.vue +++ b/examples/full/pages/star-wars/index/+Page.vue @@ -12,5 +12,5 @@ diff --git a/examples/full/pages/star-wars/index/+data.ts b/examples/full/pages/star-wars/index/+data.ts index 7aa9cb4d..07c030cc 100644 --- a/examples/full/pages/star-wars/index/+data.ts +++ b/examples/full/pages/star-wars/index/+data.ts @@ -11,7 +11,7 @@ const data = async () => { // We remove data we don't need because the data is passed to the client; we should // minimize what is sent over the network. const movies = minimize(moviesData) - return movies + return { movies } } function minimize(movies: MovieDetails[]): Movie[] { diff --git a/examples/full/pages/star-wars/index/+title.ts b/examples/full/pages/star-wars/index/+title.ts index 6357ab89..299afc25 100644 --- a/examples/full/pages/star-wars/index/+title.ts +++ b/examples/full/pages/star-wars/index/+title.ts @@ -4,6 +4,6 @@ import type { Data } from './+data' import type { PageContext } from 'vike/types' function title(pageContext: PageContext) { - const movies = pageContext.data + const { movies } = pageContext.data return `${movies.length} Star Wars Movies` } diff --git a/packages/vike-vue/src/hooks/useData.ts b/packages/vike-vue/src/hooks/useData.ts index 2368e12a..0c385f7e 100644 --- a/packages/vike-vue/src/hooks/useData.ts +++ b/packages/vike-vue/src/hooks/useData.ts @@ -3,17 +3,17 @@ export { useData } export { setData } import { inject } from 'vue' -import type { App } from 'vue' +import type { App, ShallowReactive } from 'vue' const key = 'vike-vue:useData' /** https://vike.dev/useData */ -function useData(): Data { - const data = inject(key) +function useData(): ShallowReactive { + const data = inject>(key) if (!data) throw new Error('setData() not called') - return data as any + return data } -function setData(app: App, data: unknown): void { +function setData(app: App, data: ShallowReactive): void { app.provide(key, data) } diff --git a/packages/vike-vue/src/hooks/usePageContext.ts b/packages/vike-vue/src/hooks/usePageContext.ts index 9ce66602..8b8348b7 100644 --- a/packages/vike-vue/src/hooks/usePageContext.ts +++ b/packages/vike-vue/src/hooks/usePageContext.ts @@ -3,16 +3,18 @@ export { usePageContext } export { setPageContext } import { inject } from 'vue' -import type { App } from 'vue' +import type { App, ShallowReactive } from 'vue' import type { PageContext } from 'vike/types' const key = 'vike-vue:usePageContext' -function usePageContext() { - const pageContext = inject(key) - return pageContext as PageContext +/** https://vike.dev/usePageContext */ +function usePageContext(): ShallowReactive { + const pageContext = inject>(key) + if (!pageContext) throw new Error('setPageContext() not called') + return pageContext } -function setPageContext(app: App, pageContext: PageContext) { +function setPageContext(app: App, pageContext: ShallowReactive) { app.provide(key, pageContext) } diff --git a/packages/vike-vue/src/renderer/createVueApp.ts b/packages/vike-vue/src/renderer/createVueApp.ts index 59a33ed0..f0841f12 100644 --- a/packages/vike-vue/src/renderer/createVueApp.ts +++ b/packages/vike-vue/src/renderer/createVueApp.ts @@ -1,12 +1,12 @@ export { createVueApp } export type { ChangePage } -import { type App, createApp, createSSRApp, h, markRaw, nextTick, reactive, ref } from 'vue' +import { type App, createApp, createSSRApp, h, markRaw, nextTick, ref, shallowReactive } from 'vue' import type { PageContext } from 'vike/types' import { setPageContext } from '../hooks/usePageContext' import { objectAssign } from '../utils/objectAssign' import { callCumulativeHooks } from '../utils/callCumulativeHooks' -import { isObject } from '../utils/isObject' +import { isPlainObject } from '../utils/isPlainObject' import { setData } from '../hooks/useData' type ChangePage = (pageContext: PageContext) => Promise @@ -41,8 +41,8 @@ async function createVueApp(pageContext: PageContext, ssr: boolean, rootComponen } const data = pageContext.data ?? {} assertDataIsObject(data) - Object.assign(dataReactive, data) - Object.assign(pageContextReactive, pageContext) + objectReplace(dataReactive, data) + objectReplace(pageContextReactive, pageContext) rootComponentRef.value = markRaw(pageContext.config[rootComponentName]) layoutRef.value = markRaw(pageContext.config.Layout) await nextTick() @@ -52,9 +52,9 @@ async function createVueApp(pageContext: PageContext, ssr: boolean, rootComponen const data = pageContext.data ?? {} assertDataIsObject(data) - const dataReactive = reactive(data) - const pageContextReactive = reactive(pageContext) - setPageContext(app, pageContextReactive as typeof pageContext) + const dataReactive = shallowReactive(data) + const pageContextReactive = shallowReactive(pageContext) + setPageContext(app, pageContextReactive) setData(app, dataReactive) const { onCreateApp } = pageContext.config @@ -73,5 +73,11 @@ async function createVueApp(pageContext: PageContext, ssr: boolean, rootComponen } function assertDataIsObject(data: unknown): asserts data is Record { - if (!isObject(data)) throw new Error('Return value of data() should be an object, undefined, or null') + if (!isPlainObject(data)) throw new Error('Return value of data() should be a plain object, undefined, or null') +} + +export function objectReplace(obj: object, objAddendum: object) { + // @ts-ignore + Object.keys(obj).forEach((key) => delete obj[key]) + Object.assign(obj, objAddendum) } diff --git a/packages/vike-vue/src/utils/isObject.ts b/packages/vike-vue/src/utils/isObject.ts deleted file mode 100644 index b5fd3133..00000000 --- a/packages/vike-vue/src/utils/isObject.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function isObject(value: unknown): value is Record { - return typeof value === 'object' && value !== null -} diff --git a/packages/vike-vue/src/utils/isPlainObject.ts b/packages/vike-vue/src/utils/isPlainObject.ts new file mode 100644 index 00000000..afe0dfe7 --- /dev/null +++ b/packages/vike-vue/src/utils/isPlainObject.ts @@ -0,0 +1,19 @@ +export function isPlainObject(value: unknown): value is Record { + // Is object? + if (typeof value !== 'object' || value === null) { + return false + } + + // Support `Object.create(null)` + if (Object.getPrototypeOf(value) === null) { + return true + } + + // Is plain object? + return ( + /* Doesn't work in Cloudflare Pages workers + value.constructor === Object + */ + value.constructor.name === 'Object' + ) +}