Merge branch '0.10.0'

This commit is contained in:
Sébastien Chopin 2017-03-24 19:00:48 +01:00
commit d1550f914b
73 changed files with 6896 additions and 521 deletions

2
.gitignore vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -14,7 +14,7 @@ export default {
validate ({ params }) {
return !isNaN(+params.id)
},
data ({ params, error }) {
asyncData ({ params, error }) {
return axios.get(`https://jsonplaceholder.typicode.com/users/${+params.id}`)
.then((res) => res.data)
.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

@ -20,6 +20,3 @@ export default {
}
}
</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"
}
}

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>
export default {
data ({ req }) {
asyncData ({ req }) {
return {
name: req ? 'server' : 'client'
}

View File

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

View File

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

View File

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

View File

@ -3,6 +3,7 @@ module.exports = {
vendor: ['axios', 'mini-toastr', 'vue-notifications']
},
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'
export default {
data () {
asyncData () {
return axios.get('https://jsonplaceholder.typicode.com/photos/4').then(res => res.data)
}
}

View File

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

View File

@ -1,26 +1,23 @@
// This code will be injected before initializing the root App
import Vue from 'vue'
import VueNotifications from 'vue-notifications'
// Include mini-toaster (or any other UI-notification library
import miniToastr from 'mini-toastr'
if (process.BROWSER_BUILD) {
// Include mini-toaster (or any other UI-notification library
const miniToastr = require('mini-toastr')
// 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)
// 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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -12,7 +12,7 @@ let layouts = {
<%
var layoutsKeys = Object.keys(layouts);
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'
this.layoutName = layout
let _layout = '_' + layout
if (typeof layouts[_layout] === 'function') {
return this.loadLayout(_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]()
.then((Component) => {
layouts[_layout] = Component
this.layout = layouts[_layout]
return this.layout
return layouts[_layout]
})
.catch((e) => {
if (this.$nuxt) {

View File

@ -2,7 +2,7 @@
import Vue from 'vue'
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'
const noopData = () => { return {} }
const noopFetch = () => {}
@ -55,23 +55,28 @@ function loadAsyncComponents (to, from, next) {
}
function callMiddleware (Components, context, layout) {
// Call middleware
// if layout is undefined, only call global middleware
let midd = <%= serialize(router.middleware, { isJSON: true }) %>
if (layout.middleware) {
midd = midd.concat(layout.middleware)
}
Components.forEach((Component) => {
if (Component.options.middleware) {
midd = midd.concat(Component.options.middleware)
let unknownMiddleware = false
if (typeof layout !== 'undefined') {
midd = [] // exclude global middleware if layout defined (already called before)
if (layout.middleware) {
midd = midd.concat(layout.middleware)
}
})
Components.forEach((Component) => {
if (Component.options.middleware) {
midd = midd.concat(Component.options.middleware)
}
})
}
midd = midd.map((name) => {
if (typeof middleware[name] !== 'function') {
unknownMiddleware = true
this.error({ statusCode: 500, message: 'Unknown middleware ' + name })
}
return middleware[name]
})
if (this.$options._nuxt.err) return
if (unknownMiddleware) return
return promiseSeries(midd, context)
}
@ -82,13 +87,15 @@ function render (to, from, next) {
nextCalled = true
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)
this._context = context
this._dateLastError = this.$options._nuxt.dateErr
this._hadError = !!this.$options._nuxt.err
if (!Components.length) {
// 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(() => {
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
Components.forEach(function (Component) {
if (!Component._data) {
Component._data = Component.options.data || noopData
}
if (Component._Ctor && Component._Ctor.options) {
Component.options.asyncData = Component._Ctor.options.asyncData
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))
let nextCalled = false
// 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(() => {
// Pass validation?
@ -141,13 +144,19 @@ function render (to, from, next) {
return Promise.resolve()
}
let promises = []
// Validate method
if (Component._data && typeof Component._data === 'function') {
var promise = promisify(Component._data, context)
promise.then((data) => {
Component._cData = () => data || {}
Component.options.data = Component._cData
Component._dataFn = Component.options.data.toString().replace(/\s/g, '')
// asyncData method
if (Component.options.asyncData && typeof Component.options.asyncData === 'function') {
var promise = promisify(Component.options.asyncData, context)
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)
// Overwrite .data() method with merged data
Component.options.data = () => data
if (Component._Ctor && Component._Ctor.options) {
Component._Ctor.options.data = Component.options.data
}
@ -175,8 +184,15 @@ function render (to, from, next) {
.catch((error) => {
_lastPaths = []
error.statusCode = error.statusCode || error.status || (error.response && error.response.status) || 500
this.error(error)
next(false)
let layout = NuxtError.layout
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) {
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
hotReloadAPI(this)
setTimeout(() => hotReloadAPI(this), 100)
})
}
// Special hot reload with data(context)
// Special hot reload with asyncData(context)
function hotReloadAPI (_app) {
if (!module.hot) return
const $nuxt = _app.$nuxt
var _forceUpdate = $nuxt.$forceUpdate.bind($nuxt)
$nuxt.$forceUpdate = function () {
let Component = getMatchedComponents(router.currentRoute)[0]
let $components = []
let $nuxt = _app.$nuxt
while ($nuxt && $nuxt.$children && $nuxt.$children.length) {
$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 (typeof Component === 'object' && !Component.options) {
// Updated via vue-router resolveAsyncComponents()
Component = Vue.extend(Component)
Component._Ctor = Component
}
this.error()
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) {
<%= (loading ? 'this.$loading.finish && this.$loading.finish()' : '') %>
router.push(path)
}
const context = getContext({ route: router.currentRoute<%= (store ? ', store' : '') %>, isClient: true, next: next.bind(this), error: _app.error })
// Check if data has been updated
const originalDataFn = (Component._data || noopData).toString().replace(/\s/g, '')
const newDataFn = (Component._Ctor.options.data || noopData).toString().replace(/\s/g, '')
if (originalDataFn !== newDataFn) {
Component._data = Component._Ctor.options.data || noopData
let p = promisify(Component._data, context)
p.then((data) => {
Component._cData = () => data || {}
Component.options.data = Component._cData
Component._dataFn = Component.options.data.toString().replace(/\s/g, '')
let context = getContext({ route: router.currentRoute<%= (store ? ', store' : '') %>, isClient: true, next: next.bind(this), error: this.error })
<%= (loading ? 'this.$loading.start && this.$loading.start()' : '') %>
callMiddleware.call(this, Components, context)
.then(() => {
// If layout changed
if (depth !== 0) return Promise.resolve()
let layout = Component.options.layout || 'default'
if (typeof layout === 'function') {
layout = layout(context)
}
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
<%= (loading ? 'this.$loading.increase && this.$loading.increase(30)' : '') %>
})
promises.push(p)
} else if (Component._cData) {
Component._data = Component.options.data
Component.options.data = Component._cData
Component._Ctor.options.data = Component.options.data
}
// Check if fetch has been updated
const originalFetchFn = (Component.options.fetch || noopFetch).toString().replace(/\s/g, '')
const newFetchFn = (Component._Ctor.options.fetch || noopFetch).toString().replace(/\s/g, '')
// Fetch has been updated, we call it to update the store
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(() => {
promises.push(pAsyncData)
// Call fetch()
Component.options.fetch = Component.options.fetch || noopFetch
let pFetch = Component.options.fetch(context)
if (!(pFetch instanceof Promise)) { pFetch = Promise.resolve(pFetch) }
<%= (loading ? 'pFetch.then(() => this.$loading.increase && this.$loading.increase(30))' : '') %>
promises.push(pFetch)
return Promise.all(promises)
})
.then(() => {
<%= (loading ? 'this.$loading.finish && this.$loading.finish()' : '') %>
_forceUpdate()
setTimeout(() => hotReloadAPI(this), 100)
})
}
}
@ -302,13 +349,14 @@ const resolveComponents = flatMapComponents(router.match(path), (Component, _, m
Component._Ctor = Component
Component.extendOptions = Component.options
}
if (Component.options.data && typeof Component.options.data === 'function') {
Component._data = Component.options.data
if (NUXT.serverRendered) {
Component._cData = () => NUXT.data[index] || {}
Component.options.data = Component._cData
Component._dataFn = Component.options.data.toString().replace(/\s/g, '')
if (NUXT.serverRendered) {
let data = {}
if (Component.options.data && typeof Component.options.data === 'function') {
data = Component.options.data()
}
// Merge data() and asyncData() results
data = Object.assign(data, NUXT.data[index])
Component.options.data = () => data
if (Component._Ctor && Component._Ctor.options) {
Component._Ctor.options.data = Component.options.data
}
@ -338,25 +386,31 @@ Promise.all(resolveComponents)
.then((Components) => {
const _app = new Vue(app)
return _app.setLayout(Components.length ? Components[0].options.layout : '')
let context = getContext({ to: router.currentRoute, isClient: true })
let layoutName = 'default'
return callMiddleware.call(_app, Components, context)
.then(() => {
layoutName = Components.length ? Components[0].options.layout : NuxtError.layout
if (typeof layoutName === 'function') {
layoutName = layoutName(context)
}
return _app.loadLayout(layoutName)
})
.then(() => {
_app.setLayout(layoutName)
return { _app, Components }
})
})
.then(({ _app, Components }) => {
const mountApp = () => {
_app.$mount('#__nuxt')
// Hot reloading
hotReloadAPI(_app)
// Call window.onNuxtReady callbacks
Vue.nextTick(() => nuxtReady(_app))
Vue.nextTick(() => {
// Hot reloading
hotReloadAPI(_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)
if (Components.length) {
_app.setTransitions(mapTransitions(Components, router.currentRoute))

View File

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

View File

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

View File

@ -6,6 +6,7 @@ import router from './router.js'
<% if (store) { %>import store from './store.js'<% } %>
import NuxtChild from './components/nuxt-child.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 App from '<%= appPath %>'
@ -19,12 +20,18 @@ Vue.component(Nuxt.name, Nuxt)
// vue-meta configuration
Vue.use(Meta, {
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
ssrAttribute: 'n-head-ssr', // the attribute name that lets vue-meta know that meta info has already been server-rendered
attribute: 'data-n-head', // the attribute name vue-meta adds to the tags it observes
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
})
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
// Useful for jsdom testing or plugins (https://github.com/tmpvar/jsdom#dealing-with-asynchronous-script-loading)
window._nuxtReadyCbs = []
@ -34,8 +41,16 @@ if (process.BROWSER_BUILD) {
}
// Includes external plugins
<% plugins.forEach(function (pluginPath) { %>
require('<%= pluginPath %>')
<% plugins.forEach(function (plugin) {
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
@ -85,4 +100,4 @@ const 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 _routes = recursiveRoutes(router.routes, '\t\t', _components)
uniqBy(_components, '_name').forEach((route) => { %>
const <%= route._name %> = process.BROWSER_BUILD ? () => System.import('<%= route.component %>') : require('<%= route.component %>')
const <%= route._name %> = () => import('<%= route.component %>')
<% }) %>
<% if (router.scrollBehavior) { %>

View File

@ -3,10 +3,11 @@
const debug = require('debug')('nuxt:render')
debug.color = 4 // force blue color
import Vue from 'vue'
import { stringify } from 'querystring'
import { omit } from 'lodash'
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'
const isDev = <%= isDev %>
@ -25,9 +26,9 @@ export default context => {
// create context.next for simulate next() of beforeEach() when wanted to redirect
context.redirected = false
context.next = function (opts) {
context.redirected = opts
// if nuxt generate
if (!context.res) {
context.redirected = opts
context.nuxt.serverRendered = false
return
}
@ -39,25 +40,53 @@ export default context => {
})
context.res.end()
}
// set router's location
router.push(context.url)
// Add route to the context
context.route = router.currentRoute
// Add meta infos
context.meta = _app.$meta()
// Error function
context.error = _app.$options._nuxt.error.bind(_app)
<%= (isDev ? 'const s = isDev && Date.now()' : '') %>
const ctx = getContext(context)
let Components = getMatchedComponents(context.route)
<% if (store) { %>
let promise = (store._actions && store._actions.nuxtServerInit ? store.dispatch('nuxtServerInit', omit(getContext(context), 'redirect', 'error')) : null)
if (!(promise instanceof Promise)) promise = Promise.resolve()
<% } else { %>
let promise = Promise.resolve()
<% } %>
return promise
let ctx = null
let componentsLoaded = false
let Components = []
let promises = getMatchedComponents(router.match(context.url)).map((Component) => {
return new Promise((resolve, reject) => {
const _resolve = (Component) => {
if (!Component.options) {
Component = Vue.extend(Component) // fix issue #6
Component._Ctor = Component
} else {
Component._Ctor = Component
Component.extendOptions = Component.options
}
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 instanceof Promise)) promise = Promise.resolve()
<% } else { %>
let promise = Promise.resolve()
<% } %>
return promise
})
.then(() => {
// Sanitize Components
Components = Components.map((Component) => {
@ -71,12 +100,28 @@ export default context => {
}
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
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) => {
// Call middleware
let midd = <%= serialize(router.middleware, { isJSON: true }) %>
// Call middleware (layout + pages)
let midd = []
if (layout.middleware) {
midd = midd.concat(layout.middleware)
}
@ -114,13 +159,20 @@ export default context => {
Components = []
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) => {
let promises = []
if (Component.options.data && typeof Component.options.data === 'function') {
Component._data = Component.options.data
let promise = promisify(Component._data, ctx)
promise.then((data) => {
if (Component.options.asyncData && typeof Component.options.asyncData === 'function') {
let promise = promisify(Component.options.asyncData, ctx)
// Call asyncData(context)
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._Ctor.options.data = Component.options.data
})
@ -130,6 +182,8 @@ export default context => {
}
if (Component.options.fetch) {
promises.push(Component.options.fetch(ctx))
} else {
promises.push(null)
}
return Promise.all(promises)
}))
@ -141,16 +195,19 @@ export default context => {
return _app
}
<% 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
context.nuxt.data = res.map((tab) => tab[0])
context.nuxt.data = res.map((r) => (r[0] || {}))
context.nuxt.error = _app.$options._nuxt.err
<%= (store ? '// Add the state from the vuex store' : '') %>
<%= (store ? 'context.nuxt.state = store.state' : '') %>
return _app
})
.catch(function (error) {
if (!componentsLoaded && error instanceof Error) {
return Promise.reject(error)
}
if (error && (error instanceof Error || error.constructor.toString().indexOf('Error()') !== -1)) {
let statusCode = error.statusCode || error.status || (error.response && error.response.status) || 500
error = { statusCode, message: error.message }

View File

@ -1,6 +1,5 @@
'use strict'
const debug = require('debug')('nuxt:build')
import _ from 'lodash'
import co from 'co'
import chokidar from 'chokidar'
@ -11,10 +10,10 @@ import webpack from 'webpack'
import serialize from 'serialize-javascript'
import { createBundleRenderer } from 'vue-server-renderer'
import { join, resolve, sep } from 'path'
import { isUrl } from './utils'
import clientWebpackConfig from './webpack/client.config.js'
import serverWebpackConfig from './webpack/server.config.js'
import chalk from 'chalk'
import PostCompilePlugin from 'post-compile-webpack-plugin'
const debug = require('debug')('nuxt:build')
const remove = pify(fs.remove)
const readFile = pify(fs.readFile)
const writeFile = pify(fs.writeFile)
@ -38,21 +37,16 @@ const r = function () {
args = args.map(normalize)
return wp(resolve.apply(null, args))
}
const webpackStats = {
chunks: false,
children: false,
modules: false,
colors: true
}
let webpackStats = 'none'
// force green color
debug.color = 2
const defaults = {
analyze: false,
publicPath: '/_nuxt/',
filenames: {
css: 'style.css',
vendor: 'vendor.bundle.js',
app: 'nuxt.bundle.js'
vendor: 'vendor.bundle.[hash].js',
app: 'nuxt.bundle.[chunkhash].js'
},
vendor: [],
loaders: [],
@ -90,13 +84,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.postcss)) extraDefaults.postcss = defaultsPostcss
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
if (!this.dev) {
webpackStats = {
chunks: false,
children: false,
modules: false,
colors: true
}
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)) {
const bundle = fs.readFileSync(bundlePath, 'utf8')
createRenderer.call(this, bundle)
createRenderer.call(this, JSON.parse(bundle))
addAppTemplate.call(this)
}
}
}
@ -138,6 +143,16 @@ function * buildFiles () {
webpackRunClient.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 +166,8 @@ function * generateRoutesAndFiles () {
if (name === 'error') return
layouts[name] = r(this.srcDir, file)
})
// Generate routes based on files
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/
debug('Generating files...')
let templatesFiles = [
'App.vue',
'client.js',
@ -191,21 +197,29 @@ function * generateRoutesAndFiles () {
middleware: this.options.middleware,
store: this.options.store,
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',
layouts: layouts,
loading: (typeof this.options.loading === 'string' ? r(this.srcDir, this.options.loading) : this.options.loading),
transition: this.options.transition,
components: {
ErrorPage: './nuxt-error.vue'
ErrorPage: null
}
}
// Format routes for the lib/app/router.js template
templateVars.router.routes = createRoutes(files, this.srcDir)
if (typeof this.options.router.extendRoutes === 'function') {
// 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')) {
templateVars.components.ErrorPage = r(this.srcDir, 'layouts/error.vue')
}
@ -325,6 +339,19 @@ function cleanChildrenRoutes (routes, isChild = false) {
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 () {
return clientWebpackConfig.call(this)
}
@ -335,27 +362,11 @@ function getWebpackServerConfig () {
function createWebpackMiddleware () {
const clientConfig = getWebpackClientConfig.call(this)
const host = process.env.HOST || '127.0.0.1'
const port = process.env.PORT || '3000'
// setup on the fly compilation + hot-reload
clientConfig.entry.app = _.flatten(['webpack-hot-middleware/client?reload=true', clientConfig.entry.app])
clientConfig.plugins.push(
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
new PostCompilePlugin(stats => {
process.stdout.write('\x1Bc')
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
})
new webpack.NoEmitOnErrorsPlugin()
)
const clientCompiler = webpack(clientConfig)
// Add the middleware to the instance context
@ -368,6 +379,16 @@ function createWebpackMiddleware () {
this.webpackHotMiddleware = pify(require('webpack-hot-middleware')(clientCompiler, {
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 () {
@ -375,11 +396,11 @@ function webpackWatchAndUpdate () {
const mfs = new MFS()
const serverConfig = getWebpackServerConfig.call(this)
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
this.webpackServerWatcher = serverCompiler.watch({}, (err) => {
if (err) throw err
createRenderer.call(this, mfs.readFileSync(outputPath, 'utf-8'))
createRenderer.call(this, JSON.parse(mfs.readFileSync(outputPath, 'utf-8')))
})
}
@ -390,7 +411,7 @@ function webpackRunClient () {
clientCompiler.run((err, stats) => {
if (err) return reject(err)
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()
})
})
@ -403,11 +424,11 @@ function webpackRunServer () {
serverCompiler.run((err, stats) => {
if (err) return reject(err)
console.log('[nuxt:build:server]\n', stats.toString(webpackStats)) // eslint-disable-line no-console
if (stats.hasErrors()) return reject('Webpack build exited with errors')
const bundlePath = join(serverConfig.output.path, serverConfig.output.filename)
if (stats.hasErrors()) return reject(new Error('Webpack build exited with errors'))
const bundlePath = join(serverConfig.output.path, 'server-bundle.json')
readFile(bundlePath, 'utf8')
.then((bundle) => {
createRenderer.call(this, bundle)
createRenderer.call(this, JSON.parse(bundle))
resolve()
})
})

View File

@ -1,14 +1,13 @@
'use strict'
const debug = require('debug')('nuxt:generate')
import fs from 'fs-extra'
import co from 'co'
import pify from 'pify'
import pathToRegexp from 'path-to-regexp'
import _ from 'lodash'
import { resolve, join, dirname, sep } from 'path'
import { promisifyRouteParams } from './utils'
import { isUrl, promisifyRoute } from './utils'
import { minify } from 'html-minifier'
const debug = require('debug')('nuxt:generate')
const copy = pify(fs.copy)
const remove = pify(fs.remove)
const writeFile = pify(fs.writeFile)
@ -16,21 +15,32 @@ const mkdirp = pify(fs.mkdirp)
const defaults = {
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 () {
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
*/
this.options.generate = _.defaultsDeep(this.options.generate, defaults)
@ -38,7 +48,7 @@ export default function () {
var srcStaticPath = resolve(this.srcDir, 'static')
var srcBuiltPath = resolve(this.dir, '.nuxt', 'dist')
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 * () {
/*
** Launch build process
@ -61,55 +71,31 @@ export default function () {
debug('Static & build files copied')
})
.then(() => {
// Resolve config.generate.routesParams promises before generating the routes
return resolveRouteParams(this.options.generate.routeParams)
// Resolve config.generate.routes promises before generating the routes
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
*/
let routes = []
this.routes.forEach((route) => {
if (route.includes(':') || route.includes('*')) {
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)
generateRoutes.forEach((route) => {
if (this.routes.indexOf(route) < 0) {
this.routes.push(route)
}
})
let routes = this.routes
return co(function * () {
while (routes.length) {
yield routes.splice(0, 500).map((route) => {
return co(function * () {
var { html } = yield self.renderRoute(route, { _generate: true })
html = minify(html, {
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
})
html = minify(html, self.options.generate.minify)
var path = join(route, sep, 'index.html') // /about -> /about/index.html
debug('Generate file: ' + path)
path = join(distPath, path)
@ -133,20 +119,3 @@ export default function () {
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 co from 'co'
import compression from 'compression'
import fs from 'fs-extra'
import pify from 'pify'
import ansiHTML from 'ansi-html'
import serialize from 'serialize-javascript'
import Server from './server'
import * as build from './build'
import * as render from './render'
@ -13,10 +12,8 @@ import generate from './generate'
import serveStatic from 'serve-static'
import { resolve, join } from 'path'
import * as utils from './utils'
utils.setAnsiColors(ansiHTML)
class Nuxt {
constructor (options = {}) {
var defaults = {
dev: true,
@ -43,6 +40,10 @@ class Nuxt {
extendRoutes: null,
scrollBehavior: null
},
performance: {
gzip: true,
prefetch: true
},
build: {}
}
// Sanitization
@ -63,28 +64,34 @@ class Nuxt {
if (fs.existsSync(join(this.srcDir, 'middleware'))) {
this.options.middleware = true
}
// 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
}
})
// If app.html is defined, set the template path to the user template
this.options.appTemplatePath = resolve(__dirname, 'views/app.template.html')
if (fs.existsSync(join(this.srcDir, 'app.html'))) {
this.options.appTemplatePath = join(this.srcDir, 'app.html')
}
// renderer used by Vue.js (via createBundleRenderer)
this.renderer = null
// For serving static/ files to /
this.serveStatic = pify(serveStatic(resolve(this.srcDir, 'static')))
// For serving .nuxt/dist/ files
this._nuxtRegexp = /^\/_nuxt\//
this.serveStaticNuxt = pify(serveStatic(resolve(this.dir, '.nuxt', 'dist')))
// For serving .nuxt/dist/ files (only when build.publicPath is not an URL)
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.gzipMiddleware = pify(compression({
threshold: 0
}))
}
// Add this.Server Class
this.Server = Server
// Add this.build
build.options.call(this) // Add build options
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
this.render = render.render.bind(this)
this.renderRoute = render.renderRoute.bind(this)
@ -123,7 +130,6 @@ class Nuxt {
if (typeof callback === 'function') callback()
})
}
}
export default Nuxt

View File

@ -1,10 +1,13 @@
'use strict'
const debug = require('debug')('nuxt:render')
import ansiHTML from 'ansi-html'
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
debug.color = 4
setAnsiColors(ansiHTML)
export function render (req, res) {
if (!this.renderer && !this.dev) {
@ -12,20 +15,25 @@ export function render (req, res) {
process.exit(1)
}
/* istanbul ignore if */
if (!this.renderer) {
setTimeout(() => {
this.render(req, res)
}, 1000)
return
if (!this.renderer || !this.appTemplate) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(this.render(req, res))
}, 1000)
})
}
const self = this
const context = getContext(req, res)
return co(function * () {
res.statusCode = 200
if (self.dev) {
// Call webpack middleware only in development
yield self.webpackDevMiddleware(req, res)
yield self.webpackHotMiddleware(req, res)
}
if (!self.dev && self.options.performance.gzip === true) {
yield self.gzipMiddleware(req, res)
}
// 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) {
// Compatibility with base url for dev server
@ -34,9 +42,9 @@ export function render (req, res) {
// Serve static/ files
yield self.serveStatic(req, res)
// 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
req.url = req.url.replace(self._nuxtRegexp, '/')
req.url = req.url.replace(self.options.build.publicPath, '/')
yield self.serveStaticNuxt(req, res)
/* istanbul ignore next */
req.url = url
@ -44,23 +52,34 @@ export function render (req, res) {
})
.then(() => {
/* 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
return res.end()
return { html: '' }
}
return this.renderRoute(req.url, context)
})
.then(({ html, error }) => {
.then(({ html, error, redirected }) => {
if (redirected) {
return html
}
if (error) {
res.statusCode = context.nuxt.error.statusCode || 500
}
res.setHeader('Content-Type', 'text/html; charset=utf-8')
res.setHeader('Content-Length', Buffer.byteLength(html))
res.end(html, 'utf8')
return html
})
.catch((err) => {
const html = this.errorTemplate({
error: err,
stack: ansiHTML(encodeHtml(err.stack))
})
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 +91,22 @@ export function renderRoute (url, context = {}) {
// Call rendertoSting from the bundleRenderer and generate the HTML (will update the context as well)
const self = this
return co(function * () {
let app = yield self.renderToString(context)
let APP = yield self.renderToString(context)
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({
dev: self.dev, // Use to add the extracted CSS <link> in production
baseUrl: self.options.router.base,
APP: app,
context: context,
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)
}
HTML_ATTRS: 'data-n-head-ssr ' + m.htmlAttrs.text(),
BODY_ATTRS: m.bodyAttrs.text(),
HEAD,
APP
})
return {
html,
@ -128,10 +149,9 @@ export function renderAndGetWindow (url, opts = {}) {
window.scrollTo = function () {}
// If Nuxt could not be loaded (error from the server-side)
if (!window.__NUXT__) {
return reject({
message: 'Could not load the nuxt app',
body: window.document.getElementsByTagName('body')[0].innerHTML
})
let error = new Error('Could not load the nuxt app')
error.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
window.onNuxtReady(() => {

View File

@ -3,7 +3,6 @@
const http = require('http')
class Server {
constructor (nuxt) {
this.nuxt = nuxt
this.server = http.createServer(this.render.bind(this))
@ -27,7 +26,6 @@ class Server {
close (cb) {
return this.server.close(cb)
}
}
export default Server

View File

@ -28,15 +28,19 @@ export function * waitFor (ms) {
}
export function urlJoin () {
return [].slice.call(arguments).join('/').replace(/\/+/g, '/')
return [].slice.call(arguments).join('/').replace(/\/+/g, '/').replace(':/', '://')
}
export function promisifyRouteParams (fn) {
// If routeParams[route] is an array
export function isUrl (url) {
return (url.indexOf('http') === 0 || url.indexOf('//') === 0)
}
export function promisifyRoute (fn) {
// If routes is an array
if (Array.isArray(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) {
return new Promise((resolve, reject) => {
fn(function (err, routeParams) {

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

View File

@ -3,7 +3,7 @@
import vueLoaderConfig from './vue-loader.config'
import { defaults } from 'lodash'
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 }) {
const nodeModulesDir = join(__dirname, '..', 'node_modules')
let config = {
devtool: 'source-map',
devtool: (this.dev ? 'cheap-module-eval-source-map' : false),
entry: {
vendor: ['vue', 'vue-router', 'vue-meta']
},
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: {
maxEntrypointSize: 300000,
maxAssetSize: 300000,
hints: (this.dev ? false : 'warning')
},
resolve: {
@ -64,16 +66,13 @@ export default function ({ isClient, isServer }) {
loader: 'babel-loader',
exclude: /node_modules/,
query: defaults(this.options.build.babel, {
plugins: [
'transform-async-to-generator',
'transform-runtime'
],
presets: [
['es2015', { modules: false }],
'stage-2'
],
presets: ['vue-app'],
cacheDirectory: !!this.dev
})
},
{
test: /\.css$/,
loader: 'vue-style-loader!css-loader'
}
]
},

View File

@ -2,7 +2,10 @@
import { each } from 'lodash'
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 { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'
import base from './base.config.js'
@ -40,38 +43,55 @@ export default function () {
})
// Webpack plugins
config.plugins = (config.plugins || []).concat([
// strip comments in Vue code
// Strip comments in Vue code
new webpack.DefinePlugin(Object.assign(env, {
'process.env.NODE_ENV': JSON.stringify(this.dev ? 'development' : 'production'),
'process.BROWSER_BUILD': true,
'process.SERVER_BUILD': false
'process.SERVER_BUILD': false,
'process.browser': true,
'process.server': true
})),
// Extract vendor chunks for better caching
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
filename: this.options.build.filenames.vendor
}),
// 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
config.plugins.push(
new ProgressBarPlugin()
)
// Add friendly error plugin
if (this.dev) {
config.plugins.push(new FriendlyErrorsWebpackPlugin())
}
// Production client build
if (!this.dev) {
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
new webpack.LoaderOptionsPlugin({
minimize: true
}),
// Minify JS
new webpack.optimize.UglifyJsPlugin({
sourceMap: true,
compress: {
warnings: false
}

View File

@ -1,6 +1,7 @@
'use strict'
import webpack from 'webpack'
import VueSSRPlugin from 'vue-ssr-webpack-plugin'
import base from './base.config.js'
import { each, uniq } from 'lodash'
import { existsSync, readFileSync } from 'fs'
@ -22,7 +23,7 @@ export default function () {
config = Object.assign(config, {
target: 'node',
devtool: false,
devtool: 'source-map',
entry: resolve(this.dir, '.nuxt', 'server.js'),
output: Object.assign({}, config.output, {
path: resolve(this.dir, '.nuxt', 'dist'),
@ -30,14 +31,26 @@ export default function () {
libraryTarget: 'commonjs2'
}),
plugins: (config.plugins || []).concat([
new VueSSRPlugin({
filename: 'server-bundle.json'
}),
new webpack.DefinePlugin(Object.assign(env, {
'process.env.NODE_ENV': JSON.stringify(this.dev ? 'development' : 'production'),
'process.BROWSER_BUILD': false,
'process.SERVER_BUILD': true
'process.BROWSER_BUILD': false, // deprecated
'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
const nuxtPackageJson = require('../../package.json')
const projectPackageJsonPath = resolve(this.dir, 'package.json')
@ -48,6 +61,7 @@ export default function () {
config.externals = config.externals.concat(Object.keys(projectPackageJson.dependencies || {}))
} catch (e) {}
}
config.externals = config.externals.concat(this.options.build.vendor)
config.externals = uniq(config.externals)
// Extend config

View File

@ -4,19 +4,14 @@ import { defaults } from 'lodash'
export default function ({ isClient }) {
let babelOptions = JSON.stringify(defaults(this.options.build.babel, {
plugins: [
'transform-async-to-generator',
'transform-runtime'
],
presets: [
['es2015', { modules: false }],
'stage-2'
]
presets: ['vue-app'],
cacheDirectory: !!this.dev
}))
let config = {
postcss: this.options.build.postcss,
loaders: {
'js': 'babel-loader?' + babelOptions,
'css': 'vue-style-loader!css-loader',
'less': 'vue-style-loader!css-loader!less-loader',
'sass': 'vue-style-loader!css-loader!sass-loader?indentedSyntax',
'scss': 'vue-style-loader!css-loader!sass-loader',
@ -25,17 +20,6 @@ export default function ({ isClient }) {
},
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 config
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

@ -0,0 +1 @@
console.log('Only called in client-side!')

View File

@ -37,9 +37,9 @@ test('urlJoin', t => {
t.is(utils.urlJoin('test', '/about'), 'test/about')
})
test('promisifyRouteParams (array)', t => {
test('promisifyRoute (array)', t => {
const array = [1]
const promise = utils.promisifyRouteParams(array)
const promise = utils.promisifyRoute(array)
t.is(typeof promise, 'object')
return promise
.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 fn = function () {
return array
}
const promise = utils.promisifyRouteParams(fn)
const promise = utils.promisifyRoute(fn)
t.is(typeof promise, 'object')
return promise
.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 fn = function () {
return new Promise((resolve) => {
resolve(array)
})
}
const promise = utils.promisifyRouteParams(fn)
const promise = utils.promisifyRoute(fn)
t.is(typeof promise, 'object')
return promise
.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) {
cb('Error here')
cb(new Error('Error here'))
}
const promise = utils.promisifyRouteParams(fn)
const promise = utils.promisifyRoute(fn)
t.is(typeof promise, 'object')
return promise
.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 fn = function (cb) {
cb(null, array)
}
const promise = utils.promisifyRouteParams(fn)
const promise = utils.promisifyRoute(fn)
t.is(typeof promise, 'object')
return promise
.then((res) => {

View File

@ -24,6 +24,16 @@ test('/', async t => {
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 => {
const window = await nuxt.renderAndGetWindow(url('/test/'))
const html = window.document.body.innerHTML
@ -62,7 +72,7 @@ test('/test/about-bis (added with extendRoutes)', async t => {
test('Check stats.json generated by build.analyze', t => {
const stats = require(resolve(__dirname, 'fixtures/with-config/.nuxt/dist/stats.json'))
t.is(stats.assets.length, 11)
t.is(stats.assets.length, 19)
})
// 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 ProgressBarPlugin = require('progress-bar-webpack-plugin')
var CopyWebpackPlugin = require('copy-webpack-plugin')

6063
yarn.lock Normal file

File diff suppressed because it is too large Load Diff