mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-27 08:02:01 +00:00
Merge branch '0.10.0'
This commit is contained in:
commit
d1550f914b
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,6 +1,6 @@
|
||||
# dependencies
|
||||
yarn.lock
|
||||
node_modules
|
||||
examples/**/*/yarn.lock
|
||||
|
||||
# logs
|
||||
*.log
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data ({ req }, callback) {
|
||||
asyncData ({ req }, callback) {
|
||||
setTimeout(function () {
|
||||
// callback(err, data)
|
||||
callback(null, {
|
||||
|
@ -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 }
|
||||
|
@ -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) => {
|
||||
|
@ -8,7 +8,7 @@
|
||||
<script>
|
||||
export default {
|
||||
layout: 'dark',
|
||||
data ({ req }) {
|
||||
asyncData ({ req }) {
|
||||
return {
|
||||
name: req ? 'server' : 'client'
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data () {
|
||||
asyncData () {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(function () {
|
||||
resolve({})
|
||||
|
@ -7,7 +7,7 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data () {
|
||||
asyncData () {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(function () {
|
||||
resolve({ name: 'world' })
|
||||
|
@ -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 }
|
||||
|
@ -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(() => {
|
||||
|
3
examples/dynamic-layouts/README.md
Normal file
3
examples/dynamic-layouts/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Dynamic Layouts
|
||||
|
||||
https://nuxtjs.org/examples/layouts
|
25
examples/dynamic-layouts/layouts/error.vue
Normal file
25
examples/dynamic-layouts/layouts/error.vue
Normal 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>
|
37
examples/dynamic-layouts/layouts/mobile.vue
Normal file
37
examples/dynamic-layouts/layouts/mobile.vue
Normal 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>
|
4
examples/dynamic-layouts/middleware/mobile.js
Normal file
4
examples/dynamic-layouts/middleware/mobile.js
Normal 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)
|
||||
}
|
10
examples/dynamic-layouts/nuxt.config.js
Normal file
10
examples/dynamic-layouts/nuxt.config.js
Normal file
@ -0,0 +1,10 @@
|
||||
module.exports = {
|
||||
head: {
|
||||
meta: [
|
||||
{ content: 'width=device-width,initial-scale=1', name: 'viewport' }
|
||||
]
|
||||
},
|
||||
router: {
|
||||
middleware: ['mobile']
|
||||
}
|
||||
}
|
11
examples/dynamic-layouts/package.json
Normal file
11
examples/dynamic-layouts/package.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "nuxt-dynamic-layouts",
|
||||
"dependencies": {
|
||||
"nuxt": "latest"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "nuxt",
|
||||
"build": "nuxt build",
|
||||
"start": "nuxt start"
|
||||
}
|
||||
}
|
17
examples/dynamic-layouts/pages/about.vue
Normal file
17
examples/dynamic-layouts/pages/about.vue
Normal 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>
|
12
examples/dynamic-layouts/pages/index.vue
Normal file
12
examples/dynamic-layouts/pages/index.vue
Normal 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>
|
BIN
examples/dynamic-layouts/static/logo.png
Normal file
BIN
examples/dynamic-layouts/static/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
@ -20,6 +20,3 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css">
|
||||
</style>
|
||||
|
11
examples/hello-world-jsx/package.json
Normal file
11
examples/hello-world-jsx/package.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "hello-nuxt-jsx",
|
||||
"dependencies": {
|
||||
"nuxt": "latest"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "nuxt",
|
||||
"build": "nuxt build",
|
||||
"start": "nuxt"
|
||||
}
|
||||
}
|
15
examples/hello-world-jsx/pages/about.vue
Normal file
15
examples/hello-world-jsx/pages/about.vue
Normal 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>
|
10
examples/hello-world-jsx/pages/index.vue
Normal file
10
examples/hello-world-jsx/pages/index.vue
Normal 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>
|
@ -7,7 +7,7 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data ({ req }) {
|
||||
asyncData ({ req }) {
|
||||
return {
|
||||
name: req ? 'server' : 'client'
|
||||
}
|
||||
|
@ -11,7 +11,7 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data ({ store, route, userAgent }) {
|
||||
asyncData ({ store, route, userAgent }) {
|
||||
return {
|
||||
userAgent,
|
||||
slugs: [
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data ({ env }) {
|
||||
asyncData ({ env }) {
|
||||
return { users: env.users }
|
||||
}
|
||||
}
|
||||
|
@ -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 })
|
||||
|
@ -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 }
|
||||
]
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
|
||||
<script>
|
||||
let miniToastr
|
||||
if (process.BROWSER_BUILD) {
|
||||
if (process.browser) {
|
||||
miniToastr = require('mini-toastr')
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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) => {
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data ({ req }) {
|
||||
asyncData ({ req }) {
|
||||
return {
|
||||
name: req ? 'server' : 'client'
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data ({ req, isServer }) {
|
||||
asyncData ({ req, isServer }) {
|
||||
return {
|
||||
name: req ? 'server' : 'client'
|
||||
}
|
||||
|
@ -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,
|
||||
|
3
index.js
3
index.js
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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))
|
||||
|
@ -5,6 +5,7 @@ const transitionsKeys = [
|
||||
'mode',
|
||||
'css',
|
||||
'type',
|
||||
'duration',
|
||||
'enterClass',
|
||||
'leaveClass',
|
||||
'enterActiveClass',
|
||||
|
@ -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',
|
||||
|
@ -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 }
|
||||
|
@ -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) { %>
|
||||
|
@ -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 }
|
||||
|
119
lib/build.js
119
lib/build.js
@ -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()
|
||||
})
|
||||
})
|
||||
|
107
lib/generate.js
107
lib/generate.js
@ -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)
|
||||
}
|
||||
|
42
lib/nuxt.js
42
lib/nuxt.js
@ -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
|
||||
|
@ -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(() => {
|
||||
|
@ -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
|
||||
|
12
lib/utils.js
12
lib/utils.js
@ -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) {
|
||||
|
@ -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>
|
9
lib/views/app.template.html
Normal file
9
lib/views/app.template.html
Normal file
@ -0,0 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<html {{ HTML_ATTRS }}>
|
||||
<head>
|
||||
{{ HEAD }}
|
||||
</head>
|
||||
<body {{ BODY_ATTRS }}>
|
||||
{{ APP }}
|
||||
</body>
|
||||
</html>
|
@ -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>
|
||||
|
@ -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'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
91
package.json
91
package.json
@ -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"
|
||||
}
|
||||
|
@ -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!')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
12
test/fixtures/basic/nuxt.config.js
vendored
12
test/fixtures/basic/nuxt.config.js
vendored
@ -1,11 +1,9 @@
|
||||
module.exports = {
|
||||
generate: {
|
||||
routeParams: {
|
||||
'/users/:id': [
|
||||
{ id: 1 },
|
||||
{ id: 2 },
|
||||
{ id: 3 }
|
||||
]
|
||||
}
|
||||
routes: [
|
||||
'/users/1',
|
||||
'/users/2',
|
||||
'/users/3'
|
||||
]
|
||||
}
|
||||
}
|
||||
|
2
test/fixtures/basic/pages/async-data.vue
vendored
2
test/fixtures/basic/pages/async-data.vue
vendored
@ -4,7 +4,7 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data () {
|
||||
asyncData () {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => resolve({ name: 'Nuxt.js' }), 10)
|
||||
})
|
||||
|
@ -10,7 +10,7 @@ const fetchData = () => {
|
||||
}
|
||||
|
||||
export default {
|
||||
async data () {
|
||||
async asyncData () {
|
||||
return await fetchData()
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
async data (context, callback) {
|
||||
async asyncData (context, callback) {
|
||||
setTimeout(function () {
|
||||
callback(null, { name: 'Callback Nuxt.js' })
|
||||
}, 10)
|
||||
|
2
test/fixtures/basic/pages/error.vue
vendored
2
test/fixtures/basic/pages/error.vue
vendored
@ -4,7 +4,7 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data () {
|
||||
asyncData () {
|
||||
throw new Error('Error mouahahah')
|
||||
}
|
||||
}
|
||||
|
2
test/fixtures/basic/pages/error2.vue
vendored
2
test/fixtures/basic/pages/error2.vue
vendored
@ -4,7 +4,7 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data ({ error }) {
|
||||
asyncData ({ error }) {
|
||||
error({ message: 'Custom error' })
|
||||
}
|
||||
}
|
||||
|
2
test/fixtures/basic/pages/users/_id.vue
vendored
2
test/fixtures/basic/pages/users/_id.vue
vendored
@ -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
10
test/fixtures/with-config/app.html
vendored
Normal 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>
|
6
test/fixtures/with-config/nuxt.config.js
vendored
6
test/fixtures/with-config/nuxt.config.js
vendored
@ -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
|
||||
|
2
test/fixtures/with-config/pages/env.vue
vendored
2
test/fixtures/with-config/pages/env.vue
vendored
@ -5,7 +5,7 @@
|
||||
<script>
|
||||
export default {
|
||||
layout: 'custom-env',
|
||||
data ({ env }) {
|
||||
asyncData ({ env }) {
|
||||
return { env }
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
<script>
|
||||
export default {
|
||||
middleware: 'user-agent',
|
||||
data ({ userAgent }) {
|
||||
asyncData ({ userAgent }) {
|
||||
return { userAgent }
|
||||
}
|
||||
}
|
||||
|
1
test/fixtures/with-config/plugins/only-client.js
vendored
Normal file
1
test/fixtures/with-config/plugins/only-client.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
console.log('Only called in client-side!')
|
@ -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) => {
|
||||
|
@ -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
|
||||
|
@ -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')
|
||||
|
Loading…
Reference in New Issue
Block a user