From d9f584b489c9b6279cf41b1f47ae157e4e3541f2 Mon Sep 17 00:00:00 2001 From: Rom Date: Thu, 13 Jun 2024 14:44:01 +0200 Subject: [PATCH] feat: make Layout setting cumulative (#129) --- .../HeadDefault.vue => pages/+Head.vue} | 0 .../LayoutDefault.vue => pages/+Layout.vue} | 1 + examples/full/pages/+config.ts | 6 +-- examples/full/pages/starship/+Layout.vue | 49 +++++++++++++++++ examples/full/pages/starship/+Page.vue | 6 +++ examples/full/pages/starship/Counter.vue | 15 ++++++ examples/full/pages/starship/DummyText.vue | 54 +++++++++++++++++++ .../full/pages/starship/reviews/+Page.vue | 5 ++ examples/full/pages/starship/spec/+Page.vue | 10 ++++ examples/full/readme.md | 1 + packages/vike-vue/src/+config.ts | 1 + .../vike-vue/src/renderer/createVueApp.ts | 33 ++++++------ packages/vike-vue/src/types/PageContext.ts | 1 + 13 files changed, 162 insertions(+), 20 deletions(-) rename examples/full/{layouts/HeadDefault.vue => pages/+Head.vue} (100%) rename examples/full/{layouts/LayoutDefault.vue => pages/+Layout.vue} (96%) create mode 100644 examples/full/pages/starship/+Layout.vue create mode 100644 examples/full/pages/starship/+Page.vue create mode 100644 examples/full/pages/starship/Counter.vue create mode 100644 examples/full/pages/starship/DummyText.vue create mode 100644 examples/full/pages/starship/reviews/+Page.vue create mode 100644 examples/full/pages/starship/spec/+Page.vue diff --git a/examples/full/layouts/HeadDefault.vue b/examples/full/pages/+Head.vue similarity index 100% rename from examples/full/layouts/HeadDefault.vue rename to examples/full/pages/+Head.vue diff --git a/examples/full/layouts/LayoutDefault.vue b/examples/full/pages/+Layout.vue similarity index 96% rename from examples/full/layouts/LayoutDefault.vue rename to examples/full/pages/+Layout.vue index c5c66817..c16d4a88 100644 --- a/examples/full/layouts/LayoutDefault.vue +++ b/examples/full/pages/+Layout.vue @@ -7,6 +7,7 @@ Welcome Markdown Data Fetching + Nested Layout Route Function Without SSR Client-only diff --git a/examples/full/pages/+config.ts b/examples/full/pages/+config.ts index ac15daac..c213bc53 100644 --- a/examples/full/pages/+config.ts +++ b/examples/full/pages/+config.ts @@ -1,12 +1,8 @@ import type { Config } from 'vike/types' -import LayoutDefault from '../layouts/LayoutDefault.vue' -import HeadDefault from '../layouts/HeadDefault.vue' import vikeVue from 'vike-vue/config' -// Default configs (can be overridden by pages) +// https://vike.dev/config export default { - Layout: LayoutDefault, - Head: HeadDefault, // title: 'My Vike + Vue App', // <link rel="canonical" /> diff --git a/examples/full/pages/starship/+Layout.vue b/examples/full/pages/starship/+Layout.vue new file mode 100644 index 00000000..794aa0a1 --- /dev/null +++ b/examples/full/pages/starship/+Layout.vue @@ -0,0 +1,49 @@ +<template> + <h1>Starship 🚀</h1> + <p> + <b> + This page uses the layout <code>/pages/+Layout.vue</code> with a nested layout + <code>/pages/starship/+Layout.vue</code>. + </b> + </p> + <p> + <b> + State is preserved upon navigating within the nested layout. Click on the counter then on the links below: + observe how the counter state is preserved. + </b> + <Counter /> + </p> + <br /> + <div class="sub-navigation"> + <a keep-scroll-position href="/starship">Overview</a> + <a keep-scroll-position href="/starship/reviews">Reviews</a> + <a keep-scroll-position href="/starship/spec">Tech Spec</a> + </div> + <div style="margin-top: 20px; border: 1px solid black; padding: 10px 40px"> + <slot /> + </div> + <br /> + <p> + <b>The scroll position is preserved when navigating the nested layout.</b> + </p> + <DummyText /> +</template> + +<style> +.sub-navigation a { + margin-right: 10px; +} +code { + font-family: monospace; + background-color: #eaeaea; + padding: 3px 5px; + border-radius: 4px; +} +</style> + +<script setup> +import Counter from './Counter.vue' +import DummyText from './DummyText.vue' +import { usePageContext } from 'vike-vue/usePageContext' +const pageContext = usePageContext() +</script> diff --git a/examples/full/pages/starship/+Page.vue b/examples/full/pages/starship/+Page.vue new file mode 100644 index 00000000..f2acfcbc --- /dev/null +++ b/examples/full/pages/starship/+Page.vue @@ -0,0 +1,6 @@ +<template> + <h2>Overview</h2> + <p>The Starship will, at term, repalce all SpaceX's rocket models.</p> + <p>The mission: Make life multi planetary.</p> + <p>Starship drastically reduces the cost of sending payload to space, ensuring SpaceX's financial prosperity.</p> +</template> diff --git a/examples/full/pages/starship/Counter.vue b/examples/full/pages/starship/Counter.vue new file mode 100644 index 00000000..51f98c8f --- /dev/null +++ b/examples/full/pages/starship/Counter.vue @@ -0,0 +1,15 @@ +<template> + <button type="button" @click="state.count++">Counter {{ state.count }}</button> +</template> + +<script> +import { reactive } from 'vue' +export default { + setup(props) { + const state = reactive({ count: 0 }) + return { + state, + } + }, +} +</script> diff --git a/examples/full/pages/starship/DummyText.vue b/examples/full/pages/starship/DummyText.vue new file mode 100644 index 00000000..f756b8e4 --- /dev/null +++ b/examples/full/pages/starship/DummyText.vue @@ -0,0 +1,54 @@ +<template> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis + natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, + pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, + vulputate eget, arcu. + </p> + <p> + In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer + tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, + porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. + </p> + <p> + Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. + Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, + sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, + hendrerit id, lorem. + </p> + <p> + Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam + sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis + magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc, quis gravida magna mi a libero. Fusce + vulputate eleifend sapien. + </p> + <p> + Vestibulum purus quam, scelerisque ut, mollis sed, nonummy id, metus. Nullam accumsan lorem in dui. Cras ultricies + mi eu turpis hendrerit fringilla. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia + Curae; In ac dui quis mi consectetuer lacinia. Nam pretium turpis et arcu. + </p> + <p> + Duis arcu tortor, suscipit eget, imperdiet nec, imperdiet iaculis, ipsum. Sed aliquam ultrices mauris. Integer ante + arcu, accumsan a, consectetuer eget, posuere ut, mauris. Praesent adipiscing. Phasellus ullamcorper ipsum rutrum + nunc. Nunc nonummy metus. Vestibulum volutpat pretium libero. Cras id dui. Aenean ut eros et nisl sagittis + vestibulum. + </p> + <p> + Nullam nulla eros, ultricies sit amet, nonummy id, imperdiet feugiat, pede. Sed lectus. Donec mollis hendrerit + risus. Phasellus nec sem in justo pellentesque facilisis. Etiam imperdiet imperdiet orci. Nunc nec neque. Phasellus + leo dolor, tempus non, auctor et, hendrerit quis, nisi. Curabitur ligula sapien, tincidunt non, euismod vitae, + posuere imperdiet, leo. + </p> + <p> + Maecenas malesuada. Praesent congue erat at massa. Sed cursus turpis vitae tortor. Donec posuere vulputate arcu. + Phasellus accumsan cursus velit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia + Curae; Sed aliquam, nisi quis porttitor congue, elit erat euismod orci, ac placerat dolor lectus quis orci. + Phasellus consectetuer vestibulum elit. + </p> + <p> + Aenean tellus metus, bibendum sed, posuere ac, mattis non, nunc. Vestibulum fringilla pede sit amet augue. In + turpis. Pellentesque posuere. Praesent turpis. Aenean posuere, tortor sed cursus feugiat, nunc augue blandit nunc, + eu sollicitudin urna dolor sagittis lacus. Donec elit libero, sodales nec, volutpat a, suscipit non, turpis. Nullam + sagittis. + </p> +</template> diff --git a/examples/full/pages/starship/reviews/+Page.vue b/examples/full/pages/starship/reviews/+Page.vue new file mode 100644 index 00000000..71fe38b9 --- /dev/null +++ b/examples/full/pages/starship/reviews/+Page.vue @@ -0,0 +1,5 @@ +<template> + <h2>Reviews</h2> + <p>"The Starship brought me and my family to Mars safely." -- Anonymous Family</p> + <p>"A handful of Starships was enough to set up SkyNet. It worked like a charm." -- Skynet Research</p> +</template> diff --git a/examples/full/pages/starship/spec/+Page.vue b/examples/full/pages/starship/spec/+Page.vue new file mode 100644 index 00000000..65ecb66a --- /dev/null +++ b/examples/full/pages/starship/spec/+Page.vue @@ -0,0 +1,10 @@ +<template> + <h2>Spec</h2> + <pre> +HEIGHT 50 m / 164 ft +DIAMETER 9 m / 30 ft +PROPELLANT CAPACITY 1200 t / 2.6 Mlb +THRUST 1500 tf / 3.2Mlbf +PAYLOAD CAPACITY 100-150 t orbit dependent + </pre> +</template> diff --git a/examples/full/readme.md b/examples/full/readme.md index 8d496120..d1ec2215 100644 --- a/examples/full/readme.md +++ b/examples/full/readme.md @@ -2,6 +2,7 @@ Full-fledged example of using `vike-vue`, showcasing: - [Layout](https://vike.dev/Layout) - Fetching data with [`data()`](https://vike.dev/data) +- [Nested Layout](https://vike.dev/Layout#nested-layouts) - [`<ClientOnly>`](https://vike.dev/ClientOnly) - [Toggling SSR](https://vike.dev/ssr) on a per-page basis. - [Markdown](https://vike.dev/markdown) diff --git a/packages/vike-vue/src/+config.ts b/packages/vike-vue/src/+config.ts index 82322cd6..0477e331 100644 --- a/packages/vike-vue/src/+config.ts +++ b/packages/vike-vue/src/+config.ts @@ -60,6 +60,7 @@ export default { }, Layout: { env: { server: true, client: true }, + cumulative: true, }, title: { env: { server: true, client: true }, diff --git a/packages/vike-vue/src/renderer/createVueApp.ts b/packages/vike-vue/src/renderer/createVueApp.ts index f0841f12..3bd3e336 100644 --- a/packages/vike-vue/src/renderer/createVueApp.ts +++ b/packages/vike-vue/src/renderer/createVueApp.ts @@ -10,22 +10,25 @@ import { isPlainObject } from '../utils/isPlainObject' import { setData } from '../hooks/useData' type ChangePage = (pageContext: PageContext) => Promise<void> -async function createVueApp(pageContext: PageContext, ssr: boolean, rootComponentName: 'Head' | 'Page') { - const rootComponentRef = ref(markRaw(pageContext.config[rootComponentName])) - const layoutRef = ref(markRaw(pageContext.config.Layout)) +async function createVueApp(pageContext: PageContext, ssr: boolean, mainComponentName: 'Head' | 'Page') { + const mainComponentRef = ref(markRaw(pageContext.config[mainComponentName])) + const layoutRef = ref(markRaw(pageContext.config.Layout || [])) - const PageWithLayout = { - render() { - if (!!layoutRef.value && rootComponentName === 'Page') { - // Wrap <Page> with <Layout> - return h(layoutRef.value, {}, { default: () => h(rootComponentRef.value) }) - } else { - return h(rootComponentRef.value) - } - }, + const MainComponent = () => h(mainComponentRef.value) + let RootComponent = MainComponent + // Wrap <Page> with <Layout> + if (mainComponentName === 'Page') { + RootComponent = () => { + let RootComp = MainComponent + layoutRef.value.forEach((layout) => { + const Comp = RootComp + RootComp = () => h(layout, null, Comp) + }) + return RootComp() + } } - const app: App = ssr ? createSSRApp(PageWithLayout) : createApp(PageWithLayout) + const app: App = ssr ? createSSRApp(RootComponent) : createApp(RootComponent) objectAssign(pageContext, { app }) // changePage() is called upon navigation, see +onRenderClient.ts @@ -43,8 +46,8 @@ async function createVueApp(pageContext: PageContext, ssr: boolean, rootComponen assertDataIsObject(data) objectReplace(dataReactive, data) objectReplace(pageContextReactive, pageContext) - rootComponentRef.value = markRaw(pageContext.config[rootComponentName]) - layoutRef.value = markRaw(pageContext.config.Layout) + mainComponentRef.value = markRaw(pageContext.config[mainComponentName]) + layoutRef.value = markRaw(pageContext.config.Layout || []) await nextTick() returned = true if (err) throw err diff --git a/packages/vike-vue/src/types/PageContext.ts b/packages/vike-vue/src/types/PageContext.ts index fd292411..54cf7f1d 100644 --- a/packages/vike-vue/src/types/PageContext.ts +++ b/packages/vike-vue/src/types/PageContext.ts @@ -36,6 +36,7 @@ declare global { onBeforeRenderClient?: Array<OnBeforeRenderClientSync | OnBeforeRenderClientAsync> bodyHtmlStart?: BodyInjectHtml[] bodyHtmlEnd?: BodyInjectHtml[] + Layout?: Component[] } } }