Skip to content

Commit 05cf957

Browse files
authored
fix: use memfs for ts artifacts to prevent mtime conflict (#617)
When ts-loader uses projectReferences mode, it writes down .js and .d.ts files. If it does it faster than the plugin, the typescript instance in the plugin is confused and doesn't work properly. To fix that, let's use memfs for dirs and files that are detected as a project artifacts.
1 parent c297d58 commit 05cf957

13 files changed

+348
-149
lines changed

src/ForkTsCheckerWebpackPluginState.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { Tap } from 'tapable';
2-
import { Dependencies, Report } from './reporter';
2+
import { FilesMatch, Report } from './reporter';
33
import { Issue } from './issue';
44

55
interface ForkTsCheckerWebpackPluginState {
66
reportPromise: Promise<Report | undefined>;
77
issuesPromise: Promise<Issue[] | undefined>;
8-
dependenciesPromise: Promise<Dependencies | undefined>;
9-
lastDependencies: Dependencies | undefined;
8+
dependenciesPromise: Promise<FilesMatch | undefined>;
9+
lastDependencies: FilesMatch | undefined;
1010
watching: boolean;
1111
initialized: boolean;
1212
webpackDevServerDoneTap: Tap | undefined;

src/hooks/tapStartToConnectAndRunReporter.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import webpack from 'webpack';
22
import { ForkTsCheckerWebpackPluginConfiguration } from '../ForkTsCheckerWebpackPluginConfiguration';
33
import { ForkTsCheckerWebpackPluginState } from '../ForkTsCheckerWebpackPluginState';
44
import { getForkTsCheckerWebpackPluginHooks } from './pluginHooks';
5-
import { Dependencies, FilesChange, getFilesChange, ReporterRpcClient } from '../reporter';
5+
import { FilesMatch, FilesChange, getFilesChange, ReporterRpcClient } from '../reporter';
66
import { OperationCanceledError } from '../error/OperationCanceledError';
77
import { tapDoneToAsyncGetIssues } from './tapDoneToAsyncGetIssues';
88
import { tapAfterCompileToGetIssues } from './tapAfterCompileToGetIssues';
@@ -63,7 +63,7 @@ function tapStartToConnectAndRunReporter(
6363
configuration.logger.infrastructure.info('Calling reporter service for single check.');
6464
}
6565

66-
let resolveDependencies: (dependencies: Dependencies | undefined) => void;
66+
let resolveDependencies: (dependencies: FilesMatch | undefined) => void;
6767
let rejectedDependencies: (error: Error) => void;
6868
let resolveIssues: (issues: Issue[] | undefined) => void;
6969
let rejectIssues: (error: Error) => void;
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
interface Dependencies {
1+
interface FilesMatch {
22
files: string[];
33
dirs: string[];
44
extensions: string[];
55
}
66

7-
export { Dependencies };
7+
export { FilesMatch };

src/reporter/Report.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { Dependencies } from './Dependencies';
1+
import { FilesMatch } from './FilesMatch';
22
import { Issue } from '../issue';
33

44
interface Report {
5-
getDependencies(): Promise<Dependencies>;
5+
getDependencies(): Promise<FilesMatch>;
66
getIssues(): Promise<Issue[]>;
77
close(): Promise<void>;
88
}

src/reporter/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ export * from './Reporter';
33
export * from './AggregatedReporter';
44

55
export * from './FilesChange';
6-
export * from './Dependencies';
6+
export * from './FilesMatch';
77

88
export * from './reporter-rpc/ReporterRpcClient';
99
export * from './reporter-rpc/ReporterRpcService';

src/reporter/reporter-rpc/ReporterRpcProcedure.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { RpcProcedure } from '../../rpc';
22
import { FilesChange } from '../FilesChange';
33
import { Issue } from '../../issue';
4-
import { Dependencies } from '../Dependencies';
4+
import { FilesMatch } from '../FilesMatch';
55

66
const configure: RpcProcedure<object, void> = 'configure';
77
const getReport: RpcProcedure<FilesChange, void> = 'getReport';
8-
const getDependencies: RpcProcedure<void, Dependencies> = 'getDependencies';
8+
const getDependencies: RpcProcedure<void, FilesMatch> = 'getDependencies';
99
const getIssues: RpcProcedure<void, Issue[]> = 'getIssues';
1010
const closeReport: RpcProcedure<void, void> = 'closeReport';
1111

src/typescript-reporter/extension/TypeScriptEmbeddedExtension.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as ts from 'typescript';
22
import { extname } from 'path';
33
import { TypeScriptExtension } from './TypeScriptExtension';
44
import { Issue } from '../../issue';
5-
import { Dependencies } from '../../reporter';
5+
import { FilesMatch } from '../../reporter';
66

77
interface TypeScriptEmbeddedSource {
88
sourceText: string;
@@ -168,7 +168,7 @@ function createTypeScriptEmbeddedExtension({
168168
},
169169
};
170170
},
171-
extendDependencies(dependencies: Dependencies) {
171+
extendDependencies(dependencies: FilesMatch) {
172172
return {
173173
...dependencies,
174174
files: dependencies.files.map((fileName) => {

src/typescript-reporter/extension/TypeScriptExtension.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as ts from 'typescript';
22
import { Issue } from '../../issue';
3-
import { Dependencies } from '../../reporter';
3+
import { FilesMatch } from '../../reporter';
44

55
interface TypeScriptHostExtension {
66
extendWatchSolutionBuilderHost?<
@@ -26,7 +26,7 @@ interface TypeScriptHostExtension {
2626

2727
interface TypeScriptReporterExtension {
2828
extendIssues?(issues: Issue[]): Issue[];
29-
extendDependencies?(dependencies: Dependencies): Dependencies;
29+
extendDependencies?(dependencies: FilesMatch): FilesMatch;
3030
}
3131

3232
interface TypeScriptExtension extends TypeScriptHostExtension, TypeScriptReporterExtension {}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import { dirname } from 'path';
2+
import { fs as mem } from 'memfs';
3+
import { FileSystem } from './FileSystem';
4+
// eslint-disable-next-line node/no-unsupported-features/node-builtins
5+
import { Dirent, Stats } from 'fs';
6+
7+
/**
8+
* It's an implementation of FileSystem interface which reads and writes to the in-memory file system.
9+
*
10+
* @param realFileSystem
11+
*/
12+
function createMemFileSystem(realFileSystem: FileSystem): FileSystem {
13+
function exists(path: string): boolean {
14+
return mem.existsSync(realFileSystem.normalizePath(path));
15+
}
16+
17+
function readStats(path: string): Stats | undefined {
18+
return exists(path) ? mem.statSync(realFileSystem.normalizePath(path)) : undefined;
19+
}
20+
21+
function readFile(path: string, encoding?: string): string | undefined {
22+
const stats = readStats(path);
23+
24+
if (stats && stats.isFile()) {
25+
return mem
26+
.readFileSync(realFileSystem.normalizePath(path), { encoding: encoding as BufferEncoding })
27+
.toString();
28+
}
29+
}
30+
31+
function readDir(path: string): Dirent[] {
32+
const stats = readStats(path);
33+
34+
if (stats && stats.isDirectory()) {
35+
return mem.readdirSync(realFileSystem.normalizePath(path), {
36+
withFileTypes: true,
37+
}) as Dirent[];
38+
}
39+
40+
return [];
41+
}
42+
43+
function createDir(path: string) {
44+
mem.mkdirSync(realFileSystem.normalizePath(path), { recursive: true });
45+
}
46+
47+
function writeFile(path: string, data: string) {
48+
if (!exists(dirname(path))) {
49+
createDir(dirname(path));
50+
}
51+
52+
mem.writeFileSync(realFileSystem.normalizePath(path), data);
53+
}
54+
55+
function deleteFile(path: string) {
56+
if (exists(path)) {
57+
mem.unlinkSync(realFileSystem.normalizePath(path));
58+
}
59+
}
60+
61+
function updateTimes(path: string, atime: Date, mtime: Date) {
62+
if (exists(path)) {
63+
mem.utimesSync(realFileSystem.normalizePath(path), atime, mtime);
64+
}
65+
}
66+
67+
return {
68+
...realFileSystem,
69+
exists(path: string) {
70+
return exists(realFileSystem.realPath(path));
71+
},
72+
readFile(path: string, encoding?: string) {
73+
return readFile(realFileSystem.realPath(path), encoding);
74+
},
75+
readDir(path: string) {
76+
return readDir(realFileSystem.realPath(path));
77+
},
78+
readStats(path: string) {
79+
return readStats(realFileSystem.realPath(path));
80+
},
81+
writeFile(path: string, data: string) {
82+
writeFile(realFileSystem.realPath(path), data);
83+
},
84+
deleteFile(path: string) {
85+
deleteFile(realFileSystem.realPath(path));
86+
},
87+
createDir(path: string) {
88+
createDir(realFileSystem.realPath(path));
89+
},
90+
updateTimes(path: string, atime: Date, mtime: Date) {
91+
updateTimes(realFileSystem.realPath(path), atime, mtime);
92+
},
93+
clearCache() {
94+
realFileSystem.clearCache();
95+
},
96+
};
97+
}
98+
99+
export { createMemFileSystem };

src/typescript-reporter/file-system/PassiveFileSystem.ts

Lines changed: 12 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,37 @@
1-
import { dirname, normalize } from 'path';
2-
import { fs as mem } from 'memfs';
31
import { FileSystem } from './FileSystem';
4-
// eslint-disable-next-line node/no-unsupported-features/node-builtins
5-
import { Dirent, Stats } from 'fs';
62

73
/**
84
* It's an implementation of FileSystem interface which reads from the real file system, but write to the in-memory file system.
95
*
10-
* @param caseSensitive
6+
* @param memFileSystem
117
* @param realFileSystem
128
*/
13-
function createPassiveFileSystem(caseSensitive = false, realFileSystem: FileSystem): FileSystem {
14-
function normalizePath(path: string): string {
15-
return caseSensitive ? normalize(path) : normalize(path).toLowerCase();
16-
}
17-
18-
function memExists(path: string): boolean {
19-
return mem.existsSync(normalizePath(path));
20-
}
21-
22-
function memReadStats(path: string): Stats | undefined {
23-
return memExists(path) ? mem.statSync(normalizePath(path)) : undefined;
24-
}
25-
26-
function memReadFile(path: string, encoding?: string): string | undefined {
27-
const stats = memReadStats(path);
28-
29-
if (stats && stats.isFile()) {
30-
return mem
31-
.readFileSync(normalizePath(path), { encoding: encoding as BufferEncoding })
32-
.toString();
33-
}
34-
}
35-
36-
function memReadDir(path: string): Dirent[] {
37-
const stats = memReadStats(path);
38-
39-
if (stats && stats.isDirectory()) {
40-
return mem.readdirSync(normalizePath(path), { withFileTypes: true }) as Dirent[];
41-
}
42-
43-
return [];
44-
}
45-
9+
function createPassiveFileSystem(
10+
memFileSystem: FileSystem,
11+
realFileSystem: FileSystem
12+
): FileSystem {
4613
function exists(path: string) {
47-
return realFileSystem.exists(path) || memExists(path);
14+
return realFileSystem.exists(path) || memFileSystem.exists(path);
4815
}
4916

5017
function readFile(path: string, encoding?: string) {
5118
const fsStats = realFileSystem.readStats(path);
52-
const memStats = memReadStats(path);
19+
const memStats = memFileSystem.readStats(path);
5320

5421
if (fsStats && memStats) {
5522
return fsStats.mtimeMs > memStats.mtimeMs
5623
? realFileSystem.readFile(path, encoding)
57-
: memReadFile(path, encoding);
24+
: memFileSystem.readFile(path, encoding);
5825
} else if (fsStats) {
5926
return realFileSystem.readFile(path, encoding);
6027
} else if (memStats) {
61-
return memReadFile(path, encoding);
28+
return memFileSystem.readFile(path, encoding);
6229
}
6330
}
6431

6532
function readDir(path: string) {
6633
const fsDirents = realFileSystem.readDir(path);
67-
const memDirents = memReadDir(path);
34+
const memDirents = memFileSystem.readDir(path);
6835

6936
// merge list of dirents from fs and mem
7037
return fsDirents
@@ -74,7 +41,7 @@ function createPassiveFileSystem(caseSensitive = false, realFileSystem: FileSyst
7441

7542
function readStats(path: string) {
7643
const fsStats = realFileSystem.readStats(path);
77-
const memStats = memReadStats(path);
44+
const memStats = memFileSystem.readStats(path);
7845

7946
if (fsStats && memStats) {
8047
return fsStats.mtimeMs > memStats.mtimeMs ? fsStats : memStats;
@@ -85,31 +52,8 @@ function createPassiveFileSystem(caseSensitive = false, realFileSystem: FileSyst
8552
}
8653
}
8754

88-
function createDir(path: string) {
89-
mem.mkdirSync(normalizePath(path), { recursive: true });
90-
}
91-
92-
function writeFile(path: string, data: string) {
93-
if (!memExists(dirname(path))) {
94-
createDir(dirname(path));
95-
}
96-
97-
mem.writeFileSync(normalizePath(path), data);
98-
}
99-
100-
function deleteFile(path: string) {
101-
if (memExists(path)) {
102-
mem.unlinkSync(normalizePath(path));
103-
}
104-
}
105-
106-
function updateTimes(path: string, atime: Date, mtime: Date) {
107-
if (memExists(path)) {
108-
mem.utimesSync(normalizePath(path), atime, mtime);
109-
}
110-
}
111-
11255
return {
56+
...memFileSystem,
11357
exists(path: string) {
11458
return exists(realFileSystem.realPath(path));
11559
},
@@ -125,21 +69,6 @@ function createPassiveFileSystem(caseSensitive = false, realFileSystem: FileSyst
12569
realPath(path: string) {
12670
return realFileSystem.realPath(path);
12771
},
128-
normalizePath(path: string) {
129-
return normalizePath(path);
130-
},
131-
writeFile(path: string, data: string) {
132-
writeFile(realFileSystem.realPath(path), data);
133-
},
134-
deleteFile(path: string) {
135-
deleteFile(realFileSystem.realPath(path));
136-
},
137-
createDir(path: string) {
138-
createDir(realFileSystem.realPath(path));
139-
},
140-
updateTimes(path: string, atime: Date, mtime: Date) {
141-
updateTimes(realFileSystem.realPath(path), atime, mtime);
142-
},
14372
clearCache() {
14473
realFileSystem.clearCache();
14574
},

0 commit comments

Comments
 (0)