mirror of
https://github.com/nuxt/nuxt.git
synced 2025-01-27 05:42:36 +00:00
better SSR error pages
full stack trace and source maps support
This commit is contained in:
parent
6bcfaf8a3a
commit
67bd208c73
@ -25,7 +25,7 @@ const excludes = [
|
|||||||
].concat(Object.keys(packageJSON.devDependencies))
|
].concat(Object.keys(packageJSON.devDependencies))
|
||||||
|
|
||||||
// Parse dist/core.js for all external dependencies
|
// Parse dist/core.js for all external dependencies
|
||||||
const requireRegex = /require\('([-\w]+)'\)/g
|
const requireRegex = /require\('([-@/\w]+)'\)/g
|
||||||
const rawCore = readFileSync(resolve(rootDir, 'dist/core.js'))
|
const rawCore = readFileSync(resolve(rootDir, 'dist/core.js'))
|
||||||
let match = requireRegex.exec(rawCore)
|
let match = requireRegex.exec(rawCore)
|
||||||
while (match) {
|
while (match) {
|
||||||
|
File diff suppressed because one or more lines are too long
@ -48,6 +48,11 @@ export default function Options (_options) {
|
|||||||
options.store = true
|
options.store = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Debug errors
|
||||||
|
if (options.render.debug === undefined) {
|
||||||
|
options.render.debug = options.dev
|
||||||
|
}
|
||||||
|
|
||||||
// Resolve mode
|
// Resolve mode
|
||||||
let mode = options.mode
|
let mode = options.mode
|
||||||
if (typeof mode === 'function') {
|
if (typeof mode === 'function') {
|
||||||
@ -180,6 +185,7 @@ Options.defaults = {
|
|||||||
bundleRenderer: {},
|
bundleRenderer: {},
|
||||||
resourceHints: true,
|
resourceHints: true,
|
||||||
ssr: undefined,
|
ssr: undefined,
|
||||||
|
debug: undefined, // Will be set equal to dev
|
||||||
http2: {
|
http2: {
|
||||||
push: false
|
push: false
|
||||||
},
|
},
|
||||||
|
@ -10,8 +10,10 @@ import _ from 'lodash'
|
|||||||
import { join, resolve } from 'path'
|
import { join, resolve } from 'path'
|
||||||
import fs from 'fs-extra'
|
import fs from 'fs-extra'
|
||||||
import { createBundleRenderer } from 'vue-server-renderer'
|
import { createBundleRenderer } from 'vue-server-renderer'
|
||||||
import { encodeHtml, getContext, setAnsiColors, isUrl } from 'utils'
|
import { getContext, setAnsiColors, isUrl } from 'utils'
|
||||||
import Debug from 'debug'
|
import Debug from 'debug'
|
||||||
|
import Youch from '@nuxtjs/youch'
|
||||||
|
import { SourceMapConsumer } from 'source-map'
|
||||||
import connect from 'connect'
|
import connect from 'connect'
|
||||||
import { Options } from 'common'
|
import { Options } from 'common'
|
||||||
|
|
||||||
@ -44,7 +46,7 @@ export default class Renderer extends Tapable {
|
|||||||
serverBundle: null,
|
serverBundle: null,
|
||||||
ssrTemplate: null,
|
ssrTemplate: null,
|
||||||
spaTemplate: null,
|
spaTemplate: null,
|
||||||
errorTemplate: parseTemplate('<pre>{{ stack }}</pre>') // Will be loaded on ready
|
errorTemplate: parseTemplate('Nuxt.js Internal Server Error')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,7 +56,7 @@ export default class Renderer extends Tapable {
|
|||||||
// Setup nuxt middleware
|
// Setup nuxt middleware
|
||||||
await this.setupMiddleware()
|
await this.setupMiddleware()
|
||||||
|
|
||||||
// Load error template
|
// Load error template for when debug is disabled
|
||||||
const errorTemplatePath = resolve(this.options.buildDir, 'views/error.html')
|
const errorTemplatePath = resolve(this.options.buildDir, 'views/error.html')
|
||||||
if (fs.existsSync(errorTemplatePath)) {
|
if (fs.existsSync(errorTemplatePath)) {
|
||||||
this.resources.errorTemplate = parseTemplate(fs.readFileSync(errorTemplatePath, 'utf8'))
|
this.resources.errorTemplate = parseTemplate(fs.readFileSync(errorTemplatePath, 'utf8'))
|
||||||
@ -211,6 +213,8 @@ export default class Renderer extends Tapable {
|
|||||||
this.useMiddleware(this.nuxtMiddleware.bind(this))
|
this.useMiddleware(this.nuxtMiddleware.bind(this))
|
||||||
|
|
||||||
// Error middleware for errors that occurred in middleware that declared above
|
// Error middleware for errors that occurred in middleware that declared above
|
||||||
|
// Middleware should exactly take 4 arguments
|
||||||
|
// https://github.com/senchalabs/connect#error-middleware
|
||||||
this.useMiddleware(this.errorMiddleware.bind(this))
|
this.useMiddleware(this.errorMiddleware.bind(this))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -262,28 +266,116 @@ export default class Renderer extends Tapable {
|
|||||||
res.end(html, 'utf8')
|
res.end(html, 'utf8')
|
||||||
return html
|
return html
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
next(this.errorMiddleware(err, req, res, next, context))
|
/* istanbul ignore if */
|
||||||
|
if (context && context.redirected) {
|
||||||
|
console.error(err) // eslint-disable-line no-console
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
next(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
errorMiddleware (err, req, res, next, context) {
|
errorMiddleware (err, req, res, next) {
|
||||||
/* istanbul ignore if */
|
const sendResponse = html => {
|
||||||
if (context && context.redirected) {
|
// Set Headers
|
||||||
console.error(err) // eslint-disable-line no-console
|
res.statusCode = 500
|
||||||
return err
|
res.setHeader('Content-Type', 'text/html; charset=utf-8')
|
||||||
|
res.setHeader('Content-Length', Buffer.byteLength(html))
|
||||||
|
|
||||||
|
// Send Response
|
||||||
|
res.end(html, 'utf8')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render error template
|
// Use basic errors when debug mode is disabled
|
||||||
const html = this.resources.errorTemplate({
|
if (!this.options.render.debug) {
|
||||||
error: err,
|
const html = this.resources.errorTemplate({
|
||||||
stack: ansiHTML(encodeHtml(err.stack))
|
code: err.statusCode || 500,
|
||||||
})
|
message: err.message || 'Nuxt Server Error'
|
||||||
// Send response
|
})
|
||||||
res.statusCode = 500
|
sendResponse(html)
|
||||||
res.setHeader('Content-Type', 'text/html; charset=utf-8')
|
return
|
||||||
res.setHeader('Content-Length', Buffer.byteLength(html))
|
}
|
||||||
res.end(html, 'utf8')
|
|
||||||
return err
|
// Show stack trace
|
||||||
|
err.name = 'Nuxt Server Error'
|
||||||
|
err.status = 500
|
||||||
|
const youch = new Youch(err, req, this.readSource.bind(this))
|
||||||
|
youch.toHTML().then(html => { sendResponse(html) })
|
||||||
|
}
|
||||||
|
|
||||||
|
async readSource (frame) {
|
||||||
|
const serverBundle = this.resources.serverBundle
|
||||||
|
// Initialize smc cache
|
||||||
|
if (!serverBundle.$maps) {
|
||||||
|
serverBundle.$maps = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove webpack:/// & query string from the end
|
||||||
|
const sanitizeName = name => name ? name.replace('webpack:///', '').split('?')[0] : ''
|
||||||
|
|
||||||
|
// SourceMap Support for SSR Bundle
|
||||||
|
if (serverBundle && serverBundle.maps[frame.fileName]) {
|
||||||
|
// Read SourceMap object
|
||||||
|
const smc = serverBundle.$maps[frame.fileName] || new SourceMapConsumer(serverBundle.maps[frame.fileName])
|
||||||
|
serverBundle.$maps[frame.fileName] = smc
|
||||||
|
|
||||||
|
// Try to find original position
|
||||||
|
const { line, column, name, source } = smc.originalPositionFor({
|
||||||
|
line: frame.getLineNumber() || 0,
|
||||||
|
column: frame.getColumnNumber() || 0
|
||||||
|
})
|
||||||
|
if (line) {
|
||||||
|
frame.lineNumber = line
|
||||||
|
}
|
||||||
|
if (column) {
|
||||||
|
frame.columnNumber = column
|
||||||
|
}
|
||||||
|
if (name) {
|
||||||
|
frame.functionName = name
|
||||||
|
}
|
||||||
|
if (source) {
|
||||||
|
frame.fileName = sanitizeName(source)
|
||||||
|
|
||||||
|
// Source detected, try to get original source code
|
||||||
|
const contents = smc.sourceContentFor(source)
|
||||||
|
if (contents) {
|
||||||
|
frame.contents = contents
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return if fileName is still unknown
|
||||||
|
if (!frame.fileName) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
frame.fileName = sanitizeName(frame.fileName)
|
||||||
|
|
||||||
|
// Try to read from SSR bundle files
|
||||||
|
if (serverBundle && serverBundle.files[frame.fileName]) {
|
||||||
|
frame.contents = serverBundle.files[frame.fileName]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Possible paths for file
|
||||||
|
const searchPath = [
|
||||||
|
this.options.rootDir,
|
||||||
|
join(this.options.buildDir, 'dist'),
|
||||||
|
this.options.srcDir,
|
||||||
|
this.options.buildDir
|
||||||
|
]
|
||||||
|
|
||||||
|
// Scan filesystem
|
||||||
|
for (let pathDir of searchPath) {
|
||||||
|
let fullPath = resolve(pathDir, frame.fileName)
|
||||||
|
let source = await fs.readFile(fullPath, 'utf-8').catch(() => null)
|
||||||
|
if (source) {
|
||||||
|
frame.contents = source
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async renderRoute (url, context = {}) {
|
async renderRoute (url, context = {}) {
|
||||||
|
@ -67,6 +67,7 @@
|
|||||||
"npm": ">=3.0.0"
|
"npm": ">=3.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@nuxtjs/youch": "3.0.1",
|
||||||
"ansi-html": "^0.0.7",
|
"ansi-html": "^0.0.7",
|
||||||
"autoprefixer": "^7.1.2",
|
"autoprefixer": "^7.1.2",
|
||||||
"babel-core": "^6.25.0",
|
"babel-core": "^6.25.0",
|
||||||
@ -100,6 +101,7 @@
|
|||||||
"serialize-javascript": "^1.4.0",
|
"serialize-javascript": "^1.4.0",
|
||||||
"serve-static": "^1.12.3",
|
"serve-static": "^1.12.3",
|
||||||
"server-destroy": "^1.0.1",
|
"server-destroy": "^1.0.1",
|
||||||
|
"source-map": "^0.5.6",
|
||||||
"source-map-support": "^0.4.15",
|
"source-map-support": "^0.4.15",
|
||||||
"tappable": "^1.1.0",
|
"tappable": "^1.1.0",
|
||||||
"url-loader": "^0.5.9",
|
"url-loader": "^0.5.9",
|
||||||
|
@ -62,6 +62,8 @@
|
|||||||
"compression": "^1.7.0",
|
"compression": "^1.7.0",
|
||||||
"fs-extra": "^4.0.1",
|
"fs-extra": "^4.0.1",
|
||||||
"vue-server-renderer": "~2.4.2",
|
"vue-server-renderer": "~2.4.2",
|
||||||
|
"@nuxtjs/youch": "3.0.1",
|
||||||
|
"source-map": "^0.5.6",
|
||||||
"connect": "^3.6.3",
|
"connect": "^3.6.3",
|
||||||
"server-destroy": "^1.0.1"
|
"server-destroy": "^1.0.1"
|
||||||
},
|
},
|
||||||
|
18
yarn.lock
18
yarn.lock
@ -44,6 +44,14 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
arrify "^1.0.1"
|
arrify "^1.0.1"
|
||||||
|
|
||||||
|
"@nuxtjs/youch@3.0.1":
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@nuxtjs/youch/-/youch-3.0.1.tgz#332bfea84c91c60798cbb693b5622d40ab782fb0"
|
||||||
|
dependencies:
|
||||||
|
cookie "^0.3.1"
|
||||||
|
mustache "^2.3.0"
|
||||||
|
stack-trace "0.0.10"
|
||||||
|
|
||||||
"@types/node@^6.0.46":
|
"@types/node@^6.0.46":
|
||||||
version "6.0.85"
|
version "6.0.85"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.85.tgz#ec02bfe54a61044f2be44f13b389c6a0e8ee05ae"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.85.tgz#ec02bfe54a61044f2be44f13b389c6a0e8ee05ae"
|
||||||
@ -1722,7 +1730,7 @@ cookie-signature@1.0.6:
|
|||||||
version "1.0.6"
|
version "1.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
|
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
|
||||||
|
|
||||||
cookie@0.3.1:
|
cookie@0.3.1, cookie@^0.3.1:
|
||||||
version "0.3.1"
|
version "0.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
|
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
|
||||||
|
|
||||||
@ -4117,6 +4125,10 @@ multimatch@^2.1.0:
|
|||||||
arrify "^1.0.0"
|
arrify "^1.0.0"
|
||||||
minimatch "^3.0.0"
|
minimatch "^3.0.0"
|
||||||
|
|
||||||
|
mustache@^2.3.0:
|
||||||
|
version "2.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/mustache/-/mustache-2.3.0.tgz#4028f7778b17708a489930a6e52ac3bca0da41d0"
|
||||||
|
|
||||||
mute-stream@0.0.7:
|
mute-stream@0.0.7:
|
||||||
version "0.0.7"
|
version "0.0.7"
|
||||||
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
|
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
|
||||||
@ -5666,6 +5678,10 @@ sshpk@^1.7.0:
|
|||||||
jsbn "~0.1.0"
|
jsbn "~0.1.0"
|
||||||
tweetnacl "~0.14.0"
|
tweetnacl "~0.14.0"
|
||||||
|
|
||||||
|
stack-trace@0.0.10:
|
||||||
|
version "0.0.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0"
|
||||||
|
|
||||||
stack-utils@^1.0.0:
|
stack-utils@^1.0.0:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.1.tgz#d4f33ab54e8e38778b0ca5cfd3b3afb12db68620"
|
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.1.tgz#d4f33ab54e8e38778b0ca5cfd3b3afb12db68620"
|
||||||
|
Loading…
Reference in New Issue
Block a user