Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: improve production debugging dx #7463

Merged
merged 14 commits into from Jun 9, 2020
6 changes: 3 additions & 3 deletions packages/cli/test/unit/__snapshots__/webpack.test.js.snap
Expand Up @@ -567,7 +567,7 @@ exports[`webpack nuxt webpack module.rules 1`] = `
\\"loader\\": \\"url-loader\\",
\\"options\\": Object {
\\"limit\\": 1000,
\\"name\\": \\"img/[contenthash:7].[ext]\\",
\\"name\\": \\"img/[name].[contenthash:7].[ext]\\",
},
},
],
Expand All @@ -579,7 +579,7 @@ exports[`webpack nuxt webpack module.rules 1`] = `
\\"loader\\": \\"url-loader\\",
\\"options\\": Object {
\\"limit\\": 1000,
\\"name\\": \\"fonts/[contenthash:7].[ext]\\",
\\"name\\": \\"fonts/[name].[contenthash:7].[ext]\\",
},
},
],
Expand All @@ -590,7 +590,7 @@ exports[`webpack nuxt webpack module.rules 1`] = `
Object {
\\"loader\\": \\"file-loader\\",
\\"options\\": Object {
\\"name\\": \\"videos/[contenthash:7].[ext]\\",
\\"name\\": \\"videos/[name].[contenthash:7].[ext]\\",
},
},
],
Expand Down
12 changes: 6 additions & 6 deletions packages/config/src/config/build.js
Expand Up @@ -14,12 +14,12 @@ export default () => ({
serverURLPolyfill: 'url',
filenames: {
// { isDev, isClient, isServer }
app: ({ isDev, isModern }) => isDev ? `${isModern ? 'modern-' : ''}[name].js` : '[contenthash].js',
chunk: ({ isDev, isModern }) => isDev ? `${isModern ? 'modern-' : ''}[name].js` : '[contenthash].js',
css: ({ isDev }) => isDev ? '[name].css' : '[contenthash].css',
img: ({ isDev }) => isDev ? '[path][name].[ext]' : 'img/[contenthash:7].[ext]',
font: ({ isDev }) => isDev ? '[path][name].[ext]' : 'fonts/[contenthash:7].[ext]',
video: ({ isDev }) => isDev ? '[path][name].[ext]' : 'videos/[contenthash:7].[ext]'
app: ({ isDev, isModern }) => isDev ? `${isModern ? 'modern-' : ''}[name].js` : '[name].[contenthash:7].js',
chunk: ({ isDev, isModern }) => isDev ? `${isModern ? 'modern-' : ''}[name].js` : '[name].[contenthash:7].js',
css: ({ isDev }) => isDev ? '[name].css' : '[name].[contenthash:7].css',
manniL marked this conversation as resolved.
Show resolved Hide resolved
img: ({ isDev }) => isDev ? '[path][name].[ext]' : 'img/[name].[contenthash:7].[ext]',
font: ({ isDev }) => isDev ? '[path][name].[ext]' : 'fonts/[name].[contenthash:7].[ext]',
video: ({ isDev }) => isDev ? '[path][name].[ext]' : 'videos/[name].[contenthash:7].[ext]'
},
loaders: {
file: {},
Expand Down
12 changes: 6 additions & 6 deletions packages/config/test/config/build.test.js
Expand Up @@ -15,12 +15,12 @@ describe('config: build', () => {
test('should return prod filenames', () => {
const { filenames } = buildConfig()
const env = { isDev: false }
expect(filenames.app(env)).toEqual('[contenthash].js')
expect(filenames.chunk(env)).toEqual('[contenthash].js')
expect(filenames.css(env)).toEqual('[contenthash].css')
expect(filenames.img(env)).toEqual('img/[contenthash:7].[ext]')
expect(filenames.font(env)).toEqual('fonts/[contenthash:7].[ext]')
expect(filenames.video(env)).toEqual('videos/[contenthash:7].[ext]')
expect(filenames.app(env)).toEqual('[name].[contenthash:7].js')
expect(filenames.chunk(env)).toEqual('[name].[contenthash:7].js')
expect(filenames.css(env)).toEqual('[name].[contenthash:7].css')
expect(filenames.img(env)).toEqual('img/[name].[contenthash:7].[ext]')
expect(filenames.font(env)).toEqual('fonts/[name].[contenthash:7].[ext]')
expect(filenames.video(env)).toEqual('videos/[name].[contenthash:7].[ext]')
})

test('should return modern filenames', () => {
Expand Down
94 changes: 48 additions & 46 deletions packages/server/src/middleware/error.js
Expand Up @@ -4,20 +4,14 @@ import consola from 'consola'

import Youch from '@nuxtjs/youch'

export default ({ resources, options }) => async function errorMiddleware (err, req, res, next) {
// ensure statusCode, message and name fields

const error = {
statusCode: err.statusCode || 500,
message: err.message || 'Nuxt Server Error',
name: !err.name || err.name === 'Error' ? 'NuxtServerError' : err.name,
headers: err.headers
}
export default ({ resources, options }) => async function errorMiddleware (_error, req, res, next) {
// Normalize error
const error = normalizeError(_error, options)

const sendResponse = (content, type = 'text/html') => {
// Set Headers
res.statusCode = error.statusCode
res.statusMessage = error.name
res.statusMessage = 'RuntimeError'
res.setHeader('Content-Type', type + '; charset=utf-8')
res.setHeader('Content-Length', Buffer.byteLength(content))
res.setHeader('Cache-Control', 'no-cache, no-store, max-age=0, must-revalidate')
Expand All @@ -43,9 +37,10 @@ export default ({ resources, options }) => async function errorMiddleware (err,
// Use basic errors when debug mode is disabled
if (!options.debug) {
// We hide actual errors from end users, so show them on server logs
if (err.statusCode !== 404) {
consola.error(err)
if (error.statusCode !== 404) {
consola.error(error)
}

// Json format is compatible with Youch json responses
const json = {
status: error.statusCode,
Expand All @@ -61,25 +56,11 @@ export default ({ resources, options }) => async function errorMiddleware (err,
return
}

const errorFull = err instanceof Error
? err
: typeof err === 'string'
? new Error(err)
: new Error(err.message || JSON.stringify(err))

errorFull.name = error.name
errorFull.statusCode = error.statusCode
errorFull.stack = err.stack || undefined

// Show stack trace
const youch = new Youch(
errorFull,
error,
req,
readSourceFactory({
srcDir: options.srcDir,
rootDir: options.rootDir,
buildDir: options.buildDir
}),
readSource,
options.router.base,
true
)
Expand All @@ -93,17 +74,21 @@ export default ({ resources, options }) => async function errorMiddleware (err,
sendResponse(html)
}

const readSourceFactory = ({ srcDir, rootDir, buildDir }) => async function readSource (frame) {
// Remove webpack:/// & query string from the end
const sanitizeName = name => name ? name.replace('webpack:///', '').split('?')[0] : null
frame.fileName = sanitizeName(frame.fileName)
const sanitizeName = name => name ? name.replace('webpack:///', '').split('?')[0] : null

// Return if fileName is unknown
if (!frame.fileName) {
return
const normalizeError = (_error, { srcDir, rootDir, buildDir }) => {
if (typeof _error === 'string') {
_error = { message: _error }
} else if (!_error) {
_error = { message: '<empty>' }
}

// Possible paths for file
const error = new Error()
error.message = _error.message
error.name = _error.name
error.statusCode = _error.statusCode || 500
error.headers = _error.headers

const searchPath = [
srcDir,
rootDir,
Expand All @@ -112,17 +97,34 @@ const readSourceFactory = ({ srcDir, rootDir, buildDir }) => async function read
process.cwd()
]

// Scan filesystem for real source
for (const pathDir of searchPath) {
const fullPath = path.resolve(pathDir, frame.fileName)
const source = await fs.readFile(fullPath, 'utf-8').catch(() => null)
if (source) {
frame.contents = source
frame.fullPath = fullPath
if (path.isAbsolute(frame.fileName)) {
frame.fileName = path.relative(rootDir, fullPath)
const findInPaths = (fileName) => {
for (const dir of searchPath) {
const fullPath = path.resolve(dir, fileName)
if (fs.existsSync(fullPath)) {
return fullPath
}
return
}
return fileName
}

error.stack = (_error.stack || '')
.split('\n')
.map((line) => {
const match = line.match(/\(([^)]+)\)|([^\s]+\.[^\s]+):/)
if (!match) {
return line
}
const src = match[1] || match[2] || ''
return line.replace(src, findInPaths(sanitizeName(src)))
})
.join('\n')

return error
}

async function readSource (frame) {
if (fs.existsSync(frame.fileName)) {
frame.fullPath = frame.fileName // Youch BW compat
frame.contents = await fs.readFile(frame.fileName, 'utf-8')
}
}