Merge branch 'master' into offline-plugin-integration

This commit is contained in:
Sébastien Chopin 2017-04-08 11:33:32 +02:00 committed by GitHub
commit 2560bfb512
95 changed files with 7235 additions and 559 deletions

2
.gitignore vendored
View File

@ -1,6 +1,6 @@
# dependencies # dependencies
yarn.lock
node_modules node_modules
examples/**/*/yarn.lock
# logs # logs
*.log *.log

View File

@ -10,15 +10,17 @@
<a href="https://donorbox.org/nuxt"><img src="https://img.shields.io/badge/Support%20us-donate-41B883.svg" alt="Support us"></a> <a href="https://donorbox.org/nuxt"><img src="https://img.shields.io/badge/Support%20us-donate-41B883.svg" alt="Support us"></a>
</p> </p>
> Nuxt.js is a framework for server-rendered Vue applications (inspired by [Next.js](https://github.com/zeit/next.js)) > Nuxt.js is a framework for server-rendered Vue applications (inspired by [Next.js](https://github.com/zeit/next.js))
## 🚧 Under active development, 1.0 will be released soon :fire: ## 🚧 Under active development, [1.0](https://github.com/nuxt/nuxt.js/projects/1) will be released soon :fire:
## Links ## Links
- 📘 Documentation: [https://nuxtjs.org](https://nuxtjs.org) - 📘 Documentation: [https://nuxtjs.org](https://nuxtjs.org)
- 🎬 Video: [1 minute demo](https://www.youtube.com/watch?v=kmf-p-pTi40) - 🎬 Video: [1 minute demo](https://www.youtube.com/watch?v=kmf-p-pTi40)
- 🐦 Twitter: [@nuxt_js](https://twitter.com/nuxt_js) - 🐦 Twitter: [@nuxt_js](https://twitter.com/nuxt_js)
- 👉 [Play with Nuxt.js online](https://glitch.com/edit/#!/nuxt-hello-world)
## Getting started ## Getting started
@ -119,7 +121,7 @@ This is mostly used for `nuxt generate` and test purposes but you might find ano
nuxt.renderRoute('/about', context) nuxt.renderRoute('/about', context)
.then(function ({ html, error }) { .then(function ({ html, error }) {
// You can check error to know if your app displayed the error page for this route // You can check error to know if your app displayed the error page for this route
// Useful to set the correct status status code if an error appended: // Useful to set the correct status code if an error appended:
if (error) { if (error) {
return res.status(error.statusCode || 500).send(html) return res.status(error.statusCode || 500).send(html)
} }

View File

@ -15,13 +15,32 @@ if (process.argv.indexOf('--analyze') !== -1 || process.argv.indexOf('-a') !== -
process.argv = without(process.argv, '--analyze', '-a') process.argv = without(process.argv, '--analyze', '-a')
} }
var nuxtConfigFileName = 'nuxt.config.js'
// --config-file option
var indexOfConfig = false
if (process.argv.indexOf('--config-file') !== -1) {
indexOfConfig = process.argv.indexOf('--config-file')
} else if (process.argv.indexOf('-c') !== -1) {
indexOfConfig = process.argv.indexOf('-c')
}
if (indexOfConfig !== false) {
nuxtConfigFileName = process.argv.slice(indexOfConfig)[1]
process.argv = without(process.argv, '--config-file', '-c', nuxtConfigFileName)
}
// Root directory parameter
var rootDir = resolve(process.argv.slice(2)[0] || '.') var rootDir = resolve(process.argv.slice(2)[0] || '.')
var nuxtConfigFile = resolve(rootDir, 'nuxt.config.js') var nuxtConfigFilePath = resolve(rootDir, nuxtConfigFileName)
var options = {} var options = {}
if (fs.existsSync(nuxtConfigFile)) { if (fs.existsSync(nuxtConfigFilePath)) {
options = require(nuxtConfigFile) options = require(nuxtConfigFilePath)
} else {
console.log(`Could not locate ${nuxtConfigFilePath}`) // eslint-disable-line no-console
} }
if (typeof options.rootDir !== 'string') { if (typeof options.rootDir !== 'string') {
options.rootDir = rootDir options.rootDir = rootDir
} }

View File

@ -63,6 +63,6 @@ function listenOnConfigChanges (nuxt, server) {
}) })
}, 200) }, 200)
var nuxtConfigFile = resolve(rootDir, 'nuxt.config.js') var nuxtConfigFile = resolve(rootDir, 'nuxt.config.js')
chokidar.watch(nuxtConfigFile, { ignoreInitial: true }) chokidar.watch(nuxtConfigFile, Object.assign({}, nuxt.options.watchers.chokidar, { ignoreInitial: true }))
.on('all', build) .on('all', build)
} }

View File

