Skip to content

Commit a9a5f89

Browse files
authored
fix(core): better error logging on SSR/dev failures + log stacktraces and error causes (#8872)
1 parent 46d2aa2 commit a9a5f89

File tree

13 files changed

+155
-75
lines changed

13 files changed

+155
-75
lines changed

packages/docusaurus-plugin-pwa/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"tslib": "^2.5.0",
3636
"webpack": "^5.76.0",
3737
"webpack-merge": "^5.8.0",
38+
"webpackbar": "^5.0.2",
3839
"workbox-build": "^6.5.4",
3940
"workbox-precaching": "^6.5.4",
4041
"workbox-window": "^6.5.4"

packages/docusaurus-plugin-pwa/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77

88
import path from 'path';
99
import webpack, {type Configuration} from 'webpack';
10+
import WebpackBar from 'webpackbar';
1011
import Terser from 'terser-webpack-plugin';
1112
import {injectManifest} from 'workbox-build';
1213
import {normalizeUrl} from '@docusaurus/utils';
1314
import {compile} from '@docusaurus/core/lib/webpack/utils';
14-
import LogPlugin from '@docusaurus/core/lib/webpack/plugins/LogPlugin';
1515
import {readDefaultCodeTranslationMessages} from '@docusaurus/theme-translations';
1616
import type {HtmlTags, LoadContext, Plugin} from '@docusaurus/types';
1717
import type {PluginOptions} from '@docusaurus/plugin-pwa';
@@ -160,7 +160,7 @@ export default function pluginPWA(
160160
// Fallback value required with Webpack 5
161161
PWA_SW_CUSTOM: swCustom ?? '',
162162
}),
163-
new LogPlugin({
163+
new WebpackBar({
164164
name: 'Service Worker',
165165
color: 'red',
166166
}),

packages/docusaurus/bin/docusaurus.mjs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,11 @@ if (!process.argv.slice(2).length) {
244244
cli.parse(process.argv);
245245

246246
process.on('unhandledRejection', (err) => {
247-
logger.error(err instanceof Error ? err.stack : err);
247+
console.log('');
248+
// Do not use logger.error here: it does not print error causes
249+
console.error(err);
250+
console.log('');
251+
248252
logger.info`Docusaurus version: number=${DOCUSAURUS_VERSION}
249253
Node version: number=${process.version}`;
250254
process.exit(1);

packages/docusaurus/src/client/serverEntry.tsx

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import path from 'path';
1010
import fs from 'fs-extra';
1111
// eslint-disable-next-line no-restricted-imports
1212
import _ from 'lodash';
13-
import chalk from 'chalk';
1413
import * as eta from 'eta';
1514
import {StaticRouter} from 'react-router-dom';
1615
import ReactDOMServer from 'react-dom/server';
@@ -37,29 +36,43 @@ function renderSSRTemplate(ssrTemplate: string, data: object) {
3736
return compiled(data, eta.defaultConfig);
3837
}
3938

39+
function buildSSRErrorMessage({
40+
error,
41+
pathname,
42+
}: {
43+
error: Error;
44+
pathname: string;
45+
}): string {
46+
const parts = [
47+
`Docusaurus server-side rendering could not render static page with path ${pathname} because of error: ${error.message}`,
48+
];
49+
50+
const isNotDefinedErrorRegex =
51+
/(?:window|document|localStorage|navigator|alert|location|buffer|self) is not defined/i;
52+
53+
if (isNotDefinedErrorRegex.test(error.message)) {
54+
// prettier-ignore
55+
parts.push(`It looks like you are using code that should run on the client-side only.
56+
To get around it, try using \`<BrowserOnly>\` (https://docusaurus.io/docs/docusaurus-core/#browseronly) or \`ExecutionEnvironment\` (https://docusaurus.io/docs/docusaurus-core/#executionenvironment).
57+
It might also require to wrap your client code in \`useEffect\` hook and/or import a third-party library dynamically (if any).`);
58+
}
59+
60+
return parts.join('\n');
61+
}
62+
4063
export default async function render(
4164
locals: Locals & {path: string},
4265
): Promise<string> {
4366
try {
4467
return await doRender(locals);
45-
} catch (err) {
46-
// We are not using logger in this file, because it seems to fail with some
47-
// compilers / some polyfill methods. This is very likely a bug, but in the
48-
// long term, when we output native ES modules in SSR, the bug will be gone.
49-
// prettier-ignore
50-
console.error(chalk.red(`${chalk.bold('[ERROR]')} Docusaurus server-side rendering could not render static page with path ${chalk.cyan.underline(locals.path)}.`));
51-
52-
const isNotDefinedErrorRegex =
53-
/(?:window|document|localStorage|navigator|alert|location|buffer|self) is not defined/i;
54-
55-
if (isNotDefinedErrorRegex.test((err as Error).message)) {
56-
// prettier-ignore
57-
console.info(`${chalk.cyan.bold('[INFO]')} It looks like you are using code that should run on the client-side only.
58-
To get around it, try using ${chalk.cyan('`<BrowserOnly>`')} (${chalk.cyan.underline('https://docusaurus.io/docs/docusaurus-core/#browseronly')}) or ${chalk.cyan('`ExecutionEnvironment`')} (${chalk.cyan.underline('https://docusaurus.io/docs/docusaurus-core/#executionenvironment')}).
59-
It might also require to wrap your client code in ${chalk.cyan('`useEffect`')} hook and/or import a third-party library dynamically (if any).`);
60-
}
61-
62-
throw err;
68+
} catch (errorUnknown) {
69+
const error = errorUnknown as Error;
70+
const message = buildSSRErrorMessage({error, pathname: locals.path});
71+
const ssrError = new Error(message, {cause: error});
72+
// It is important to log the error here because the stacktrace causal chain
73+
// is not available anymore upper in the tree (this SSR runs in eval)
74+
console.error(ssrError);
75+
throw ssrError;
6376
}
6477
}
6578

@@ -158,7 +171,8 @@ async function doRender(locals: Locals & {path: string}) {
158171
});
159172
} catch (err) {
160173
// prettier-ignore
161-
console.error(chalk.red(`${chalk.bold('[ERROR]')} Minification of page ${chalk.cyan.underline(locals.path)} failed.`));
174+
console.error(`Minification of page ${locals.path} failed.`);
175+
console.error(err);
162176
throw err;
163177
}
164178
}

packages/docusaurus/src/commands/build.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,12 @@ export async function build(
7272
isLastLocale,
7373
});
7474
} catch (err) {
75-
logger.error`Unable to build website for locale name=${locale}.`;
76-
throw err;
75+
throw new Error(
76+
logger.interpolate`Unable to build website for locale name=${locale}.`,
77+
{
78+
cause: err,
79+
},
80+
);
7781
}
7882
}
7983
const context = await loadContext({

packages/docusaurus/src/commands/start.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import {
2424
applyConfigureWebpack,
2525
applyConfigurePostCss,
2626
getHttpsConfig,
27+
formatStatsErrorMessage,
28+
printStatsWarnings,
2729
} from '../webpack/utils';
2830
import {getHostPort, type HostPortOptions} from '../server/getHostPort';
2931

@@ -170,16 +172,23 @@ export async function start(
170172
});
171173

172174
const compiler = webpack(config);
173-
if (process.env.E2E_TEST) {
174-
compiler.hooks.done.tap('done', (stats) => {
175+
compiler.hooks.done.tap('done', (stats) => {
176+
const errorsWarnings = stats.toJson('errors-warnings');
177+
const statsErrorMessage = formatStatsErrorMessage(errorsWarnings);
178+
if (statsErrorMessage) {
179+
console.error(statsErrorMessage);
180+
}
181+
printStatsWarnings(errorsWarnings);
182+
183+
if (process.env.E2E_TEST) {
175184
if (stats.hasErrors()) {
176185
logger.error('E2E_TEST: Project has compiler errors.');
177186
process.exit(1);
178187
}
179188
logger.success('E2E_TEST: Project can compile.');
180189
process.exit(0);
181-
});
182-
}
190+
}
191+
});
183192

184193
// https://webpack.js.org/configuration/dev-server
185194
const defaultDevServerConfig: WebpackDevServer.Configuration = {

packages/docusaurus/src/webpack/client.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@
88
import path from 'path';
99
import logger from '@docusaurus/logger';
1010
import merge from 'webpack-merge';
11+
import WebpackBar from 'webpackbar';
1112
import {createBaseConfig} from './base';
1213
import ChunkAssetPlugin from './plugins/ChunkAssetPlugin';
13-
import LogPlugin from './plugins/LogPlugin';
14+
import {formatStatsErrorMessage} from './utils';
1415
import type {Props} from '@docusaurus/types';
1516
import type {Configuration} from 'webpack';
1617

@@ -34,7 +35,7 @@ export default async function createClientConfig(
3435
plugins: [
3536
new ChunkAssetPlugin(),
3637
// Show compilation progress bar and build time.
37-
new LogPlugin({
38+
new WebpackBar({
3839
name: 'Client',
3940
}),
4041
],
@@ -47,8 +48,11 @@ export default async function createClientConfig(
4748
apply: (compiler) => {
4849
compiler.hooks.done.tap('client:done', (stats) => {
4950
if (stats.hasErrors()) {
51+
const errorsWarnings = stats.toJson('errors-warnings');
5052
logger.error(
51-
'Client bundle compiled with errors therefore further build is impossible.',
53+
`Client bundle compiled with errors therefore further build is impossible.\n${formatStatsErrorMessage(
54+
errorsWarnings,
55+
)}`,
5256
);
5357
process.exit(1);
5458
}

packages/docusaurus/src/webpack/plugins/LogPlugin.ts

Lines changed: 0 additions & 35 deletions
This file was deleted.

packages/docusaurus/src/webpack/server.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ import {
1616
import StaticSiteGeneratorPlugin, {
1717
type Locals,
1818
} from '@slorber/static-site-generator-webpack-plugin';
19+
import WebpackBar from 'webpackbar';
1920
import {createBaseConfig} from './base';
2021
import WaitPlugin from './plugins/WaitPlugin';
21-
import LogPlugin from './plugins/LogPlugin';
2222
import ssrDefaultTemplate from './templates/ssr.html.template';
2323
import type {Props} from '@docusaurus/types';
2424
import type {Configuration} from 'webpack';
@@ -99,7 +99,7 @@ export default async function createServerConfig({
9999
}),
100100

101101
// Show compilation progress bar.
102-
new LogPlugin({
102+
new WebpackBar({
103103
name: 'Server',
104104
color: 'yellow',
105105
}),

packages/docusaurus/src/webpack/utils.ts

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import webpack, {
2323
} from 'webpack';
2424
import TerserPlugin from 'terser-webpack-plugin';
2525
import CssMinimizerPlugin from 'css-minimizer-webpack-plugin';
26+
import formatWebpackMessages from 'react-dev-utils/formatWebpackMessages';
2627
import type {CustomOptions, CssNanoOptions} from 'css-minimizer-webpack-plugin';
2728
import type {TransformOptions} from '@babel/core';
2829
import type {
@@ -31,6 +32,29 @@ import type {
3132
ConfigureWebpackUtils,
3233
} from '@docusaurus/types';
3334

35+
export function formatStatsErrorMessage(
36+
statsJson: ReturnType<webpack.Stats['toJson']> | undefined,
37+
): string | undefined {
38+
if (statsJson?.errors?.length) {
39+
// TODO formatWebpackMessages does not print stack-traces
40+
// Also the error causal chain is lost here
41+
// We log the stacktrace inside serverEntry.tsx for now (not ideal)
42+
const {errors} = formatWebpackMessages(statsJson);
43+
return errors.join('\n---\n');
44+
}
45+
return undefined;
46+
}
47+
48+
export function printStatsWarnings(
49+
statsJson: ReturnType<webpack.Stats['toJson']> | undefined,
50+
): void {
51+
if (statsJson?.warnings?.length) {
52+
statsJson.warnings?.forEach((warning) => {
53+
logger.warn(warning);
54+
});
55+
}
56+
}
57+
3458
// Utility method to get style loaders
3559
export function getStyleLoaders(
3660
isServer: boolean,
@@ -250,13 +274,15 @@ export function compile(config: Configuration[]): Promise<void> {
250274
// Let plugins consume all the stats
251275
const errorsWarnings = stats?.toJson('errors-warnings');
252276
if (stats?.hasErrors()) {
253-
reject(new Error('Failed to compile with errors.'));
254-
}
255-
if (errorsWarnings && stats?.hasWarnings()) {
256-
errorsWarnings.warnings?.forEach((warning) => {
257-
logger.warn(warning);
258-
});
277+
const statsErrorMessage = formatStatsErrorMessage(errorsWarnings);
278+
reject(
279+
new Error(
280+
`Failed to compile due to Webpack errors.\n${statsErrorMessage}`,
281+
),
282+
);
259283
}
284+
printStatsWarnings(errorsWarnings);
285+
260286
// Webpack 5 requires calling close() so that persistent caching works
261287
// See https://github.com/webpack/webpack.js.org/pull/4775
262288
compiler.close((errClose) => {

0 commit comments

Comments
 (0)