mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-27 08:02:01 +00:00
Merge branch 'nested-dynamic-routes'
This commit is contained in:
commit
004e21b929
13
.editorconfig
Normal file
13
.editorconfig
Normal file
@ -0,0 +1,13 @@
|
||||
# editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
@ -4,6 +4,8 @@ node_js:
|
||||
- "6.9"
|
||||
- "5.12"
|
||||
- "4.7"
|
||||
before_install:
|
||||
- if [[ `npm -v` != 3* ]]; then npm i -g npm@3; fi
|
||||
install:
|
||||
- npm install
|
||||
- npm run build
|
||||
|
@ -5,6 +5,7 @@ process.env.DEBUG = 'nuxt:*'
|
||||
|
||||
var _ = require('lodash')
|
||||
var debug = require('debug')('nuxt:build')
|
||||
debug.color = 2 // force green color
|
||||
var fs = require('fs')
|
||||
var Nuxt = require('../')
|
||||
var chokidar = require('chokidar')
|
||||
@ -37,7 +38,7 @@ nuxt.build()
|
||||
function listenOnConfigChanges (nuxt, server) {
|
||||
// Listen on nuxt.config.js changes
|
||||
var build = _.debounce(() => {
|
||||
debug('[nuxt.config.js] changed, rebuilding the app...')
|
||||
debug('[nuxt.config.js] changed')
|
||||
delete require.cache[nuxtConfigFile]
|
||||
var options = {}
|
||||
if (fs.existsSync(nuxtConfigFile)) {
|
||||
@ -50,6 +51,8 @@ function listenOnConfigChanges (nuxt, server) {
|
||||
options.rootDir = rootDir
|
||||
nuxt.close()
|
||||
.then(() => {
|
||||
nuxt.renderer = null
|
||||
debug('Rebuilding the app...')
|
||||
return new Nuxt(options).build()
|
||||
})
|
||||
.then((nuxt) => {
|
||||
|
@ -2,7 +2,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<p>{{ userAgent }}</p>
|
||||
<p><router-link to="/post">See a post (http request / Ajax)</router-link></p>
|
||||
<p><nuxt-link to="/post">See a post (http request / Ajax)</nuxt-link></p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<p>{{ post.title }}!</p>
|
||||
<p><router-link to="/">Back home</router-link></p>
|
||||
<p><nuxt-link to="/">Back home</nuxt-link></p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -183,7 +183,7 @@ Let's add a `/secret` route where only the connected user can see its content:
|
||||
<template>
|
||||
<div>
|
||||
<h1>Super secret page</h1>
|
||||
<router-link to="/">Back to the home page</router-link>
|
||||
<nuxt-link to="/">Back to the home page</nuxt-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -14,7 +14,7 @@
|
||||
<p><i>You can also refresh this page, you'll still be connected!</i></p>
|
||||
<button @click="logout">Logout</button>
|
||||
</div>
|
||||
<p><router-link to="/secret">Super secret page</router-link></p>
|
||||
<p><nuxt-link to="/secret">Super secret page</nuxt-link></p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div>
|
||||
<h1>Super secret page</h1>
|
||||
<p>If you try to access this URL not connected, you will be redirected to the home page (server-side or client-side)</p>
|
||||
<router-link to="/">Back to the home page</router-link>
|
||||
<nuxt-link to="/">Back to the home page</nuxt-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<p>About Page</p>
|
||||
<router-link to="/">Go to /</router-link>
|
||||
<nuxt-link to="/">Go to /</nuxt-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<p>Hello {{ name }}!</p>
|
||||
<router-link to="/about">Go to /about</router-link>
|
||||
<nuxt-link to="/about">Go to /about</nuxt-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -2,38 +2,177 @@
|
||||
|
||||
> Nuxt.js is based on vue-router and allows you to defined custom routes :rocket:
|
||||
|
||||
## Usage
|
||||
## Concept
|
||||
|
||||
Nuxt.js detect and generate automatically the vue-router config according to your file tree of .vue files inside the `pages` directory.
|
||||
|
||||
## Basic routes
|
||||
|
||||
This file tree:
|
||||
|
||||
```bash
|
||||
/pages
|
||||
|-> /team
|
||||
|-> index.vue
|
||||
|-> about.vue
|
||||
|-> index.vue
|
||||
```
|
||||
|
||||
will automatically generate:
|
||||
|
||||
Add your custom routes inside `nuxt.config.js`:
|
||||
```js
|
||||
module.exports = {
|
||||
router: {
|
||||
routes: [
|
||||
{ name: 'user', path: '/users/:id', component: 'pages/user' }
|
||||
]
|
||||
}
|
||||
router: {
|
||||
routes: [
|
||||
{
|
||||
name: 'index',
|
||||
path: '/',
|
||||
component: 'pages/index'
|
||||
},
|
||||
{
|
||||
name: 'team',
|
||||
path: '/team',
|
||||
component: 'pages/team/index'
|
||||
},
|
||||
{
|
||||
name: 'team-about',
|
||||
path: '/team/about',
|
||||
component: 'pages/team/about'
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
| key | Optional? | definition |
|
||||
|------|------------|-----------|
|
||||
| `path` | **Required** | Route path, it can have dynamic mapping, look at [vue-router documentation](https://router.vuejs.org/en/essentials/dynamic-matching.html) about it. |
|
||||
| `component` | **Required** | Path to the `.vue` component, if relative, it has to be from the app folder. |
|
||||
| `name` | Optional | Route name, useful for linking to it with `<router-link>`, see [vue-router documentation](https://router.vuejs.org/en/essentials/named-routes.html) about it. |
|
||||
| `meta` | Optional | Let you add custom fields to get back inside your component (available in the context via `route.meta` inside `data` and `fetch` methods). See [vue-router documentation](https://router.vuejs.org/en/advanced/meta.html) about it. |
|
||||
| `children` | Optional | *Not supported* |
|
||||
## Dynamic routes
|
||||
|
||||
## Hidden pages
|
||||
To define a dynamic route with a param, you need to define a .vue file prefixed by an underscore.
|
||||
|
||||
>If you want don't want nuxt.js to generate a route for a specific page, you just have to **rename it with _ at the beginning**.
|
||||
This file tree:
|
||||
|
||||
Let's say I have a component `pages/user.vue` and I don't want nuxt.js to create the `/user`. I can rename it to `pages/_user.vue` and voilà!
|
||||
```bash
|
||||
/pages
|
||||
|-> /projects
|
||||
|-> index.vue
|
||||
|-> _slug.vue
|
||||
```
|
||||
|
||||
will automatically generate:
|
||||
|
||||
You can then change the component path in the `nuxt.config.js`:
|
||||
```js
|
||||
// ...
|
||||
{ name: 'user', path: '/users/:id', component: 'pages/_user' }
|
||||
// ...
|
||||
router: {
|
||||
routes: [
|
||||
{
|
||||
name: 'projects',
|
||||
path: '/projects',
|
||||
component: 'pages/projects/index'
|
||||
},
|
||||
{
|
||||
name: 'projects-slug',
|
||||
path: '/projects/:slug',
|
||||
component: 'pages/projects/_slug'
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Additional feature : validate (optional)
|
||||
|
||||
Nuxt.js allows you to define a validator function inside your dynamic route component (In this example: `pages/projects/_slug.vue`).
|
||||
|
||||
If validate function fails, Nuxt.js will automatically load the 404 error page.
|
||||
|
||||
```js
|
||||
<script>
|
||||
export default {
|
||||
validate ({ params }) {
|
||||
return /^[A-z]+$/.test(params.slug)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
## Nested Routes (children)
|
||||
|
||||
To define a nested route, you need to define a .vue file with the same name as the directory which contain your children views.
|
||||
> Don't forget to put `<nuxt-child></nuxt-child>` inside your parent .vue file.
|
||||
|
||||
This file tree:
|
||||
|
||||
```bash
|
||||
/pages
|
||||
|-> /users
|
||||
|-> _id.vue
|
||||
|-> users.vue
|
||||
```
|
||||
|
||||
will automatically generate:
|
||||
|
||||
```js
|
||||
router: {
|
||||
routes: [
|
||||
{
|
||||
path: '/users',
|
||||
component: 'pages/users',
|
||||
children: [
|
||||
{
|
||||
path: ':id',
|
||||
component: 'pages/users/_id',
|
||||
name: 'users-id'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Dynamic Nested Routes
|
||||
|
||||
This file tree:
|
||||
|
||||
```bash
|
||||
/pages
|
||||
|-> /posts
|
||||
|-> /_slug
|
||||
|-> _name.vue
|
||||
|-> comments.vue
|
||||
|-> _slug.vue
|
||||
|-> index.vue
|
||||
|-> posts.vue
|
||||
```
|
||||
|
||||
will automatically generate:
|
||||
|
||||
```js
|
||||
router: {
|
||||
routes: [
|
||||
{
|
||||
path: '/posts',
|
||||
component: 'pages/posts',
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
component: 'pages/posts/index',
|
||||
name: 'posts'
|
||||
},
|
||||
{
|
||||
path: ':slug',
|
||||
component: 'pages/posts/_slug',
|
||||
children: [
|
||||
{
|
||||
path: 'comments',
|
||||
component: 'pages/posts/_slug/comments',
|
||||
name: 'posts-slug-comments'
|
||||
},
|
||||
{
|
||||
path: ':name',
|
||||
component: 'pages/posts/_slug/_name',
|
||||
name: 'posts-slug-name'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Demo
|
||||
|
@ -1,9 +1,4 @@
|
||||
module.exports = {
|
||||
router: {
|
||||
routes: [
|
||||
{ name: 'user', path: '/users/:id(\\d+)', component: 'pages/_user' }
|
||||
]
|
||||
},
|
||||
build: {
|
||||
vendor: ['axios']
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
<h2>Users</h2>
|
||||
<ul class="users">
|
||||
<li v-for="user in users">
|
||||
<router-link :to="{ name: 'user', params: { id: user.id } }">{{ user.name }}</router-link>
|
||||
<nuxt-link :to="'/users/'+user.id">{{ user.name }}</nuxt-link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -3,7 +3,7 @@
|
||||
<h3>{{ name }}</h3>
|
||||
<h4>@{{ username }}</h4>
|
||||
<p>Email : {{ email }}</p>
|
||||
<p><router-link to="/">List of users</router-link></p>
|
||||
<p><nuxt-link to="/">List of users</nuxt-link></p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -11,8 +11,11 @@
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
validate ({ params }) {
|
||||
return !isNaN(+params.id)
|
||||
},
|
||||
data ({ params, error }) {
|
||||
return axios.get(`https://jsonplaceholder.typicode.com/users/${params.id}`)
|
||||
return axios.get(`https://jsonplaceholder.typicode.com/users/${+params.id}`)
|
||||
.then((res) => res.data)
|
||||
.catch(() => {
|
||||
error({ message: 'User not found', statusCode: 404 })
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<p>Hi from {{ name }}</p>
|
||||
<router-link to="/">Home page</router-link>
|
||||
<nuxt-link to="/">Home page</nuxt-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<h1>Welcome!</h1>
|
||||
<router-link to="/about">About page</router-link>
|
||||
<nuxt-link to="/about">About page</nuxt-link>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="content">
|
||||
<h1 class="title">Another Page</h1>
|
||||
<p><router-link to="/" class="button is-medium is-info hvr-wobble-vertical">Another button</router-link></p>
|
||||
<p><router-link to="/">Back home</router-link></p>
|
||||
<p><nuxt-link to="/" class="button is-medium is-info hvr-wobble-vertical">Another button</nuxt-link></p>
|
||||
<p><nuxt-link to="/">Back home</nuxt-link></p>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="content">
|
||||
<h1 class="title">Custom CSS!</h1>
|
||||
<p><router-link to="/about" class="button is-medium is-primary hvr-float-shadow">I am a button</router-link></p>
|
||||
<p><router-link to="/about">About page</router-link></p>
|
||||
<p><nuxt-link to="/about" class="button is-medium is-primary hvr-float-shadow">I am a button</nuxt-link></p>
|
||||
<p><nuxt-link to="/about">About page</nuxt-link></p>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -3,7 +3,7 @@
|
||||
<h1>About page</h1>
|
||||
<p>Click below to see the custom meta tags added with our custom component <code>twitter-head-card</code></p>
|
||||
<twitter-head-card></twitter-head-card>
|
||||
<p><router-link to="/">Home page</router-link></p>
|
||||
<p><nuxt-link to="/">Home page</nuxt-link></p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<h1>Home page 🚀</h1>
|
||||
<router-link to="/about">About page</router-link>
|
||||
<nuxt-link to="/about">About page</nuxt-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Welcome!</h1>
|
||||
<router-link to="/about">About page</router-link>
|
||||
<nuxt-link to="/about">About page</nuxt-link>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<img :src="thumbnailUrl" />
|
||||
<p><router-link to="/">Home</router-link> - About</p>
|
||||
<p><nuxt-link to="/">Home</nuxt-link> - About</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<p><button @click="showLoginError">Notif me!</button></p>
|
||||
<p>Home - <router-link to="/about">About</router-link></p>
|
||||
<p>Home - <nuxt-link to="/about">About</nuxt-link></p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -71,7 +71,7 @@ To define a custom transition for a specific route, simply add the `transition`
|
||||
<template>
|
||||
<div class="container">
|
||||
<h1>About page</h1>
|
||||
<router-link to="/">Home page</router-link>
|
||||
<nuxt-link to="/">Home page</nuxt-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<h1>About page</h1>
|
||||
<router-link to="/">Home page</router-link>
|
||||
<nuxt-link to="/">Home page</nuxt-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<h1>Home page</h1>
|
||||
<router-link to="/about">About page</router-link>
|
||||
<nuxt-link to="/about">About page</nuxt-link>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -3,7 +3,7 @@
|
||||
<img src="~static/nuxt-black.png" />
|
||||
<h2>Thank you for testing nuxt.js</h2>
|
||||
<p>Loaded from the {{ name }}</p>
|
||||
<p><router-link to="/">Back home</router-link></p>
|
||||
<p><nuxt-link to="/">Back home</nuxt-link></p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div class="container">
|
||||
<img src="nuxt.png" />
|
||||
<h2>Hello World.</h2>
|
||||
<p><router-link to="/about">About</router-link></p>
|
||||
<p><nuxt-link to="/about">About</nuxt-link></p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div>
|
||||
<p>
|
||||
<button @click="$store.commit('increment')">{{ $store.state.counter }}</button><br>
|
||||
<router-link to="/">Home</router-link>
|
||||
<nuxt-link to="/">Home</nuxt-link>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div>
|
||||
<p>
|
||||
<button @click="increment">{{ counter }}</button><br>
|
||||
<router-link to="/about">About</router-link>
|
||||
<nuxt-link to="/about">About</nuxt-link>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -4,16 +4,26 @@ require('es6-object-assign').polyfill()
|
||||
import 'es6-promise/auto'
|
||||
import Vue from 'vue'
|
||||
import { app, router<%= (store ? ', store' : '') %> } from './index'
|
||||
import { getMatchedComponents, getMatchedComponentsInstances, flatMapComponents, getContext, promisify, getLocation } from './utils'
|
||||
import { getMatchedComponents, getMatchedComponentsInstances, flatMapComponents, getContext, promisify, getLocation, compile } from './utils'
|
||||
const noopData = () => { return {} }
|
||||
const noopFetch = () => {}
|
||||
let _lastPaths = []
|
||||
|
||||
function mapTransitions(Components, to, from) {
|
||||
return Components.map((Component) => {
|
||||
let transition = Component.options.transition
|
||||
if (typeof transition === 'function') {
|
||||
return transition(to, from)
|
||||
}
|
||||
return transition
|
||||
})
|
||||
}
|
||||
|
||||
function loadAsyncComponents (to, ___, next) {
|
||||
const resolveComponents = flatMapComponents(to, (Component, _, match, key) => {
|
||||
if (typeof Component === 'function' && !Component.options) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
const _resolve = (Component) => {
|
||||
// console.log('Component loaded', Component, match.path, key)
|
||||
if (!Component.options) {
|
||||
Component = Vue.extend(Component) // fix issue #6
|
||||
Component._Ctor = Component
|
||||
@ -44,13 +54,12 @@ function loadAsyncComponents (to, ___, next) {
|
||||
})
|
||||
}
|
||||
|
||||
function render (to, ___, next) {
|
||||
function render (to, from, next) {
|
||||
let Components = getMatchedComponents(to)
|
||||
if (!Components.length) {
|
||||
this.error({ statusCode: 404, message: 'This page could not be found.', url: to.path })
|
||||
return next()
|
||||
}
|
||||
// console.log('Load components', Components, to.path)
|
||||
// Update ._data and other properties if hot reloaded
|
||||
Components.forEach(function (Component) {
|
||||
if (!Component._data) {
|
||||
@ -67,10 +76,26 @@ function render (to, ___, next) {
|
||||
}
|
||||
}
|
||||
})
|
||||
this.setTransition(Components[0].options.transition)
|
||||
this.setTransitions(mapTransitions(Components, to, from))
|
||||
this.error()
|
||||
let nextCalled = false
|
||||
Promise.all(Components.map((Component) => {
|
||||
let isValid = Components.some((Component) => {
|
||||
if (typeof Component.options.validate !== 'function') return true
|
||||
return Component.options.validate({
|
||||
params: to.params || {},
|
||||
query: to.query || {}
|
||||
})
|
||||
})
|
||||
if (!isValid) {
|
||||
this.error({ statusCode: 404, message: 'This page could not be found.', url: to.path })
|
||||
return next()
|
||||
}
|
||||
Promise.all(Components.map((Component, i) => {
|
||||
// Check if only children route changed
|
||||
Component._path = compile(to.matched[i].path)(to.params)
|
||||
if (Component._path === _lastPaths[i] && (i + 1) !== Components.length) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
let promises = []
|
||||
const _next = function (path) {
|
||||
<%= (loading ? 'this.$loading.finish && this.$loading.finish()' : '') %>
|
||||
@ -78,6 +103,7 @@ function render (to, ___, next) {
|
||||
next(path)
|
||||
}
|
||||
const context = getContext({ to<%= (store ? ', store' : '') %>, isClient: true, next: _next.bind(this), error: this.error.bind(this) })
|
||||
// Validate method
|
||||
if (Component._data && typeof Component._data === 'function') {
|
||||
var promise = promisify(Component._data, context)
|
||||
promise.then((data) => {
|
||||
@ -99,6 +125,7 @@ function render (to, ___, next) {
|
||||
return Promise.all(promises)
|
||||
}))
|
||||
.then(() => {
|
||||
_lastPaths = Components.map((Component, i) => compile(to.matched[i].path)(to.params))
|
||||
<%= (loading ? 'this.$loading.finish && this.$loading.finish()' : '') %>
|
||||
// If not redirected
|
||||
if (!nextCalled) {
|
||||
@ -106,6 +133,7 @@ function render (to, ___, next) {
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
_lastPaths = []
|
||||
this.error(error)
|
||||
next(false)
|
||||
})
|
||||
@ -118,12 +146,14 @@ function fixPrepatch (to, ___) {
|
||||
return
|
||||
}
|
||||
Vue.nextTick(() => {
|
||||
let RouterViewComponentFile = this.$nuxt._routerViewCache.default.__file
|
||||
if (typeof this.$nuxt._routerViewCache.default === 'function') RouterViewComponentFile = this.$nuxt._routerViewCache.default.options.__file
|
||||
let instances = getMatchedComponentsInstances(to)
|
||||
instances.forEach((instance, i) => {
|
||||
instances.forEach((instance) => {
|
||||
if (!instance) return;
|
||||
if (instance.constructor.options.__file === RouterViewComponentFile) {
|
||||
let file = instance.$parent._routerViewCache.default.__file
|
||||
if (typeof instance.$parent._routerViewCache.default === 'function') {
|
||||
file = instance.$parent._routerViewCache.default.options.__file
|
||||
}
|
||||
if (instance.constructor.options.__file === file) {
|
||||
let newData = instance.constructor.options.data()
|
||||
for (let key in newData) {
|
||||
Vue.set(instance.$data, key, newData[key])
|
||||
@ -254,8 +284,11 @@ Promise.all(resolveComponents)
|
||||
store.replaceState(NUXT.state)
|
||||
}
|
||||
<% } %>
|
||||
_app.setTransition = _app.$options._nuxt.setTransition.bind(_app)
|
||||
if (Components.length) _app.setTransition(Components[0].options.transition)
|
||||
_app.setTransitions = _app.$options._nuxt.setTransitions.bind(_app)
|
||||
if (Components.length) {
|
||||
_app.setTransitions(mapTransitions(Components, router.currentRoute))
|
||||
_lastPaths = router.currentRoute.matched.map((route) => compile(route.path)(router.currentRoute.params))
|
||||
}
|
||||
_app.error = _app.$options._nuxt.error.bind(_app)
|
||||
_app.$loading = {} // to avoid error while _app.$nuxt does not exist
|
||||
if (NUXT.error) _app.error(NUXT.error)
|
||||
|
60
lib/app/components/nuxt-child.js
Normal file
60
lib/app/components/nuxt-child.js
Normal file
@ -0,0 +1,60 @@
|
||||
import Vue from 'vue'
|
||||
|
||||
const transitionsKeys = [
|
||||
'name',
|
||||
'mode',
|
||||
'css',
|
||||
'type',
|
||||
'enterClass',
|
||||
'leaveClass',
|
||||
'enterActiveClass',
|
||||
'leaveActiveClass'
|
||||
]
|
||||
const listenersKeys = [
|
||||
'beforeEnter',
|
||||
'enter',
|
||||
'afterEnter',
|
||||
'enterCancelled',
|
||||
'beforeLeave',
|
||||
'leave',
|
||||
'afterLeave',
|
||||
'leaveCancelled'
|
||||
]
|
||||
|
||||
export default {
|
||||
name: 'nuxt-child',
|
||||
functional: true,
|
||||
render (h, { parent, data }) {
|
||||
data.nuxtChild = true
|
||||
|
||||
const transitions = parent.$nuxt.nuxt.transitions
|
||||
const defaultTransition = parent.$nuxt.nuxt.defaultTransition
|
||||
let depth = 0
|
||||
while (parent) {
|
||||
if (parent.$vnode && parent.$vnode.data.nuxtChild) {
|
||||
depth++
|
||||
}
|
||||
parent = parent.$parent
|
||||
}
|
||||
data.nuxtChildDepth = depth
|
||||
const transition = transitions[depth] || defaultTransition
|
||||
let transitionProps = {}
|
||||
transitionsKeys.forEach((key) => {
|
||||
if (typeof transition[key] !== 'undefined') {
|
||||
transitionProps[key] = transition[key]
|
||||
}
|
||||
})
|
||||
let listeners = {}
|
||||
listenersKeys.forEach((key) => {
|
||||
if (typeof transition[key] === 'function') {
|
||||
listeners[key] = transition[key]
|
||||
}
|
||||
})
|
||||
return h('transition', {
|
||||
props: transitionProps,
|
||||
on: listeners
|
||||
}, [
|
||||
h('router-view', data)
|
||||
])
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@
|
||||
<div class="error-wrapper-message">
|
||||
<h2 class="error-message">{{ error.message }}</h2>
|
||||
</div>
|
||||
<p v-if="error.statusCode === 404"><router-link class="error-link" to="/">Back to the home page</router-link></p>
|
||||
<p v-if="error.statusCode === 404"><nuxt-link class="error-link" to="/">Back to the home page</nuxt-link></p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
9
lib/app/components/nuxt-link.js
Normal file
9
lib/app/components/nuxt-link.js
Normal file
@ -0,0 +1,9 @@
|
||||
import Vue from 'vue'
|
||||
|
||||
export default {
|
||||
name: 'nuxt-link',
|
||||
functional: true,
|
||||
render (h, { data, children }) {
|
||||
return h('router-link', data, children)
|
||||
}
|
||||
}
|
@ -1,15 +1,14 @@
|
||||
<template>
|
||||
<div>
|
||||
<% if (loading) { %><nuxt-loading ref="loading"></nuxt-loading><% } %>
|
||||
<transition :name="nuxt.transition.name" :mode="nuxt.transition.mode">
|
||||
<router-view v-if="!nuxt.err"></router-view>
|
||||
<nuxt-error v-if="nuxt.err" :error="nuxt.err"></nuxt-error>
|
||||
</transition>
|
||||
<nuxt-child v-if="!nuxt.err"></nuxt-child>
|
||||
<nuxt-error v-if="nuxt.err" :error="nuxt.err"></nuxt-error>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from 'vue'
|
||||
import NuxtChild from './nuxt-child'
|
||||
import NuxtError from '<%= components.ErrorPage %>'
|
||||
<% if (loading) { %>import NuxtLoading from '<%= (typeof loading === "string" ? loading : "./nuxt-loading.vue") %>'<% } %>
|
||||
|
||||
@ -47,6 +46,7 @@ export default {
|
||||
},
|
||||
<% } %>
|
||||
components: {
|
||||
NuxtChild,
|
||||
NuxtError<%= (loading ? ',\n NuxtLoading' : '') %>
|
||||
}
|
||||
}
|
||||
|
@ -5,11 +5,17 @@ import Meta from 'vue-meta'
|
||||
import router from './router.js'
|
||||
<% if (store) { %>import store from '~store/index.js'<% } %>
|
||||
import NuxtContainer from './components/nuxt-container.vue'
|
||||
import NuxtChild from './components/nuxt-child.js'
|
||||
import NuxtLink from './components/nuxt-link.js'
|
||||
import Nuxt from './components/nuxt.vue'
|
||||
import App from '<%= appPath %>'
|
||||
|
||||
// Component: <nuxt-container>
|
||||
Vue.component(NuxtContainer.name, NuxtContainer)
|
||||
// Component: <nuxt-child>
|
||||
Vue.component(NuxtChild.name, NuxtChild)
|
||||
// Component: <nuxt-link>
|
||||
Vue.component(NuxtLink.name, NuxtLink)
|
||||
// Component: <nuxt>
|
||||
Vue.component(Nuxt.name, Nuxt)
|
||||
|
||||
@ -43,16 +49,24 @@ const app = {
|
||||
router,
|
||||
<%= (store ? 'store,' : '') %>
|
||||
_nuxt: {
|
||||
transition: Object.assign({}, defaultTransition),
|
||||
setTransition (transition) {
|
||||
if (!transition) {
|
||||
transition = defaultTransition
|
||||
} else if (typeof transition === 'string') {
|
||||
transition = Object.assign({}, defaultTransition, { name: transition })
|
||||
defaultTransition: defaultTransition,
|
||||
transitions: [ defaultTransition ],
|
||||
setTransitions (transitions) {
|
||||
if (!Array.isArray(transitions)) {
|
||||
transitions = [ transitions ]
|
||||
}
|
||||
this.$options._nuxt.transition.name = transition.name
|
||||
this.$options._nuxt.transition.mode = transition.mode
|
||||
return transition
|
||||
transitions = transitions.map((transition) => {
|
||||
if (!transition) {
|
||||
transition = defaultTransition
|
||||
} else if (typeof transition === 'string') {
|
||||
transition = Object.assign({}, defaultTransition, { name: transition })
|
||||
} else {
|
||||
transition = Object.assign({}, defaultTransition, transition)
|
||||
}
|
||||
return transition
|
||||
})
|
||||
this.$options._nuxt.transitions = transitions
|
||||
return transitions
|
||||
},
|
||||
err: null,
|
||||
error (err) {
|
||||
|
@ -5,8 +5,24 @@ import Router from 'vue-router'
|
||||
|
||||
Vue.use(Router)
|
||||
|
||||
<% uniqBy(router.routes, '_name').forEach((route) => { %>
|
||||
const <%= route._name %> = process.BROWSER_BUILD ? () => System.import('<%= route._component %>') : require('<%= route._component %>')
|
||||
<%
|
||||
function recursiveRoutes(routes, tab, components) {
|
||||
var res = ''
|
||||
routes.forEach((route, i) => {
|
||||
components.push({ _name: route._name, component: route.component })
|
||||
res += tab + '{\n'
|
||||
res += tab + '\tpath: ' + JSON.stringify(route.path) + ',\n'
|
||||
res += tab + '\tcomponent: ' + route._name
|
||||
res += (route.name) ? ',\n\t' + tab + 'name: ' + JSON.stringify(route.name) : ''
|
||||
res += (route.children) ? ',\n\t' + tab + 'children: [\n' + recursiveRoutes(routes[i].children, tab + '\t\t', components) + '\n\t' + tab + ']' : ''
|
||||
res += '\n' + tab + '}' + (i + 1 === routes.length ? '' : ',\n')
|
||||
})
|
||||
return res
|
||||
}
|
||||
var _components = []
|
||||
var _routes = recursiveRoutes(router.routes, '\t\t', _components)
|
||||
uniqBy(_components, '_name').forEach((route) => { %>
|
||||
const <%= route._name %> = process.BROWSER_BUILD ? () => System.import('<%= route.component %>') : require('<%= route.component %>')
|
||||
<% }) %>
|
||||
|
||||
const scrollBehavior = (to, from, savedPosition) => {
|
||||
@ -14,8 +30,11 @@ const scrollBehavior = (to, from, savedPosition) => {
|
||||
// savedPosition is only available for popstate navigations.
|
||||
return savedPosition
|
||||
} else {
|
||||
// Scroll to the top by default
|
||||
let position = { x: 0, y: 0 }
|
||||
let position = {}
|
||||
// if no children detected
|
||||
if (to.matched.length < 2) {
|
||||
position = { x: 0, y: 0 }
|
||||
}
|
||||
// if link has anchor, scroll to anchor by returning the selector
|
||||
if (to.hash) {
|
||||
position = { selector: to.hash }
|
||||
@ -30,13 +49,6 @@ export default new Router({
|
||||
linkActiveClass: '<%= router.linkActiveClass %>',
|
||||
scrollBehavior,
|
||||
routes: [
|
||||
<% router.routes.forEach((route, i) => { %>
|
||||
{
|
||||
path: '<%= route.path %>',
|
||||
component: <%= route._name %><% if (route.name) { %>,
|
||||
name: '<%= route.name %>'<% } %><% if (route.meta) { %>,
|
||||
meta: <%= JSON.stringify(route.meta) %><% } %>
|
||||
}<%= (i + 1 === router.routes.length ? '' : ',') %>
|
||||
<% }) %>
|
||||
<%= _routes %>
|
||||
]
|
||||
})
|
||||
|
@ -1,6 +1,7 @@
|
||||
'use strict'
|
||||
|
||||
const debug = require('debug')('nuxt:render')
|
||||
debug.color = 4 // force blue color
|
||||
import Vue from 'vue'
|
||||
import { stringify } from 'querystring'
|
||||
import { omit } from 'lodash'
|
||||
@ -60,8 +61,8 @@ export default context => {
|
||||
<% } %>
|
||||
return promise
|
||||
.then(() => {
|
||||
// Call data & fetch hooks on components matched by the route.
|
||||
return Promise.all(Components.map((Component) => {
|
||||
// Sanitize Components
|
||||
Components = Components.map((Component) => {
|
||||
let promises = []
|
||||
if (!Component.options) {
|
||||
Component = Vue.extend(Component)
|
||||
@ -70,6 +71,24 @@ export default context => {
|
||||
Component._Ctor = Component
|
||||
Component.extendOptions = Component.options
|
||||
}
|
||||
return Component
|
||||
})
|
||||
// Call .validate()
|
||||
let isValid = Components.some((Component) => {
|
||||
if (typeof Component.options.validate !== 'function') return true
|
||||
return Component.options.validate({
|
||||
params: context.route.params || {},
|
||||
query: context.route.query || {}
|
||||
})
|
||||
})
|
||||
if (!isValid) {
|
||||
// Call the 404 error by making the Components array empty
|
||||
Components = []
|
||||
return _app
|
||||
}
|
||||
// Call data & fetch hooks on components matched by the route.
|
||||
return Promise.all(Components.map((Component) => {
|
||||
let promises = []
|
||||
const ctx = getContext(context)
|
||||
if (Component.options.data && typeof Component.options.data === 'function') {
|
||||
Component._data = Component.options.data
|
||||
|
232
lib/app/utils.js
232
lib/app/utils.js
@ -91,3 +91,235 @@ export function getLocation (base) {
|
||||
export function urlJoin () {
|
||||
return [].slice.call(arguments).join('/').replace(/\/+/g, '/')
|
||||
}
|
||||
|
||||
// Imported from path-to-regexp
|
||||
|
||||
/**
|
||||
* Compile a string to a template function for the path.
|
||||
*
|
||||
* @param {string} str
|
||||
* @param {Object=} options
|
||||
* @return {!function(Object=, Object=)}
|
||||
*/
|
||||
export function compile (str, options) {
|
||||
return tokensToFunction(parse(str, options))
|
||||
}
|
||||
|
||||
/**
|
||||
* The main path matching regexp utility.
|
||||
*
|
||||
* @type {RegExp}
|
||||
*/
|
||||
const PATH_REGEXP = new RegExp([
|
||||
// Match escaped characters that would otherwise appear in future matches.
|
||||
// This allows the user to escape special characters that won't transform.
|
||||
'(\\\\.)',
|
||||
// Match Express-style parameters and un-named parameters with a prefix
|
||||
// and optional suffixes. Matches appear as:
|
||||
//
|
||||
// "/:test(\\d+)?" => ["/", "test", "\d+", undefined, "?", undefined]
|
||||
// "/route(\\d+)" => [undefined, undefined, undefined, "\d+", undefined, undefined]
|
||||
// "/*" => ["/", undefined, undefined, undefined, undefined, "*"]
|
||||
'([\\/.])?(?:(?:\\:(\\w+)(?:\\(((?:\\\\.|[^\\\\()])+)\\))?|\\(((?:\\\\.|[^\\\\()])+)\\))([+*?])?|(\\*))'
|
||||
].join('|'), 'g')
|
||||
|
||||
/**
|
||||
* Parse a string for the raw tokens.
|
||||
*
|
||||
* @param {string} str
|
||||
* @param {Object=} options
|
||||
* @return {!Array}
|
||||
*/
|
||||
function parse (str, options) {
|
||||
var tokens = []
|
||||
var key = 0
|
||||
var index = 0
|
||||
var path = ''
|
||||
var defaultDelimiter = options && options.delimiter || '/'
|
||||
var res
|
||||
|
||||
while ((res = PATH_REGEXP.exec(str)) != null) {
|
||||
var m = res[0]
|
||||
var escaped = res[1]
|
||||
var offset = res.index
|
||||
path += str.slice(index, offset)
|
||||
index = offset + m.length
|
||||
|
||||
// Ignore already escaped sequences.
|
||||
if (escaped) {
|
||||
path += escaped[1]
|
||||
continue
|
||||
}
|
||||
|
||||
var next = str[index]
|
||||
var prefix = res[2]
|
||||
var name = res[3]
|
||||
var capture = res[4]
|
||||
var group = res[5]
|
||||
var modifier = res[6]
|
||||
var asterisk = res[7]
|
||||
|
||||
// Push the current path onto the tokens.
|
||||
if (path) {
|
||||
tokens.push(path)
|
||||
path = ''
|
||||
}
|
||||
|
||||
var partial = prefix != null && next != null && next !== prefix
|
||||
var repeat = modifier === '+' || modifier === '*'
|
||||
var optional = modifier === '?' || modifier === '*'
|
||||
var delimiter = res[2] || defaultDelimiter
|
||||
var pattern = capture || group
|
||||
|
||||
tokens.push({
|
||||
name: name || key++,
|
||||
prefix: prefix || '',
|
||||
delimiter: delimiter,
|
||||
optional: optional,
|
||||
repeat: repeat,
|
||||
partial: partial,
|
||||
asterisk: !!asterisk,
|
||||
pattern: pattern ? escapeGroup(pattern) : (asterisk ? '.*' : '[^' + escapeString(delimiter) + ']+?')
|
||||
})
|
||||
}
|
||||
|
||||
// Match any characters still remaining.
|
||||
if (index < str.length) {
|
||||
path += str.substr(index)
|
||||
}
|
||||
|
||||
// If the path exists, push it onto the end.
|
||||
if (path) {
|
||||
tokens.push(path)
|
||||
}
|
||||
|
||||
return tokens
|
||||
}
|
||||
|
||||
/**
|
||||
* Prettier encoding of URI path segments.
|
||||
*
|
||||
* @param {string}
|
||||
* @return {string}
|
||||
*/
|
||||
function encodeURIComponentPretty (str) {
|
||||
return encodeURI(str).replace(/[\/?#]/g, function (c) {
|
||||
return '%' + c.charCodeAt(0).toString(16).toUpperCase()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the asterisk parameter. Similar to `pretty`, but allows slashes.
|
||||
*
|
||||
* @param {string}
|
||||
* @return {string}
|
||||
*/
|
||||
function encodeAsterisk (str) {
|
||||
return encodeURI(str).replace(/[?#]/g, function (c) {
|
||||
return '%' + c.charCodeAt(0).toString(16).toUpperCase()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Expose a method for transforming tokens into the path function.
|
||||
*/
|
||||
function tokensToFunction (tokens) {
|
||||
// Compile all the tokens into regexps.
|
||||
var matches = new Array(tokens.length)
|
||||
|
||||
// Compile all the patterns before compilation.
|
||||
for (var i = 0; i < tokens.length; i++) {
|
||||
if (typeof tokens[i] === 'object') {
|
||||
matches[i] = new RegExp('^(?:' + tokens[i].pattern + ')$')
|
||||
}
|
||||
}
|
||||
|
||||
return function (obj, opts) {
|
||||
var path = ''
|
||||
var data = obj || {}
|
||||
var options = opts || {}
|
||||
var encode = options.pretty ? encodeURIComponentPretty : encodeURIComponent
|
||||
|
||||
for (var i = 0; i < tokens.length; i++) {
|
||||
var token = tokens[i]
|
||||
|
||||
if (typeof token === 'string') {
|
||||
path += token
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
var value = data[token.name]
|
||||
var segment
|
||||
|
||||
if (value == null) {
|
||||
if (token.optional) {
|
||||
// Prepend partial segment prefixes.
|
||||
if (token.partial) {
|
||||
path += token.prefix
|
||||
}
|
||||
|
||||
continue
|
||||
} else {
|
||||
throw new TypeError('Expected "' + token.name + '" to be defined')
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
if (!token.repeat) {
|
||||
throw new TypeError('Expected "' + token.name + '" to not repeat, but received `' + JSON.stringify(value) + '`')
|
||||
}
|
||||
|
||||
if (value.length === 0) {
|
||||
if (token.optional) {
|
||||
continue
|
||||
} else {
|
||||
throw new TypeError('Expected "' + token.name + '" to not be empty')
|
||||
}
|
||||
}
|
||||
|
||||
for (var j = 0; j < value.length; j++) {
|
||||
segment = encode(value[j])
|
||||
|
||||
if (!matches[i].test(segment)) {
|
||||
throw new TypeError('Expected all "' + token.name + '" to match "' + token.pattern + '", but received `' + JSON.stringify(segment) + '`')
|
||||
}
|
||||
|
||||
path += (j === 0 ? token.prefix : token.delimiter) + segment
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
segment = token.asterisk ? encodeAsterisk(value) : encode(value)
|
||||
|
||||
if (!matches[i].test(segment)) {
|
||||
throw new TypeError('Expected "' + token.name + '" to match "' + token.pattern + '", but received "' + segment + '"')
|
||||
}
|
||||
|
||||
path += token.prefix + segment
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape a regular expression string.
|
||||
*
|
||||
* @param {string} str
|
||||
* @return {string}
|
||||
*/
|
||||
function escapeString (str) {
|
||||
return str.replace(/([.+*?=^!:${}()[\]|\/\\])/g, '\\$1')
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape the capturing group by escaping special characters and meaning.
|
||||
*
|
||||
* @param {string} group
|
||||
* @return {string}
|
||||
*/
|
||||
function escapeGroup (group) {
|
||||
return group.replace(/([=!:$\/()])/g, '\\$1')
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
'use strict'
|
||||
|
||||
const debug = require('debug')('nuxt:build')
|
||||
debug.color = 2 // force green color
|
||||
const _ = require('lodash')
|
||||
const co = require('co')
|
||||
const chokidar = require('chokidar')
|
||||
@ -9,10 +10,9 @@ const hash = require('hash-sum')
|
||||
const pify = require('pify')
|
||||
const webpack = require('webpack')
|
||||
const { createBundleRenderer } = require('vue-server-renderer')
|
||||
const { join, resolve, sep, posix } = require('path')
|
||||
const { join, resolve, sep } = require('path')
|
||||
const clientWebpackConfig = require('./webpack/client.config.js')
|
||||
const serverWebpackConfig = require('./webpack/server.config.js')
|
||||
const basename = posix.basename
|
||||
const remove = pify(fs.remove)
|
||||
const readFile = pify(fs.readFile)
|
||||
const writeFile = pify(fs.writeFile)
|
||||
@ -111,13 +111,6 @@ exports.build = function * () {
|
||||
if (!this.dev) {
|
||||
yield mkdirp(r(this.dir, '.nuxt/dist'))
|
||||
}
|
||||
// Resolve custom routes component path
|
||||
this.options.router.routes.forEach((route) => {
|
||||
if (route.component.slice(-4) !== '.vue') {
|
||||
route.component = route.component + '.vue'
|
||||
}
|
||||
route.component = r(this.srcDir, route.component)
|
||||
})
|
||||
// Generate routes and interpret the template files
|
||||
yield generateRoutesAndFiles.call(this)
|
||||
/*
|
||||
@ -144,15 +137,9 @@ function * generateRoutesAndFiles () {
|
||||
** Generate routes based on files
|
||||
*/
|
||||
const files = yield glob('pages/**/*.vue', { cwd: this.srcDir })
|
||||
let routes = []
|
||||
files.forEach((file) => {
|
||||
let path = file.replace(/^pages/, '').replace(/index\.vue$/, '/').replace(/\.vue$/, '').replace(/\/{2,}/g, '/')
|
||||
let name = file.replace(/^pages/, '').replace(/\.vue$/, '').replace(/\/{2,}/g, '/').split('/').slice(1).join('-')
|
||||
if (basename(path)[0] === '_') return
|
||||
routes.push({ path: path, component: r(this.srcDir, file), name: name })
|
||||
})
|
||||
// Concat pages routes and custom routes in this.routes
|
||||
this.routes = routes.concat(this.options.router.routes)
|
||||
this.routes = _.uniq(_.map(files, (file) => {
|
||||
return file.replace(/^pages/, '').replace(/\.vue$/, '').replace(/\/index/g, '').replace(/_/g, ':').replace('', '/').replace(/\/{2,}/g, '/')
|
||||
}))
|
||||
/*
|
||||
** Interpret and move template files to .nuxt/
|
||||
*/
|
||||
@ -165,8 +152,10 @@ function * generateRoutesAndFiles () {
|
||||
'server.js',
|
||||
'utils.js',
|
||||
'components/nuxt-container.vue',
|
||||
'components/nuxt.vue',
|
||||
'components/nuxt-loading.vue'
|
||||
'components/nuxt-loading.vue',
|
||||
'components/nuxt-child.js',
|
||||
'components/nuxt-link.js',
|
||||
'components/nuxt.vue'
|
||||
]
|
||||
let templateVars = {
|
||||
uniqBy: _.uniqBy,
|
||||
@ -192,23 +181,10 @@ function * generateRoutesAndFiles () {
|
||||
templateVars.loading = templateVars.loading + '.vue'
|
||||
}
|
||||
// Format routes for the lib/app/router.js template
|
||||
templateVars.router.routes = this.routes.map((route) => {
|
||||
const r = Object.assign({}, route)
|
||||
r._component = r.component
|
||||
r._name = '_' + hash(r._component)
|
||||
r.component = r._name
|
||||
r.path = r.path.replace(/\\/g, '\\\\') // regex expression in route path escaping for lodash templating
|
||||
return r
|
||||
})
|
||||
if (files.includes('pages/_app.vue')) {
|
||||
templateVars.appPath = r(this.srcDir, 'pages/_app.vue')
|
||||
}
|
||||
templateVars.router.routes = createRoutes(files, this.srcDir)
|
||||
if (fs.existsSync(join(this.srcDir, 'layouts', 'app.vue'))) {
|
||||
templateVars.appPath = r(this.srcDir, 'layouts/app.vue')
|
||||
}
|
||||
if (files.includes('pages/_error.vue')) {
|
||||
templateVars.components.ErrorPage = r(this.srcDir, 'pages/_error.vue')
|
||||
}
|
||||
if (fs.existsSync(join(this.srcDir, 'layouts', 'error.vue'))) {
|
||||
templateVars.components.ErrorPage = r(this.srcDir, 'layouts/error.vue')
|
||||
}
|
||||
@ -223,6 +199,46 @@ function * generateRoutesAndFiles () {
|
||||
yield moveTemplates
|
||||
}
|
||||
|
||||
function createRoutes (files, srcDir) {
|
||||
let routes = []
|
||||
files.forEach((file) => {
|
||||
let keys = file.replace(/^pages/, '').replace(/\.vue$/, '').replace(/\/{2,}/g, '/').split('/').slice(1)
|
||||
let route = { name: '', path: '', component: r(srcDir, file), _name: null }
|
||||
let parent = routes
|
||||
keys.forEach((key, i) => {
|
||||
route.name = route.name ? route.name + (key === 'index' ? '' : '-' + key.replace('_', '')) : key.replace('_', '')
|
||||
let child = _.find(parent, { name: route.name })
|
||||
if (child) {
|
||||
if (!child.children) {
|
||||
child.children = []
|
||||
}
|
||||
parent = child.children
|
||||
} else {
|
||||
route.path = route.path + (key === 'index' ? (i > 0 ? '' : '/') : '/' + key.replace('_', ':'))
|
||||
}
|
||||
})
|
||||
route._name = '_' + hash(route.component)
|
||||
// Order Routes path
|
||||
if (_.last(keys)[0] === '_') {
|
||||
parent.push(route)
|
||||
} else {
|
||||
parent.unshift(route)
|
||||
}
|
||||
})
|
||||
return cleanChildrenRoutes(routes)
|
||||
}
|
||||
|
||||
function cleanChildrenRoutes (routes, isChild = false) {
|
||||
routes.forEach((route) => {
|
||||
route.path = (isChild) ? route.path.replace('/', '') : route.path
|
||||
if (route.children) {
|
||||
delete route.name
|
||||
route.children = cleanChildrenRoutes(route.children, true)
|
||||
}
|
||||
})
|
||||
return routes
|
||||
}
|
||||
|
||||
function getWebpackClientConfig () {
|
||||
return clientWebpackConfig.call(this)
|
||||
}
|
||||
|
@ -23,8 +23,11 @@ module.exports = function () {
|
||||
output: {
|
||||
publicPath: urlJoin(this.options.router.base, '/_nuxt/')
|
||||
},
|
||||
performance: {
|
||||
hints: (this.dev ? false : 'warning')
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.js', '.vue'],
|
||||
extensions: ['.js', '.json', '.vue'],
|
||||
// Disable for now
|
||||
alias: {
|
||||
'~': join(this.srcDir),
|
||||
|
@ -1,10 +1,18 @@
|
||||
'use strict'
|
||||
|
||||
const { defaults } = require('lodash')
|
||||
|
||||
module.exports = function () {
|
||||
let babelOptions = JSON.stringify(defaults(this.options.build.babel, {
|
||||
presets: [
|
||||
['es2015', { modules: false }],
|
||||
'stage-2'
|
||||
]
|
||||
}))
|
||||
let config = {
|
||||
postcss: this.options.build.postcss,
|
||||
loaders: {
|
||||
'js': 'babel-loader?presets[]=es2015&presets[]=stage-2',
|
||||
'js': 'babel-loader?' + babelOptions,
|
||||
'less': 'vue-style-loader!css-loader!less-loader',
|
||||
'sass': 'vue-style-loader!css-loader!sass-loader?indentedSyntax',
|
||||
'scss': 'vue-style-loader!css-loader!sass-loader',
|
||||
|
@ -8,6 +8,7 @@ const pathToRegexp = require('path-to-regexp')
|
||||
const _ = require('lodash')
|
||||
const { resolve, join, dirname, sep } = require('path')
|
||||
const { promisifyRouteParams } = require('./utils')
|
||||
const { minify } = require('html-minifier')
|
||||
const copy = pify(fs.copy)
|
||||
const remove = pify(fs.remove)
|
||||
const writeFile = pify(fs.writeFile)
|
||||
@ -69,15 +70,15 @@ module.exports = function () {
|
||||
*/
|
||||
let routes = []
|
||||
this.routes.forEach((route) => {
|
||||
if (route.path.includes(':') || route.path.includes('*')) {
|
||||
const routeParams = this.options.generate.routeParams[route.path]
|
||||
if (route.includes(':') || route.includes('*')) {
|
||||
const routeParams = this.options.generate.routeParams[route]
|
||||
if (!routeParams) {
|
||||
console.error(`Could not generate the dynamic route ${route.path}, please add the mapping params in nuxt.config.js (generate.routeParams).`) // eslint-disable-line no-console
|
||||
console.error(`Could not generate the dynamic route ${route}, please add the mapping params in nuxt.config.js (generate.routeParams).`) // eslint-disable-line no-console
|
||||
return process.exit(1)
|
||||
}
|
||||
const toPath = pathToRegexp.compile(route.path)
|
||||
const toPath = pathToRegexp.compile(route)
|
||||
routes = routes.concat(routeParams.map((params) => {
|
||||
return Object.assign({}, route, { path: toPath(params) })
|
||||
return toPath(params)
|
||||
}))
|
||||
} else {
|
||||
routes.push(route)
|
||||
@ -87,8 +88,28 @@ module.exports = function () {
|
||||
while (routes.length) {
|
||||
yield routes.splice(0, 500).map((route) => {
|
||||
return co(function * () {
|
||||
const { html } = yield self.renderRoute(route.path)
|
||||
var path = join(route.path, sep, 'index.html') // /about -> /about/index.html
|
||||
var { html } = yield self.renderRoute(route)
|
||||
html = minify(html, {
|
||||
collapseBooleanAttributes: true,
|
||||
collapseWhitespace: true,
|
||||
decodeEntities: true,
|
||||
minifyCSS: true,
|
||||
minifyJS: true,
|
||||
processConditionalComments: true,
|
||||
removeAttributeQuotes: true,
|
||||
removeComments: true,
|
||||
removeEmptyAttributes: true,
|
||||
removeOptionalTags: true,
|
||||
removeRedundantAttributes: true,
|
||||
removeScriptTypeAttributes: true,
|
||||
removeStyleLinkTypeAttributes: true,
|
||||
removeTagWhitespace: true,
|
||||
sortAttributes: true,
|
||||
sortClassName: true,
|
||||
trimCustomFragments: true,
|
||||
useShortDoctype: true
|
||||
})
|
||||
var path = join(route, sep, 'index.html') // /about -> /about/index.html
|
||||
debug('Generate file: ' + path)
|
||||
path = join(distPath, path)
|
||||
// Make sure the sub folders are created
|
||||
|
@ -1,5 +1,3 @@
|
||||
'use strict'
|
||||
|
||||
const _ = require('lodash')
|
||||
const co = require('co')
|
||||
const fs = require('fs-extra')
|
||||
@ -41,8 +39,7 @@ class Nuxt {
|
||||
},
|
||||
router: {
|
||||
base: '/',
|
||||
linkActiveClass: 'router-link-active',
|
||||
routes: []
|
||||
linkActiveClass: 'nuxt-link-active'
|
||||
},
|
||||
build: {}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
const debug = require('debug')('nuxt:render')
|
||||
debug.color = 4 // force blue color
|
||||
const co = require('co')
|
||||
const { urlJoin } = require('./utils')
|
||||
const { getContext } = require('./utils')
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "nuxt",
|
||||
"version": "0.8.8",
|
||||
"version": "0.9.0",
|
||||
"description": "A minimalistic framework for server-rendered Vue.js applications (inspired by Next.js)",
|
||||
"contributors": [
|
||||
{
|
||||
@ -63,6 +63,7 @@
|
||||
"fs-extra": "^1.0.0",
|
||||
"glob": "^7.1.1",
|
||||
"hash-sum": "^1.0.2",
|
||||
"html-minifier": "^3.2.3",
|
||||
"lodash": "^4.17.2",
|
||||
"lru-cache": "^4.0.2",
|
||||
"memory-fs": "^0.4.1",
|
||||
@ -78,7 +79,7 @@
|
||||
"vue-server-renderer": "^2.1.3",
|
||||
"vue-template-compiler": "^2.1.3",
|
||||
"vuex": "^2.0.0",
|
||||
"webpack": "2.1.0-beta.27",
|
||||
"webpack": "2.2.0-rc.0",
|
||||
"webpack-dev-middleware": "^1.8.4",
|
||||
"webpack-hot-middleware": "^2.13.2"
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user