@ -9,7 +9,7 @@
<script> <script>
export default { export default {
data ({ req }, callback) { asyncData ({ req }, callback) {
setTimeout(function () { setTimeout(function () {
// callback(err, data) // callback(err, data)
callback(null, { callback(null, {

View File

@ -11,7 +11,7 @@
import axios from 'axios' import axios from 'axios'
export default { export default {
async data ({ params }) { async asyncData ({ params }) {
// We can use async/await ES6 feature // We can use async/await ES6 feature
let { data } = await axios.get(`https://jsonplaceholder.typicode.com/posts/${params.id}`) let { data } = await axios.get(`https://jsonplaceholder.typicode.com/posts/${params.id}`)
return { post: data } return { post: data }

View File

@ -15,7 +15,7 @@
import axios from 'axios' import axios from 'axios'
export default { export default {
data ({ req, params }) { asyncData ({ req, params }) {
// We can return a Promise instead of calling the callback // We can return a Promise instead of calling the callback
return axios.get('https://jsonplaceholder.typicode.com/posts') return axios.get('https://jsonplaceholder.typicode.com/posts')
.then((res) => { .then((res) => {

View File

@ -8,7 +8,7 @@
<script> <script>
export default { export default {
layout: 'dark', layout: 'dark',
data ({ req }) { asyncData ({ req }) {
return { return {
name: req ? 'server' : 'client' name: req ? 'server' : 'client'
} }

View File

@ -7,7 +7,7 @@
<script> <script>
export default { export default {
data () { asyncData () {
return new Promise((resolve) => { return new Promise((resolve) => {
setTimeout(function () { setTimeout(function () {
resolve({}) resolve({})

View File

@ -7,7 +7,7 @@
<script> <script>
export default { export default {
data () { asyncData () {
return new Promise((resolve) => { return new Promise((resolve) => {
setTimeout(function () { setTimeout(function () {
resolve({ name: 'world' }) resolve({ name: 'world' })

View File

@ -13,7 +13,7 @@
import axios from 'axios' import axios from 'axios'
export default { export default {
data () { asyncData () {
return axios.get('https://jsonplaceholder.typicode.com/users') return axios.get('https://jsonplaceholder.typicode.com/users')
.then((res) => { .then((res) => {
return { users: res.data } return { users: res.data }

View File

@ -14,7 +14,7 @@ export default {
validate ({ params }) { validate ({ params }) {
return !isNaN(+params.id) return !isNaN(+params.id)
}, },
data ({ params, error }) { asyncData ({ 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) .then((res) => res.data)
.catch(() => { .catch(() => {

View File

@ -0,0 +1,3 @@
# Dynamic Layouts
https://nuxtjs.org/examples/layouts

View File

@ -0,0 +1,25 @@
<template>
<div class="container">
<h1 v-if="error.statusCode === 404">Page not found</h1>
<h1 v-else>An error occured</h1>
<nuxt-link to="/">Home page</nuxt-link>
</div>
</template>
<script>
export default {
layout: ({ isMobile }) => isMobile ? 'mobile' : 'default',
props: ['error']
}
</script>
<style scoped>
.container {
font-family: sans-serif;
padding-top: 10%;
text-align: center;
}
h1 {
font-size: 20px;
}
</style>

View File

@ -0,0 +1,37 @@
<template>
<div class="dark">
<div class="mobile-banner">Mobile website</div>
<nuxt/>
</div>
</template>
<style>
* {
box-sizing: border-box;
}
.dark {
position: absolute;
top: 0;
left: 0;
width: 100%;
min-height: 100%;
background: black;
color: white;
padding: 10px;
padding-top: 40px;
}
.dark a {
color: white;
}
.mobile-banner {
position: absolute;
top: 0;
left: 0;
width: 100%;
text-align: center;
padding: 10px;
background: #333;
font-family: sans-serif;
font-size: 13px;
}
</style>

View File

@ -0,0 +1,4 @@
export default function (ctx) {
let userAgent = ctx.req ? ctx.req.headers['user-agent'] : navigator.userAgent
ctx.isMobile = /mobile/i.test(userAgent)
}

View File

@ -0,0 +1,10 @@
module.exports = {
head: {
meta: [
{ content: 'width=device-width,initial-scale=1', name: 'viewport' }
]
},
router: {
middleware: ['mobile']
}
}

View File

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

View File

@ -0,0 +1,17 @@
<template>
<div>
<p>Hi from {{ name }}</p>
<nuxt-link to="/">Home page</nuxt-link>
</div>
</template>
<script>
export default {
layout: ({ isMobile }) => isMobile ? 'mobile' : 'default',
asyncData ({ req }) {
return {
name: req ? 'server' : 'client'
}
}
}
</script>

View File

@ -0,0 +1,12 @@
<template>
<div class="container">
<h1>Welcome!</h1>
<nuxt-link to="/about">About page</nuxt-link>
</div>
</template>
<script>
export default {
layout: ({ isMobile }) => isMobile ? 'mobile' : 'default',
}
</script>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -1,11 +1,11 @@
{ {
"name": "nuxt-global-css", "name": "nuxt-global-css",
"dependencies": { "dependencies": {
"bulma": "^0.2.3", "bulma": "^0.4.0",
"hover.css": "^2.0.2", "hover.css": "^2.2.0",
"node-sass": "^3.11.2", "node-sass": "^4.5.1",
"nuxt": "latest", "nuxt": "^0.10.0",
"sass-loader": "^4.0.2" "sass-loader": "^6.0.3"
}, },
"scripts": { "scripts": {
"dev": "nuxt", "dev": "nuxt",

View File

@ -20,6 +20,3 @@ export default {
} }
} }
</script> </script>
<style lang="css">
</style>

View File

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

View File

@ -0,0 +1,15 @@
<script>
export default {
asyncData ({ req }) {
return {
name: req ? 'server' : 'client'
}
},
render (h) {
return <div>
<p>Hi from {this.name}</p>
<nuxt-link to="/">Home page</nuxt-link>
</div>
}
}
</script>

View File

@ -0,0 +1,10 @@
<script>
export default {
render (h) {
return <div>
<h1>Welcome!</h1>
<nuxt-link to="/about">About page</nuxt-link>
</div>
}
}
</script>

View File

@ -7,7 +7,7 @@
<script> <script>
export default { export default {
data ({ req }) { asyncData ({ req }) {
return { return {
name: req ? 'server' : 'client' name: req ? 'server' : 'client'
} }

View File

@ -11,7 +11,7 @@
<script> <script>
export default { export default {
data ({ store, route, userAgent }) { asyncData ({ store, route, userAgent }) {
return { return {
userAgent, userAgent,
slugs: [ slugs: [

View File

@ -16,7 +16,7 @@
<script> <script>
export default { export default {
data ({ env }) { asyncData ({ env }) {
return { users: env.users } return { users: env.users }
} }
} }

View File

@ -10,7 +10,7 @@ export default {
validate ({ params }) { validate ({ params }) {
return !isNaN(+params.id) return !isNaN(+params.id)
}, },
data ({ params, env, error }) { asyncData ({ params, env, error }) {
const user = env.users.find((user) => String(user.id) === params.id) const user = env.users.find((user) => String(user.id) === params.id)
if (!user) { if (!user) {
return error({ message: 'User not found', statusCode: 404 }) return error({ message: 'User not found', statusCode: 404 })

View File

@ -3,6 +3,7 @@ module.exports = {
vendor: ['axios', 'mini-toastr', 'vue-notifications'] vendor: ['axios', 'mini-toastr', 'vue-notifications']
}, },
plugins: [ plugins: [
'~plugins/vue-notifications.js' // ssr: false to only include it on client-side
{ src: '~plugins/vue-notifications.js', ssr: false }
] ]
} }

View File

@ -9,7 +9,7 @@
import axios from 'axios' import axios from 'axios'
export default { export default {
data () { asyncData () {
return axios.get('https://jsonplaceholder.typicode.com/photos/4').then(res => res.data) return axios.get('https://jsonplaceholder.typicode.com/photos/4').then(res => res.data)
} }
} }

View File

@ -7,7 +7,7 @@
<script> <script>
let miniToastr let miniToastr
if (process.BROWSER_BUILD) { if (process.browser) {
miniToastr = require('mini-toastr') miniToastr = require('mini-toastr')
} }

View File

@ -1,26 +1,23 @@
// This code will be injected before initializing the root App // This code will be injected before initializing the root App
import Vue from 'vue' import Vue from 'vue'
import VueNotifications from 'vue-notifications' import VueNotifications from 'vue-notifications'
// Include mini-toaster (or any other UI-notification library
import miniToastr from 'mini-toastr'
if (process.BROWSER_BUILD) { // Here we setup messages output to `mini-toastr`
// Include mini-toaster (or any other UI-notification library const toast = function ({ title, message, type, timeout, cb }) {
const miniToastr = require('mini-toastr') return miniToastr[type](message, title, timeout, cb)
// Here we setup messages output to `mini-toastr`
const toast = function ({ title, message, type, timeout, cb }) {
return miniToastr[type](message, title, timeout, cb)
}
// Binding for methods .success(), .error() and etc. You can specify and map your own methods here.
// Required to pipe our outout to UI library (mini-toastr in example here)
// All not-specified events (types) would be piped to output in console.
const options = {
success: toast,
error: toast,
info: toast,
warn: toast
}
// Activate plugin
Vue.use(VueNotifications, options)
} }
// Binding for methods .success(), .error() and etc. You can specify and map your own methods here.
// Required to pipe our outout to UI library (mini-toastr in example here)
// All not-specified events (types) would be piped to output in console.
const options = {
success: toast,
error: toast,
info: toast,
warn: toast
}
// Activate plugin
Vue.use(VueNotifications, options)

View File

@ -23,7 +23,7 @@ export default {
if (!from) return 'slide-left' if (!from) return 'slide-left'
return +to.query.page < +from.query.page ? 'slide-right' : 'slide-left' return +to.query.page < +from.query.page ? 'slide-right' : 'slide-left'
}, },
data ({ query }) { asyncData ({ query }) {
const page = +query.page || 1 const page = +query.page || 1
return axios.get('https://reqres.in/api/users?page=' + page) return axios.get('https://reqres.in/api/users?page=' + page)
.then((res) => { .then((res) => {

View File

@ -9,7 +9,7 @@
<script> <script>
export default { export default {
data ({ req }) { asyncData ({ req }) {
return { return {
name: req ? 'server' : 'client' name: req ? 'server' : 'client'
} }

View File

@ -1,11 +1,18 @@
<template> <template>
<div> <div>
<p> <p>
<h3>Index Module</h3>
<button @click="increment">{{ counter }}</button> <button @click="increment">{{ counter }}</button>
<br> <br>
<nuxt-link to="/about">About</nuxt-link> <nuxt-link to="/about">About</nuxt-link>
<br> <br>
<br>
<h3>Todo Module</h3>
<nuxt-link to="/todos">Todos</nuxt-link> <nuxt-link to="/todos">Todos</nuxt-link>
<br>
<br>
<h3>Nested Modules</h3>
<nuxt-link to="/website">Website</nuxt-link>
</p> </p>
</div> </div>
</template> </template>

View File

@ -0,0 +1,33 @@
<template>
<div>
<h2>Articles</h2>
<ul>
<li v-for="article in articles">
<span>{{article}}</span>
</li>
</ul>
<h2>Comments <small>(nested under articles)</small></h2>
<ul>
<li v-for="comment in comments">
<span>{{comment}}</span>
</li>
</ul>
<nuxt-link to="/">Home</nuxt-link>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
computed: mapGetters({
articles: 'articles/get',
comments: 'articles/comments/get'
}),
methods: {
}
}
</script>
<style>
</style>

View File

@ -0,0 +1,19 @@
export const state = {
list: [
'Lorem ipsum',
'dolor sit amet',
'consetetur sadipscing elitr'
]
}
export const mutations = {
add (state, title) {
state.list.push(title)
}
}
export const getters = {
get (state) {
return state.list
}
}

View File

@ -0,0 +1,19 @@
export const state = {
list: [
'Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.',
'Lorem ipsum dolor sit amet, consetetur sadipscing elit.',
'Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.'
]
}
export const mutations = {
add (state, title) {
state.list.push(title)
}
}
export const getters = {
get (state) {
return state.list
}
}

View File

@ -7,7 +7,7 @@
<script> <script>
export default { export default {
data ({ req, isServer }) { asyncData ({ req, isServer }) {
return { return {
name: req ? 'server' : 'client' name: req ? 'server' : 'client'
} }

View File

@ -17,7 +17,7 @@
import socket from '~plugins/socket.io.js' import socket from '~plugins/socket.io.js'
export default { export default {
data (context, callback) { asyncData (context, callback) {
socket.emit('last-messages', function (messages) { socket.emit('last-messages', function (messages) {
callback(null, { callback(null, {
messages, messages,

View File

@ -0,0 +1,4 @@
# Using Vuetify.js with Nuxt.js
https://nuxtjs.org/examples/with-vuetify<br>
https://vuetifyjs.com/

View File

@ -0,0 +1 @@
@require './vendor/vuetify.styl'

View File

@ -0,0 +1,14 @@
// Specify overrides (theme and/or base variables etc.)
// See https://vuetifyjs.com/quick-start
$theme := {
primary: #9c27b0
accent: #ce93d8
secondary: #424242
info: #0D47A1
warning: #ffb300
error: #B71C1C
success: #2E7D32
}
// Import Vuetify styling
@require '~vuetify/src/stylus/main.styl'

View File

@ -0,0 +1,18 @@
const { join } = require('path')
module.exports = {
build: {
vendor: ['vuetify']
},
plugins: ['~plugins/vuetify.js'],
css: [
{ src: join(__dirname, 'css/app.styl'), lang: 'styl' }
],
head: {
link: [
{ rel: 'stylesheet', href: 'https://fonts.googleapis.com/css?family=Roboto' },
{ rel: 'stylesheet', href: 'https://fonts.googleapis.com/icon?family=Material+Icons' }
]
}
}

View File

@ -0,0 +1,16 @@
{
"name": "with-vuetify",
"dependencies": {
"nuxt": "0.10",
"vuetify": "0.9.4"
},
"scripts": {
"dev": "nuxt",
"build": "nuxt build",
"start": "nuxt start"
},
"devDependencies": {
"stylus": "^0.54.5",
"stylus-loader": "^3.0.1"
}
}

View File

@ -0,0 +1,45 @@
<template>
<v-app top-toolbar left-fixed-sidebar>
<v-toolbar>
<v-toolbar-side-icon @click.native.stop="sidebar = !sidebar" />
<v-toolbar-logo>Toolbar</v-toolbar-logo>
</v-toolbar>
<main>
<v-sidebar left fixed drawer v-model="sidebar">
<v-list>
<v-list-item v-for="i in 3" :key="i">
<v-list-tile>
<v-list-tile-title>Item {{ i }}</v-list-tile-title>
</v-list-tile>
</v-list-item>
</v-list>
</v-sidebar>
<v-content>
<v-container fluid>
<div class="title">
<h2>Main content</h2>
<v-btn primary>Primary button</v-btn>
<v-btn secondary>Secondary button</v-btn>
<v-btn success>Success button</v-btn>
</div>
</v-container>
</v-content>
</main>
</v-app>
</template>
<script>
export default {
asyncData() {
return {
sidebar: false
}
}
}
</script>
<style scoped>
.title {
padding-left: 20px;
}
</style>

View File

@ -0,0 +1,4 @@
import Vue from 'vue'
import Vuetify from 'vuetify'
Vue.use(Vuetify)

View File

@ -4,6 +4,9 @@
** @Atinux & @alexchopin ** @Atinux & @alexchopin
*/ */
// Until babel-loader 7 is released
process.noDeprecation = true
var Nuxt = require('./dist/nuxt.js') var Nuxt = require('./dist/nuxt.js')
module.exports = Nuxt.default ? Nuxt.default : Nuxt module.exports = Nuxt.default ? Nuxt.default : Nuxt

View File

@ -12,7 +12,7 @@ let layouts = {
<% <%
var layoutsKeys = Object.keys(layouts); var layoutsKeys = Object.keys(layouts);
layoutsKeys.forEach(function (key, i) { %> layoutsKeys.forEach(function (key, i) { %>
"_<%= key %>": process.BROWSER_BUILD ? () => System.import('<%= layouts[key] %>') : require('<%= layouts[key] %>')<%= (i + 1) < layoutsKeys.length ? ',' : '' %> "_<%= key %>": () => import('<%= layouts[key] %>')<%= (i + 1) < layoutsKeys.length ? ',' : '' %>
<% }) %> <% }) %>
} }
@ -33,18 +33,19 @@ export default {
if (!layout || !layouts['_' + layout]) layout = 'default' if (!layout || !layouts['_' + layout]) layout = 'default'
this.layoutName = layout this.layoutName = layout
let _layout = '_' + layout let _layout = '_' + layout
if (typeof layouts[_layout] === 'function') {
return this.loadLayout(_layout)
}
this.layout = layouts[_layout] this.layout = layouts[_layout]
return Promise.resolve(this.layout) return this.layout
}, },
loadLayout (_layout) { loadLayout (layout) {
if (!layout || !layouts['_' + layout]) layout = 'default'
let _layout = '_' + layout
if (typeof layouts[_layout] !== 'function') {
return Promise.resolve(layouts[_layout])
}
return layouts[_layout]() return layouts[_layout]()
.then((Component) => { .then((Component) => {
layouts[_layout] = Component layouts[_layout] = Component
this.layout = layouts[_layout] return layouts[_layout]
return this.layout
}) })
.catch((e) => { .catch((e) => {
if (this.$nuxt) { if (this.$nuxt) {

View File

@ -2,7 +2,7 @@
import Vue from 'vue' import Vue from 'vue'
import middleware from './middleware' import middleware from './middleware'
import { app, router<%= (store ? ', store' : '') %> } from './index' import { app, router<%= (store ? ', store' : '') %>, NuxtError } from './index'
import { getMatchedComponents, getMatchedComponentsInstances, flatMapComponents, getContext, promiseSeries, promisify, getLocation, compile } from './utils' import { getMatchedComponents, getMatchedComponentsInstances, flatMapComponents, getContext, promiseSeries, promisify, getLocation, compile } from './utils'
const noopData = () => { return {} } const noopData = () => { return {} }
const noopFetch = () => {} const noopFetch = () => {}
@ -55,23 +55,28 @@ function loadAsyncComponents (to, from, next) {
} }
function callMiddleware (Components, context, layout) { function callMiddleware (Components, context, layout) {
// Call middleware // if layout is undefined, only call global middleware
let midd = <%= serialize(router.middleware, { isJSON: true }) %> let midd = <%= serialize(router.middleware, { isJSON: true }) %>
if (layout.middleware) { let unknownMiddleware = false
midd = midd.concat(layout.middleware) if (typeof layout !== 'undefined') {
} midd = [] // exclude global middleware if layout defined (already called before)
Components.forEach((Component) => { if (layout.middleware) {
if (Component.options.middleware) { midd = midd.concat(layout.middleware)
midd = midd.concat(Component.options.middleware)
} }
}) Components.forEach((Component) => {
if (Component.options.middleware) {
midd = midd.concat(Component.options.middleware)
}
})
}
midd = midd.map((name) => { midd = midd.map((name) => {
if (typeof middleware[name] !== 'function') { if (typeof middleware[name] !== 'function') {
unknownMiddleware = true
this.error({ statusCode: 500, message: 'Unknown middleware ' + name }) this.error({ statusCode: 500, message: 'Unknown middleware ' + name })
} }
return middleware[name] return middleware[name]
}) })
if (this.$options._nuxt.err) return if (unknownMiddleware) return
return promiseSeries(midd, context) return promiseSeries(midd, context)
} }
@ -82,13 +87,15 @@ function render (to, from, next) {
nextCalled = true nextCalled = true
next(path) next(path)
} }
const context = getContext({ to<%= (store ? ', store' : '') %>, isClient: true, next: _next.bind(this), error: this.error.bind(this) }) let context = getContext({ to<%= (store ? ', store' : '') %>, isClient: true, next: _next.bind(this), error: this.error.bind(this) })
let Components = getMatchedComponents(to) let Components = getMatchedComponents(to)
this._context = context
this._dateLastError = this.$options._nuxt.dateErr this._dateLastError = this.$options._nuxt.dateErr
this._hadError = !!this.$options._nuxt.err this._hadError = !!this.$options._nuxt.err
if (!Components.length) { if (!Components.length) {
// Default layout // Default layout
this.setLayout() callMiddleware.call(this, Components, context)
.then(() => this.loadLayout(typeof NuxtError.layout === 'function' ? NuxtError.layout(context) : NuxtError.layout))
.then(callMiddleware.bind(this, Components, context)) .then(callMiddleware.bind(this, Components, context))
.then(() => { .then(() => {
this.error({ statusCode: 404, message: 'This page could not be found.' }) this.error({ statusCode: 404, message: 'This page could not be found.' })
@ -98,26 +105,22 @@ function render (to, from, next) {
} }
// Update ._data and other properties if hot reloaded // Update ._data and other properties if hot reloaded
Components.forEach(function (Component) { Components.forEach(function (Component) {
if (!Component._data) {
Component._data = Component.options.data || noopData
}
if (Component._Ctor && Component._Ctor.options) { if (Component._Ctor && Component._Ctor.options) {
Component.options.asyncData = Component._Ctor.options.asyncData
Component.options.fetch = Component._Ctor.options.fetch Component.options.fetch = Component._Ctor.options.fetch
if (Component._dataFn) {
const originalDataFn = Component._data.toString().replace(/\s/g, '')
const dataFn = Component._dataFn
const newDataFn = (Component._Ctor.options.data || noopData).toString().replace(/\s/g, '')
// If component data method changed
if (newDataFn !== originalDataFn && newDataFn !== dataFn) {
Component._data = Component._Ctor.options.data || noopData
}
}
} }
}) })
this.setTransitions(mapTransitions(Components, to, from)) this.setTransitions(mapTransitions(Components, to, from))
let nextCalled = false let nextCalled = false
// Set layout // Set layout
this.setLayout(Components[0].options.layout) callMiddleware.call(this, Components, context)
.then(() => {
let layout = Components[0].options.layout
if (typeof layout === 'function') {
layout = layout(context)
}
return this.loadLayout(layout)
})
.then(callMiddleware.bind(this, Components, context)) .then(callMiddleware.bind(this, Components, context))
.then(() => { .then(() => {
// Pass validation? // Pass validation?
@ -127,7 +130,7 @@ function render (to, from, next) {
if (typeof Component.options.validate !== 'function') return if (typeof Component.options.validate !== 'function') return
isValid = Component.options.validate({ isValid = Component.options.validate({
params: to.params || {}, params: to.params || {},
query: to.query || {} query : to.query || {}<%= (store ? ', store: context.store' : '') %>
}) })
}) })
if (!isValid) { if (!isValid) {
@ -141,13 +144,19 @@ function render (to, from, next) {
return Promise.resolve() return Promise.resolve()
} }
let promises = [] let promises = []
// Validate method // asyncData method
if (Component._data && typeof Component._data === 'function') { if (Component.options.asyncData && typeof Component.options.asyncData === 'function') {
var promise = promisify(Component._data, context) var promise = promisify(Component.options.asyncData, context)
promise.then((data) => { promise.then((asyncDataResult) => {
Component._cData = () => data || {} let data = {}
Component.options.data = Component._cData // Call data() if defined
Component._dataFn = Component.options.data.toString().replace(/\s/g, '') if (Component.options.data && typeof Component.options.data === 'function') {
data = Component.options.data()
}
// Merge data() and asyncData() results
data = Object.assign(data, asyncDataResult)
// Overwrite .data() method with merged data
Component.options.data = () => data
if (Component._Ctor && Component._Ctor.options) { if (Component._Ctor && Component._Ctor.options) {
Component._Ctor.options.data = Component.options.data Component._Ctor.options.data = Component.options.data
} }
@ -157,7 +166,7 @@ function render (to, from, next) {
} }
if (Component.options.fetch) { if (Component.options.fetch) {
var p = Component.options.fetch(context) var p = Component.options.fetch(context)
if (!(p instanceof Promise)) { p = Promise.resolve(p) } if (!p || (!(p instanceof Promise) && (typeof p.then !== 'function'))) { p = Promise.resolve(p) }
<%= (loading ? 'p.then(() => this.$loading.increase && this.$loading.increase(30))' : '') %> <%= (loading ? 'p.then(() => this.$loading.increase && this.$loading.increase(30))' : '') %>
promises.push(p) promises.push(p)
} }
@ -175,8 +184,15 @@ function render (to, from, next) {
.catch((error) => { .catch((error) => {
_lastPaths = [] _lastPaths = []
error.statusCode = error.statusCode || error.status || (error.response && error.response.status) || 500 error.statusCode = error.statusCode || error.status || (error.response && error.response.status) || 500
this.error(error) let layout = NuxtError.layout
next(false) if (typeof layout === 'function') {
layout = layout(context)
}
this.loadLayout(layout)
.then(() => {
this.error(error)
next(false)
})
}) })
} }
@ -213,73 +229,104 @@ function fixPrepatch (to, ___) {
if (this._hadError && this._dateLastError === this.$options._nuxt.dateErr) { if (this._hadError && this._dateLastError === this.$options._nuxt.dateErr) {
this.error() this.error()
} }
// Set layout
let layout = this.$options._nuxt.err ? NuxtError.layout : to.matched[0].components.default.options.layout
if (typeof layout === 'function') {
layout = layout(this._context)
}
this.setLayout(layout)
// hot reloading // hot reloading
hotReloadAPI(this) setTimeout(() => hotReloadAPI(this), 100)
}) })
} }
// Special hot reload with data(context) // Special hot reload with asyncData(context)
function hotReloadAPI (_app) { function hotReloadAPI (_app) {
if (!module.hot) return if (!module.hot) return
const $nuxt = _app.$nuxt let $components = []
var _forceUpdate = $nuxt.$forceUpdate.bind($nuxt) let $nuxt = _app.$nuxt
$nuxt.$forceUpdate = function () { while ($nuxt && $nuxt.$children && $nuxt.$children.length) {
let Component = getMatchedComponents(router.currentRoute)[0] $nuxt.$children.forEach(function (child, i) {
if (child.$vnode.data.nuxtChild) {
let hasAlready = false
$components.forEach(function (component) {
if (component.$options.__file === child.$options.__file) {
hasAlready = true
}
})
if (!hasAlready) {
$components.push(child)
}
}
$nuxt = child
})
}
$components.forEach(addHotReload.bind(_app))
}
function addHotReload ($component, depth) {
if ($component.$vnode.data._hasHotReload) return
$component.$vnode.data._hasHotReload = true
var _forceUpdate = $component.$forceUpdate.bind($component.$parent)
$component.$vnode.context.$forceUpdate = () => {
let Components = getMatchedComponents(router.currentRoute)
let Component = Components[depth]
if (!Component) return _forceUpdate() if (!Component) return _forceUpdate()
if (typeof Component === 'object' && !Component.options) { if (typeof Component === 'object' && !Component.options) {
// Updated via vue-router resolveAsyncComponents() // Updated via vue-router resolveAsyncComponents()
Component = Vue.extend(Component) Component = Vue.extend(Component)
Component._Ctor = Component Component._Ctor = Component
} }
this.error()
let promises = [] let promises = []
// If layout changed
if (_app.layoutName !== Component.options.layout) {
let promise = _app.setLayout(Component.options.layout)
promise.then(() => {
hotReloadAPI(_app)
})
promises.push(promise)
}
const next = function (path) { const next = function (path) {
<%= (loading ? 'this.$loading.finish && this.$loading.finish()' : '') %> <%= (loading ? 'this.$loading.finish && this.$loading.finish()' : '') %>
router.push(path) router.push(path)
} }
const context = getContext({ route: router.currentRoute<%= (store ? ', store' : '') %>, isClient: true, next: next.bind(this), error: _app.error }) let context = getContext({ route: router.currentRoute<%= (store ? ', store' : '') %>, isClient: true, next: next.bind(this), error: this.error })
// Check if data has been updated <%= (loading ? 'this.$loading.start && this.$loading.start()' : '') %>
const originalDataFn = (Component._data || noopData).toString().replace(/\s/g, '') callMiddleware.call(this, Components, context)
const newDataFn = (Component._Ctor.options.data || noopData).toString().replace(/\s/g, '') .then(() => {
if (originalDataFn !== newDataFn) { // If layout changed
Component._data = Component._Ctor.options.data || noopData if (depth !== 0) return Promise.resolve()
let p = promisify(Component._data, context) let layout = Component.options.layout || 'default'
p.then((data) => { if (typeof layout === 'function') {
Component._cData = () => data || {} layout = layout(context)
Component.options.data = Component._cData }
Component._dataFn = Component.options.data.toString().replace(/\s/g, '') if (this.layoutName === layout) return Promise.resolve()
let promise = this.loadLayout(layout)
promise.then(() => {
this.setLayout(layout)
Vue.nextTick(() => hotReloadAPI(this))
})
return promise
})
.then(() => {
return callMiddleware.call(this, Components, context, this.layout)
})
.then(() => {
// Call asyncData()
let pAsyncData = promisify(Component.options.asyncData || noopData, context)
pAsyncData.then((asyncDataResult) => {
let data = (typeof Component.options.data === 'function' ? Component.options.data() : noopData())
data = Object.assign(data, asyncDataResult)
Component.options.data = () => data
Component._Ctor.options.data = Component.options.data Component._Ctor.options.data = Component.options.data
<%= (loading ? 'this.$loading.increase && this.$loading.increase(30)' : '') %> <%= (loading ? 'this.$loading.increase && this.$loading.increase(30)' : '') %>
}) })
promises.push(p) promises.push(pAsyncData)
} else if (Component._cData) { // Call fetch()
Component._data = Component.options.data Component.options.fetch = Component.options.fetch || noopFetch
Component.options.data = Component._cData let pFetch = Component.options.fetch(context)
Component._Ctor.options.data = Component.options.data if (!pFetch || (!(pFetch instanceof Promise) && (typeof pFetch.then !== 'function'))) { pFetch = Promise.resolve(pFetch) }
} <%= (loading ? 'pFetch.then(() => this.$loading.increase && this.$loading.increase(30))' : '') %>
// Check if fetch has been updated promises.push(pFetch)
const originalFetchFn = (Component.options.fetch || noopFetch).toString().replace(/\s/g, '') return Promise.all(promises)
const newFetchFn = (Component._Ctor.options.fetch || noopFetch).toString().replace(/\s/g, '') })
// Fetch has been updated, we call it to update the store .then(() => {
if (originalFetchFn !== newFetchFn) {
Component.options.fetch = Component._Ctor.options.fetch || noopFetch
let p = Component.options.fetch(context)
if (!(p instanceof Promise)) { p = Promise.resolve(p) }
<%= (loading ? 'p.then(() => this.$loading.increase && this.$loading.increase(30))' : '') %>
promises.push(p)
}
if (!promises.length) return;
<%= (loading ? 'this.$loading.start && this.$loading.start()' : '') %>
return Promise.all(promises).then(() => {
<%= (loading ? 'this.$loading.finish && this.$loading.finish()' : '') %> <%= (loading ? 'this.$loading.finish && this.$loading.finish()' : '') %>
_forceUpdate() _forceUpdate()
setTimeout(() => hotReloadAPI(this), 100)
}) })
} }
} }
@ -302,13 +349,14 @@ const resolveComponents = flatMapComponents(router.match(path), (Component, _, m
Component._Ctor = Component Component._Ctor = Component
Component.extendOptions = Component.options Component.extendOptions = Component.options
} }
if (Component.options.data && typeof Component.options.data === 'function') { if (NUXT.serverRendered) {
Component._data = Component.options.data let data = {}
if (NUXT.serverRendered) { if (Component.options.data && typeof Component.options.data === 'function') {
Component._cData = () => NUXT.data[index] || {} data = Component.options.data()
Component.options.data = Component._cData
Component._dataFn = Component.options.data.toString().replace(/\s/g, '')
} }
// Merge data() and asyncData() results
data = Object.assign(data, NUXT.data[index])
Component.options.data = () => data
if (Component._Ctor && Component._Ctor.options) { if (Component._Ctor && Component._Ctor.options) {
Component._Ctor.options.data = Component.options.data Component._Ctor.options.data = Component.options.data
} }
@ -338,25 +386,23 @@ Promise.all(resolveComponents)
.then((Components) => { .then((Components) => {
const _app = new Vue(app) const _app = new Vue(app)
return _app.setLayout(Components.length ? Components[0].options.layout : '') let layout = NUXT.layout || 'default'
return _app.loadLayout(layout)
.then(() => { .then(() => {
_app.setLayout(layout)
return { _app, Components } return { _app, Components }
}) })
}) })
.then(({ _app, Components }) => { .then(({ _app, Components }) => {
const mountApp = () => { const mountApp = () => {
_app.$mount('#__nuxt') _app.$mount('#__nuxt')
// Hot reloading Vue.nextTick(() => {
hotReloadAPI(_app) // Hot reloading
// Call window.onNuxtReady callbacks hotReloadAPI(_app)
Vue.nextTick(() => nuxtReady(_app)) // Call window.onNuxtReady callbacks
nuxtReady(_app)
})
} }
<% if (store) { %>
// Replace store state
if (NUXT.state) {
store.replaceState(NUXT.state)
}
<% } %>
_app.setTransitions = _app.$options._nuxt.setTransitions.bind(_app) _app.setTransitions = _app.$options._nuxt.setTransitions.bind(_app)
if (Components.length) { if (Components.length) {
_app.setTransitions(mapTransitions(Components, router.currentRoute)) _app.setTransitions(mapTransitions(Components, router.currentRoute))

View File

@ -5,6 +5,7 @@ const transitionsKeys = [
'mode', 'mode',
'css', 'css',
'type', 'type',
'duration',
'enterClass', 'enterClass',
'leaveClass', 'leaveClass',
'enterActiveClass', 'enterActiveClass',

View File

@ -6,7 +6,7 @@
<script> <script>
import Vue from 'vue' import Vue from 'vue'
import NuxtChild from './nuxt-child' import NuxtChild from './nuxt-child'
import NuxtError from '<%= components.ErrorPage %>' import NuxtError from '<%= components.ErrorPage ? components.ErrorPage : "./nuxt-error.vue" %>'
export default { export default {
name: 'nuxt', name: 'nuxt',

View File

@ -6,6 +6,7 @@ import router from './router.js'
<% if (store) { %>import store from './store.js'<% } %> <% if (store) { %>import store from './store.js'<% } %>
import NuxtChild from './components/nuxt-child.js' import NuxtChild from './components/nuxt-child.js'
import NuxtLink from './components/nuxt-link.js' import NuxtLink from './components/nuxt-link.js'
import NuxtError from '<%= components.ErrorPage ? components.ErrorPage : "./components/nuxt-error.vue" %>'
import Nuxt from './components/nuxt.vue' import Nuxt from './components/nuxt.vue'
import App from '<%= appPath %>' import App from '<%= appPath %>'
@ -19,12 +20,18 @@ Vue.component(Nuxt.name, Nuxt)
// vue-meta configuration // vue-meta configuration
Vue.use(Meta, { Vue.use(Meta, {
keyName: 'head', // the component option name that vue-meta looks for meta info on. keyName: 'head', // the component option name that vue-meta looks for meta info on.
attribute: 'n-head', // the attribute name vue-meta adds to the tags it observes attribute: 'data-n-head', // the attribute name vue-meta adds to the tags it observes
ssrAttribute: 'n-head-ssr', // the attribute name that lets vue-meta know that meta info has already been server-rendered ssrAttribute: 'data-n-head-ssr', // the attribute name that lets vue-meta know that meta info has already been server-rendered
tagIDKeyName: 'hid' // the property name that vue-meta uses to determine whether to overwrite or append a tag tagIDKeyName: 'hid' // the property name that vue-meta uses to determine whether to overwrite or append a tag
}) })
if (process.BROWSER_BUILD) { if (process.browser) {
<% if (store) { %>
// Replace store state before calling plugins
if (window.__NUXT__ && window.__NUXT__.state) {
store.replaceState(window.__NUXT__.state)
}
<% } %>
// window.onNuxtReady(() => console.log('Ready')) hook // window.onNuxtReady(() => console.log('Ready')) hook
// Useful for jsdom testing or plugins (https://github.com/tmpvar/jsdom#dealing-with-asynchronous-script-loading) // Useful for jsdom testing or plugins (https://github.com/tmpvar/jsdom#dealing-with-asynchronous-script-loading)
window._nuxtReadyCbs = [] window._nuxtReadyCbs = []
@ -34,8 +41,16 @@ if (process.BROWSER_BUILD) {
} }
// Includes external plugins // Includes external plugins
<% plugins.forEach(function (pluginPath) { %> <% plugins.forEach(function (plugin) {
require('<%= pluginPath %>') if (typeof plugin === 'string') { %>
require('<%= plugin %>')
<% } else if (plugin.src && plugin.ssr !== false) { %>
require('<%= plugin.src %>')
<% } else if (plugin.src) { %>
if (process.browser) {
require('<%= plugin.src %>')
}
<% } %>
<% }) %> <% }) %>
// root instance // root instance
@ -85,4 +100,4 @@ const app = {
...App ...App
} }
export { app, router<%= (store ? ', store' : '') %> } export { app, router<%= (store ? ', store' : '') %>, NuxtError }

View File

@ -23,7 +23,7 @@ function recursiveRoutes(routes, tab, components) {
var _components = [] var _components = []
var _routes = recursiveRoutes(router.routes, '\t\t', _components) var _routes = recursiveRoutes(router.routes, '\t\t', _components)
uniqBy(_components, '_name').forEach((route) => { %> uniqBy(_components, '_name').forEach((route) => { %>
const <%= route._name %> = process.BROWSER_BUILD ? () => System.import('<%= route.component %>') : require('<%= route.component %>') const <%= route._name %> = () => import('<%= route.component %>')
<% }) %> <% }) %>
<% if (router.scrollBehavior) { %> <% if (router.scrollBehavior) { %>

View File

@ -3,10 +3,11 @@
const debug = require('debug')('nuxt:render') const debug = require('debug')('nuxt:render')
debug.color = 4 // force blue color debug.color = 4 // force blue color
import Vue from 'vue' import Vue from 'vue'
import { stringify } from 'querystring' import { stringify } from 'querystring'
import { omit } from 'lodash' import { omit } from 'lodash'
import middleware from './middleware' import middleware from './middleware'
import { app, router<%= (store ? ', store' : '') %> } from './index' import { app, router<%= (store ? ', store' : '') %>, NuxtError } from './index'
import { getMatchedComponents, getContext, promiseSeries, promisify, urlJoin } from './utils' import { getMatchedComponents, getContext, promiseSeries, promisify, urlJoin } from './utils'
const isDev = <%= isDev %> const isDev = <%= isDev %>
@ -21,13 +22,13 @@ export default context => {
// Add store to the context // Add store to the context
<%= (store ? 'context.store = store' : '') %> <%= (store ? 'context.store = store' : '') %>
// Nuxt object // Nuxt object
context.nuxt = { data: [], error: null<%= (store ? ', state: null' : '') %>, serverRendered: true } context.nuxt = { layout: 'default', data: [], error: null<%= (store ? ', state: null' : '') %>, serverRendered: true }
// create context.next for simulate next() of beforeEach() when wanted to redirect // create context.next for simulate next() of beforeEach() when wanted to redirect
context.redirected = false context.redirected = false
context.next = function (opts) { context.next = function (opts) {
context.redirected = opts
// if nuxt generate // if nuxt generate
if (!context.res) { if (!context.res) {
context.redirected = opts
context.nuxt.serverRendered = false context.nuxt.serverRendered = false
return return
} }
@ -39,25 +40,57 @@ export default context => {
}) })
context.res.end() context.res.end()
} }
// set router's location
router.push(context.url)
// Add route to the context
context.route = router.currentRoute
// Add meta infos // Add meta infos
context.meta = _app.$meta() context.meta = _app.$meta()
// Error function // Error function
context.error = _app.$options._nuxt.error.bind(_app) context.error = _app.$options._nuxt.error.bind(_app)
<%= (isDev ? 'const s = isDev && Date.now()' : '') %> <%= (isDev ? 'const s = isDev && Date.now()' : '') %>
const ctx = getContext(context) let ctx = null
let Components = getMatchedComponents(context.route) let componentsLoaded = false
<% if (store) { %> let Components = []
let promise = (store._actions && store._actions.nuxtServerInit ? store.dispatch('nuxtServerInit', omit(getContext(context), 'redirect', 'error')) : null) let promises = getMatchedComponents(router.match(context.url)).map((Component) => {
if (!(promise instanceof Promise)) promise = Promise.resolve() return new Promise((resolve, reject) => {
<% } else { %> const _resolve = (Component) => {
let promise = Promise.resolve() if (!Component.options) {
<% } %> Component = Vue.extend(Component) // fix issue #6
return promise Component._Ctor = Component
} else {
Component._Ctor = Component
Component.extendOptions = Component.options
}
// For debugging purpose
if (!Component.options.name && Component.options.__file) {
Component.options.name = Component.options.__file
}
resolve(Component)
}
Component().then(_resolve).catch(reject)
})
})
return Promise.all(promises)
.then((_Components) => {
componentsLoaded = true
Components = _Components
// set router's location
return new Promise((resolve) => {
router.push(context.url, resolve)
})
})
.then(() => {
// Add route to the context
context.route = router.currentRoute
// Update context
ctx = getContext(context)
// nuxtServerInit
<% if (store) { %>
let promise = (store._actions && store._actions.nuxtServerInit ? store.dispatch('nuxtServerInit', omit(getContext(context), 'redirect', 'error')) : null)
if (!promise || (!(promise instanceof Promise) && (typeof promise.then !== 'function'))) promise = Promise.resolve()
<% } else { %>
let promise = Promise.resolve()
<% } %>
return promise
})
.then(() => { .then(() => {
// Sanitize Components // Sanitize Components
Components = Components.map((Component) => { Components = Components.map((Component) => {
@ -71,12 +104,30 @@ export default context => {
} }
return Component return Component
}) })
// Call global middleware (nuxt.config.js)
let midd = <%= serialize(router.middleware, { isJSON: true }) %>
midd = midd.map((name) => {
if (typeof middleware[name] !== 'function') {
context.nuxt.error = context.error({ statusCode: 500, message: 'Unknown middleware ' + name })
}
return middleware[name]
})
if (context.nuxt.error) return
return promiseSeries(midd, ctx)
})
.then(() => {
// Set layout // Set layout
return _app.setLayout(Components.length ? Components[0].options.layout : '') let layout = Components.length ? Components[0].options.layout : NuxtError.layout
if (typeof layout === 'function') {
layout = layout(ctx)
}
return _app.loadLayout(layout).then(() => _app.setLayout(layout))
}) })
.then((layout) => { .then((layout) => {
// Call middleware // Set layout to __NUXT__
let midd = <%= serialize(router.middleware, { isJSON: true }) %> context.nuxt.layout = _app.layoutName
// Call middleware (layout + pages)
let midd = []
if (layout.middleware) { if (layout.middleware) {
midd = midd.concat(layout.middleware) midd = midd.concat(layout.middleware)
} }
@ -102,7 +153,7 @@ export default context => {
if (typeof Component.options.validate !== 'function') return if (typeof Component.options.validate !== 'function') return
isValid = Component.options.validate({ isValid = Component.options.validate({
params: context.route.params || {}, params: context.route.params || {},
query: context.route.query || {} query: context.route.query || {}<%= (store ? ', store: ctx.store' : '') %>
}) })
}) })
if (!isValid) { if (!isValid) {
@ -114,13 +165,20 @@ export default context => {
Components = [] Components = []
return _app return _app
} }
// Call data & fetch hooks on components matched by the route. // Call asyncData & fetch hooks on components matched by the route.
return Promise.all(Components.map((Component) => { return Promise.all(Components.map((Component) => {
let promises = [] let promises = []
if (Component.options.data && typeof Component.options.data === 'function') { if (Component.options.asyncData && typeof Component.options.asyncData === 'function') {
Component._data = Component.options.data let promise = promisify(Component.options.asyncData, ctx)
let promise = promisify(Component._data, ctx) // Call asyncData(context)
promise.then((data) => { promise.then((asyncDataResult) => {
let data = {}
// Call data() if defined
if (Component.options.data && typeof Component.options.data === 'function') {
data = Component.options.data()
}
// Merge data() and asyncData() results
data = Object.assign(data, asyncDataResult)
Component.options.data = () => data Component.options.data = () => data
Component._Ctor.options.data = Component.options.data Component._Ctor.options.data = Component.options.data
}) })
@ -130,6 +188,8 @@ export default context => {
} }
if (Component.options.fetch) { if (Component.options.fetch) {
promises.push(Component.options.fetch(ctx)) promises.push(Component.options.fetch(ctx))
} else {
promises.push(null)
} }
return Promise.all(promises) return Promise.all(promises)
})) }))
@ -141,16 +201,28 @@ export default context => {
return _app return _app
} }
<% if (isDev) { %> <% if (isDev) { %>
debug('Data fetching ' + context.req.url + ': ' + (Date.now() - s) + 'ms') debug('Data fetching ' + context.url + ': ' + (Date.now() - s) + 'ms')
<% } %> <% } %>
// datas are the first row of each // datas are the first row of each
context.nuxt.data = res.map((tab) => tab[0]) context.nuxt.data = res.map((r) => (r[0] || {}))
context.nuxt.error = _app.$options._nuxt.err context.nuxt.error = _app.$options._nuxt.err
<%= (store ? '// Add the state from the vuex store' : '') %> <%= (store ? '// Add the state from the vuex store' : '') %>
<%= (store ? 'context.nuxt.state = store.state' : '') %> <%= (store ? 'context.nuxt.state = store.state' : '') %>
return _app // If no error, return main app
if (!context.nuxt.error) {
return _app
}
// Load layout for error page
let layout = (typeof NuxtError.layout === 'function' ? NuxtError.layout(ctx) : NuxtError.layout)
context.nuxt.layout = layout || ''
return _app.loadLayout(layout)
.then(() => _app.setLayout(layout))
.then(() => _app)
}) })
.catch(function (error) { .catch(function (error) {
if (!componentsLoaded && error instanceof Error) {
return Promise.reject(error)
}
if (error && (error instanceof Error || error.constructor.toString().indexOf('Error()') !== -1)) { if (error && (error instanceof Error || error.constructor.toString().indexOf('Error()') !== -1)) {
let statusCode = error.statusCode || error.status || (error.response && error.response.status) || 500 let statusCode = error.statusCode || error.status || (error.response && error.response.status) || 500
error = { statusCode, message: error.message } error = { statusCode, message: error.message }

View File

@ -2,7 +2,7 @@ import Vue from 'vue'
import Vuex from 'vuex' import Vuex from 'vuex'
Vue.use(Vuex) Vue.use(Vuex)
let files = require.context('~/store', false, /^\.\/.*\.js$/) let files = require.context('~/store', true, /^\.\/.*\.js$/)
let filenames = files.keys() let filenames = files.keys()
function getModule (filename) { function getModule (filename) {
@ -12,6 +12,17 @@ function getModule (filename) {
: file : file
} }
function getModuleNamespace (storeData, namePath) {
if (namePath.length === 1) {
return storeData.modules
}
let namespace = namePath.shift()
storeData.modules[namespace] = storeData.modules[namespace] || {}
storeData.modules[namespace].namespaced = true
storeData.modules[namespace].modules = storeData.modules[namespace].modules || {}
return getModuleNamespace(storeData.modules[namespace], namePath)
}
let store let store
let storeData = {} let storeData = {}
@ -31,8 +42,13 @@ if (store == null) {
for (let filename of filenames) { for (let filename of filenames) {
let name = filename.replace(/^\.\//, '').replace(/\.js$/, '') let name = filename.replace(/^\.\//, '').replace(/\.js$/, '')
if (name === 'index') continue if (name === 'index') continue
storeData.modules[name] = getModule(filename)
storeData.modules[name].namespaced = true let namePath = name.split(/\//)
let module = getModuleNamespace(storeData, namePath)
name = namePath.pop()
module[name] = getModule(filename)
module[name].namespaced = true
} }
store = new Vuex.Store(storeData) store = new Vuex.Store(storeData)
} }

View File

@ -84,7 +84,7 @@ export function promisify (fn, context) {
} else { } else {
promise = fn(context) promise = fn(context)
} }
if (!(promise instanceof Promise)) { if (!promise || (!(promise instanceof Promise) && (typeof promise.then !== 'function'))) {
promise = Promise.resolve(promise) promise = Promise.resolve(promise)
} }
return promise return promise

View File

@ -1,6 +1,5 @@
'use strict' 'use strict'
const debug = require('debug')('nuxt:build')
import _ from 'lodash' import _ from 'lodash'
import co from 'co' import co from 'co'
import chokidar from 'chokidar' import chokidar from 'chokidar'
@ -8,15 +7,17 @@ import fs from 'fs-extra'
import hash from 'hash-sum' import hash from 'hash-sum'
import pify from 'pify' import pify from 'pify'
import webpack from 'webpack' import webpack from 'webpack'
import PostCompilePlugin from 'post-compile-webpack-plugin'
import serialize from 'serialize-javascript' import serialize from 'serialize-javascript'
import { createBundleRenderer } from 'vue-server-renderer' import { createBundleRenderer } from 'vue-server-renderer'
import { join, resolve, sep } from 'path' import { join, resolve, sep } from 'path'
import { isUrl } from './utils'
import clientWebpackConfig from './webpack/client.config.js' import clientWebpackConfig from './webpack/client.config.js'
import serverWebpackConfig from './webpack/server.config.js' import serverWebpackConfig from './webpack/server.config.js'
import chalk from 'chalk' const debug = require('debug')('nuxt:build')
import PostCompilePlugin from 'post-compile-webpack-plugin'
const remove = pify(fs.remove) const remove = pify(fs.remove)
const readFile = pify(fs.readFile) const readFile = pify(fs.readFile)
const utimes = pify(fs.utimes)
const writeFile = pify(fs.writeFile) const writeFile = pify(fs.writeFile)
const mkdirp = pify(fs.mkdirp) const mkdirp = pify(fs.mkdirp)
const glob = pify(require('glob')) const glob = pify(require('glob'))
@ -38,21 +39,17 @@ const r = function () {
args = args.map(normalize) args = args.map(normalize)
return wp(resolve.apply(null, args)) return wp(resolve.apply(null, args))
} }
const webpackStats = { let webpackStats = 'none'
chunks: false,
children: false,
modules: false,
colors: true
}
// force green color // force green color
debug.color = 2 debug.color = 2
const defaults = { const defaults = {
analyze: false, analyze: false,
publicPath: '/_nuxt/',
filenames: { filenames: {
css: 'style.css', manifest: 'manifest.[hash].js',
vendor: 'vendor.bundle.js', vendor: 'vendor.bundle.[hash].js',
app: 'nuxt.bundle.js' app: 'nuxt.bundle.[chunkhash].js'
}, },
vendor: [], vendor: [],
loaders: [], loaders: [],
@ -90,13 +87,24 @@ export function options () {
if (this.options.build && !Array.isArray(this.options.build.loaders)) extraDefaults.loaders = defaultsLoaders if (this.options.build && !Array.isArray(this.options.build.loaders)) extraDefaults.loaders = defaultsLoaders
if (this.options.build && !Array.isArray(this.options.build.postcss)) extraDefaults.postcss = defaultsPostcss if (this.options.build && !Array.isArray(this.options.build.postcss)) extraDefaults.postcss = defaultsPostcss
this.options.build = _.defaultsDeep(this.options.build, defaults, extraDefaults) this.options.build = _.defaultsDeep(this.options.build, defaults, extraDefaults)
/* istanbul ignore if */
if (this.dev && isUrl(this.options.build.publicPath)) {
this.options.build.publicPath = defaults.publicPath
}
// Production, create server-renderer // Production, create server-renderer
if (!this.dev) { if (!this.dev) {
webpackStats = {
chunks: false,
children: false,
modules: false,
colors: true
}
const serverConfig = getWebpackServerConfig.call(this) const serverConfig = getWebpackServerConfig.call(this)
const bundlePath = join(serverConfig.output.path, serverConfig.output.filename) const bundlePath = join(serverConfig.output.path, 'server-bundle.json')
if (fs.existsSync(bundlePath)) { if (fs.existsSync(bundlePath)) {
const bundle = fs.readFileSync(bundlePath, 'utf8') const bundle = fs.readFileSync(bundlePath, 'utf8')
createRenderer.call(this, bundle) createRenderer.call(this, JSON.parse(bundle))
addAppTemplate.call(this)
} }
} }
} }
@ -138,6 +146,16 @@ function * buildFiles () {
webpackRunClient.call(this), webpackRunClient.call(this),
webpackRunServer.call(this) webpackRunServer.call(this)
] ]
addAppTemplate.call(this)
}
}
function addAppTemplate () {
let templatePath = resolve(this.dir, '.nuxt', 'dist', 'index.html')
if (fs.existsSync(templatePath)) {
this.appTemplate = _.template(fs.readFileSync(templatePath, 'utf8'), {
interpolate: /{{([\s\S]+?)}}/g
})
} }
} }
@ -151,17 +169,8 @@ function * generateRoutesAndFiles () {
if (name === 'error') return if (name === 'error') return
layouts[name] = r(this.srcDir, file) layouts[name] = r(this.srcDir, file)
}) })
// Generate routes based on files
const files = yield glob('pages/**/*.vue', { cwd: this.srcDir }) const files = yield glob('pages/**/*.vue', { cwd: this.srcDir })
this.routes = _.uniq(_.map(files, (file) => {
return file.replace(/^pages/, '').replace(/\.vue$/, '').replace(/\/index/g, '').replace(/_/g, ':').replace('', '/').replace(/\/{2,}/g, '/')
}))
if (typeof this.options.router.extendRoutes === 'function') {
// let the user extend the routes
this.options.router.extendRoutes(this.routes)
}
// Interpret and move template files to .nuxt/ // Interpret and move template files to .nuxt/
debug('Generating files...')
let templatesFiles = [ let templatesFiles = [
'App.vue', 'App.vue',
'client.js', 'client.js',
@ -176,6 +185,7 @@ function * generateRoutesAndFiles () {
'components/nuxt-link.js', 'components/nuxt-link.js',
'components/nuxt.vue' 'components/nuxt.vue'
] ]
this.options.store = fs.existsSync(join(this.srcDir, 'store'))
let templateVars = { let templateVars = {
uniqBy: _.uniqBy, uniqBy: _.uniqBy,
isDev: this.dev, isDev: this.dev,
@ -188,24 +198,32 @@ function * generateRoutesAndFiles () {
}, },
env: this.options.env, env: this.options.env,
head: this.options.head, head: this.options.head,
middleware: this.options.middleware, middleware: fs.existsSync(join(this.srcDir, 'middleware')),
store: this.options.store, store: this.options.store,
css: this.options.css, css: this.options.css,
plugins: this.options.plugins.map((p) => r(this.srcDir, p)), plugins: this.options.plugins.map((p) => {
if (typeof p === 'string') {
return { src: r(this.srcDir, p), ssr: true }
}
return { src: r(this.srcDir, p.src), ssr: !!p.ssr }
}),
appPath: './App.vue', appPath: './App.vue',
layouts: layouts, layouts: layouts,
loading: (typeof this.options.loading === 'string' ? r(this.srcDir, this.options.loading) : this.options.loading), loading: (typeof this.options.loading === 'string' ? r(this.srcDir, this.options.loading) : this.options.loading),
transition: this.options.transition, transition: this.options.transition,
components: { components: {
ErrorPage: './nuxt-error.vue' ErrorPage: null
} }
} }
// Format routes for the lib/app/router.js template // Format routes for the lib/app/router.js template
templateVars.router.routes = createRoutes(files, this.srcDir) templateVars.router.routes = createRoutes(files, this.srcDir)
if (typeof this.options.router.extendRoutes === 'function') { if (typeof this.options.router.extendRoutes === 'function') {
// let the user extend the routes // let the user extend the routes
this.options.router.extendRoutes(templateVars.router.routes) this.options.router.extendRoutes(templateVars.router.routes, r)
} }
// Routes for Generate command
this.routes = flatRoutes(templateVars.router.routes)
debug('Generating files...')
if (layoutsFiles.includes('layouts/error.vue')) { if (layoutsFiles.includes('layouts/error.vue')) {
templateVars.components.ErrorPage = r(this.srcDir, 'layouts/error.vue') templateVars.components.ErrorPage = r(this.srcDir, 'layouts/error.vue')
} }
@ -229,7 +247,13 @@ function * generateRoutesAndFiles () {
} }
}) })
const content = template(templateVars) const content = template(templateVars)
return writeFile(r(this.dir, '.nuxt', file), content, 'utf8') const path = r(this.dir, '.nuxt', file)
return writeFile(path, content, 'utf8')
.then(() => {
// Fix webpack loop (https://github.com/webpack/watchpack/issues/25#issuecomment-287789288)
const dateFS = Date.now() / 1000 - 30
return utimes(path, dateFS, dateFS)
})
}) })
}) })
yield moveTemplates yield moveTemplates
@ -325,6 +349,19 @@ function cleanChildrenRoutes (routes, isChild = false) {
return routes return routes
} }
function flatRoutes (router, path = '', routes = []) {
router.forEach((r) => {
if (!r.path.includes(':') && !r.path.includes('*')) {
if (r.children) {
flatRoutes(r.children, path + r.path + '/', routes)
} else {
routes.push((r.path === '' && path[path.length - 1] === '/' ? path.slice(0, -1) : path) + r.path)
}
}
})
return routes
}
function getWebpackClientConfig () { function getWebpackClientConfig () {
return clientWebpackConfig.call(this) return clientWebpackConfig.call(this)
} }
@ -335,26 +372,17 @@ function getWebpackServerConfig () {
function createWebpackMiddleware () { function createWebpackMiddleware () {
const clientConfig = getWebpackClientConfig.call(this) const clientConfig = getWebpackClientConfig.call(this)
const host = process.env.HOST || '127.0.0.1' const host = process.env.HOST || process.env.npm_package_config_nuxt_host || '127.0.0.1'
const port = process.env.PORT || '3000' const port = process.env.PORT || process.env.npm_package_config_nuxt_port || '3000'
// setup on the fly compilation + hot-reload // setup on the fly compilation + hot-reload
clientConfig.entry.app = _.flatten(['webpack-hot-middleware/client?reload=true', clientConfig.entry.app]) clientConfig.entry.app = _.flatten(['webpack-hot-middleware/client?reload=true', clientConfig.entry.app])
clientConfig.plugins.push( clientConfig.plugins.push(
new webpack.HotModuleReplacementPlugin(), new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin(), new webpack.NoEmitOnErrorsPlugin(),
new PostCompilePlugin(stats => { new PostCompilePlugin(stats => {
process.stdout.write('\x1Bc') if (!stats.hasErrors() && !stats.hasWarnings()) {
console.log(`> Open http://${host}:${port}\n`) // eslint-disable-line no-console
if (stats.hasErrors() || stats.hasWarnings()) {
console.log(stats.toString('errors-only')) // eslint-disable-line no-console
console.log() // eslint-disable-line no-console
console.log(chalk.bgRed.black(' ERROR '), 'Compiling failed!') // eslint-disable-line no-console
} else {
console.log(stats.toString(webpackStats)) // eslint-disable-line no-console
console.log(chalk.bold(`\n> Open http://${host}:${port}\n`)) // eslint-disable-line no-console
console.log(chalk.bgGreen.black(' DONE '), 'Compiled successfully!') // eslint-disable-line no-console
} }
console.log() // eslint-disable-line no-console
}) })
) )
const clientCompiler = webpack(clientConfig) const clientCompiler = webpack(clientConfig)
@ -363,11 +391,22 @@ function createWebpackMiddleware () {
publicPath: clientConfig.output.publicPath, publicPath: clientConfig.output.publicPath,
stats: webpackStats, stats: webpackStats,
quiet: true, quiet: true,
noInfo: true noInfo: true,
watchOptions: this.options.watchers.webpack
})) }))
this.webpackHotMiddleware = pify(require('webpack-hot-middleware')(clientCompiler, { this.webpackHotMiddleware = pify(require('webpack-hot-middleware')(clientCompiler, {
log: () => {} log: () => {}
})) }))
clientCompiler.plugin('done', () => {
const fs = this.webpackDevMiddleware.fileSystem
const filePath = join(clientConfig.output.path, 'index.html')
if (fs.existsSync(filePath)) {
const template = fs.readFileSync(filePath, 'utf-8')
this.appTemplate = _.template(template, {
interpolate: /{{([\s\S]+?)}}/g
})
}
})
} }
function webpackWatchAndUpdate () { function webpackWatchAndUpdate () {
@ -375,22 +414,22 @@ function webpackWatchAndUpdate () {
const mfs = new MFS() const mfs = new MFS()
const serverConfig = getWebpackServerConfig.call(this) const serverConfig = getWebpackServerConfig.call(this)
const serverCompiler = webpack(serverConfig) const serverCompiler = webpack(serverConfig)
const outputPath = join(serverConfig.output.path, serverConfig.output.filename) const outputPath = join(serverConfig.output.path, 'server-bundle.json')
serverCompiler.outputFileSystem = mfs serverCompiler.outputFileSystem = mfs
this.webpackServerWatcher = serverCompiler.watch({}, (err) => { this.webpackServerWatcher = serverCompiler.watch(this.options.watchers.webpack, (err) => {
if (err) throw err if (err) throw err
createRenderer.call(this, mfs.readFileSync(outputPath, 'utf-8')) createRenderer.call(this, JSON.parse(mfs.readFileSync(outputPath, 'utf-8')))
}) })
} }
function webpackRunClient () { function webpackRunClient () {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const clientConfig = getWebpackClientConfig.call(this) const clientConfig = getWebpackClientConfig.call(this)
const serverCompiler = webpack(clientConfig) const clientCompiler = webpack(clientConfig)
serverCompiler.run((err, stats) => { clientCompiler.run((err, stats) => {
if (err) return reject(err) if (err) return reject(err)
console.log('[nuxt:build:client]\n', stats.toString(webpackStats)) // eslint-disable-line no-console console.log('[nuxt:build:client]\n', stats.toString(webpackStats)) // eslint-disable-line no-console
if (stats.hasErrors()) return reject('Webpack build exited with errors') if (stats.hasErrors()) return reject(new Error('Webpack build exited with errors'))
resolve() resolve()
}) })
}) })
@ -403,11 +442,11 @@ function webpackRunServer () {
serverCompiler.run((err, stats) => { serverCompiler.run((err, stats) => {
if (err) return reject(err) if (err) return reject(err)
console.log('[nuxt:build:server]\n', stats.toString(webpackStats)) // eslint-disable-line no-console console.log('[nuxt:build:server]\n', stats.toString(webpackStats)) // eslint-disable-line no-console
if (stats.hasErrors()) return reject('Webpack build exited with errors') if (stats.hasErrors()) return reject(new Error('Webpack build exited with errors'))
const bundlePath = join(serverConfig.output.path, serverConfig.output.filename) const bundlePath = join(serverConfig.output.path, 'server-bundle.json')
readFile(bundlePath, 'utf8') readFile(bundlePath, 'utf8')
.then((bundle) => { .then((bundle) => {
createRenderer.call(this, bundle) createRenderer.call(this, JSON.parse(bundle))
resolve() resolve()
}) })
}) })
@ -434,22 +473,20 @@ function createRenderer (bundle) {
function watchPages () { function watchPages () {
const patterns = [ const patterns = [
r(this.srcDir, 'pages'), r(this.srcDir, 'pages'),
r(this.srcDir, 'layouts'),
r(this.srcDir, 'store'),
r(this.srcDir, 'middleware'),
r(this.srcDir, 'pages/*.vue'), r(this.srcDir, 'pages/*.vue'),
r(this.srcDir, 'pages/**/*.vue'), r(this.srcDir, 'pages/**/*.vue'),
r(this.srcDir, 'layouts'),
r(this.srcDir, 'layouts/*.vue'), r(this.srcDir, 'layouts/*.vue'),
r(this.srcDir, 'layouts/**/*.vue') r(this.srcDir, 'layouts/**/*.vue')
] ]
const options = { const options = Object.assign({}, this.options.watchers.chokidar, {
ignoreInitial: true ignoreInitial: true
} })
/* istanbul ignore next */ /* istanbul ignore next */
const refreshFiles = _.debounce(() => { const refreshFiles = _.debounce(() => {
var d = Date.now()
co(generateRoutesAndFiles.bind(this)) co(generateRoutesAndFiles.bind(this))
.then(() => {
console.log('Time to gen:' + (Date.now() - d) + 'ms') // eslint-disable-line no-console
})
}, 200) }, 200)
this.pagesFilesWatcher = chokidar.watch(patterns, options) this.pagesFilesWatcher = chokidar.watch(patterns, options)
.on('add', refreshFiles) .on('add', refreshFiles)

View File

@ -1,14 +1,13 @@
'use strict' 'use strict'
const debug = require('debug')('nuxt:generate')
import fs from 'fs-extra' import fs from 'fs-extra'
import co from 'co' import co from 'co'
import pify from 'pify' import pify from 'pify'
import pathToRegexp from 'path-to-regexp'
import _ from 'lodash' import _ from 'lodash'
import { resolve, join, dirname, sep } from 'path' import { resolve, join, dirname, sep } from 'path'
import { promisifyRouteParams } from './utils' import { isUrl, promisifyRoute } from './utils'
import { minify } from 'html-minifier' import { minify } from 'html-minifier'
const debug = require('debug')('nuxt:generate')
const copy = pify(fs.copy) const copy = pify(fs.copy)
const remove = pify(fs.remove) const remove = pify(fs.remove)
const writeFile = pify(fs.writeFile) const writeFile = pify(fs.writeFile)
@ -16,21 +15,32 @@ const mkdirp = pify(fs.mkdirp)
const defaults = { const defaults = {
dir: 'dist', dir: 'dist',
routeParams: {} routes: [],
minify: {
collapseBooleanAttributes: true,
collapseWhitespace: true,
decodeEntities: true,
minifyCSS: true,
minifyJS: true,
processConditionalComments: true,
removeAttributeQuotes: false,
removeComments: false,
removeEmptyAttributes: true,
removeOptionalTags: true,
removeRedundantAttributes: true,
removeScriptTypeAttributes: false,
removeStyleLinkTypeAttributes: false,
removeTagWhitespace: false,
sortAttributes: true,
sortClassName: true,
trimCustomFragments: true,
useShortDoctype: true
}
} }
export default function () { export default function () {
const s = Date.now() const s = Date.now()
/* /*
** Update loaders config to add router.base path
*/
// this.options.build.loaders.forEach((config) => {
// if (['file', 'url', 'file-loader', 'url-loader'].includes(config.loader)) {
// config.query = config.query || {}
// config.query.publicPath = urlJoin(this.options.router.base, '/_nuxt/')
// }
// })
/*
** Set variables ** Set variables
*/ */
this.options.generate = _.defaultsDeep(this.options.generate, defaults) this.options.generate = _.defaultsDeep(this.options.generate, defaults)
@ -38,7 +48,7 @@ export default function () {
var srcStaticPath = resolve(this.srcDir, 'static') var srcStaticPath = resolve(this.srcDir, 'static')
var srcBuiltPath = resolve(this.dir, '.nuxt', 'dist') var srcBuiltPath = resolve(this.dir, '.nuxt', 'dist')
var distPath = resolve(this.dir, this.options.generate.dir) var distPath = resolve(this.dir, this.options.generate.dir)
var distNuxtPath = resolve(distPath, '_nuxt') var distNuxtPath = join(distPath, (isUrl(this.options.build.publicPath) ? '_nuxt' : this.options.build.publicPath))
return co(function * () { return co(function * () {
/* /*
** Launch build process ** Launch build process
@ -61,55 +71,31 @@ export default function () {
debug('Static & build files copied') debug('Static & build files copied')
}) })
.then(() => { .then(() => {
// Resolve config.generate.routesParams promises before generating the routes // Resolve config.generate.routes promises before generating the routes
return resolveRouteParams(this.options.generate.routeParams) return promisifyRoute(this.options.generate.routes || [])
.catch((e) => {
console.error('Could not resolve routes') // eslint-disable-line no-console
console.error(e) // eslint-disable-line no-console
process.exit(1)
throw e // eslint-disable-line no-unreachable
})
}) })
.then(() => { .then((generateRoutes) => {
/* /*
** Generate html files from routes ** Generate html files from routes
*/ */
let routes = [] generateRoutes.forEach((route) => {
this.routes.forEach((route) => { if (this.routes.indexOf(route) < 0) {
if (route.includes(':') || route.includes('*')) { this.routes.push(route)
const routeParams = this.options.generate.routeParams[route]
if (!routeParams) {
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)
}
route = route + '?'
const toPath = pathToRegexp.compile(route)
routes = routes.concat(routeParams.map((params) => {
return toPath(params)
}))
} else {
routes.push(route)
} }
}) })
let routes = this.routes
return co(function * () { return co(function * () {
while (routes.length) { while (routes.length) {
yield routes.splice(0, 500).map((route) => { yield routes.splice(0, 500).map((route) => {
return co(function * () { return co(function * () {
var { html } = yield self.renderRoute(route, { _generate: true }) var { html } = yield self.renderRoute(route, { _generate: true })
html = minify(html, { html = minify(html, self.options.generate.minify)
collapseBooleanAttributes: true,
collapseWhitespace: true,
decodeEntities: true,
minifyCSS: true,
minifyJS: true,
processConditionalComments: true,
removeAttributeQuotes: false,
removeComments: false,
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 var path = join(route, sep, 'index.html') // /about -> /about/index.html
debug('Generate file: ' + path) debug('Generate file: ' + path)
path = join(distPath, path) path = join(distPath, path)
@ -133,20 +119,3 @@ export default function () {
return this return this
}) })
} }
function resolveRouteParams (routeParams) {
let promises = []
Object.keys(routeParams).forEach(function (routePath) {
let promise = promisifyRouteParams(routeParams[routePath])
promise.then((routeParamsData) => {
routeParams[routePath] = routeParamsData
})
.catch((e) => {
console.error(`Could not resolve routeParams[${routePath}]`) // eslint-disable-line no-console
console.error(e) // eslint-disable-line no-console
process.exit(1)
})
promises.push(promise)
})
return Promise.all(promises)
}

View File

@ -2,10 +2,9 @@
import _ from 'lodash' import _ from 'lodash'
import co from 'co' import co from 'co'
import compression from 'compression'
import fs from 'fs-extra' import fs from 'fs-extra'
import pify from 'pify' import pify from 'pify'
import ansiHTML from 'ansi-html'
import serialize from 'serialize-javascript'
import Server from './server' import Server from './server'
import * as build from './build' import * as build from './build'
import * as render from './render' import * as render from './render'
@ -13,10 +12,8 @@ import generate from './generate'
import serveStatic from 'serve-static' import serveStatic from 'serve-static'
import { resolve, join } from 'path' import { resolve, join } from 'path'
import * as utils from './utils' import * as utils from './utils'
utils.setAnsiColors(ansiHTML)
class Nuxt { class Nuxt {
constructor (options = {}) { constructor (options = {}) {
var defaults = { var defaults = {
dev: true, dev: true,
@ -43,6 +40,16 @@ class Nuxt {
extendRoutes: null, extendRoutes: null,
scrollBehavior: null scrollBehavior: null
}, },
performance: {
gzip: {
threshold: 0
},
prefetch: true
},
watchers: {
webpack: {},
chokidar: {}
},
build: {} build: {}
} }
// Sanitization // Sanitization
@ -58,33 +65,32 @@ class Nuxt {
if (fs.existsSync(join(this.srcDir, 'store'))) { if (fs.existsSync(join(this.srcDir, 'store'))) {
this.options.store = true this.options.store = true
} }
// If middleware defined, update middleware option to true // If app.html is defined, set the template path to the user template
this.options.middleware = false this.options.appTemplatePath = resolve(__dirname, 'views/app.template.html')
if (fs.existsSync(join(this.srcDir, 'middleware'))) { if (fs.existsSync(join(this.srcDir, 'app.html'))) {
this.options.middleware = true this.options.appTemplatePath = join(this.srcDir, 'app.html')
} }
// Template
this.appTemplate = _.template(fs.readFileSync(resolve(__dirname, 'views', 'app.html'), 'utf8'), {
imports: { serialize }
})
this.errorTemplate = _.template(fs.readFileSync(resolve(__dirname, 'views', 'error.html'), 'utf8'), {
imports: {
ansiHTML,
encodeHtml: utils.encodeHtml
}
})
// renderer used by Vue.js (via createBundleRenderer) // renderer used by Vue.js (via createBundleRenderer)
this.renderer = null this.renderer = null
// For serving static/ files to / // For serving static/ files to /
this.serveStatic = pify(serveStatic(resolve(this.srcDir, 'static'))) this.serveStatic = pify(serveStatic(resolve(this.srcDir, 'static')))
// For serving .nuxt/dist/ files // For serving .nuxt/dist/ files (only when build.publicPath is not an URL)
this._nuxtRegexp = /^\/_nuxt\// this.serveStaticNuxt = pify(serveStatic(resolve(this.dir, '.nuxt', 'dist'), {
this.serveStaticNuxt = pify(serveStatic(resolve(this.dir, '.nuxt', 'dist'))) maxAge: (this.dev ? 0 : '1y') // 1 year in production
}))
// gzip for production
if (!this.dev && this.options.performance.gzip) {
this.gzipMiddleware = pify(compression(this.options.performance.gzip))
}
// Add this.Server Class // Add this.Server Class
this.Server = Server this.Server = Server
// Add this.build // Add this.build
build.options.call(this) // Add build options build.options.call(this) // Add build options
this.build = () => co(build.build.bind(this)) this.build = () => co(build.build.bind(this))
// Error template
this.errorTemplate = _.template(fs.readFileSync(resolve(__dirname, 'views', 'error.html'), 'utf8'), {
interpolate: /{{([\s\S]+?)}}/g
})
// Add this.render and this.renderRoute // Add this.render and this.renderRoute
this.render = render.render.bind(this) this.render = render.render.bind(this)
this.renderRoute = render.renderRoute.bind(this) this.renderRoute = render.renderRoute.bind(this)
@ -123,7 +129,6 @@ class Nuxt {
if (typeof callback === 'function') callback() if (typeof callback === 'function') callback()
}) })
} }
} }
export default Nuxt export default Nuxt

View File

@ -1,10 +1,13 @@
'use strict' 'use strict'
const debug = require('debug')('nuxt:render') import ansiHTML from 'ansi-html'
import co from 'co' import co from 'co'
import { urlJoin, getContext } from './utils' import serialize from 'serialize-javascript'
import { getContext, setAnsiColors, encodeHtml } from './utils'
const debug = require('debug')('nuxt:render')
// force blue color // force blue color
debug.color = 4 debug.color = 4
setAnsiColors(ansiHTML)
export function render (req, res) { export function render (req, res) {
if (!this.renderer && !this.dev) { if (!this.renderer && !this.dev) {
@ -12,20 +15,25 @@ export function render (req, res) {
process.exit(1) process.exit(1)
} }
/* istanbul ignore if */ /* istanbul ignore if */
if (!this.renderer) { if (!this.renderer || !this.appTemplate) {
setTimeout(() => { return new Promise((resolve) => {
this.render(req, res) setTimeout(() => {
}, 1000) resolve(this.render(req, res))
return }, 1000)
})
} }
const self = this const self = this
const context = getContext(req, res) const context = getContext(req, res)
return co(function * () { return co(function * () {
res.statusCode = 200
if (self.dev) { if (self.dev) {
// Call webpack middleware only in development // Call webpack middleware only in development
yield self.webpackDevMiddleware(req, res) yield self.webpackDevMiddleware(req, res)
yield self.webpackHotMiddleware(req, res) yield self.webpackHotMiddleware(req, res)
} }
if (!self.dev && self.options.performance.gzip) {
yield self.gzipMiddleware(req, res)
}
// If base in req.url, remove it for the middleware and vue-router // If base in req.url, remove it for the middleware and vue-router
if (self.options.router.base !== '/' && req.url.indexOf(self.options.router.base) === 0) { if (self.options.router.base !== '/' && req.url.indexOf(self.options.router.base) === 0) {
// Compatibility with base url for dev server // Compatibility with base url for dev server
@ -34,9 +42,9 @@ export function render (req, res) {
// Serve static/ files // Serve static/ files
yield self.serveStatic(req, res) yield self.serveStatic(req, res)
// Serve .nuxt/dist/ files (only for production) // Serve .nuxt/dist/ files (only for production)
if (!self.dev && self._nuxtRegexp.test(req.url)) { if (!self.dev && req.url.indexOf(self.options.build.publicPath) === 0) {
const url = req.url const url = req.url
req.url = req.url.replace(self._nuxtRegexp, '/') req.url = req.url.replace(self.options.build.publicPath, '/')
yield self.serveStaticNuxt(req, res) yield self.serveStaticNuxt(req, res)
/* istanbul ignore next */ /* istanbul ignore next */
req.url = url req.url = url
@ -44,23 +52,39 @@ export function render (req, res) {
}) })
.then(() => { .then(() => {
/* istanbul ignore if */ /* istanbul ignore if */
if (this.dev && this._nuxtRegexp.test(req.url) && req.url.includes('.hot-update.json')) { if (this.dev && req.url.indexOf(self.options.build.publicPath) === 0 && req.url.includes('.hot-update.json')) {
res.statusCode = 404 res.statusCode = 404
return res.end() return { html: '' }
} }
return this.renderRoute(req.url, context) return this.renderRoute(req.url, context)
}) })
.then(({ html, error }) => { .then(({ html, error, redirected }) => {
if (redirected) {
return html
}
if (error) { if (error) {
res.statusCode = context.nuxt.error.statusCode || 500 res.statusCode = context.nuxt.error.statusCode || 500
} }
res.setHeader('Content-Type', 'text/html; charset=utf-8') res.setHeader('Content-Type', 'text/html; charset=utf-8')
res.setHeader('Content-Length', Buffer.byteLength(html)) res.setHeader('Content-Length', Buffer.byteLength(html))
res.end(html, 'utf8') res.end(html, 'utf8')
return html
}) })
.catch((err) => { .catch((err) => {
/* istanbul ignore if */
if (context.redirected) {
console.error(err) // eslint-disable-line no-console
return err
}
const html = this.errorTemplate({
error: err,
stack: ansiHTML(encodeHtml(err.stack))
})
res.statusCode = 500 res.statusCode = 500
res.end(this.errorTemplate({ err }), 'utf8') res.setHeader('Content-Type', 'text/html; charset=utf-8')
res.setHeader('Content-Length', Buffer.byteLength(html))
res.end(html, 'utf8')
return err
}) })
} }
@ -72,20 +96,22 @@ export function renderRoute (url, context = {}) {
// Call rendertoSting from the bundleRenderer and generate the HTML (will update the context as well) // Call rendertoSting from the bundleRenderer and generate the HTML (will update the context as well)
const self = this const self = this
return co(function * () { return co(function * () {
let app = yield self.renderToString(context) let APP = yield self.renderToString(context)
if (!context.nuxt.serverRendered) { if (!context.nuxt.serverRendered) {
app = '<div id="__nuxt"></div>' APP = '<div id="__nuxt"></div>'
} }
const m = context.meta.inject()
let HEAD = m.meta.text() + m.title.text() + m.link.text() + m.style.text() + m.script.text() + m.noscript.text()
if (self.options.router.base !== '/') {
HEAD += `<base href="${self.options.router.base}">`
}
HEAD += context.styles
APP += `<script type="text/javascript">window.__NUXT__=${serialize(context.nuxt, { isJSON: true })}</script>`
const html = self.appTemplate({ const html = self.appTemplate({
dev: self.dev, // Use to add the extracted CSS <link> in production HTML_ATTRS: 'data-n-head-ssr ' + m.htmlAttrs.text(),
baseUrl: self.options.router.base, BODY_ATTRS: m.bodyAttrs.text(),
APP: app, HEAD,
context: context, APP
files: {
css: urlJoin(self.options.router.base, '/_nuxt/', self.options.build.filenames.css),
vendor: urlJoin(self.options.router.base, '/_nuxt/', self.options.build.filenames.vendor),
app: urlJoin(self.options.router.base, '/_nuxt/', self.options.build.filenames.app)
}
}) })
return { return {
html, html,
@ -128,10 +154,9 @@ export function renderAndGetWindow (url, opts = {}) {
window.scrollTo = function () {} window.scrollTo = function () {}
// If Nuxt could not be loaded (error from the server-side) // If Nuxt could not be loaded (error from the server-side)
if (!window.__NUXT__) { if (!window.__NUXT__) {
return reject({ let error = new Error('Could not load the nuxt app')
message: 'Could not load the nuxt app', error.body = window.document.getElementsByTagName('body')[0].innerHTML
body: window.document.getElementsByTagName('body')[0].innerHTML return reject(error)
})
} }
// Used by nuxt.js to say when the components are loaded and the app ready // Used by nuxt.js to say when the components are loaded and the app ready
window.onNuxtReady(() => { window.onNuxtReady(() => {

View File

@ -3,7 +3,6 @@
const http = require('http') const http = require('http')
class Server { class Server {
constructor (nuxt) { constructor (nuxt) {
this.nuxt = nuxt this.nuxt = nuxt
this.server = http.createServer(this.render.bind(this)) this.server = http.createServer(this.render.bind(this))
@ -16,7 +15,7 @@ class Server {
} }
listen (port, host) { listen (port, host) {
host = host || 'localhost' host = host || '127.0.0.1'
port = port || 3000 port = port || 3000
this.server.listen(port, host, () => { this.server.listen(port, host, () => {
console.log('Ready on http://%s:%s', host, port) // eslint-disable-line no-console console.log('Ready on http://%s:%s', host, port) // eslint-disable-line no-console
@ -27,7 +26,6 @@ class Server {
close (cb) { close (cb) {
return this.server.close(cb) return this.server.close(cb)
} }
} }
export default Server export default Server

View File

@ -28,15 +28,19 @@ export function * waitFor (ms) {
} }
export function urlJoin () { export function urlJoin () {
return [].slice.call(arguments).join('/').replace(/\/+/g, '/') return [].slice.call(arguments).join('/').replace(/\/+/g, '/').replace(':/', '://')
} }
export function promisifyRouteParams (fn) { export function isUrl (url) {
// If routeParams[route] is an array return (url.indexOf('http') === 0 || url.indexOf('//') === 0)
}
export function promisifyRoute (fn) {
// If routes is an array
if (Array.isArray(fn)) { if (Array.isArray(fn)) {
return Promise.resolve(fn) return Promise.resolve(fn)
} }
// If routeParams[route] is a function expecting a callback // If routes is a function expecting a callback
if (fn.length === 1) { if (fn.length === 1) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
fn(function (err, routeParams) { fn(function (err, routeParams) {
@ -48,7 +52,7 @@ export function promisifyRouteParams (fn) {
}) })
} }
let promise = fn() let promise = fn()
if (!(promise instanceof Promise)) { if (!promise || (!(promise instanceof Promise) && (typeof promise.then !== 'function'))) {
promise = Promise.resolve(promise) promise = Promise.resolve(promise)
} }
return promise return promise

View File

@ -1,19 +0,0 @@
<% var m = context.meta.inject() %><!DOCTYPE html>
<html n-head-ssr <%= m.htmlAttrs.text() %>>
<head>
<%= m.meta.text() %>
<%= m.title.text() %>
<%= m.link.text() %>
<%= m.style.text() %>
<%= m.script.text() %>
<%= m.noscript.text() %>
<% if (baseUrl !== '/') { %><base href="<%= baseUrl %>"><% } %>
<% if (!dev) { %><link rel="stylesheet" href="<%= files.css %>"><% } %>
</head>
<body <%= m.bodyAttrs.text() %>>
<%= APP %>
<script type="text/javascript" defer>window.__NUXT__=<%= serialize(context.nuxt, { isJSON: true }) %></script>
<script src="<%= files.vendor %>" defer></script>
<script src="<%= files.app %>" defer></script>
</body>
</html>

View File

@ -0,0 +1,9 @@
<!DOCTYPE html>
<html {{ HTML_ATTRS }}>
<head>
{{ HEAD }}
</head>
<body {{ BODY_ATTRS }}>
{{ APP }}
</body>
</html>

View File

@ -2,10 +2,10 @@
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>Vue.js error</title> <title>Nuxt.js error</title>
</head> </head>
<body style="background-color: #a6004c;color: #efe;font-family: monospace;"> <body style="background-color: #a6004c;color: #efe;font-family: monospace;">
<h2>Vue.js error</h2> <h2>Nuxt.js error</h2>
<pre><%= ansiHTML(encodeHtml(err.stack)) %></pre> <pre>{{ stack }}</pre>
</body> </body>
</html> </html>

View File

@ -3,7 +3,7 @@
import vueLoaderConfig from './vue-loader.config' import vueLoaderConfig from './vue-loader.config'
import { defaults } from 'lodash' import { defaults } from 'lodash'
import { join } from 'path' import { join } from 'path'
import { urlJoin } from '../utils' import { isUrl, urlJoin } from '../utils'
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -16,14 +16,16 @@ import { urlJoin } from '../utils'
export default function ({ isClient, isServer }) { export default function ({ isClient, isServer }) {
const nodeModulesDir = join(__dirname, '..', 'node_modules') const nodeModulesDir = join(__dirname, '..', 'node_modules')
let config = { let config = {
devtool: 'source-map', devtool: (this.dev ? 'cheap-module-eval-source-map' : false),
entry: { entry: {
vendor: ['vue', 'vue-router', 'vue-meta'] vendor: ['vue', 'vue-router', 'vue-meta']
}, },
output: { output: {
publicPath: urlJoin(this.options.router.base, '/_nuxt/') publicPath: (isUrl(this.options.build.publicPath) ? this.options.build.publicPath : urlJoin(this.options.router.base, this.options.build.publicPath))
}, },
performance: { performance: {
maxEntrypointSize: 300000,
maxAssetSize: 300000,
hints: (this.dev ? false : 'warning') hints: (this.dev ? false : 'warning')
}, },
resolve: { resolve: {
@ -64,17 +66,15 @@ export default function ({ isClient, isServer }) {
loader: 'babel-loader', loader: 'babel-loader',
exclude: /node_modules/, exclude: /node_modules/,
query: defaults(this.options.build.babel, { query: defaults(this.options.build.babel, {
plugins: [ presets: ['vue-app'],
'transform-async-to-generator',
'transform-runtime'
],
presets: [
['es2015', { modules: false }],
'stage-2'
],
cacheDirectory: !!this.dev cacheDirectory: !!this.dev
}) })
} },
{ test: /\.css$/, loader: 'vue-style-loader!css-loader' },
{ test: /\.less$/, loader: 'vue-style-loader!css-loader!less-loader' },
{ test: /\.sass$/, loader: 'vue-style-loader!css-loader!sass-loader?indentedSyntax' },
{ test: /\.scss$/, loader: 'vue-style-loader!css-loader!sass-loader' },
{ test: /\.styl(us)?$/, loader: 'vue-style-loader!css-loader!stylus-loader' }
] ]
}, },
plugins: this.options.build.plugins plugins: this.options.build.plugins

View File

@ -2,7 +2,10 @@
import { each } from 'lodash' import { each } from 'lodash'
import webpack from 'webpack' import webpack from 'webpack'
import ExtractTextPlugin from 'extract-text-webpack-plugin' import HTMLPlugin from 'html-webpack-plugin'
import FriendlyErrorsWebpackPlugin from 'friendly-errors-webpack-plugin'
import ScriptExtHtmlWebpackPlugin from 'script-ext-html-webpack-plugin'
import PreloadWebpackPlugin from 'preload-webpack-plugin'
import ProgressBarPlugin from 'progress-bar-webpack-plugin' import ProgressBarPlugin from 'progress-bar-webpack-plugin'
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer' import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'
import OfflinePlugin from 'offline-plugin' import OfflinePlugin from 'offline-plugin'
@ -74,38 +77,60 @@ export default function () {
}) })
// Webpack plugins // Webpack plugins
config.plugins = (config.plugins || []).concat([ config.plugins = (config.plugins || []).concat([
// strip comments in Vue code // Strip comments in Vue code
new webpack.DefinePlugin(Object.assign(env, { new webpack.DefinePlugin(Object.assign(env, {
'process.env.NODE_ENV': JSON.stringify(this.dev ? 'development' : 'production'), 'process.env.NODE_ENV': JSON.stringify(this.dev ? 'development' : 'production'),
'process.BROWSER_BUILD': true, 'process.BROWSER_BUILD': true,
'process.SERVER_BUILD': false 'process.SERVER_BUILD': false,
'process.browser': true,
'process.server': true
})), })),
// Extract vendor chunks for better caching // Extract vendor chunks for better caching
new webpack.optimize.CommonsChunkPlugin({ new webpack.optimize.CommonsChunkPlugin({
name: 'vendor', name: 'vendor',
filename: this.options.build.filenames.vendor filename: this.options.build.filenames.vendor
}),
// Extract manifest
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
filename: this.options.build.filenames.manifest
}),
// Generate output HTML
new HTMLPlugin({
template: this.options.appTemplatePath
}),
// Add defer to scripts
new ScriptExtHtmlWebpackPlugin({
defaultAttribute: 'defer'
}) })
]) ])
if (!this.dev && this.options.performance.prefetch === true) {
// Add prefetch code-splitted routes
config.plugins.push(
new PreloadWebpackPlugin({
rel: 'prefetch'
})
)
}
// client bundle progress bar // client bundle progress bar
config.plugins.push( config.plugins.push(
new ProgressBarPlugin() new ProgressBarPlugin()
) )
// Add friendly error plugin
if (this.dev) {
config.plugins.push(new FriendlyErrorsWebpackPlugin())
}
// Production client build // Production client build
if (!this.dev) { if (!this.dev) {
config.plugins.push( config.plugins.push(
// Use ExtractTextPlugin to extract CSS into a single file
new ExtractTextPlugin({
filename: this.options.build.filenames.css,
allChunks: true
}),
// This is needed in webpack 2 for minifying CSS // This is needed in webpack 2 for minifying CSS
new webpack.LoaderOptionsPlugin({ new webpack.LoaderOptionsPlugin({
minimize: true minimize: true
}), }),
// Minify JS // Minify JS
new webpack.optimize.UglifyJsPlugin({ new webpack.optimize.UglifyJsPlugin({
sourceMap: true,
compress: { compress: {
warnings: false warnings: false
} }
@ -114,7 +139,7 @@ export default function () {
} }
// Extend config // Extend config
if (typeof this.options.build.extend === 'function') { if (typeof this.options.build.extend === 'function') {
this.options.build.extend(config, { this.options.build.extend.call(this, config, {
dev: this.dev, dev: this.dev,
isClient: true isClient: true
}) })

View File

@ -1,6 +1,7 @@
'use strict' 'use strict'
import webpack from 'webpack' import webpack from 'webpack'
import VueSSRPlugin from 'vue-ssr-webpack-plugin'
import base from './base.config.js' import base from './base.config.js'
import { each, uniq } from 'lodash' import { each, uniq } from 'lodash'
import { existsSync, readFileSync } from 'fs' import { existsSync, readFileSync } from 'fs'
@ -22,22 +23,37 @@ export default function () {
config = Object.assign(config, { config = Object.assign(config, {
target: 'node', target: 'node',
devtool: false, devtool: (this.dev ? 'source-map' : false),
entry: resolve(this.dir, '.nuxt', 'server.js'), entry: resolve(this.dir, '.nuxt', 'server.js'),
output: Object.assign({}, config.output, { output: Object.assign({}, config.output, {
path: resolve(this.dir, '.nuxt', 'dist'), path: resolve(this.dir, '.nuxt', 'dist'),
filename: 'server-bundle.js', filename: 'server-bundle.js',
libraryTarget: 'commonjs2' libraryTarget: 'commonjs2'
}), }),
performance: {
hints: false
},
plugins: (config.plugins || []).concat([ plugins: (config.plugins || []).concat([
new VueSSRPlugin({
filename: 'server-bundle.json'
}),
new webpack.DefinePlugin(Object.assign(env, { new webpack.DefinePlugin(Object.assign(env, {
'process.env.NODE_ENV': JSON.stringify(this.dev ? 'development' : 'production'), 'process.env.NODE_ENV': JSON.stringify(this.dev ? 'development' : 'production'),
'process.BROWSER_BUILD': false, 'process.BROWSER_BUILD': false, // deprecated
'process.SERVER_BUILD': true 'process.SERVER_BUILD': true, // deprecated
'process.browser': false,
'process.server': true
})) }))
]) ])
}) })
// This is needed in webpack 2 for minifying CSS
if (!this.dev) {
config.plugins.push(
new webpack.LoaderOptionsPlugin({
minimize: true
})
)
}
// Externals // Externals
const nuxtPackageJson = require('../../package.json') const nuxtPackageJson = require('../../package.json')
const projectPackageJsonPath = resolve(this.dir, 'package.json') const projectPackageJsonPath = resolve(this.dir, 'package.json')
@ -48,6 +64,7 @@ export default function () {
config.externals = config.externals.concat(Object.keys(projectPackageJson.dependencies || {})) config.externals = config.externals.concat(Object.keys(projectPackageJson.dependencies || {}))
} catch (e) {} } catch (e) {}
} }
config.externals = config.externals.concat(this.options.build.vendor)
config.externals = uniq(config.externals) config.externals = uniq(config.externals)
// Extend config // Extend config

View File

@ -4,19 +4,14 @@ import { defaults } from 'lodash'
export default function ({ isClient }) { export default function ({ isClient }) {
let babelOptions = JSON.stringify(defaults(this.options.build.babel, { let babelOptions = JSON.stringify(defaults(this.options.build.babel, {
plugins: [ presets: ['vue-app'],
'transform-async-to-generator', cacheDirectory: !!this.dev
'transform-runtime'
],
presets: [
['es2015', { modules: false }],
'stage-2'
]
})) }))
let config = { let config = {
postcss: this.options.build.postcss, postcss: this.options.build.postcss,
loaders: { loaders: {
'js': 'babel-loader?' + babelOptions, 'js': 'babel-loader?' + babelOptions,
'css': 'vue-style-loader!css-loader',
'less': 'vue-style-loader!css-loader!less-loader', 'less': 'vue-style-loader!css-loader!less-loader',
'sass': 'vue-style-loader!css-loader!sass-loader?indentedSyntax', 'sass': 'vue-style-loader!css-loader!sass-loader?indentedSyntax',
'scss': 'vue-style-loader!css-loader!sass-loader', 'scss': 'vue-style-loader!css-loader!sass-loader',
@ -25,17 +20,6 @@ export default function ({ isClient }) {
}, },
preserveWhitespace: false preserveWhitespace: false
} }
if (!this.dev && isClient) {
// Use ExtractTextPlugin to extract CSS into a single file
const ExtractTextPlugin = require('extract-text-webpack-plugin')
config.loaders.css = ExtractTextPlugin.extract({ loader: 'css-loader' })
config.loaders.scss = ExtractTextPlugin.extract({ loader: 'css-loader!sass-loader', fallbackLoader: 'vue-style-loader' })
config.loaders.sass = ExtractTextPlugin.extract({ loader: 'css-loader!sass-loader?indentedSyntax', fallbackLoader: 'vue-style-loader' })
config.loaders.stylus = ExtractTextPlugin.extract({ loader: 'css-loader!stylus-loader', fallbackLoader: 'vue-style-loader' })
config.loaders.less = ExtractTextPlugin.extract({ loader: 'css-loader!less-loader', fallbackLoader: 'vue-style-loader' })
}
// Return the config // Return the config
return config return config
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "nuxt", "name": "nuxt",
"version": "0.9.9", "version": "0.10.5",
"description": "A minimalistic framework for server-rendered Vue.js applications (inspired by Next.js)", "description": "A minimalistic framework for server-rendered Vue.js applications (inspired by Next.js)",
"contributors": [ "contributors": [
{ {
@ -38,7 +38,7 @@
"nuxt": "./bin/nuxt" "nuxt": "./bin/nuxt"
}, },
"scripts": { "scripts": {
"test": "nyc ava --serial test/", "test": "nyc ava --verbose --serial test/",
"coverage": "nyc report --reporter=text-lcov > coverage.lcov && codecov", "coverage": "nyc report --reporter=text-lcov > coverage.lcov && codecov",
"lint": "eslint --ext .js,.vue bin lib pages test/*.js --ignore-pattern lib/app", "lint": "eslint --ext .js,.vue bin lib pages test/*.js --ignore-pattern lib/app",
"build": "webpack", "build": "webpack",
@ -52,63 +52,70 @@
}, },
"dependencies": { "dependencies": {
"ansi-html": "^0.0.7", "ansi-html": "^0.0.7",
"autoprefixer": "^6.7.2", "autoprefixer": "^6.7.7",
"babel-core": "^6.22.1", "babel-core": "^6.24.0",
"babel-loader": "^6.2.10", "babel-loader": "^6.4.1",
"babel-plugin-array-includes": "^2.0.3", "babel-preset-es2015": "^6.24.0",
"babel-plugin-transform-async-to-generator": "^6.22.0", "babel-preset-vue-app": "^1.1.1",
"babel-plugin-transform-runtime": "^6.22.0",
"babel-preset-es2015": "^6.22.0",
"babel-preset-stage-2": "^6.22.0",
"chalk": "^1.1.3",
"chokidar": "^1.6.1", "chokidar": "^1.6.1",
"co": "^4.6.0", "co": "^4.6.0",
"css-loader": "^0.26.1", "compression": "^1.6.2",
"debug": "^2.6.1", "css-loader": "^0.27.3",
"extract-text-webpack-plugin": "2.0.0-beta.4", "debug": "^2.6.3",
"file-loader": "^0.10.0", "file-loader": "^0.10.1",
"fs-extra": "^2.0.0", "friendly-errors-webpack-plugin": "^1.6.1",
"fs-extra": "^2.1.2",
"glob": "^7.1.1", "glob": "^7.1.1",
"hash-sum": "^1.0.2", "hash-sum": "^1.0.2",
"html-minifier": "^3.3.1", "html-minifier": "^3.4.2",
"html-webpack-plugin": "^2.28.0",
"lodash": "^4.17.4", "lodash": "^4.17.4",
"lru-cache": "^4.0.2", "lru-cache": "^4.0.2",
"memory-fs": "^0.4.1", "memory-fs": "^0.4.1",
"path-to-regexp": "^1.7.0", "offline-plugin": "^4.6.1",
"pify": "^2.3.0", "pify": "^2.3.0",
"post-compile-webpack-plugin": "^0.1.1", "post-compile-webpack-plugin": "^0.1.1",
"preload-webpack-plugin": "^1.2.1",
"progress-bar-webpack-plugin": "^1.9.3", "progress-bar-webpack-plugin": "^1.9.3",
"script-ext-html-webpack-plugin": "^1.7.1",
"serialize-javascript": "^1.3.0", "serialize-javascript": "^1.3.0",
"serve-static": "^1.11.2", "serve-static": "^1.12.1",
"url-loader": "^0.5.7", "url-loader": "^0.5.8",
"vue": "^2.1.10", "vue": "^2.2.5",
"vue-loader": "^10.3.0", "vue-loader": "^11.3.3",
"vue-meta": "^0.5.3", "vue-meta": "^0.5.5",
"vue-router": "^2.2.0", "vue-router": "^2.3.0",
"vue-server-renderer": "^2.1.10", "vue-server-renderer": "^2.2.5",
"vue-template-compiler": "^2.1.10", "vue-ssr-html-stream": "^2.2.0",
"vuex": "^2.1.2", "vue-ssr-webpack-plugin": "^1.0.2",
"webpack": "^2.2.1", "vue-template-compiler": "^2.2.5",
"webpack-bundle-analyzer": "^2.2.3", "vuex": "^2.2.1",
"webpack-dev-middleware": "^1.10.0", "webpack": "^2.3.2",
"webpack-hot-middleware": "^2.16.1" "webpack-bundle-analyzer": "^2.3.1",
"webpack-dev-middleware": "^1.10.1",
"webpack-hot-middleware": "^2.17.1"
}, },
"devDependencies": { "devDependencies": {
"ava": "^0.18.1", "ava": "^0.18.2",
"babel-eslint": "^7.1.1", "babel-eslint": "^7.2.1",
"codecov": "^1.0.1", "babel-plugin-array-includes": "^2.0.3",
"babel-plugin-transform-async-to-generator": "^6.22.0",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-stage-2": "^6.22.0",
"codecov": "^2.1.0",
"copy-webpack-plugin": "^4.0.1", "copy-webpack-plugin": "^4.0.1",
"eslint": "^3.15.0", "eslint": "^3.18.0",
"eslint-config-standard": "^6.2.1", "eslint-config-standard": "^8.0.0-beta.2",
"eslint-plugin-html": "^2.0.0", "eslint-plugin-html": "^2.0.1",
"eslint-plugin-promise": "^3.4.1", "eslint-plugin-import": "^2.2.0",
"eslint-plugin-standard": "^2.0.1", "eslint-plugin-node": "^4.2.1",
"finalhandler": "^0.5.1", "eslint-plugin-promise": "^3.5.0",
"jsdom": "^9.10.0", "eslint-plugin-standard": "^2.1.1",
"finalhandler": "^1.0.1",
"jsdom": "^9.12.0",
"json-loader": "^0.5.4", "json-loader": "^0.5.4",
"nyc": "^10.1.2", "nyc": "^10.2.0-candidate.0",
"offline-plugin": "^4.6.1", "request": "^2.81.0",
"request": "^2.79.0",
"request-promise-native": "^1.0.3", "request-promise-native": "^1.0.3",
"webpack-node-externals": "^1.5.4" "webpack-node-externals": "^1.5.4"
} }

View File

@ -1,42 +1,16 @@
import test from 'ava' import test from 'ava'
import { resolve } from 'path' import { resolve } from 'path'
test('Fail to generate without routeParams', t => { test('Fail with routes() which throw an error', t => {
const Nuxt = require('../')
const options = {
rootDir: resolve(__dirname, 'fixtures/basic'),
dev: false
// no generate.routeParams
}
const nuxt = new Nuxt(options)
return new Promise((resolve) => {
var oldExit = process.exit
var oldCE = console.error // eslint-disable-line no-console
var _log = ''
console.error = (s) => { _log += s } // eslint-disable-line no-console
process.exit = (code) => {
process.exit = oldExit
console.error = oldCE // eslint-disable-line no-console
t.is(code, 1)
t.true(_log.includes('Could not generate the dynamic route /users/:id'))
resolve()
}
nuxt.generate()
})
})
test('Fail with routeParams which throw an error', t => {
const Nuxt = require('../') const Nuxt = require('../')
const options = { const options = {
rootDir: resolve(__dirname, 'fixtures/basic'), rootDir: resolve(__dirname, 'fixtures/basic'),
dev: false, dev: false,
generate: { generate: {
routeParams: { routes: function () {
'/users/:id': function () { return new Promise((resolve, reject) => {
return new Promise((resolve, reject) => { reject(new Error('Not today!'))
reject('Not today!') })
})
}
} }
} }
} }
@ -50,12 +24,12 @@ test('Fail with routeParams which throw an error', t => {
process.exit = oldExit process.exit = oldExit
console.error = oldCE // eslint-disable-line no-console console.error = oldCE // eslint-disable-line no-console
t.is(code, 1) t.is(code, 1)
t.true(_log.includes('Could not resolve routeParams[/users/:id]')) t.true(_log.includes('Could not resolve routes'))
resolve() resolve()
} }
nuxt.generate() nuxt.generate()
.catch((e) => { .catch((e) => {
t.true(e === 'Not today!') t.true(e.message === 'Not today!')
}) })
}) })
}) })

View File

@ -42,6 +42,11 @@ test('/stateful', async t => {
t.true(html.includes('<div><p>The answer is 42</p></div>')) t.true(html.includes('<div><p>The answer is 42</p></div>'))
}) })
test('/store', async t => {
const { html } = await nuxt.renderRoute('/store')
t.true(html.includes('<h1>Vuex Nested Modules</h1>'))
})
test('/head', async t => { test('/head', async t => {
const window = await nuxt.renderAndGetWindow(url('/head'), { virtualConsole: false }) const window = await nuxt.renderAndGetWindow(url('/head'), { virtualConsole: false })
const html = window.document.body.innerHTML const html = window.document.body.innerHTML

View File

@ -1,11 +1,9 @@
module.exports = { module.exports = {
generate: { generate: {
routeParams: { routes: [
'/users/:id': [ '/users/1',
{ id: 1 }, '/users/2',
{ id: 2 }, '/users/3'
{ id: 3 } ]
]
}
} }
} }

View File

@ -4,7 +4,7 @@
<script> <script>
export default { export default {
data () { asyncData () {
return new Promise((resolve) => { return new Promise((resolve) => {
setTimeout(() => resolve({ name: 'Nuxt.js' }), 10) setTimeout(() => resolve({ name: 'Nuxt.js' }), 10)
}) })

View File

@ -10,7 +10,7 @@ const fetchData = () => {
} }
export default { export default {
async data () { async asyncData () {
return await fetchData() return await fetchData()
} }
} }

View File

@ -4,7 +4,7 @@
<script> <script>
export default { export default {
async data (context, callback) { async asyncData (context, callback) {
setTimeout(function () { setTimeout(function () {
callback(null, { name: 'Callback Nuxt.js' }) callback(null, { name: 'Callback Nuxt.js' })
}, 10) }, 10)

View File

@ -4,7 +4,7 @@
<script> <script>
export default { export default {
data () { asyncData () {
throw new Error('Error mouahahah') throw new Error('Error mouahahah')
} }
} }

View File

@ -4,7 +4,7 @@
<script> <script>
export default { export default {
data ({ error }) { asyncData ({ error }) {
error({ message: 'Custom error' }) error({ message: 'Custom error' })
} }
} }

11
test/fixtures/basic/pages/store.vue vendored Normal file
View File

@ -0,0 +1,11 @@
<template>
<h1>{{ baz }}</h1>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
computed: mapGetters('foo/bar', ['baz'])
}
</script>

View File

@ -4,7 +4,7 @@
<script> <script>
export default { export default {
data ({ params }) { asyncData ({ params }) {
return { id: params.id } return { id: params.id }
} }
} }

9
test/fixtures/basic/store/foo/bar.js vendored Normal file
View File

@ -0,0 +1,9 @@
export const state = {
baz: 'Vuex Nested Modules'
}
export const getters = {
baz (state) {
return state.baz
}
}

10
test/fixtures/with-config/app.html vendored Normal file
View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html {{ HTML_ATTRS }}>
<head>
{{ HEAD }}
</head>
<body {{ BODY_ATTRS }}>
{{ APP }}
<p>Made by Nuxt.js team</p>
</body>
</html>

View File

@ -1,3 +1,9 @@
<template> <template>
<h1>Error page</h1> <h1>Error page</h1>
</template> </template>
<script>
export default {
layout: 'custom'
}
</script>

View File

@ -10,7 +10,10 @@ module.exports = {
} }
}, },
cache: true, cache: true,
plugins: ['~plugins/test.js'], plugins: [
'~plugins/test.js',
{ src: '~plugins/only-client.js', ssr: false }
],
loading: '~components/loading', loading: '~components/loading',
env: { env: {
bool: true, bool: true,
@ -18,6 +21,7 @@ module.exports = {
string: 'Nuxt.js' string: 'Nuxt.js'
}, },
build: { build: {
publicPath: '/orion/',
analyze: { analyze: {
analyzerMode: 'disabled', analyzerMode: 'disabled',
generateStatsFile: true generateStatsFile: true

View File

@ -5,7 +5,7 @@
<script> <script>
export default { export default {
layout: 'custom-env', layout: 'custom-env',
data ({ env }) { asyncData ({ env }) {
return { env } return { env }
} }
} }

View File

@ -0,0 +1,11 @@
<template>
<p>Never displayed</p>
</template>
<script>
export default {
fetch ({ error }) {
error({ message: 'Nuxt Error', statusCode: 300 })
}
}
</script>

View File

@ -5,7 +5,7 @@
<script> <script>
export default { export default {
middleware: 'user-agent', middleware: 'user-agent',
data ({ userAgent }) { asyncData ({ userAgent }) {
return { userAgent } return { userAgent }
} }
} }

View File

@ -0,0 +1 @@
// Custom plugin (only included on client-side)

View File

@ -37,9 +37,9 @@ test('urlJoin', t => {
t.is(utils.urlJoin('test', '/about'), 'test/about') t.is(utils.urlJoin('test', '/about'), 'test/about')
}) })
test('promisifyRouteParams (array)', t => { test('promisifyRoute (array)', t => {
const array = [1] const array = [1]
const promise = utils.promisifyRouteParams(array) const promise = utils.promisifyRoute(array)
t.is(typeof promise, 'object') t.is(typeof promise, 'object')
return promise return promise
.then((res) => { .then((res) => {
@ -47,12 +47,12 @@ test('promisifyRouteParams (array)', t => {
}) })
}) })
test('promisifyRouteParams (fn => array)', t => { test('promisifyRoute (fn => array)', t => {
const array = [1, 2] const array = [1, 2]
const fn = function () { const fn = function () {
return array return array
} }
const promise = utils.promisifyRouteParams(fn) const promise = utils.promisifyRoute(fn)
t.is(typeof promise, 'object') t.is(typeof promise, 'object')
return promise return promise
.then((res) => { .then((res) => {
@ -60,14 +60,14 @@ test('promisifyRouteParams (fn => array)', t => {
}) })
}) })
test('promisifyRouteParams (fn => promise)', t => { test('promisifyRoute (fn => promise)', t => {
const array = [1, 2, 3] const array = [1, 2, 3]
const fn = function () { const fn = function () {
return new Promise((resolve) => { return new Promise((resolve) => {
resolve(array) resolve(array)
}) })
} }
const promise = utils.promisifyRouteParams(fn) const promise = utils.promisifyRoute(fn)
t.is(typeof promise, 'object') t.is(typeof promise, 'object')
return promise return promise
.then((res) => { .then((res) => {
@ -75,24 +75,24 @@ test('promisifyRouteParams (fn => promise)', t => {
}) })
}) })
test('promisifyRouteParams (fn(cb) with error)', t => { test('promisifyRoute (fn(cb) with error)', t => {
const fn = function (cb) { const fn = function (cb) {
cb('Error here') cb(new Error('Error here'))
} }
const promise = utils.promisifyRouteParams(fn) const promise = utils.promisifyRoute(fn)
t.is(typeof promise, 'object') t.is(typeof promise, 'object')
return promise return promise
.catch((e) => { .catch((e) => {
t.is(e, 'Error here') t.is(e.message, 'Error here')
}) })
}) })
test('promisifyRouteParams (fn(cb) with result)', t => { test('promisifyRoute (fn(cb) with result)', t => {
const array = [1, 2, 3, 4] const array = [1, 2, 3, 4]
const fn = function (cb) { const fn = function (cb) {
cb(null, array) cb(null, array)
} }
const promise = utils.promisifyRouteParams(fn) const promise = utils.promisifyRoute(fn)
t.is(typeof promise, 'object') t.is(typeof promise, 'object')
return promise return promise
.then((res) => { .then((res) => {

View File

@ -24,9 +24,20 @@ test('/', async t => {
t.true(html.includes('<h1>I have custom configurations</h1>')) t.true(html.includes('<h1>I have custom configurations</h1>'))
}) })
test('/ (custom app.html)', async t => {
const { html } = await nuxt.renderRoute('/')
t.true(html.includes('<p>Made by Nuxt.js team</p>'))
})
test('/ (custom build.publicPath)', async t => {
const { html } = await nuxt.renderRoute('/')
t.true(html.includes('src="/test/orion/vendor.bundle'))
})
test('/test/ (router base)', async t => { test('/test/ (router base)', async t => {
const window = await nuxt.renderAndGetWindow(url('/test/')) const window = await nuxt.renderAndGetWindow(url('/test/'))
const html = window.document.body.innerHTML const html = window.document.body.innerHTML
t.is(window.__NUXT__.layout, 'default')
t.true(html.includes('<h1>Default layout</h1>')) t.true(html.includes('<h1>Default layout</h1>'))
t.true(html.includes('<h1>I have custom configurations</h1>')) t.true(html.includes('<h1>I have custom configurations</h1>'))
}) })
@ -34,6 +45,7 @@ test('/test/ (router base)', async t => {
test('/test/about (custom layout)', async t => { test('/test/about (custom layout)', async t => {
const window = await nuxt.renderAndGetWindow(url('/test/about')) const window = await nuxt.renderAndGetWindow(url('/test/about'))
const html = window.document.body.innerHTML const html = window.document.body.innerHTML
t.is(window.__NUXT__.layout, 'custom')
t.true(html.includes('<h1>Custom layout</h1>')) t.true(html.includes('<h1>Custom layout</h1>'))
t.true(html.includes('<h1>About page</h1>')) t.true(html.includes('<h1>About page</h1>'))
}) })
@ -47,6 +59,14 @@ test('/test/env', async t => {
t.true(html.includes('"string": "Nuxt.js"')) t.true(html.includes('"string": "Nuxt.js"'))
}) })
test('/test/error', async t => {
const window = await nuxt.renderAndGetWindow(url('/test/error'))
const html = window.document.body.innerHTML
t.is(window.__NUXT__.layout, 'custom')
t.true(html.includes('<h1>Custom layout</h1>'))
t.true(html.includes('Error page'))
})
test('/test/user-agent', async t => { test('/test/user-agent', async t => {
const window = await nuxt.renderAndGetWindow(url('/test/user-agent')) const window = await nuxt.renderAndGetWindow(url('/test/user-agent'))
const html = window.document.body.innerHTML const html = window.document.body.innerHTML
@ -62,7 +82,7 @@ test('/test/about-bis (added with extendRoutes)', async t => {
test('Check stats.json generated by build.analyze', t => { test('Check stats.json generated by build.analyze', t => {
const stats = require(resolve(__dirname, 'fixtures/with-config/.nuxt/dist/stats.json')) const stats = require(resolve(__dirname, 'fixtures/with-config/.nuxt/dist/stats.json'))
t.is(stats.assets.length, 11) t.is(stats.assets.length, 23)
}) })
// Close server and ask nuxt to stop listening to file changes // Close server and ask nuxt to stop listening to file changes

View File

@ -1,3 +1,7 @@
// Until babel-loader 7 is released
process.noDeprecation = true
var nodeExternals = require('webpack-node-externals') var nodeExternals = require('webpack-node-externals')
var ProgressBarPlugin = require('progress-bar-webpack-plugin') var ProgressBarPlugin = require('progress-bar-webpack-plugin')
var CopyWebpackPlugin = require('copy-webpack-plugin') var CopyWebpackPlugin = require('copy-webpack-plugin')

6057
yarn.lock Normal file

File diff suppressed because it is too large Load Diff