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:
Andrey Shertsinger 2018-12-20 22:50:22 +07:00 committed by Sébastien Chopin
parent 2f1c87994e
commit b1b9e0bcbc
21 changed files with 458 additions and 4 deletions

View File

@ -0,0 +1,15 @@
<template>
<div>
Child Left content!
</div>
</template>
<script>
export default {
name: 'ChildLeft'
}
</script>
<style scoped>
</style>

View 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>

View File

@ -0,0 +1,6 @@
<template>
<div>
<Nuxt name="top" />
<Nuxt />
</div>
</template>

View 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'
}
}
}
}
}

View File

@ -0,0 +1,11 @@
{
"name": "my-app",
"dependencies": {
"nuxt": "latest"
},
"scripts": {
"dev": "nuxt",
"build": "nuxt build",
"start": "nuxt start"
}
}

View 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>

View 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>

View 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>

View 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>

View File

@ -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

View File

@ -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) : ''

View File

@ -0,0 +1,13 @@
<template>
<div>Child Left content!</div>
</template>
<script>
export default {
name: 'ChildLeft'
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,13 @@
<template>
<div>Main Top content!</div>
</template>
<script>
export default {
name: 'MainTop'
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,6 @@
<template>
<div>
TOP:<Nuxt name="top" />:TOP
PAGE:<Nuxt />:PAGE
</div>
</template>

View File

@ -0,0 +1,3 @@
import { buildFixture } from '../../utils/build'
buildFixture('named-views')

View 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
View 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>

View 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>

View 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
View 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>

View 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')
})
})