diff --git a/packages/cli/test/unit/__snapshots__/webpack.test.js.snap b/packages/cli/test/unit/__snapshots__/webpack.test.js.snap index b5000549cdf2..31e9d84ba5ca 100644 --- a/packages/cli/test/unit/__snapshots__/webpack.test.js.snap +++ b/packages/cli/test/unit/__snapshots__/webpack.test.js.snap @@ -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]\\", }, }, ], @@ -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]\\", }, }, ], @@ -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]\\", }, }, ], diff --git a/packages/config/src/config/build.js b/packages/config/src/config/build.js index ae72a82e8a9a..b45d01e19ccc 100644 --- a/packages/config/src/config/build.js +++ b/packages/config/src/config/build.js @@ -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', + 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: {}, diff --git a/packages/config/test/config/build.test.js b/packages/config/test/config/build.test.js index f55f79e6e085..f9336df0e8ad 100644 --- a/packages/config/test/config/build.test.js +++ b/packages/config/test/config/build.test.js @@ -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', () => { diff --git a/packages/server/src/middleware/error.js b/packages/server/src/middleware/error.js index 05e61ffaa753..9603281b84a7 100644 --- a/packages/server/src/middleware/error.js +++ b/packages/server/src/middleware/error.js @@ -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') @@ -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, @@ -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 ) @@ -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: '' } } - // 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, @@ -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') } } diff --git a/packages/server/test/middleware/error.test.js b/packages/server/test/middleware/error.test.js index fd8e5a09d3d8..92421f37a6c5 100644 --- a/packages/server/test/middleware/error.test.js +++ b/packages/server/test/middleware/error.test.js @@ -29,6 +29,13 @@ const createServerContext = () => ({ next: jest.fn() }) +const errorFileName = 'test-error.js' +const createError = () => { + const err = new Error('Error!') + err.stack = `Error!\n at foo (webpack:///${errorFileName}?foo:1)` + return err +} + describe('server: errorMiddleware', () => { beforeAll(() => { path.join.mockImplementation((...args) => `join(${args.join(', ')})`) @@ -48,26 +55,21 @@ describe('server: errorMiddleware', () => { test('should send html error response', async () => { const params = createParams() const errorMiddleware = createErrorMiddleware(params) - const error = new Error() + const error = {} error.headers = { 'Custom-Header': 'test' } const ctx = createServerContext() await errorMiddleware(error, ctx.req, ctx.res, ctx.next) - expect(consola.error).toBeCalledWith(error) expect(ctx.res.statusCode).toEqual(500) - expect(ctx.res.statusMessage).toEqual('NuxtServerError') + expect(ctx.res.statusMessage).toEqual('RuntimeError') expect(ctx.res.setHeader).toBeCalledTimes(4) expect(ctx.res.setHeader).nthCalledWith(1, 'Content-Type', 'text/html; charset=utf-8') expect(ctx.res.setHeader).nthCalledWith(2, 'Content-Length', Buffer.byteLength('error template')) expect(ctx.res.setHeader).nthCalledWith(3, 'Cache-Control', 'no-cache, no-store, max-age=0, must-revalidate') expect(ctx.res.setHeader).nthCalledWith(4, 'Custom-Header', 'test') expect(params.resources.errorTemplate).toBeCalledTimes(1) - expect(params.resources.errorTemplate).toBeCalledWith({ - status: 500, - message: 'Nuxt Server Error', - name: 'NuxtServerError' - }) + expect(params.resources.errorTemplate).toBeCalledWith({ status: 500 }) expect(ctx.res.end).toBeCalledTimes(1) expect(ctx.res.end).toBeCalledWith('error template', 'utf-8') }) @@ -77,7 +79,6 @@ describe('server: errorMiddleware', () => { const errorMiddleware = createErrorMiddleware(params) const error = { statusCode: 404, - name: 'NuxtTestError', message: 'test error' } const ctx = createServerContext() @@ -92,7 +93,6 @@ describe('server: errorMiddleware', () => { }, undefined, 2) expect(consola.error).not.toBeCalled() expect(ctx.res.statusCode).toEqual(404) - expect(ctx.res.statusMessage).toEqual(error.name) expect(ctx.res.setHeader).toBeCalledTimes(3) expect(ctx.res.setHeader).nthCalledWith(1, 'Content-Type', 'text/json; charset=utf-8') expect(ctx.res.setHeader).nthCalledWith(2, 'Content-Length', Buffer.byteLength(errJson)) @@ -108,22 +108,13 @@ describe('server: errorMiddleware', () => { const errorMiddleware = createErrorMiddleware(params) const error = new Error('test error') error.statusCode = 503 - error.name = 'NuxtTestError' const ctx = createServerContext() await errorMiddleware(error, ctx.req, ctx.res, ctx.next) const errHtml = 'youch html' - expect(Youch).toBeCalledTimes(1) - expect(Youch).toBeCalledWith( - error, - ctx.req, - expect.any(Function), - params.options.router.base, - true - ) + expect(ctx.res.statusCode).toEqual(503) - expect(ctx.res.statusMessage).toEqual(error.name) expect(ctx.res.setHeader).toBeCalledTimes(3) expect(ctx.res.setHeader).nthCalledWith(1, 'Content-Type', 'text/html; charset=utf-8') expect(ctx.res.setHeader).nthCalledWith(2, 'Content-Length', Buffer.byteLength(errHtml)) @@ -139,7 +130,6 @@ describe('server: errorMiddleware', () => { const errorMiddleware = createErrorMiddleware(params) const error = { statusCode: 404, - name: 'NuxtTestError', message: 'test error' } const ctx = createServerContext() @@ -148,20 +138,8 @@ describe('server: errorMiddleware', () => { await errorMiddleware(error, ctx.req, ctx.res, ctx.next) const errJson = JSON.stringify('youch json', undefined, 2) - const errorFull = new Error(error.message) - errorFull.name = error.name - errorFull.statusCode = error.statusCode - errorFull.stack = undefined - expect(Youch).toBeCalledTimes(1) - expect(Youch).toBeCalledWith( - errorFull, - ctx.req, - expect.any(Function), - params.options.router.base, - true - ) + expect(ctx.res.statusCode).toEqual(404) - expect(ctx.res.statusMessage).toEqual(error.name) expect(ctx.res.setHeader).toBeCalledTimes(3) expect(ctx.res.setHeader).nthCalledWith(1, 'Content-Type', 'text/json; charset=utf-8') expect(ctx.res.setHeader).nthCalledWith(2, 'Content-Length', Buffer.byteLength(errJson)) @@ -175,24 +153,16 @@ describe('server: errorMiddleware', () => { const params = createParams() params.options.debug = true const errorMiddleware = createErrorMiddleware(params) - const error = {} + const error = createError() const ctx = createServerContext() await errorMiddleware(error, ctx.req, ctx.res, ctx.next) - const frame = { fileName: 'webpack:///test-error.js?desc=test' } - const readSource = Youch.mock.calls[0][2] - await readSource(frame) - - const fileName = 'test-error.js' - expect(frame).toEqual({ fileName }) - expect(path.resolve).toBeCalledTimes(5) - expect(fs.readFile).toBeCalledTimes(5) - expect(fs.readFile).nthCalledWith(1, `resolve(${params.options.srcDir}, ${fileName})`, 'utf-8') - expect(fs.readFile).nthCalledWith(2, `resolve(${params.options.rootDir}, ${fileName})`, 'utf-8') - expect(fs.readFile).nthCalledWith(3, `resolve(join(${params.options.buildDir}, dist, server), ${fileName})`, 'utf-8') - expect(fs.readFile).nthCalledWith(4, `resolve(${params.options.buildDir}, ${fileName})`, 'utf-8') - expect(fs.readFile).nthCalledWith(5, `resolve(${process.cwd()}, ${fileName})`, 'utf-8') + expect(fs.existsSync).nthCalledWith(1, `resolve(${params.options.srcDir}, ${errorFileName})`) + expect(fs.existsSync).nthCalledWith(2, `resolve(${params.options.rootDir}, ${errorFileName})`) + expect(fs.existsSync).nthCalledWith(3, `resolve(join(${params.options.buildDir}, dist, server), ${errorFileName})`) + expect(fs.existsSync).nthCalledWith(4, `resolve(${params.options.buildDir}, ${errorFileName})`) + expect(fs.existsSync).nthCalledWith(5, `resolve(${process.cwd()}, ${errorFileName})`) }) test('should return source content after read source', async () => { @@ -204,20 +174,20 @@ describe('server: errorMiddleware', () => { await errorMiddleware(error, ctx.req, ctx.res, ctx.next) - const frame = { fileName: 'webpack:///test-error.js?desc=test' } + const frame = { fileName: errorFileName } const readSource = Youch.mock.calls[0][2] + fs.existsSync.mockImplementationOnce(() => true) fs.readFile.mockImplementationOnce(() => Promise.resolve('source content')) await readSource(frame) - const fileName = 'test-error.js' expect(frame).toEqual({ - fileName, + fileName: errorFileName, contents: 'source content', - fullPath: `resolve(${params.options.srcDir}, ${fileName})` + fullPath: errorFileName }) }) - test('should return relative fileName if fileName is absolute path', async () => { + test('should ignore if source file not exists', async () => { const params = createParams() params.options.debug = true const errorMiddleware = createErrorMiddleware(params) @@ -226,39 +196,11 @@ describe('server: errorMiddleware', () => { await errorMiddleware(error, ctx.req, ctx.res, ctx.next) - const frame = { fileName: 'webpack:///test-error.js?desc=test' } + const frame = { fileName: errorFileName } const readSource = Youch.mock.calls[0][2] - fs.readFile.mockImplementationOnce(() => Promise.resolve('source content')) - path.isAbsolute.mockReturnValueOnce(true) - path.relative.mockImplementationOnce((...args) => `relative(${args.join(', ')})`) + fs.exists.mockReturnValueOnce(false) await readSource(frame) - - const fullPath = `resolve(${params.options.srcDir}, test-error.js)` - expect(frame).toEqual({ - fileName: `relative(${params.options.rootDir}, ${fullPath})`, - contents: 'source content', - fullPath - }) - }) - - test('should ignore error when reading source', async () => { - const params = createParams() - params.options.debug = true - const errorMiddleware = createErrorMiddleware(params) - const error = {} - const ctx = createServerContext() - - await errorMiddleware(error, ctx.req, ctx.res, ctx.next) - - const frame = { fileName: 'webpack:///test-error.js?desc=test' } - const readSource = Youch.mock.calls[0][2] - fs.readFile.mockReturnValueOnce(Promise.reject(new Error('read failed'))) - await readSource(frame) - - const fileName = 'test-error.js' - expect(frame).toEqual({ fileName }) - expect(path.resolve).toBeCalledTimes(5) - expect(fs.readFile).toBeCalledTimes(5) + expect(frame).toEqual({ fileName: errorFileName }) }) test('should return if fileName is unknown when read source', async () => { @@ -274,6 +216,6 @@ describe('server: errorMiddleware', () => { const readSource = Youch.mock.calls[0][2] await readSource(frame) - expect(frame.fileName).toBeNull() + expect(frame.fileName).toBeUndefined() }) }) diff --git a/packages/webpack/src/config/server.js b/packages/webpack/src/config/server.js index d921c43304a1..f6185f4ad4c1 100644 --- a/packages/webpack/src/config/server.js +++ b/packages/webpack/src/config/server.js @@ -56,9 +56,11 @@ export default class WebpackServerConfig extends WebpackBaseConfig { } optimization () { + const { _minifyServer } = this.buildContext.buildOptions + return { splitChunks: false, - minimizer: this.minimizer() + minimizer: _minifyServer ? this.minimizer() : [] } } @@ -113,6 +115,7 @@ export default class WebpackServerConfig extends WebpackBaseConfig { }), output: Object.assign({}, config.output, { filename: 'server.js', + chunkFilename: '[name].js', libraryTarget: 'commonjs2' }), performance: { diff --git a/test/dev/with-config.test.js b/test/dev/with-config.test.js index 97b1445e338b..687f8b91339e 100644 --- a/test/dev/with-config.test.js +++ b/test/dev/with-config.test.js @@ -48,7 +48,7 @@ describe('with-config', () => { test('/ (preload fonts)', async () => { const { html } = await nuxt.server.renderRoute('/') expect(html).toContain( - '