Skip to content

Commit fb22e47

Browse files
authored
feat: use webpack's built-in watching instead of polling
Centralize files watching using webpack's `WatchFileSystem`. The default implementation of `NodeWatchFileSystem` doesn't fill our needs, so we've implemented `InclusiveNodeWatchFileSystem` BREAKING CHANGE: 🧨 Remove issue.scope option and use new watch architecture
1 parent b964d05 commit fb22e47

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1164
-836
lines changed

README.md

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,6 @@ module.exports = {
6262
};
6363
```
6464

65-
If you are using **TypeScript >= 3.8.0**, it's recommended to:
66-
* for `ts-loader` set `"importsNotUsedAsValues": "preserve"` [compiler option](https://www.typescriptlang.org/docs/handbook/compiler-options.html) in the [`tsconfig.json`](./examples/ts-loader/tsconfig.json)
67-
* for `babel-loader` set `"onlyRemoveTypeImports": true` [preset option](https://babeljs.io/docs/en/babel-preset-typescript#onlyremovetypeimports) in the [babel configuration](./examples/babel-loader/.babelrc.js)
68-
69-
[Read more](#type-only-modules-watching) about type-only modules watching.
70-
7165
> Examples how to configure it with [babel-loader](https://github.com/babel/babel-loader), [ts-loader](https://github.com/TypeStrong/ts-loader),
7266
> [eslint](https://github.com/eslint/eslint) and [Visual Studio Code](https://code.visualstudio.com/) are in the
7367
> [**examples**](./examples) directory.
@@ -204,7 +198,7 @@ Options for the issues filtering (`issues` option object).
204198
## Vue.js
205199

206200
⚠️ There are additional **constraints** regarding Vue.js Single File Component support: ⚠️
207-
* It requires **TypeScript >= 3.8.0** and `"importsNotUsedAsValues": "preserve"` option in the `tsconfig.json` (it's a limitation of the `transpileOnly` mode from `ts-loader`)
201+
* It requires **TypeScript >= 3.8.0** (it's a limitation of the `transpileOnly` mode from `ts-loader`)
208202
* It doesn't work with the `build` mode (project references)
209203

210204
To enable Vue.js support, follow these steps:
@@ -314,17 +308,6 @@ declare module "*.vue" {
314308

315309
</details>
316310

317-
## Type-Only modules watching
318-
319-
At present `ts-loader` with `transpileOnly` mode and `babel-loader` will not add type-only files (files that contains only interfaces and/or types)
320-
to the webpack dependencies set. Webpack watches only files that are in the dependencies set. This means that
321-
changes in type-only files will **not** trigger new compilation and therefore type-checker in watch mode.
322-
323-
If you use **TypeScript >=3.8.0**, you can fix it:
324-
* for `ts-loader` set `"importsNotUsedAsValues": "preserve"` [compiler option](https://www.typescriptlang.org/docs/handbook/compiler-options.html) in the [`tsconfig.json`](./examples/ts-loader/tsconfig.json)
325-
* for `babel-loader` set `"onlyRemoveTypeImports": true` [preset option](https://babeljs.io/docs/en/babel-preset-typescript#onlyremovetypeimports) in the [babel configuration](./examples/babel-loader/.babelrc.js)
326-
327-
328311
## Plugin hooks
329312

330313
This plugin provides some custom webpack hooks:

examples/babel-loader/.babelrc.js

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,3 @@
11
module.exports = {
2-
presets: [
3-
'@babel/preset-env',
4-
[
5-
'@babel/preset-typescript',
6-
{
7-
onlyRemoveTypeImports: true, // this is important for proper files watching
8-
},
9-
],
10-
],
2+
presets: ['@babel/preset-env', ['@babel/preset-typescript']],
113
};

examples/ts-loader/tsconfig.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@
1010
"strict": true,
1111
"baseUrl": "./src",
1212
"outDir": "./dist",
13-
"forceConsistentCasingInFileNames": true,
14-
"importsNotUsedAsValues": "preserve" // this is important for proper files watching
13+
"forceConsistentCasingInFileNames": true
1514
},
1615
"include": ["./src"],
1716
"exclude": ["node_modules"]

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
"@babel/code-frame": "^7.8.3",
6464
"@types/json-schema": "^7.0.5",
6565
"chalk": "^4.1.0",
66+
"chokidar": "^3.4.2",
6667
"cosmiconfig": "^6.0.0",
6768
"deepmerge": "^4.2.2",
6869
"fs-extra": "^9.0.0",

src/ForkTsCheckerWebpackPlugin.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ import { assertEsLintSupport } from './eslint-reporter/assertEsLintSupport';
1616
import { createEsLintReporterRpcClient } from './eslint-reporter/reporter/EsLintReporterRpcClient';
1717
import { tapStartToConnectAndRunReporter } from './hooks/tapStartToConnectAndRunReporter';
1818
import { tapStopToDisconnectReporter } from './hooks/tapStopToDisconnectReporter';
19-
import { tapDoneToCollectRemoved } from './hooks/tapDoneToCollectRemoved';
2019
import { tapAfterCompileToAddDependencies } from './hooks/tapAfterCompileToAddDependencies';
2120
import { tapErrorToLogMessage } from './hooks/tapErrorToLogMessage';
2221
import { getForkTsCheckerWebpackPluginHooks } from './hooks/pluginHooks';
22+
import { tapAfterEnvironmentToPatchWatching } from './hooks/tapAfterEnvironmentToPatchWatching';
2323

2424
class ForkTsCheckerWebpackPlugin implements webpack.Plugin {
2525
static readonly version: string = '{{VERSION}}'; // will be replaced by the @semantic-release/exec
@@ -62,9 +62,9 @@ class ForkTsCheckerWebpackPlugin implements webpack.Plugin {
6262
if (reporters.length) {
6363
const reporter = createAggregatedReporter(composeReporterRpcClients(reporters));
6464

65+
tapAfterEnvironmentToPatchWatching(compiler, state);
6566
tapStartToConnectAndRunReporter(compiler, reporter, configuration, state);
66-
tapDoneToCollectRemoved(compiler, configuration, state);
67-
tapAfterCompileToAddDependencies(compiler, configuration);
67+
tapAfterCompileToAddDependencies(compiler, configuration, state);
6868
tapStopToDisconnectReporter(compiler, reporter, state);
6969
tapErrorToLogMessage(compiler, configuration);
7070
} else {

src/ForkTsCheckerWebpackPluginOptions.json

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -261,11 +261,6 @@
261261
},
262262
"exclude": {
263263
"$ref": "#/definitions/IssuePredicateOption"
264-
},
265-
"scope": {
266-
"type": "string",
267-
"enum": ["all", "webpack"],
268-
"description": "Defines issues scope to be reported. If 'webpack', reports errors only related to a given webpack compilation. Reports all errors otherwise."
269264
}
270265
}
271266
},

src/ForkTsCheckerWebpackPluginState.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,23 @@
1-
import { Report } from './reporter';
21
import { Tap } from 'tapable';
2+
import { Dependencies, Report } from './reporter';
3+
import { Issue } from './issue';
34

45
interface ForkTsCheckerWebpackPluginState {
5-
report: Promise<Report | undefined>;
6-
removedFiles: string[];
6+
reportPromise: Promise<Report | undefined>;
7+
issuesPromise: Promise<Issue[] | undefined>;
8+
dependenciesPromise: Promise<Dependencies | undefined>;
9+
lastDependencies: Dependencies | undefined;
710
watching: boolean;
811
initialized: boolean;
912
webpackDevServerDoneTap: Tap | undefined;
1013
}
1114

1215
function createForkTsCheckerWebpackPluginState(): ForkTsCheckerWebpackPluginState {
1316
return {
14-
report: Promise.resolve([]),
15-
removedFiles: [],
17+
reportPromise: Promise.resolve(undefined),
18+
issuesPromise: Promise.resolve(undefined),
19+
dependenciesPromise: Promise.resolve(undefined),
20+
lastDependencies: undefined,
1621
watching: false,
1722
initialized: false,
1823
webpackDevServerDoneTap: undefined,

src/eslint-reporter/reporter/EsLintReporter.ts

Lines changed: 56 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -16,55 +16,69 @@ function createEsLintReporter(configuration: EsLintReporterConfiguration): Repor
1616

1717
return {
1818
getReport: async ({ changedFiles = [], deletedFiles = [] }) => {
19-
// cleanup old results
20-
changedFiles.forEach((changedFile) => {
21-
lintResults.delete(changedFile);
22-
});
23-
deletedFiles.forEach((removedFile) => {
24-
lintResults.delete(removedFile);
25-
});
19+
return {
20+
async getDependencies() {
21+
return {
22+
files: [],
23+
dirs: [],
24+
extensions: [],
25+
};
26+
},
27+
async getIssues() {
28+
// cleanup old results
29+
changedFiles.forEach((changedFile) => {
30+
lintResults.delete(changedFile);
31+
});
32+
deletedFiles.forEach((removedFile) => {
33+
lintResults.delete(removedFile);
34+
});
2635

27-
// get reports
28-
const lintReports: LintReport[] = [];
36+
// get reports
37+
const lintReports: LintReport[] = [];
2938

30-
if (isInitialRun) {
31-
lintReports.push(engine.executeOnFiles(includedFilesPatterns));
32-
isInitialRun = false;
33-
} else {
34-
// we need to take care to not lint files that are not included by the configuration.
35-
// the eslint engine will not exclude them automatically
36-
const changedAndIncludedFiles = changedFiles.filter(
37-
(changedFile) =>
38-
includedFilesPatterns.some((includedFilesPattern) =>
39-
minimatch(changedFile, includedFilesPattern)
40-
) &&
41-
(configuration.options.extensions || []).some((extension) =>
42-
changedFile.endsWith(extension)
43-
) &&
44-
!engine.isPathIgnored(changedFile)
45-
);
39+
if (isInitialRun) {
40+
lintReports.push(engine.executeOnFiles(includedFilesPatterns));
41+
isInitialRun = false;
42+
} else {
43+
// we need to take care to not lint files that are not included by the configuration.
44+
// the eslint engine will not exclude them automatically
45+
const changedAndIncludedFiles = changedFiles.filter(
46+
(changedFile) =>
47+
includedFilesPatterns.some((includedFilesPattern) =>
48+
minimatch(changedFile, includedFilesPattern)
49+
) &&
50+
(configuration.options.extensions || []).some((extension) =>
51+
changedFile.endsWith(extension)
52+
) &&
53+
!engine.isPathIgnored(changedFile)
54+
);
4655

47-
if (changedAndIncludedFiles.length) {
48-
lintReports.push(engine.executeOnFiles(changedAndIncludedFiles));
49-
}
50-
}
56+
if (changedAndIncludedFiles.length) {
57+
lintReports.push(engine.executeOnFiles(changedAndIncludedFiles));
58+
}
59+
}
5160

52-
// output fixes if `fix` option is provided
53-
if (configuration.options.fix) {
54-
await Promise.all(lintReports.map((lintReport) => CLIEngine.outputFixes(lintReport)));
55-
}
61+
// output fixes if `fix` option is provided
62+
if (configuration.options.fix) {
63+
await Promise.all(lintReports.map((lintReport) => CLIEngine.outputFixes(lintReport)));
64+
}
5665

57-
// store results
58-
lintReports.forEach((lintReport) => {
59-
lintReport.results.forEach((lintResult) => {
60-
lintResults.set(lintResult.filePath, lintResult);
61-
});
62-
});
66+
// store results
67+
lintReports.forEach((lintReport) => {
68+
lintReport.results.forEach((lintResult) => {
69+
lintResults.set(lintResult.filePath, lintResult);
70+
});
71+
});
6372

64-
// get actual list of previous and current reports
65-
const results = Array.from(lintResults.values());
73+
// get actual list of previous and current reports
74+
const results = Array.from(lintResults.values());
6675

67-
return createIssuesFromEsLintResults(results);
76+
return createIssuesFromEsLintResults(results);
77+
},
78+
async close() {
79+
// do nothing
80+
},
81+
};
6882
},
6983
};
7084
}

src/eslint-reporter/reporter/EsLintReporterRpcClient.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import * as path from 'path';
1+
import path from 'path';
22
import { EsLintReporterConfiguration } from '../EsLintReporterConfiguration';
33
import { createReporterRpcClient, ReporterRpcClient } from '../../reporter';
44
import { createRpcIpcMessageChannel } from '../../rpc/rpc-ipc';

src/hooks/getChangedFiles.ts

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,15 @@
1-
/* eslint-disable @typescript-eslint/no-explicit-any */
21
import webpack from 'webpack';
32
import path from 'path';
4-
import { getWatcher } from './getWatcher';
3+
import { CompilerWithWatchFileSystem } from '../watch/CompilerWithWatchFileSystem';
4+
import { InclusiveNodeWatchFileSystem } from '../watch/InclusiveNodeWatchFileSystem';
55

66
function getChangedFiles(compiler: webpack.Compiler): string[] {
7-
let changedFiles: string[] = [];
7+
const watchFileSystem = (compiler as CompilerWithWatchFileSystem<InclusiveNodeWatchFileSystem>)
8+
.watchFileSystem;
89

9-
if ((compiler as any).modifiedFiles) {
10-
// webpack 5+
11-
changedFiles = Array.from((compiler as any).modifiedFiles);
12-
} else {
13-
const watcher = getWatcher(compiler);
14-
// webpack 4
15-
changedFiles = Object.keys((watcher && watcher.mtimes) || {});
16-
}
17-
18-
return changedFiles.map((changedFile) => path.normalize(changedFile));
10+
return watchFileSystem
11+
? Array.from(watchFileSystem.changedFiles).map((changedFile) => path.normalize(changedFile))
12+
: [];
1913
}
2014

2115
export { getChangedFiles };

0 commit comments

Comments
 (0)