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',
|
||||
props: {
|
||||
nuxtChildKey: String,
|
||||
keepAlive: Boolean
|
||||
keepAlive: Boolean,
|
||||
name: {
|
||||
type: String,
|
||||
default: 'default'
|
||||
}
|
||||
},
|
||||
render(h) {
|
||||
// If there is some error
|
||||
|
@ -3,16 +3,50 @@ import Router from 'vue-router'
|
||||
import { interopDefault } from './utils'
|
||||
|
||||
<% function recursiveRoutes(routes, tab, components, indentCount) {
|
||||
let res = ''
|
||||
let res = '', resMap = ''
|
||||
const baseIndent = tab.repeat(indentCount)
|
||||
const firstIndent = '\n' + tab.repeat(indentCount + 1)
|
||||
const nextIndent = ',' + firstIndent
|
||||
routes.forEach((route, i) => {
|
||||
route._name = '_' + hash(route.component)
|
||||
components.push({ _name: route._name, component: route.component, name: route.name, chunkName: route.chunkName })
|
||||
// If need to handle named views
|
||||
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
|
||||
res += '{'
|
||||
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.redirect) ? nextIndent + 'redirect: ' + JSON.stringify(route.redirect) : ''
|
||||
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