Skip to content

Commit

Permalink
feat: display node polyfill hint on error overlay (#4263)
Browse files Browse the repository at this point in the history
  • Loading branch information
chenjiahan authored Dec 24, 2024
1 parent 4d766ad commit e45bc76
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 88 deletions.
97 changes: 89 additions & 8 deletions packages/core/src/helpers/format.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { StatsCompilation, StatsError } from '@rspack/core';
import color from '../../compiled/picocolors/index.js';

function resolveFileName(stats: StatsError) {
// Get the real source file path with stats.moduleIdentifier.
Expand Down Expand Up @@ -44,25 +45,103 @@ function hintUnknownFiles(message: string): string {
if (/File: .+\.s(c|a)ss/.test(message)) {
return message.replace(
hint,
`To enable support for Sass, use "@rsbuild/plugin-sass".`,
`To enable support for Sass, use "${color.yellow('@rsbuild/plugin-sass')}".`,
);
}
if (/File: .+\.less/.test(message)) {
return message.replace(
hint,
`To enable support for Less, use "@rsbuild/plugin-less".`,
`To enable support for Less, use "${color.yellow('@rsbuild/plugin-less')}".`,
);
}
if (/File: .+\.styl(us)?/.test(message)) {
return message.replace(
hint,
`To enable support for Stylus, use "@rsbuild/plugin-stylus".`,
`To enable support for Stylus, use "${color.yellow('@rsbuild/plugin-stylus')}".`,
);
}

return message;
}

/**
* Add node polyfill tip when failed to resolve node built-in modules.
*/
const hintNodePolyfill = (message: string): string => {
const getTips = (moduleName: string) => {
const tips = [
`Tip: "${moduleName}" is a built-in Node.js module. It cannot be imported in client-side code.`,
`Check if you need to import Node.js module. If needed, you can use "${color.cyan('@rsbuild/plugin-node-polyfill')}" to polyfill it.`,
];

return `${message}\n\n${color.yellow(tips.join('\n'))}`;
};

const isNodeProtocolError = message.includes(
'need an additional plugin to handle "node:" URIs',
);
if (isNodeProtocolError) {
return getTips('node:*');
}

if (!message.includes(`Can't resolve`)) {
return message;
}

const matchArray = message.match(/Can't resolve '(\w+)'/);
if (!matchArray) {
return message;
}

const moduleName = matchArray[1];
const nodeModules = [
'assert',
'buffer',
'child_process',
'cluster',
'console',
'constants',
'crypto',
'dgram',
'dns',
'domain',
'events',
'fs',
'http',
'https',
'module',
'net',
'os',
'path',
'punycode',
'process',
'querystring',
'readline',
'repl',
'stream',
'_stream_duplex',
'_stream_passthrough',
'_stream_readable',
'_stream_transform',
'_stream_writable',
'string_decoder',
'sys',
'timers',
'tls',
'tty',
'url',
'util',
'vm',
'zlib',
];

if (moduleName && nodeModules.includes(moduleName)) {
return getTips(moduleName);
}

return message;
};

// Cleans up Rspack error messages.
function formatMessage(stats: StatsError | string, verbose?: boolean) {
let lines: string[] = [];
Expand All @@ -82,7 +161,14 @@ function formatMessage(stats: StatsError | string, verbose?: boolean) {
message = stats;
}

// Remove inner error message
const innerError = '-- inner error --';
if (!verbose && message.includes(innerError)) {
message = message.split(innerError)[0];
}

message = hintUnknownFiles(message);
message = hintNodePolyfill(message);

lines = message.split('\n');

Expand All @@ -97,11 +183,6 @@ function formatMessage(stats: StatsError | string, verbose?: boolean) {
// Reassemble the message
message = lines.join('\n');

const innerError = '-- inner error --';
if (!verbose && message.includes(innerError)) {
message = message.split(innerError)[0];
}

return message.trim();
}

Expand Down
81 changes: 1 addition & 80 deletions packages/core/src/helpers/stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,87 +5,7 @@ import type { Rspack } from '../types';
import { isMultiCompiler } from './';
import { formatStatsMessages } from './format.js';

/**
* Add node polyfill tip when failed to resolve node built-in modules.
*/
const hintNodePolyfill = (message: string): string => {
const getTips = (moduleName: string) => {
const tips = [
`Tip: "${moduleName}" is a built-in Node.js module. It cannot be imported in client-side code.`,
`Check if you need to import Node.js module. If needed, you can use ${color.cyan('@rsbuild/plugin-node-polyfill')}.`,
];

return `${message}\n\n${color.yellow(tips.join('\n'))}`;
};

const isNodeProtocolError = message.includes(
'need an additional plugin to handle "node:" URIs',
);
if (isNodeProtocolError) {
return getTips('node:*');
}

if (!message.includes(`Can't resolve`)) {
return message;
}

const matchArray = message.match(/Can't resolve '(\w+)'/);
if (!matchArray) {
return message;
}

const moduleName = matchArray[1];
const nodeModules = [
'assert',
'buffer',
'child_process',
'cluster',
'console',
'constants',
'crypto',
'dgram',
'dns',
'domain',
'events',
'fs',
'http',
'https',
'module',
'net',
'os',
'path',
'punycode',
'process',
'querystring',
'readline',
'repl',
'stream',
'_stream_duplex',
'_stream_passthrough',
'_stream_readable',
'_stream_transform',
'_stream_writable',
'string_decoder',
'sys',
'timers',
'tls',
'tty',
'url',
'util',
'vm',
'zlib',
];

if (moduleName && nodeModules.includes(moduleName)) {
return getTips(moduleName);
}

return message;
};

function formatErrorMessage(errors: string[]) {
const messages = errors.map((error) => hintNodePolyfill(error));
const text = `${messages.join('\n\n')}\n`;
const title = color.bold(color.red('Compile error: '));

if (!errors.length) {
Expand All @@ -95,6 +15,7 @@ function formatErrorMessage(errors: string[]) {
const tip = color.yellow(
'Failed to compile, check the errors for troubleshooting.',
);
const text = `${errors.join('\n\n')}\n`;

return `${title}\n${tip}\n${text}`;
}
Expand Down

0 comments on commit e45bc76

Please sign in to comment.