Skip to content

Commit

Permalink
fix: use memfs for ts artifacts to prevent mtime conflict (#617)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
piotr-oles committed May 18, 2021
1 parent c297d58 commit 05cf957
Show file tree
Hide file tree
Showing 13 changed files with 348 additions and 149 deletions.
6 changes: 3 additions & 3 deletions src/ForkTsCheckerWebpackPluginState.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Tap } from 'tapable';
import { Dependencies, Report } from './reporter';
import { FilesMatch, Report } from './reporter';
import { Issue } from './issue';

interface ForkTsCheckerWebpackPluginState {
reportPromise: Promise<Report | undefined>;
issuesPromise: Promise<Issue[] | undefined>;
dependenciesPromise: Promise<Dependencies | undefined>;
lastDependencies: Dependencies | undefined;
dependenciesPromise: Promise<FilesMatch | undefined>;
lastDependencies: FilesMatch | undefined;
watching: boolean;
initialized: boolean;
webpackDevServerDoneTap: Tap | undefined;
Expand Down
4 changes: 2 additions & 2 deletions src/hooks/tapStartToConnectAndRunReporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import webpack from 'webpack';
import { ForkTsCheckerWebpackPluginConfiguration } from '../ForkTsCheckerWebpackPluginConfiguration';
import { ForkTsCheckerWebpackPluginState } from '../ForkTsCheckerWebpackPluginState';
import { getForkTsCheckerWebpackPluginHooks } from './pluginHooks';
import { Dependencies, FilesChange, getFilesChange, ReporterRpcClient } from '../reporter';
import { FilesMatch, FilesChange, getFilesChange, ReporterRpcClient } from '../reporter';
import { OperationCanceledError } from '../error/OperationCanceledError';
import { tapDoneToAsyncGetIssues } from './tapDoneToAsyncGetIssues';
import { tapAfterCompileToGetIssues } from './tapAfterCompileToGetIssues';
Expand Down Expand Up @@ -63,7 +63,7 @@ function tapStartToConnectAndRunReporter(
configuration.logger.infrastructure.info('Calling reporter service for single check.');
}

let resolveDependencies: (dependencies: Dependencies | undefined) => void;
let resolveDependencies: (dependencies: FilesMatch | undefined) => void;
let rejectedDependencies: (error: Error) => void;
let resolveIssues: (issues: Issue[] | undefined) => void;
let rejectIssues: (error: Error) => void;
Expand Down
4 changes: 2 additions & 2 deletions src/reporter/Dependencies.ts → src/reporter/FilesMatch.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
interface Dependencies {
interface FilesMatch {
files: string[];
dirs: string[];
extensions: string[];
}

export { Dependencies };
export { FilesMatch };
4 changes: 2 additions & 2 deletions src/reporter/Report.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Dependencies } from './Dependencies';
import { FilesMatch } from './FilesMatch';
import { Issue } from '../issue';

interface Report {
getDependencies(): Promise<Dependencies>;
getDependencies(): Promise<FilesMatch>;
getIssues(): Promise<Issue[]>;
close(): Promise<void>;
}
Expand Down
2 changes: 1 addition & 1 deletion src/reporter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ export * from './Reporter';
export * from './AggregatedReporter';

export * from './FilesChange';
export * from './Dependencies';
export * from './FilesMatch';

export * from './reporter-rpc/ReporterRpcClient';
export * from './reporter-rpc/ReporterRpcService';
4 changes: 2 additions & 2 deletions src/reporter/reporter-rpc/ReporterRpcProcedure.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { RpcProcedure } from '../../rpc';
import { FilesChange } from '../FilesChange';
import { Issue } from '../../issue';
import { Dependencies } from '../Dependencies';
import { FilesMatch } from '../FilesMatch';

const configure: RpcProcedure<object, void> = 'configure';
const getReport: RpcProcedure<FilesChange, void> = 'getReport';
const getDependencies: RpcProcedure<void, Dependencies> = 'getDependencies';
const getDependencies: RpcProcedure<void, FilesMatch> = 'getDependencies';
const getIssues: RpcProcedure<void, Issue[]> = 'getIssues';
const closeReport: RpcProcedure<void, void> = 'closeReport';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as ts from 'typescript';
import { extname } from 'path';
import { TypeScriptExtension } from './TypeScriptExtension';
import { Issue } from '../../issue';
import { Dependencies } from '../../reporter';
import { FilesMatch } from '../../reporter';

interface TypeScriptEmbeddedSource {
sourceText: string;
Expand Down Expand Up @@ -168,7 +168,7 @@ function createTypeScriptEmbeddedExtension({
},
};
},
extendDependencies(dependencies: Dependencies) {
extendDependencies(dependencies: FilesMatch) {
return {
...dependencies,
files: dependencies.files.map((fileName) => {
Expand Down
4 changes: 2 additions & 2 deletions src/typescript-reporter/extension/TypeScriptExtension.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as ts from 'typescript';
import { Issue } from '../../issue';
import { Dependencies } from '../../reporter';
import { FilesMatch } from '../../reporter';

interface TypeScriptHostExtension {
extendWatchSolutionBuilderHost?<
Expand All @@ -26,7 +26,7 @@ interface TypeScriptHostExtension {

interface TypeScriptReporterExtension {
extendIssues?(issues: Issue[]): Issue[];
extendDependencies?(dependencies: Dependencies): Dependencies;
extendDependencies?(dependencies: FilesMatch): FilesMatch;
}

interface TypeScriptExtension extends TypeScriptHostExtension, TypeScriptReporterExtension {}
Expand Down
99 changes: 99 additions & 0 deletions src/typescript-reporter/file-system/MemFileSystem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { dirname } from 'path';
import { fs as mem } from 'memfs';
import { FileSystem } from './FileSystem';
// eslint-disable-next-line node/no-unsupported-features/node-builtins
import { Dirent, Stats } from 'fs';

/**
* It's an implementation of FileSystem interface which reads and writes to the in-memory file system.
*
* @param realFileSystem
*/
function createMemFileSystem(realFileSystem: FileSystem): FileSystem {
function exists(path: string): boolean {
return mem.existsSync(realFileSystem.normalizePath(path));
}

function readStats(path: string): Stats | undefined {
return exists(path) ? mem.statSync(realFileSystem.normalizePath(path)) : undefined;
}

function readFile(path: string, encoding?: string): string | undefined {
const stats = readStats(path);

if (stats && stats.isFile()) {
return mem
.readFileSync(realFileSystem.normalizePath(path), { encoding: encoding as BufferEncoding })
.toString();
}
}

function readDir(path: string): Dirent[] {
const stats = readStats(path);

if (stats && stats.isDirectory()) {
return mem.readdirSync(realFileSystem.normalizePath(path), {
withFileTypes: true,
}) as Dirent[];
}

return [];
}

function createDir(path: string) {
mem.mkdirSync(realFileSystem.normalizePath(path), { recursive: true });
}

function writeFile(path: string, data: string) {
if (!exists(dirname(path))) {
createDir(dirname(path));
}

mem.writeFileSync(realFileSystem.normalizePath(path), data);
}

function deleteFile(path: string) {
if (exists(path)) {
mem.unlinkSync(realFileSystem.normalizePath(path));
}
}

function updateTimes(path: string, atime: Date, mtime: Date) {
if (exists(path)) {
mem.utimesSync(realFileSystem.normalizePath(path), atime, mtime);
}
}

return {
...realFileSystem,
exists(path: string) {
return exists(realFileSystem.realPath(path));
},
readFile(path: string, encoding?: string) {
return readFile(realFileSystem.realPath(path), encoding);
},
readDir(path: string) {
return readDir(realFileSystem.realPath(path));
},
readStats(path: string) {
return readStats(realFileSystem.realPath(path));
},
writeFile(path: string, data: string) {
writeFile(realFileSystem.realPath(path), data);
},
deleteFile(path: string) {
deleteFile(realFileSystem.realPath(path));
},
createDir(path: string) {
createDir(realFileSystem.realPath(path));
},
updateTimes(path: string, atime: Date, mtime: Date) {
updateTimes(realFileSystem.realPath(path), atime, mtime);
},
clearCache() {
realFileSystem.clearCache();
},
};
}

export { createMemFileSystem };
95 changes: 12 additions & 83 deletions src/typescript-reporter/file-system/PassiveFileSystem.ts
Original file line number Diff line number Diff line change
@@ -1,70 +1,37 @@
import { dirname, normalize } from 'path';
import { fs as mem } from 'memfs';
import { FileSystem } from './FileSystem';
// eslint-disable-next-line node/no-unsupported-features/node-builtins
import { Dirent, Stats } from 'fs';

/**
* It's an implementation of FileSystem interface which reads from the real file system, but write to the in-memory file system.
*
* @param caseSensitive
* @param memFileSystem
* @param realFileSystem
*/
function createPassiveFileSystem(caseSensitive = false, realFileSystem: FileSystem): FileSystem {
function normalizePath(path: string): string {
return caseSensitive ? normalize(path) : normalize(path).toLowerCase();
}

function memExists(path: string): boolean {
return mem.existsSync(normalizePath(path));
}

function memReadStats(path: string): Stats | undefined {
return memExists(path) ? mem.statSync(normalizePath(path)) : undefined;
}

function memReadFile(path: string, encoding?: string): string | undefined {
const stats = memReadStats(path);

if (stats && stats.isFile()) {
return mem
.readFileSync(normalizePath(path), { encoding: encoding as BufferEncoding })
.toString();
}
}

function memReadDir(path: string): Dirent[] {
const stats = memReadStats(path);

if (stats && stats.isDirectory()) {
return mem.readdirSync(normalizePath(path), { withFileTypes: true }) as Dirent[];
}

return [];
}

function createPassiveFileSystem(
memFileSystem: FileSystem,
realFileSystem: FileSystem
): FileSystem {
function exists(path: string) {
return realFileSystem.exists(path) || memExists(path);
return realFileSystem.exists(path) || memFileSystem.exists(path);
}

function readFile(path: string, encoding?: string) {
const fsStats = realFileSystem.readStats(path);
const memStats = memReadStats(path);
const memStats = memFileSystem.readStats(path);

if (fsStats && memStats) {
return fsStats.mtimeMs > memStats.mtimeMs
? realFileSystem.readFile(path, encoding)
: memReadFile(path, encoding);
: memFileSystem.readFile(path, encoding);
} else if (fsStats) {
return realFileSystem.readFile(path, encoding);
} else if (memStats) {
return memReadFile(path, encoding);
return memFileSystem.readFile(path, encoding);
}
}

function readDir(path: string) {
const fsDirents = realFileSystem.readDir(path);
const memDirents = memReadDir(path);
const memDirents = memFileSystem.readDir(path);

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

function readStats(path: string) {
const fsStats = realFileSystem.readStats(path);
const memStats = memReadStats(path);
const memStats = memFileSystem.readStats(path);

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

function createDir(path: string) {
mem.mkdirSync(normalizePath(path), { recursive: true });
}

function writeFile(path: string, data: string) {
if (!memExists(dirname(path))) {
createDir(dirname(path));
}

mem.writeFileSync(normalizePath(path), data);
}

function deleteFile(path: string) {
if (memExists(path)) {
mem.unlinkSync(normalizePath(path));
}
}

function updateTimes(path: string, atime: Date, mtime: Date) {
if (memExists(path)) {
mem.utimesSync(normalizePath(path), atime, mtime);
}
}

return {
...memFileSystem,
exists(path: string) {
return exists(realFileSystem.realPath(path));
},
Expand All @@ -125,21 +69,6 @@ function createPassiveFileSystem(caseSensitive = false, realFileSystem: FileSyst
realPath(path: string) {
return realFileSystem.realPath(path);
},
normalizePath(path: string) {
return normalizePath(path);
},
writeFile(path: string, data: string) {
writeFile(realFileSystem.realPath(path), data);
},
deleteFile(path: string) {
deleteFile(realFileSystem.realPath(path));
},
createDir(path: string) {
createDir(realFileSystem.realPath(path));
},
updateTimes(path: string, atime: Date, mtime: Date) {
updateTimes(realFileSystem.realPath(path), atime, mtime);
},
clearCache() {
realFileSystem.clearCache();
},
Expand Down

0 comments on commit 05cf957

Please sign in to comment.