mirror of
https://github.com/nuxt/nuxt.git
synced 2025-01-26 21:32:42 +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))
|
||||
|
||||
// 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'))
|
||||
let match = requireRegex.exec(rawCore)
|
||||
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
|
||||
}
|
||||
|
||||
// Debug errors
|
||||
if (options.render.debug === undefined) {
|
||||
options.render.debug = options.dev
|
||||
}
|
||||
|
||||
// Resolve mode
|
||||
let mode = options.mode
|
||||
if (typeof mode === 'function') {
|
||||
@ -180,6 +185,7 @@ Options.defaults = {
|
||||
bundleRenderer: {},
|
||||
resourceHints: true,
|
||||
ssr: undefined,
|
||||
debug: undefined, // Will be set equal to dev
|
||||
http2: {
|
||||
push: false
|
||||
},
|
||||
|
@ -10,8 +10,10 @@ import _ from 'lodash'
|
||||
import { join, resolve } from 'path'
|
||||
import fs from 'fs-extra'
|
||||
import { createBundleRenderer } from 'vue-server-renderer'
|
||||
import { encodeHtml, getContext, setAnsiColors, isUrl } from 'utils'
|
||||
import { getContext, setAnsiColors, isUrl } from 'utils'
|
||||
import Debug from 'debug'
|
||||
import Youch from '@nuxtjs/youch'
|
||||
import { SourceMapConsumer } from 'source-map'
|
||||
import connect from 'connect'
|
||||
import { Options } from 'common'
|
||||
|
||||
@ -44,7 +46,7 @@ export default class Renderer extends Tapable {
|
||||
serverBundle: null,
|
||||
ssrTemplate: 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
|
||||
await this.setupMiddleware()
|
||||
|
||||
// Load error template
|
||||
// Load error template for when debug is disabled
|
||||
const errorTemplatePath = resolve(this.options.buildDir, 'views/error.html')
|
||||
if (fs.existsSync(errorTemplatePath)) {
|
||||
this.resources.errorTemplate = parseTemplate(fs.readFileSync(errorTemplatePath, 'utf8'))
|
||||
@ -211,6 +213,8 @@ export default class Renderer extends Tapable {
|
||||
this.useMiddleware(this.nuxtMiddleware.bind(this))
|
||||
|
||||
// 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))
|
||||
}
|
||||
|
||||
@ -262,28 +266,116 @@ export default class Renderer extends Tapable {
|
||||
res.end(html, 'utf8')
|
||||
return html
|
||||
} 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) {
|
||||
/* istanbul ignore if */
|
||||
if (context && context.redirected) {
|
||||
console.error(err) // eslint-disable-line no-console
|
||||
return err
|
||||
errorMiddleware (err, req, res, next) {
|
||||
const sendResponse = html => {
|
||||
// Set Headers
|
||||
res.statusCode = 500
|
||||
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
|
||||
const html = this.resources.errorTemplate({
|
||||
error: err,
|
||||
stack: ansiHTML(encodeHtml(err.stack))
|
||||
})
|
||||
// Send response
|
||||
res.statusCode = 500
|
||||
res.setHeader('Content-Type', 'text/html; charset=utf-8')
|
||||
res.setHeader('Content-Length', Buffer.byteLength(html))
|
||||
res.end(html, 'utf8')
|
||||
return err
|
||||
// Use basic errors when debug mode is disabled
|
||||
if (!this.options.render.debug) {
|
||||
const html = this.resources.errorTemplate({
|
||||
code: err.statusCode || 500,
|
||||
message: err.message || 'Nuxt Server Error'
|
||||
})
|
||||
sendResponse(html)
|
||||
return
|
||||
}
|
||||
|
||||
// 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 = {}) {
|
||||
|
@ -67,6 +67,7 @@
|
||||
"npm": ">=3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nuxtjs/youch": "3.0.1",
|
||||
"ansi-html": "^0.0.7",
|
||||
"autoprefixer": "^7.1.2",
|
||||
"babel-core": "^6.25.0",
|
||||
@ -100,6 +101,7 @@
|
||||
"serialize-javascript": "^1.4.0",
|
||||
"serve-static": "^1.12.3",
|
||||
"server-destroy": "^1.0.1",
|
||||
"source-map": "^0.5.6",
|
||||
"source-map-support": "^0.4.15",
|
||||
"tappable": "^1.1.0",
|
||||
"url-loader": "^0.5.9",
|
||||
|
@ -62,6 +62,8 @@
|
||||
"compression": "^1.7.0",
|
||||
"fs-extra": "^4.0.1",
|
||||
"vue-server-renderer": "~2.4.2",
|
||||
"@nuxtjs/youch": "3.0.1",
|
||||
"source-map": "^0.5.6",
|
||||
"connect": "^3.6.3",
|
||||
"server-destroy": "^1.0.1"
|
||||
},
|
||||
|
18
yarn.lock
18
yarn.lock
@ -44,6 +44,14 @@
|
||||
dependencies:
|
||||
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":
|
||||
version "6.0.85"
|
||||
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"
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
|
||||
|
||||
@ -4117,6 +4125,10 @@ multimatch@^2.1.0:
|
||||
arrify "^1.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:
|
||||
version "0.0.7"
|
||||
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"
|
||||
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:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.1.tgz#d4f33ab54e8e38778b0ca5cfd3b3afb12db68620"
|
||||
|
Loading…
Reference in New Issue
Block a user