Skip to content
This repository has been archived by the owner on Apr 6, 2023. It is now read-only.

feat(nuxt): add setLayout utility #6826

Merged
merged 39 commits into from
Aug 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
5ec709f
Add `useLayout` composables change layouts
CoinPool-Coin Aug 22, 2022
19e603f
Add useLayout document
CoinPool-Coin Aug 22, 2022
89ca7d0
Resolution `Newline required at end of file but not found eol-last`
CoinPool-Coin Aug 22, 2022
2858c85
Merge branch 'main' into main
HomWang Aug 22, 2022
c2a18fb
Merge branch 'nuxt:main' into main
HomWang Aug 22, 2022
fd40e04
Merge branch 'nuxt:main' into main
HomWang Aug 23, 2022
c59cd90
Add `tsx` and `template`, and change the content to `Welcome to nuxt3`
CoinPool-Coin Aug 23, 2022
0bf58cd
Rollback pages.md
CoinPool-Coin Aug 23, 2022
a25ebf4
Merge branch 'nuxt:main' into main
HomWang Aug 23, 2022
8a409a5
rm useLayout and Add examples/advanced/tsx example
CoinPool-Coin Aug 23, 2022
75b6f60
Resolution error Newline required at end of file but not found …
CoinPool-Coin Aug 23, 2022
bef858b
Add // @ts-ignore
CoinPool-Coin Aug 23, 2022
992bdcc
Add eslint-disable-next-line @typescript-eslint/no-unused-vars
CoinPool-Coin Aug 23, 2022
eca7d33
remove use-layout (not related to this pr)
pi0 Aug 23, 2022
07e5239
rename package name to `example-tsx`
pi0 Aug 23, 2022
0a67113
refactor: show few different usages of tsx to define a component render
pi0 Aug 23, 2022
0393a71
use nuxt/ui
pi0 Aug 23, 2022
691a446
rename to jsx (jsx / tsx)
pi0 Aug 23, 2022
badac5e
add example link to pages tsx usage
pi0 Aug 23, 2022
44c5bd3
trigger ci
pi0 Aug 23, 2022
f48bf5f
use auto imports
pi0 Aug 23, 2022
30e8ab2
Merge branch 'nuxt:main' into main
HomWang Aug 24, 2022
11f3fff
Add setLayout
CoinPool-Coin Aug 24, 2022
fb7017f
Merge branch 'main' of https://github.com/516310460/framework
CoinPool-Coin Aug 24, 2022
5248b22
fix eslint prompt
CoinPool-Coin Aug 24, 2022
d748bff
Merge branch 'nuxt:main' into main
HomWang Aug 24, 2022
4533b54
Add function tsx component
CoinPool-Coin Aug 25, 2022
0874377
Add function tsx component
CoinPool-Coin Aug 25, 2022
949c381
Merge branch 'nuxt:main' into main
HomWang Aug 25, 2022
47769b6
chore: revert changes
danielroe Aug 25, 2022
75825b9
refactor: preserve layout state from server -> client via meta
danielroe Aug 25, 2022
71918de
docs: update example
danielroe Aug 25, 2022
c11768c
docs: add `setLayout` documentation
danielroe Aug 25, 2022
6e885e7
test: add fixture
danielroe Aug 25, 2022
8085ab4
Merge remote-tracking branch 'origin/main' into 516310460/main
danielroe Aug 25, 2022
c1aa622
chore: remove unrelated files
danielroe Aug 25, 2022
0ab573f
chore: update with main
danielroe Aug 25, 2022
81d2f50
style: lint
danielroe Aug 25, 2022
33a7ad6
Merge branch 'nuxt:main' into main
HomWang Aug 26, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions docs/content/3.api/1.composables/set-layout.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# `setLayout`

`setLayout` allows you to dynamically change the layout of a page.

`setLayout` relies on access to the Nuxt context and can only be called within component setup functions, plugins, and route middleware.

```js
export default defineNuxtRouteMiddleware(to => {
// Set the layout on the route you are navigating _to_
setLayout('other')
})
```

::alert{icon=πŸ‘‰}
If you choose to set the layout dynamically on the server-side, you _must_ do so before the layout is rendered by Vue. (In other words, within a plugin or route middleware.) Otherwise there will be a hydration mismatch.
::
4 changes: 4 additions & 0 deletions examples/routing/layouts/middleware/other.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export default defineNuxtRouteMiddleware(() => {
setLayout('other')
})
12 changes: 12 additions & 0 deletions examples/routing/layouts/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,18 @@
<NuxtLink to="/dynamic">
Dynamic layout
</NuxtLink>
<NuxtLink to="/other">
Other layout
</NuxtLink>
<NButton @click="setLayout('default')">
Change to default layout
</NButton>
<NButton @click="setLayout('custom')">
Change to custom layout
</NButton>
<NButton @click="setLayout('other')">
Change to other layout
</NButton>
</nav>
</template>
</NuxtExampleLayout>
Expand Down
13 changes: 13 additions & 0 deletions examples/routing/layouts/pages/other.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<script setup>
definePageMeta({
middleware: 'other'
})
</script>

