From 71c1511ee5df7041c0a5ad23b51b445fb617bc23 Mon Sep 17 00:00:00 2001 From: Leah Date: Thu, 28 Feb 2019 00:52:00 +0100 Subject: [PATCH 1/3] better prerender errors myb --- packages/cli/lib/lib/webpack/prerender.js | 149 ++++++++++++++-------- 1 file changed, 99 insertions(+), 50 deletions(-) diff --git a/packages/cli/lib/lib/webpack/prerender.js b/packages/cli/lib/lib/webpack/prerender.js index a3e93a2b1..0b5d1ecc0 100644 --- a/packages/cli/lib/lib/webpack/prerender.js +++ b/packages/cli/lib/lib/webpack/prerender.js @@ -1,8 +1,9 @@ -const { red, yellow } = require('kleur'); +const { red, gray } = require('kleur'); const { resolve } = require('path'); const { readFileSync } = require('fs'); const stackTrace = require('stack-trace'); const { SourceMapConsumer } = require('source-map'); +const { error, info } = require('../../util'); module.exports = function(env, params) { params = params || {}; @@ -14,8 +15,8 @@ module.exports = function(env, params) { global.location = { href: url, pathname: url }; try { - let m = require(entry), - app = (m && m.default) || m; + const m = require(entry); + const app = (m && m.default) || m; if (typeof app !== 'function') { // eslint-disable-next-line no-console @@ -32,35 +33,53 @@ module.exports = function(env, params) { )); return renderToString(preact.h(app, { ...params, url })); } catch (err) { - let stack = stackTrace.parse(err).filter(s => s.getFileName() === entry)[0]; + const stack = stackTrace + .parse(err) + .filter(s => s.getFileName().includes('ssr-build'))[0]; if (!stack) { - throw err; + error(err); + return ''; } handlePrerenderError(err, env, stack, entry); + return ''; } }; async function handlePrerenderError(err, env, stack, entry) { - let errorMessage = err.toString(); - let isReferenceError = errorMessage.startsWith('ReferenceError'); - let methodName = stack.getMethodName(); - let sourceMapContent, position, sourcePath, sourceLines, sourceCodeHighlight; + const errorMessage = err.toString(); + const isReferenceError = errorMessage.startsWith('ReferenceError'); + const methodName = stack.getMethodName(); + const fileName = stack.getFileName().replace(/\\/g, '/'); + let sourceCodeHighlight = ''; - try { - sourceMapContent = JSON.parse(readFileSync(`${entry}.map`)); - } catch (err) { - process.stderr.write(red(`Unable to read sourcemap: ${entry}.map\n`)); - } + let position; - if (sourceMapContent) { - await SourceMapConsumer.with(sourceMapContent, null, consumer => { - position = consumer.originalPositionFor({ - line: stack.getLineNumber(), - column: stack.getColumnNumber(), + info(fileName); + if (/webpack:/.test(fileName)) { + position = { + source: fileName.replace(/.+webpack:/, 'webpack://'), + line: stack.getLineNumber(), + column: stack.getColumnNumber(), + }; + } else { + try { + const sourceMapContent = JSON.parse(readFileSync(`${entry}.map`)); + + await SourceMapConsumer.with(sourceMapContent, null, consumer => { + position = consumer.originalPositionFor({ + line: stack.getLineNumber(), + column: stack.getColumnNumber(), + }); }); - }); + } catch (err) { + error(`Unable to read sourcemap: ${entry}.map`); + return; + } + } + if (position) { + info(position.source); position.source = position.source .replace('webpack://', '.') .replace(/^.*~\/((?:@[^/]+\/)?[^/]+)/, (s, name) => @@ -68,55 +87,85 @@ async function handlePrerenderError(err, env, stack, entry) { .resolve(name) .replace(/^(.*?\/node_modules\/(@[^/]+\/)?[^/]+)(\/.*)$/, '$1') ); + info(position.source); - sourcePath = resolve(env.src, position.source); - sourceLines; + let sourcePath; + let sourceLines; try { + sourcePath = resolve(env.src, position.source); sourceLines = readFileSync(sourcePath, 'utf-8').split('\n'); } catch (err) { try { - sourceLines = readFileSync( - require.resolve(position.source), - 'utf-8' - ).split('\n'); + sourcePath = resolve(env.cwd, position.source); + // sourcePath = require.resolve(position.source); + sourceLines = readFileSync(sourcePath, 'utf-8').split('\n'); } catch (err) { - process.stderr.write(red(`Unable to read file: ${sourcePath}\n`)); + error(`Unable to read file: ${sourcePath} (${position.source})\n`); + return; } - // process.stderr.write(red(`Unable to read file: ${sourcePath}\n`)); } - sourceCodeHighlight = ''; if (sourceLines) { - for (var i = -4; i <= 4; i++) { - let color = i === 0 ? red : yellow; - let line = position.line + i; - let sourceLine = sourceLines[line - 1]; - sourceCodeHighlight += sourceLine ? `${color(sourceLine)}\n` : ''; - } + let lnrl = position.line.toString().length + 1; + sourceCodeHighlight += + gray( + (position.line - 2 || '').toString().padStart(lnrl) + + ' | ' + + sourceLines[position.line - 3] || '' + ) + '\n'; + sourceCodeHighlight += + gray( + (position.line - 1 || '').toString().padStart(lnrl) + + ' | ' + + sourceLines[position.line - 2] || '' + ) + '\n'; + sourceCodeHighlight += + red(position.line.toString().padStart(lnrl)) + + gray(' | ') + + sourceLines[position.line - 1] + + '\n'; + sourceCodeHighlight += + gray('| '.padStart(lnrl + 3)) + + red('^'.padStart(position.column + 1)) + + '\n'; + sourceCodeHighlight += + gray( + (position.line + 1).toString().padStart(lnrl) + + ' | ' + + sourceLines[position.line + 0] || '' + ) + '\n'; + sourceCodeHighlight += + gray( + (position.line + 2).toString().padStart(lnrl) + + ' | ' + + sourceLines[position.line + 1] || '' + ) + '\n'; } + } else { + position = { + source: stack.getFileName(), + line: stack.getLineNumber(), + column: stack.getColumnNumber(), + }; } process.stderr.write('\n'); - process.stderr.write(red(`${errorMessage}\n`)); - process.stderr.write(`method: ${methodName}\n`); - if (sourceMapContent) { - process.stderr.write( - `at: ${sourcePath}:${position.line}:${position.column}\n` - ); - process.stderr.write('\n'); - process.stderr.write('Source code:\n\n'); - process.stderr.write(sourceCodeHighlight); - process.stderr.write('\n'); - } else { - process.stderr.write(stack.toString() + '\n'); - } + process.stderr.write(`[PrerenderError]: ${red(`${errorMessage}\n`)}`); + process.stderr.write( + ` --> ${position.source}:${position.line}:${ + position.column + } (${methodName || ''})\n` + ); + process.stderr.write(sourceCodeHighlight + '\n'); + process.stderr.write(red(`${err.stack}\n`)); + process.stderr.write( `This ${ isReferenceError ? 'is most likely' : 'could be' } caused by using DOM or Web APIs.\n` ); process.stderr.write( - `Pre-render runs in node and has no access to globals available in browsers.\n\n` + `Pre-render runs in node and has no access to globals available in browsers.\n` ); process.stderr.write( `Consider wrapping code producing error in: 'if (typeof window !== "undefined") { ... }'\n` @@ -127,7 +176,7 @@ async function handlePrerenderError(err, env, stack, entry) { } process.stderr.write('\n'); process.stderr.write( - `Alternatively use 'preact build --no-prerender' to disable prerendering.\n\n` + 'Alternatively use `preact build --no-prerender` to disable prerendering.\n' ); process.stderr.write( 'See https://github.com/developit/preact-cli#pre-rendering for further information.' From bb94b1e252b5c8c5ba4c7b2b158cbef20c7dd6a4 Mon Sep 17 00:00:00 2001 From: Leah Date: Fri, 12 Jul 2019 21:27:08 +0200 Subject: [PATCH 2/3] feat: outdent error message --- packages/cli/lib/lib/webpack/prerender.js | 153 +++++++++------------- packages/cli/package.json | 1 + yarn.lock | 5 + 3 files changed, 71 insertions(+), 88 deletions(-) diff --git a/packages/cli/lib/lib/webpack/prerender.js b/packages/cli/lib/lib/webpack/prerender.js index 0b5d1ecc0..c7ae75680 100644 --- a/packages/cli/lib/lib/webpack/prerender.js +++ b/packages/cli/lib/lib/webpack/prerender.js @@ -4,8 +4,9 @@ const { readFileSync } = require('fs'); const stackTrace = require('stack-trace'); const { SourceMapConsumer } = require('source-map'); const { error, info } = require('../../util'); +const outdent = require('outdent'); -module.exports = function(env, params) { +module.exports = function prerender(env, params) { params = params || {}; let entry = resolve(env.dest, './ssr-build/ssr-bundle.js'); @@ -46,12 +47,26 @@ module.exports = function(env, params) { } }; +function getLines(env, position) { + let sourcePath; + try { + sourcePath = resolve(env.src, position.source); + return readFileSync(sourcePath, 'utf-8').split('\n'); + } catch (err) { + try { + sourcePath = resolve(env.cwd, position.source); + return readFileSync(sourcePath, 'utf-8').split('\n'); + } catch (err) { + error(`Unable to read file: ${sourcePath} (${position.source})\n`); + } + } +} + async function handlePrerenderError(err, env, stack, entry) { const errorMessage = err.toString(); const isReferenceError = errorMessage.startsWith('ReferenceError'); const methodName = stack.getMethodName(); const fileName = stack.getFileName().replace(/\\/g, '/'); - let sourceCodeHighlight = ''; let position; @@ -64,7 +79,9 @@ async function handlePrerenderError(err, env, stack, entry) { }; } else { try { - const sourceMapContent = JSON.parse(readFileSync(`${entry}.map`)); + const sourceMapContent = JSON.parse( + readFileSync(`${entry}.map`, 'utf-8') + ); await SourceMapConsumer.with(sourceMapContent, null, consumer => { position = consumer.originalPositionFor({ @@ -74,7 +91,6 @@ async function handlePrerenderError(err, env, stack, entry) { }); } catch (err) { error(`Unable to read sourcemap: ${entry}.map`); - return; } } @@ -88,59 +104,6 @@ async function handlePrerenderError(err, env, stack, entry) { .replace(/^(.*?\/node_modules\/(@[^/]+\/)?[^/]+)(\/.*)$/, '$1') ); info(position.source); - - let sourcePath; - let sourceLines; - try { - sourcePath = resolve(env.src, position.source); - sourceLines = readFileSync(sourcePath, 'utf-8').split('\n'); - } catch (err) { - try { - sourcePath = resolve(env.cwd, position.source); - // sourcePath = require.resolve(position.source); - sourceLines = readFileSync(sourcePath, 'utf-8').split('\n'); - } catch (err) { - error(`Unable to read file: ${sourcePath} (${position.source})\n`); - return; - } - } - - if (sourceLines) { - let lnrl = position.line.toString().length + 1; - sourceCodeHighlight += - gray( - (position.line - 2 || '').toString().padStart(lnrl) + - ' | ' + - sourceLines[position.line - 3] || '' - ) + '\n'; - sourceCodeHighlight += - gray( - (position.line - 1 || '').toString().padStart(lnrl) + - ' | ' + - sourceLines[position.line - 2] || '' - ) + '\n'; - sourceCodeHighlight += - red(position.line.toString().padStart(lnrl)) + - gray(' | ') + - sourceLines[position.line - 1] + - '\n'; - sourceCodeHighlight += - gray('| '.padStart(lnrl + 3)) + - red('^'.padStart(position.column + 1)) + - '\n'; - sourceCodeHighlight += - gray( - (position.line + 1).toString().padStart(lnrl) + - ' | ' + - sourceLines[position.line + 0] || '' - ) + '\n'; - sourceCodeHighlight += - gray( - (position.line + 2).toString().padStart(lnrl) + - ' | ' + - sourceLines[position.line + 1] || '' - ) + '\n'; - } } else { position = { source: stack.getFileName(), @@ -149,37 +112,51 @@ async function handlePrerenderError(err, env, stack, entry) { }; } - process.stderr.write('\n'); - process.stderr.write(`[PrerenderError]: ${red(`${errorMessage}\n`)}`); - process.stderr.write( - ` --> ${position.source}:${position.line}:${ - position.column - } (${methodName || ''})\n` - ); - process.stderr.write(sourceCodeHighlight + '\n'); - process.stderr.write(red(`${err.stack}\n`)); - - process.stderr.write( - `This ${ - isReferenceError ? 'is most likely' : 'could be' - } caused by using DOM or Web APIs.\n` - ); - process.stderr.write( - `Pre-render runs in node and has no access to globals available in browsers.\n` - ); - process.stderr.write( - `Consider wrapping code producing error in: 'if (typeof window !== "undefined") { ... }'\n` - ); - - if (methodName === 'componentWillMount') { - process.stderr.write(`or place logic in 'componentDidMount' method.\n`); + const sourceLines = getLines(env, position); + + let sourceCodeHighlight = ''; + if (sourceLines) { + const lnrl = position.line.toString().length + 2; + const line = position.line; + const un = undefined; + + const pad = l => + (l === undefined ? '' : (line + l || '') + '').padStart(lnrl); + + sourceCodeHighlight = gray(outdent` + ${pad(-2)} | ${sourceLines[line - 3] || ''} + ${pad(-1)} | ${sourceLines[line - 2] || ''} + ${pad(-0)} | ${sourceLines[line - 1] || ''} + ${pad(un)} | ${red('^'.padStart(position.column + 1))} + ${pad(+1)} | ${sourceLines[line + 0] || ''} + ${pad(+2)} | ${sourceLines[line + 1] || ''} + `); } - process.stderr.write('\n'); - process.stderr.write( - 'Alternatively use `preact build --no-prerender` to disable prerendering.\n' - ); - process.stderr.write( - 'See https://github.com/developit/preact-cli#pre-rendering for further information.' - ); + + const stderr = process.stderr.write.bind(process.stderr); + + stderr('\n'); + stderr(outdent` + [PrerenderError]: ${red(`${errorMessage}`)} + --> ${position.source}:${position.line}:${position.column} (${methodName || + ''}) + ${sourceCodeHighlight} + + ${red(`${err.stack}`)} + + This ${ + isReferenceError ? 'is most likely' : 'could be' + } caused by using DOM or Web APIs. + Pre-render runs in node and has no access to globals available in browsers. + Consider wrapping code producing error in: 'if (typeof window !== "undefined") { ... }\ + ${ + methodName === 'componentWillMount' + ? `\nor place logic in 'componentDidMount' method.` + : '' + } + + Alternatively use \`preact build --no-prerender\` to disable prerendering. + See https://github.com/developit/preact-cli#pre-rendering for further information. + `); process.exit(1); } diff --git a/packages/cli/package.json b/packages/cli/package.json index 0ac5a5fec..829c5653a 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -105,6 +105,7 @@ "minimatch": "^3.0.3", "optimize-css-assets-webpack-plugin": "^5.0.1", "ora": "^3.4.0", + "outdent": "^0.7.0", "postcss-load-config": "^2.1.0", "postcss-loader": "^3.0.0", "progress-bar-webpack-plugin": "^1.12.1", diff --git a/yarn.lock b/yarn.lock index 3469e729c..63172bde4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9653,6 +9653,11 @@ osenv@0, osenv@^0.1.4, osenv@^0.1.5: os-homedir "^1.0.0" os-tmpdir "^1.0.0" +outdent@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/outdent/-/outdent-0.7.0.tgz#cfd1f1956305141e0cf3e898ada6547373c1997a" + integrity sha512-Ue462G+UIFoyQmOzapGIKWS3d/9NHeD/018WGEDZIhN2/VaQpVXbofMcZX0socv1fw4/tmEn7Vd3McOdPZfKzQ== + p-cancelable@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" From b13829ddd18ab9ca767bfc098ed3084af59e9646 Mon Sep 17 00:00:00 2001 From: Leah Date: Fri, 26 Jul 2019 21:26:09 +0200 Subject: [PATCH 3/3] feat: use babel code frame --- packages/cli/lib/lib/babel-config.js | 5 +++- packages/cli/lib/lib/webpack/prerender.js | 31 ++++++++--------------- packages/cli/package.json | 1 + yarn.lock | 9 ++++++- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/packages/cli/lib/lib/babel-config.js b/packages/cli/lib/lib/babel-config.js index b9a60a9ee..f49c98eaf 100644 --- a/packages/cli/lib/lib/babel-config.js +++ b/packages/cli/lib/lib/babel-config.js @@ -25,7 +25,10 @@ module.exports = function(env, options = {}) { require.resolve('@babel/plugin-syntax-dynamic-import'), require.resolve('@babel/plugin-transform-object-assign'), [require.resolve('@babel/plugin-proposal-decorators'), { legacy: true }], - [require.resolve('@babel/plugin-proposal-class-properties'), { loose: true }], + [ + require.resolve('@babel/plugin-proposal-class-properties'), + { loose: true }, + ], require.resolve('@babel/plugin-proposal-object-rest-spread'), isProd && require.resolve('babel-plugin-transform-react-remove-prop-types'), diff --git a/packages/cli/lib/lib/webpack/prerender.js b/packages/cli/lib/lib/webpack/prerender.js index c7ae75680..63974fb85 100644 --- a/packages/cli/lib/lib/webpack/prerender.js +++ b/packages/cli/lib/lib/webpack/prerender.js @@ -1,10 +1,11 @@ -const { red, gray } = require('kleur'); +const { red } = require('kleur'); const { resolve } = require('path'); const { readFileSync } = require('fs'); const stackTrace = require('stack-trace'); const { SourceMapConsumer } = require('source-map'); const { error, info } = require('../../util'); const outdent = require('outdent'); +const { codeFrameColumns } = require('@babel/code-frame'); module.exports = function prerender(env, params) { params = params || {}; @@ -95,7 +96,7 @@ async function handlePrerenderError(err, env, stack, entry) { } if (position) { - info(position.source); + info(JSON.stringify(position)); position.source = position.source .replace('webpack://', '.') .replace(/^.*~\/((?:@[^/]+\/)?[^/]+)/, (s, name) => @@ -103,7 +104,6 @@ async function handlePrerenderError(err, env, stack, entry) { .resolve(name) .replace(/^(.*?\/node_modules\/(@[^/]+\/)?[^/]+)(\/.*)$/, '$1') ); - info(position.source); } else { position = { source: stack.getFileName(), @@ -114,24 +114,13 @@ async function handlePrerenderError(err, env, stack, entry) { const sourceLines = getLines(env, position); - let sourceCodeHighlight = ''; - if (sourceLines) { - const lnrl = position.line.toString().length + 2; - const line = position.line; - const un = undefined; - - const pad = l => - (l === undefined ? '' : (line + l || '') + '').padStart(lnrl); - - sourceCodeHighlight = gray(outdent` - ${pad(-2)} | ${sourceLines[line - 3] || ''} - ${pad(-1)} | ${sourceLines[line - 2] || ''} - ${pad(-0)} | ${sourceLines[line - 1] || ''} - ${pad(un)} | ${red('^'.padStart(position.column + 1))} - ${pad(+1)} | ${sourceLines[line + 0] || ''} - ${pad(+2)} | ${sourceLines[line + 1] || ''} - `); - } + let sourceCodeHighlight = sourceLines + ? codeFrameColumns( + sourceLines.join('\n'), + { start: { line: position.line, column: position.column } }, + { highlightCode: true } + ) + : ''; const stderr = process.stderr.write.bind(process.stderr); diff --git a/packages/cli/package.json b/packages/cli/package.json index 829c5653a..a2aed0f3e 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -64,6 +64,7 @@ "preact-render-to-string": "*" }, "dependencies": { + "@babel/code-frame": "^7.5.5", "@babel/core": "^7.4.5", "@babel/plugin-proposal-class-properties": "^7.4.4", "@babel/plugin-proposal-decorators": "^7.4.4", diff --git a/yarn.lock b/yarn.lock index 63172bde4..fec1342bd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -44,6 +44,13 @@ dependencies: "@babel/highlight" "^7.0.0" +"@babel/code-frame@^7.5.5": + version "7.5.5" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.5.5.tgz#bc0782f6d69f7b7d49531219699b988f669a8f9d" + integrity sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw== + dependencies: + "@babel/highlight" "^7.0.0" + "@babel/core@^7.1.0", "@babel/core@^7.4.0", "@babel/core@^7.4.5": version "7.5.4" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.5.4.tgz#4c32df7ad5a58e9ea27ad025c11276324e0b4ddd" @@ -10216,7 +10223,7 @@ postcss-discard-overridden@^4.0.1: dependencies: postcss "^7.0.0" -postcss-load-config@^2.0.0: +postcss-load-config@^2.0.0, postcss-load-config@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-2.1.0.tgz#c84d692b7bb7b41ddced94ee62e8ab31b417b003" integrity sha512-4pV3JJVPLd5+RueiVVB+gFOAa7GWc25XQcMp86Zexzke69mKf6Nx9LRcQywdz7yZI9n1udOxmLuAwTBypypF8Q==