Skip to content

Commit 7817fcb

Browse files
authored
fix: close service on the failed hook (#417)
When a compilation is not successful and webpack is not watching, the plugin should close the service. ✅ Closes: #411
1 parent 57cf13a commit 7817fcb

File tree

6 files changed

+194
-31
lines changed

6 files changed

+194
-31
lines changed

src/ForkTsCheckerWebpackPlugin.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ import { assertEsLintSupport } from './eslint-reporter/assertEsLintSupport';
1313
import { createEsLintReporterRpcClient } from './eslint-reporter/reporter/EsLintReporterRpcClient';
1414
import { tapStartToConnectAndRunReporter } from './hooks/tapStartToConnectAndRunReporter';
1515
import { tapStopToDisconnectReporter } from './hooks/tapStopToDisconnectReporter';
16-
import { getForkTsCheckerWebpackPluginHooks } from './hooks/pluginHooks';
1716
import { tapDoneToCollectRemoved } from './hooks/tapDoneToCollectRemoved';
17+
import { tapErrorToLogMessage } from './hooks/tapErrorToLogMessage';
18+
import { getForkTsCheckerWebpackPluginHooks } from './hooks/pluginHooks';
1819

1920
class ForkTsCheckerWebpackPlugin implements webpack.Plugin {
2021
private readonly options: ForkTsCheckerWebpackPluginOptions;
@@ -56,7 +57,8 @@ class ForkTsCheckerWebpackPlugin implements webpack.Plugin {
5657

5758
tapStartToConnectAndRunReporter(compiler, reporter, configuration, state);
5859
tapDoneToCollectRemoved(compiler, configuration, state);
59-
tapStopToDisconnectReporter(compiler, reporter, configuration, state);
60+
tapStopToDisconnectReporter(compiler, reporter, state);
61+
tapErrorToLogMessage(compiler, configuration);
6062
} else {
6163
throw new Error(
6264
`ForkTsCheckerWebpackPlugin is configured to not use any issue reporter. It's probably a configuration issue.`

src/hooks/tapErrorToLogMessage.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import webpack from 'webpack';
2+
import { ForkTsCheckerWebpackPluginConfiguration } from '../ForkTsCheckerWebpackPluginConfiguration';
3+
import { getForkTsCheckerWebpackPluginHooks } from './pluginHooks';
4+
import { RpcIpcMessagePortClosedError } from '../rpc/rpc-ipc/error/RpcIpcMessagePortClosedError';
5+
import chalk from 'chalk';
6+
7+
function tapErrorToLogMessage(
8+
compiler: webpack.Compiler,
9+
configuration: ForkTsCheckerWebpackPluginConfiguration
10+
) {
11+
const hooks = getForkTsCheckerWebpackPluginHooks(compiler);
12+
13+
hooks.error.tap('ForkTsCheckerWebpackPlugin', (error) => {
14+
configuration.logger.issues.error(String(error));
15+
16+
if (error instanceof RpcIpcMessagePortClosedError) {
17+
if (error.signal === 'SIGINT') {
18+
configuration.logger.issues.error(
19+
chalk.red(
20+
'Issues checking service interrupted - If running in a docker container, this may be caused ' +
21+
"by the container running out of memory. If so, try increasing the container's memory limit " +
22+
'or lowering the `memoryLimit` value in the ForkTsCheckerWebpackPlugin configuration.'
23+
)
24+
);
25+
} else {
26+
configuration.logger.issues.error(
27+
chalk.red(
28+
'Issues checking service aborted - probably out of memory. ' +
29+
'Check the `memoryLimit` option in the ForkTsCheckerWebpackPlugin configuration.\n' +
30+
"If increasing the memory doesn't solve the issue, it's most probably a bug in the TypeScript or EsLint."
31+
)
32+
);
33+
}
34+
}
35+
});
36+
}
37+
38+
export { tapErrorToLogMessage };

src/hooks/tapStopToDisconnectReporter.ts

Lines changed: 3 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
11
import webpack from 'webpack';
2-
import { ForkTsCheckerWebpackPluginConfiguration } from '../ForkTsCheckerWebpackPluginConfiguration';
32
import { ForkTsCheckerWebpackPluginState } from '../ForkTsCheckerWebpackPluginState';
43
import { ReporterRpcClient } from '../reporter';
5-
import { getForkTsCheckerWebpackPluginHooks } from './pluginHooks';
6-
import { RpcIpcMessagePortClosedError } from '../rpc/rpc-ipc/error/RpcIpcMessagePortClosedError';
7-
import chalk from 'chalk';
84

95
function tapStopToDisconnectReporter(
106
compiler: webpack.Compiler,
117
reporter: ReporterRpcClient,
12-
configuration: ForkTsCheckerWebpackPluginConfiguration,
138
state: ForkTsCheckerWebpackPluginState
149
) {
1510
compiler.hooks.watchClose.tap('ForkTsCheckerWebpackPlugin', () => {
@@ -22,29 +17,9 @@ function tapStopToDisconnectReporter(
2217
}
2318
});
2419

25-
const hooks = getForkTsCheckerWebpackPluginHooks(compiler);
26-
27-
hooks.error.tap('ForkTsCheckerWebpackPlugin', (error) => {
28-
configuration.logger.issues.error(String(error));
29-
30-
if (error instanceof RpcIpcMessagePortClosedError) {
31-
if (error.signal === 'SIGINT') {
32-
configuration.logger.issues.error(
33-
chalk.red(
34-
'Issues checking service interrupted - If running in a docker container, this may be caused ' +
35-
"by the container running out of memory. If so, try increasing the container's memory limit " +
36-
'or lowering the `memoryLimit` value in the ForkTsCheckerWebpackPlugin configuration.'
37-
)
38-
);
39-
} else {
40-
configuration.logger.issues.error(
41-
chalk.red(
42-
'Issues checking service aborted - probably out of memory. ' +
43-
'Check the `memoryLimit` option in the ForkTsCheckerWebpackPlugin configuration.\n' +
44-
"If increasing the memory doesn't solve the issue, it's most probably a bug in the TypeScript or EsLint."
45-
)
46-
);
47-
}
20+
compiler.hooks.failed.tap('ForkTsCheckerWebpackPlugin', () => {
21+
if (!state.watching) {
22+
reporter.disconnect();
4823
}
4924
});
5025
}
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import { readFixture } from './sandbox/Fixture';
2+
import { join } from 'path';
3+
import { createSandbox, FORK_TS_CHECKER_WEBPACK_PLUGIN_VERSION, Sandbox } from './sandbox/Sandbox';
4+
import { WEBPACK_CLI_VERSION, WEBPACK_DEV_SERVER_VERSION } from './sandbox/WebpackDevServerDriver';
5+
import { extractWebpackErrors } from './sandbox/WebpackErrorsExtractor';
6+
import stripAnsi from 'strip-ansi';
7+
8+
describe('Webpack Production Build', () => {
9+
let sandbox: Sandbox;
10+
11+
beforeAll(async () => {
12+
sandbox = await createSandbox();
13+
});
14+
15+
beforeEach(async () => {
16+
await sandbox.reset();
17+
});
18+
19+
afterAll(async () => {
20+
await sandbox.cleanup();
21+
});
22+
23+
it.each([{ webpack: '4.0.0' }, { webpack: '^4.0.0' }, { webpack: '^5.0.0-beta.16' }])(
24+
'compiles the project successfully with %p',
25+
async ({ webpack }) => {
26+
await sandbox.load([
27+
await readFixture(join(__dirname, 'fixtures/environment/typescript-basic.fixture'), {
28+
FORK_TS_CHECKER_WEBPACK_PLUGIN_VERSION: JSON.stringify(
29+
FORK_TS_CHECKER_WEBPACK_PLUGIN_VERSION
30+
),
31+
TS_LOADER_VERSION: JSON.stringify('^5.0.0'),
32+
TYPESCRIPT_VERSION: JSON.stringify('~3.8.0'),
33+
WEBPACK_VERSION: JSON.stringify(webpack),
34+
WEBPACK_CLI_VERSION: JSON.stringify(WEBPACK_CLI_VERSION),
35+
WEBPACK_DEV_SERVER_VERSION: JSON.stringify(WEBPACK_DEV_SERVER_VERSION),
36+
ASYNC: JSON.stringify(false),
37+
}),
38+
await readFixture(join(__dirname, 'fixtures/implementation/typescript-basic.fixture')),
39+
]);
40+
41+
// lets remove the async option at all as the plugin should now how to set it by default
42+
await sandbox.patch(
43+
'webpack.config.js',
44+
[
45+
' new ForkTsCheckerWebpackPlugin({',
46+
' async: false,',
47+
' logger: {',
48+
' infrastructure: "console"',
49+
' }',
50+
' })',
51+
].join('\n'),
52+
[
53+
' new ForkTsCheckerWebpackPlugin({',
54+
' logger: {',
55+
' infrastructure: "console"',
56+
' }',
57+
' })',
58+
].join('\n')
59+
);
60+
61+
const result = await sandbox.exec('npm run webpack');
62+
const errors = extractWebpackErrors(result);
63+
64+
expect(errors).toEqual([]);
65+
}
66+
);
67+
68+
it.each([{ webpack: '4.0.0' }, { webpack: '^4.0.0' }, { webpack: '^5.0.0-beta.16' }])(
69+
'exits with error on the project error with %p',
70+
async ({ webpack }) => {
71+
await sandbox.load([
72+
await readFixture(join(__dirname, 'fixtures/environment/typescript-basic.fixture'), {
73+
FORK_TS_CHECKER_WEBPACK_PLUGIN_VERSION: JSON.stringify(
74+
FORK_TS_CHECKER_WEBPACK_PLUGIN_VERSION
75+
),
76+
TS_LOADER_VERSION: JSON.stringify('^5.0.0'),
77+
TYPESCRIPT_VERSION: JSON.stringify('~3.8.0'),
78+
WEBPACK_VERSION: JSON.stringify(webpack),
79+
WEBPACK_CLI_VERSION: JSON.stringify(WEBPACK_CLI_VERSION),
80+
WEBPACK_DEV_SERVER_VERSION: JSON.stringify(WEBPACK_DEV_SERVER_VERSION),
81+
ASYNC: JSON.stringify(false),
82+
}),
83+
await readFixture(join(__dirname, 'fixtures/implementation/typescript-basic.fixture')),
84+
]);
85+
86+
// remove the async option at all as the plugin should now how to set it by default
87+
await sandbox.patch(
88+
'webpack.config.js',
89+
[
90+
' new ForkTsCheckerWebpackPlugin({',
91+
' async: false,',
92+
' logger: {',
93+
' infrastructure: "console"',
94+
' }',
95+
' })',
96+
].join('\n'),
97+
[
98+
' new ForkTsCheckerWebpackPlugin({',
99+
' logger: {',
100+
' infrastructure: "console"',
101+
' }',
102+
' })',
103+
].join('\n')
104+
);
105+
106+
// introduce an error in the project
107+
await sandbox.remove('src/model/User.ts');
108+
109+
try {
110+
await sandbox.exec('npm run webpack');
111+
112+
throw new Error('The webpack command should exit with an error code.');
113+
} catch (error) {
114+
// remove npm related output
115+
const output = stripAnsi(String(error)).replace(/npm (ERR!|WARN).*/g, '');
116+
// extract errors
117+
const errors = extractWebpackErrors(output);
118+
119+
expect(errors).toEqual([
120+
// first error is from the webpack module resolution
121+
expect.anything(),
122+
[
123+
'ERROR in src/authenticate.ts 1:22-36',
124+
"TS2307: Cannot find module './model/User'.",
125+
" > 1 | import { User } from './model/User';",
126+
' | ^^^^^^^^^^^^^^',
127+
' 2 | ',
128+
' 3 | async function login(email: string, password: string): Promise<User> {',
129+
' 4 | const response = await fetch(',
130+
].join('\n'),
131+
[
132+
'ERROR in src/index.ts 2:29-43',
133+
"TS2307: Cannot find module './model/User'.",
134+
" 1 | import { login } from './authenticate';",
135+
" > 2 | import { getUserName } from './model/User';",
136+
' | ^^^^^^^^^^^^^^',
137+
' 3 | ',
138+
" 4 | const emailInput = document.getElementById('email');",
139+
" 5 | const passwordInput = document.getElementById('password');",
140+
].join('\n'),
141+
]);
142+
}
143+
}
144+
);
145+
});

test/e2e/fixtures/environment/typescript-basic.fixture

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"main": "dist/index.js",
66
"license": "MIT",
77
"scripts": {
8+
"webpack": "webpack -p",
89
"webpack-dev-server": "webpack-dev-server"
910
},
1011
"devDependencies": {

test/e2e/sandbox/WebpackErrorsExtractor.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
import stripAnsi from 'strip-ansi';
2+
13
function isLineRelatedToTsLoader(line: string) {
24
return line.includes('[tsl]') || line.includes('ts-loader');
35
}
46

57
function extractWebpackErrors(content: string): string[] {
6-
const lines = content.split(/\r\n?|\n/);
8+
const lines = stripAnsi(content).split(/\r\n?|\n/);
79
const errors: string[] = [];
810
let currentError: string | undefined = undefined;
911

0 commit comments

Comments
 (0)