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"