<template>
<div>
<NuxtLink to="/">
Back to home
</NuxtLink>
</div>
</template>
2 changes: 1 addition & 1 deletion packages/nuxt/src/app/composables/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ export type { FetchResult, UseFetchOptions } from './fetch'
export { useCookie } from './cookie'
export type { CookieOptions, CookieRef } from './cookie'
export { useRequestHeaders, useRequestEvent, setResponseStatus } from './ssr'
export { abortNavigation, addRouteMiddleware, defineNuxtRouteMiddleware, navigateTo, useRoute, useActiveRoute, useRouter } from './router'
export { abortNavigation, addRouteMiddleware, defineNuxtRouteMiddleware, setLayout, navigateTo, useRoute, useActiveRoute, useRouter } from './router'
export type { AddRouteMiddlewareOptions, RouteMiddleware } from './router'
export { preloadComponents, prefetchComponents } from './preload'
16 changes: 15 additions & 1 deletion packages/nuxt/src/app/composables/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { getCurrentInstance, inject } from 'vue'
import type { Router, RouteLocationNormalizedLoaded, NavigationGuard, RouteLocationNormalized, RouteLocationRaw, NavigationFailure, RouteLocationPathRaw } from 'vue-router'
import { sendRedirect } from 'h3'
import { hasProtocol, joinURL, parseURL } from 'ufo'
import { useNuxtApp, useRuntimeConfig } from '#app'
import { useNuxtApp, useRuntimeConfig, useState } from '#app'

export const useRouter = () => {
return useNuxtApp()?.$router as Router
Expand Down Expand Up @@ -114,3 +114,17 @@ export const abortNavigation = (err?: Error | string) => {
}
return false
}

export const setLayout = (layout: string) => {
if (process.server) {
useState('_layout').value = layout
}
if (isProcessingMiddleware()) {
const unsubscribe = useRouter().beforeResolve((to) => {
to.meta.layout = layout
unsubscribe()
})
} else {
useRoute().meta.layout = layout
}
}
6 changes: 5 additions & 1 deletion packages/nuxt/src/app/plugins/router.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { reactive, h } from 'vue'
import { parseURL, stringifyParsedURL, parseQuery, stringifyQuery, withoutBase, isEqual, joinURL } from 'ufo'
import { createError } from 'h3'
import { defineNuxtPlugin, clearError, navigateTo, showError, useRuntimeConfig } from '..'
import { defineNuxtPlugin, clearError, navigateTo, showError, useRuntimeConfig, useState } from '..'
import { callWithNuxt } from '../nuxt'
// @ts-ignore
import { globalMiddleware } from '#build/middleware'
Expand Down Expand Up @@ -218,9 +218,13 @@ export default defineNuxtPlugin<{ route: Route, router: Router }>((nuxtApp) => {
named: {}
}

const initialLayout = useState('_layout')
nuxtApp.hooks.hookOnce('app:created', async () => {
router.beforeEach(async (to, from) => {
to.meta = reactive(to.meta || {})
if (nuxtApp.isHydrating) {
to.meta.layout = initialLayout.value ?? to.meta.layout
}
nuxtApp._processingMiddleware = true

const middlewareEntries = new Set<RouteGuard>([...globalMiddleware, ...nuxtApp._middleware.global])
Expand Down
1 change: 1 addition & 0 deletions packages/nuxt/src/imports/presets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const appPreset = defineUnimportPreset({
'useAsyncData',
'useLazyAsyncData',
'refreshNuxtData',
'setLayout',
'defineNuxtComponent',
'useNuxtApp',
'defineNuxtPlugin',
Expand Down
6 changes: 5 additions & 1 deletion packages/nuxt/src/pages/runtime/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
import { createError } from 'h3'
import { withoutBase, isEqual } from 'ufo'
import NuxtPage from './page'
import { callWithNuxt, defineNuxtPlugin, useRuntimeConfig, showError, clearError, navigateTo, useError } from '#app'
import { callWithNuxt, defineNuxtPlugin, useRuntimeConfig, showError, clearError, navigateTo, useError, useState } from '#app'
// @ts-ignore
import routes from '#build/routes'
// @ts-ignore
Expand Down Expand Up @@ -114,8 +114,12 @@ export default defineNuxtPlugin(async (nuxtApp) => {
callWithNuxt(nuxtApp, showError, [error])
}

const initialLayout = useState('_layout')
router.beforeEach(async (to, from) => {
to.meta = reactive(to.meta)
if (nuxtApp.isHydrating) {
to.meta.layout = initialLayout.value ?? to.meta.layout
}
nuxtApp._processingMiddleware = true

type MiddlewareDef = string | NavigationGuard
Expand Down
10 changes: 10 additions & 0 deletions test/basic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,16 @@ describe('layouts', () => {
expect(html).toContain('with-layout.vue')
expect(html).toContain('Custom Layout:')
})
it('should work with a dynamically set layout', async () => {
const html = await $fetch('/with-dynamic-layout')

// Snapshot
// expect(html).toMatchInlineSnapshot()

expect(html).toContain('with-dynamic-layout')
expect(html).toContain('Custom Layout:')
await expectNoClientErrors('/with-dynamic-layout')
})
})

describe('reactivity transform', () => {
Expand Down
4 changes: 4 additions & 0 deletions test/fixtures/basic/middleware/sets-layout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export default defineNuxtRouteMiddleware(async () => {
await new Promise(resolve => setTimeout(resolve, 10))
setLayout('custom')
})
11 changes: 11 additions & 0 deletions test/fixtures/basic/pages/with-dynamic-layout.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<script setup>
definePageMeta({
middleware: 'sets-layout'
})
</script>

<template>
<div>
<div>with-dynamic-layout.vue</div>
</div>
</template>