mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-26 23:52:06 +00:00
feat(vue-app): support named views (#4410)
* support named views for extendRoutes config * fix lint errors * fix lint errors 2 * some refactoring * var rename * fixture & unit tests * fix: style * nuxt-child named view example and test * nuxt element with named view in layout * lint
This commit is contained in:
parent
2f1c87994e
commit
b1b9e0bcbc
15
examples/named-views/components/childLeft.vue
Normal file
15
examples/named-views/components/childLeft.vue
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
Child Left content!
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'ChildLeft'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
19
examples/named-views/components/mainTop.vue
Normal file
19
examples/named-views/components/mainTop.vue
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<template>
|
||||||
|
<div class="top">
|
||||||
|
Main Top content!
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'MainTop'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.top {
|
||||||
|
margin: auto;
|
||||||
|
max-width: 420px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
6
examples/named-views/layouts/default.vue
Normal file
6
examples/named-views/layouts/default.vue
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<Nuxt name="top" />
|
||||||
|
<Nuxt />
|
||||||
|
</div>
|
||||||
|
</template>
|
30
examples/named-views/nuxt.config.js
Normal file
30
examples/named-views/nuxt.config.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
export default {
|
||||||
|
router: {
|
||||||
|
extendRoutes(routes, resolve) {
|
||||||
|
const indexIndex = routes.findIndex(route => route.name === 'index')
|
||||||
|
let index = routes[indexIndex].children.findIndex(route => route.name === 'index-child-id')
|
||||||
|
routes[indexIndex].children[index] = {
|
||||||
|
...routes[indexIndex].children[index],
|
||||||
|
components: {
|
||||||
|
default: routes[indexIndex].children[index].component,
|
||||||
|
left: resolve(__dirname, 'components/childLeft.vue')
|
||||||
|
},
|
||||||
|
chunkNames: {
|
||||||
|
left: 'components/childLeft'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
index = routes.findIndex(route => route.name === 'main')
|
||||||
|
routes[index] = {
|
||||||
|
...routes[index],
|
||||||
|
components: {
|
||||||
|
default: routes[index].component,
|
||||||
|
top: resolve(__dirname, 'components/mainTop.vue')
|
||||||
|
},
|
||||||
|
chunkNames: {
|
||||||
|
top: 'components/mainTop'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
examples/named-views/package.json
Normal file
11
examples/named-views/package.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "my-app",
|
||||||
|
"dependencies": {
|
||||||
|
"nuxt": "latest"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "nuxt",
|
||||||
|
"build": "nuxt build",
|
||||||
|
"start": "nuxt start"
|
||||||
|
}
|
||||||
|
}
|
73
examples/named-views/pages/index.vue
Executable file
73
examples/named-views/pages/index.vue
Executable file
@ -0,0 +1,73 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<div class="main">
|
||||||
|
<h1>Hello there</h1>
|
||||||
|
<p>This is an example of a named views</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<NuxtLink to="/">
|
||||||
|
Root
|
||||||
|
</NuxtLink>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<NuxtLink to="/section">
|
||||||
|
Section
|
||||||
|
</NuxtLink>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<NuxtLink to="/child/123">
|
||||||
|
Child 123
|
||||||
|
</NuxtLink>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<NuxtLink to="/child/234">
|
||||||
|
Child 234
|
||||||
|
</NuxtLink>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<NuxtLink to="/main">
|
||||||
|
Main page with named view in layout
|
||||||
|
</NuxtLink>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div>
|
||||||
|
<div class="left">
|
||||||
|
<NuxtChild name="left" />
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<NuxtChild />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Stage'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.main {
|
||||||
|
margin: auto;
|
||||||
|
max-width: 420px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left {
|
||||||
|
max-width: 150px;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
border: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left:empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
24
examples/named-views/pages/index/child/_id/index.vue
Normal file
24
examples/named-views/pages/index/child/_id/index.vue
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h2>Child content</h2>
|
||||||
|
ID:{{ id }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Child',
|
||||||
|
validate({ params }) {
|
||||||
|
return !isNaN(+params.id)
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
id() {
|
||||||
|
return this.$route.params.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
16
examples/named-views/pages/index/section.vue
Normal file
16
examples/named-views/pages/index/section.vue
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h2>Section content</h2>
|
||||||
|
<p>This page does not have left panel.</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Section'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
50
examples/named-views/pages/main.vue
Executable file
50
examples/named-views/pages/main.vue
Executable file
@ -0,0 +1,50 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<div class="main">
|
||||||
|
<h1>Main page</h1>
|
||||||
|
<p>This is an example of a named views</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<NuxtLink to="/">
|
||||||
|
Root
|
||||||
|
</NuxtLink>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<NuxtLink to="/section">
|
||||||
|
Section
|
||||||
|
</NuxtLink>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<NuxtLink to="/child/123">
|
||||||
|
Child 123
|
||||||
|
</NuxtLink>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<NuxtLink to="/child/234">
|
||||||
|
Child 234
|
||||||
|
</NuxtLink>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<NuxtLink to="/main">
|
||||||
|
Main page with named view in layout
|
||||||
|
</NuxtLink>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<p>At top of this page there is named view from layout!</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Main'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.main {
|
||||||
|
margin: auto;
|
||||||
|
max-width: 420px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
@ -17,7 +17,11 @@ export default {
|
|||||||
name: 'Nuxt',
|
name: 'Nuxt',
|
||||||
props: {
|
props: {
|
||||||
nuxtChildKey: String,
|
nuxtChildKey: String,
|
||||||
keepAlive: Boolean
|
keepAlive: Boolean,
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
default: 'default'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
render(h) {
|
render(h) {
|
||||||
// If there is some error
|
// If there is some error
|
||||||
|
@ -3,16 +3,50 @@ import Router from 'vue-router'
|
|||||||
import { interopDefault } from './utils'
|
import { interopDefault } from './utils'
|
||||||
|
|
||||||
<% function recursiveRoutes(routes, tab, components, indentCount) {
|
<% function recursiveRoutes(routes, tab, components, indentCount) {
|
||||||
let res = ''
|
let res = '', resMap = ''
|
||||||
const baseIndent = tab.repeat(indentCount)
|
const baseIndent = tab.repeat(indentCount)
|
||||||
const firstIndent = '\n' + tab.repeat(indentCount + 1)
|
const firstIndent = '\n' + tab.repeat(indentCount + 1)
|
||||||
const nextIndent = ',' + firstIndent
|
const nextIndent = ',' + firstIndent
|
||||||
routes.forEach((route, i) => {
|
routes.forEach((route, i) => {
|
||||||
route._name = '_' + hash(route.component)
|
// If need to handle named views
|
||||||
components.push({ _name: route._name, component: route.component, name: route.name, chunkName: route.chunkName })
|
if (route.components) {
|
||||||
|
let _name = '_' + hash(route.components.default)
|
||||||
|
if (splitChunks.pages) {
|
||||||
|
resMap += `${firstIndent}${tab}default: ${_name}`
|
||||||
|
} else {
|
||||||
|
resMap += `${firstIndent}${tab}default: () => ${_name}.default || ${_name}`
|
||||||
|
}
|
||||||
|
for (const k in route.components) {
|
||||||
|
_name = '_' + hash(route.components[k])
|
||||||
|
const component = { _name, component: route.components[k] }
|
||||||
|
if (k === 'default') {
|
||||||
|
components.push({
|
||||||
|
...component,
|
||||||
|
name: route.name,
|
||||||
|
chunkName: route.chunkName
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
components.push({
|
||||||
|
...component,
|
||||||
|
name: `${route.name}-${k}`,
|
||||||
|
chunkName: route.chunkNames[k]
|
||||||
|
})
|
||||||
|
if (splitChunks.pages) {
|
||||||
|
resMap += `${nextIndent}${tab}${k}: ${_name}`
|
||||||
|
} else {
|
||||||
|
resMap += `${nextIndent}${tab}${k}: () => ${_name}.default || ${_name}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
route.component = false
|
||||||
|
} else {
|
||||||
|
route._name = '_' + hash(route.component)
|
||||||
|
components.push({ _name: route._name, component: route.component, name: route.name, chunkName: route.chunkName })
|
||||||
|
}
|
||||||
// @see: https://router.vuejs.org/api/#router-construction-options
|
// @see: https://router.vuejs.org/api/#router-construction-options
|
||||||
res += '{'
|
res += '{'
|
||||||
res += firstIndent + 'path: ' + JSON.stringify(route.path)
|
res += firstIndent + 'path: ' + JSON.stringify(route.path)
|
||||||
|
res += (route.components) ? nextIndent + 'components: {' + resMap + '\n' + baseIndent + tab + '}' : ''
|
||||||
res += (route.component) ? nextIndent + 'component: ' + (splitChunks.pages ? route._name : `() => ${route._name}.default || ${route._name}`) : ''
|
res += (route.component) ? nextIndent + 'component: ' + (splitChunks.pages ? route._name : `() => ${route._name}.default || ${route._name}`) : ''
|
||||||
res += (route.redirect) ? nextIndent + 'redirect: ' + JSON.stringify(route.redirect) : ''
|
res += (route.redirect) ? nextIndent + 'redirect: ' + JSON.stringify(route.redirect) : ''
|
||||||
res += (route.meta) ? nextIndent + 'meta: ' + JSON.stringify(route.meta) : ''
|
res += (route.meta) ? nextIndent + 'meta: ' + JSON.stringify(route.meta) : ''
|
||||||
|
13
test/fixtures/named-views/components/childLeft.vue
vendored
Normal file
13
test/fixtures/named-views/components/childLeft.vue
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<template>
|
||||||
|
<div>Child Left content!</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'ChildLeft'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
13
test/fixtures/named-views/components/mainTop.vue
vendored
Normal file
13
test/fixtures/named-views/components/mainTop.vue
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<template>
|
||||||
|
<div>Main Top content!</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'MainTop'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
6
test/fixtures/named-views/layouts/default.vue
vendored
Normal file
6
test/fixtures/named-views/layouts/default.vue
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
TOP:<Nuxt name="top" />:TOP
|
||||||
|
PAGE:<Nuxt />:PAGE
|
||||||
|
</div>
|
||||||
|
</template>
|
3
test/fixtures/named-views/named-views.test.js
vendored
Normal file
3
test/fixtures/named-views/named-views.test.js
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { buildFixture } from '../../utils/build'
|
||||||
|
|
||||||
|
buildFixture('named-views')
|
30
test/fixtures/named-views/nuxt.config.js
vendored
Normal file
30
test/fixtures/named-views/nuxt.config.js
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
export default {
|
||||||
|
router: {
|
||||||
|
extendRoutes(routes, resolve) {
|
||||||
|
const indexIndex = routes.findIndex(route => route.name === 'index')
|
||||||
|
let index = routes[indexIndex].children.findIndex(route => route.name === 'index-child-id')
|
||||||
|
routes[indexIndex].children[index] = {
|
||||||
|
...routes[indexIndex].children[index],
|
||||||
|
components: {
|
||||||
|
default: routes[indexIndex].children[index].component,
|
||||||
|
left: resolve(__dirname, 'components/childLeft.vue')
|
||||||
|
},
|
||||||
|
chunkNames: {
|
||||||
|
left: 'components/childLeft'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
index = routes.findIndex(route => route.name === 'main')
|
||||||
|
routes[index] = {
|
||||||
|
...routes[index],
|
||||||
|
components: {
|
||||||
|
default: routes[index].component,
|
||||||
|
top: resolve(__dirname, 'components/mainTop.vue')
|
||||||
|
},
|
||||||
|
chunkNames: {
|
||||||
|
top: 'components/mainTop'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
test/fixtures/named-views/pages/index.vue
vendored
Executable file
16
test/fixtures/named-views/pages/index.vue
vendored
Executable file
@ -0,0 +1,16 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<div class="main">
|
||||||
|
LEFT:<NuxtChild name="left" />:LEFT
|
||||||
|
CHILD:<NuxtChild />:CHILD
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Stage'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
21
test/fixtures/named-views/pages/index/child/_id/index.vue
vendored
Normal file
21
test/fixtures/named-views/pages/index/child/_id/index.vue
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<template>
|
||||||
|
<div>Child content ID:{{ id }}!</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Child',
|
||||||
|
validate({ params }) {
|
||||||
|
return !isNaN(+params.id)
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
id() {
|
||||||
|
return this.$route.params.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
13
test/fixtures/named-views/pages/index/section.vue
vendored
Normal file
13
test/fixtures/named-views/pages/index/section.vue
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<template>
|
||||||
|
<div>This page does not have left panel.</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Section'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
16
test/fixtures/named-views/pages/main.vue
vendored
Executable file
16
test/fixtures/named-views/pages/main.vue
vendored
Executable file
@ -0,0 +1,16 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<div class="main">
|
||||||
|
LEFT:<NuxtChild name="left" />:LEFT
|
||||||
|
CHILD:<NuxtChild />:CHILD
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Main'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
41
test/unit/named-views.test.js
Normal file
41
test/unit/named-views.test.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { getPort, loadFixture, Nuxt } from '../utils'
|
||||||
|
|
||||||
|
let port
|
||||||
|
let nuxt = null
|
||||||
|
|
||||||
|
describe('named views', () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
const options = await loadFixture('named-views')
|
||||||
|
nuxt = new Nuxt(options)
|
||||||
|
port = await getPort()
|
||||||
|
await nuxt.server.listen(port, '0.0.0.0')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('/ - no child, no named', async () => {
|
||||||
|
const { html } = await nuxt.server.renderRoute('/')
|
||||||
|
expect(html).toContain('LEFT:<!---->:LEFT')
|
||||||
|
expect(html).toContain('CHILD:<!---->:CHILD')
|
||||||
|
expect(html).toContain('TOP:<!---->:TOP')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('/section - have child, no named', async () => {
|
||||||
|
const { html } = await nuxt.server.renderRoute('/section')
|
||||||
|
expect(html).toContain('LEFT:<!---->:LEFT')
|
||||||
|
expect(html).toMatch(new RegExp('CHILD:<div( data-v-.+)*>This page does not have left panel.</div>:CHILD'))
|
||||||
|
expect(html).toContain('TOP:<!---->:TOP')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('/child/123 - have child, have named', async () => {
|
||||||
|
const { html } = await nuxt.server.renderRoute('/child/123')
|
||||||
|
expect(html).toMatch(new RegExp('LEFT:<div( data-v-.+)*>Child Left content!</div>:LEFT'))
|
||||||
|
expect(html).toMatch(new RegExp('CHILD:<div( data-v-.+)*>Child content ID:123!</div>:CHILD'))
|
||||||
|
expect(html).toContain('TOP:<!---->:TOP')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('/main - no child, no named left, have named top', async () => {
|
||||||
|
const { html } = await nuxt.server.renderRoute('/main')
|
||||||
|
expect(html).toMatch(new RegExp('TOP:<div( data-v-.+)*>Main Top content!</div>:TOP'))
|
||||||
|
expect(html).toContain('LEFT:<!---->:LEFT')
|
||||||
|
expect(html).toContain('CHILD:<!---->:CHILD')
|
||||||
|
})
|
||||||
|
})
|
Loading…
Reference in New Issue
Block a user