diff --git a/README.md b/README.md index 09a8176b..0c6517ca 100644 --- a/README.md +++ b/README.md @@ -153,15 +153,16 @@ Options passed to the plugin constructor will overwrite options from the cosmico Options for the TypeScript checker (`typescript` option object). -| Name | Type | Default value | Description | -| -------------------- | --------- | ------------------------------------------------------------------------- | ----------- | -| `enabled` | `boolean` | `true` | If `true`, it enables TypeScript checker. | -| `memoryLimit` | `number` | `2048` | Memory limit for the checker process in MB. If the process exits with the allocation failed error, try to increase this number. | -| `tsconfig` | `string` | `'tsconfig.json'` | Path to the `tsconfig.json` file (path relative to the `compiler.options.context` or absolute path) | -| `build` | `boolean` | `false` | The equivalent of the `--build` flag for the `tsc` command. To enable `incremental` mode, set it in the `tsconfig.json` file. _Note that this plugin doesn't emit any files even in the `build` mode._ | -| `compilerOptions` | `object` | `{ skipLibCheck: true, skipDefaultLibCheck: true }` | These options will overwrite compiler options from the `tsconfig.json` file. | -| `diagnosticsOptions` | `object` | `{ syntactic: false, semantic: true, declaration: false, global: false }` | Settings to select which diagnostics do we want to perform. | -| `extensions` | `object` | `{}` | See [TypeScript extensions options](#typescript-extensions-options). | +| Name | Type | Default value | Description | +| -------------------- | --------------------- | ------------------------------------------------------------------------- | ----------- | +| `enabled` | `boolean` | `true` | If `true`, it enables TypeScript checker. | +| `memoryLimit` | `number` | `2048` | Memory limit for the checker process in MB. If the process exits with the allocation failed error, try to increase this number. | +| `tsconfig` | `string` | `'tsconfig.json'` | Path to the `tsconfig.json` file (path relative to the `compiler.options.context` or absolute path) | +| `build` | `boolean` | `false` | If truthy, it's the equivalent of the `--build` flag for the `tsc` command. | +| `mode` | `'readonly'` or `'write-tsbuildinfo'` or `'write-references'` | `'readonly'` | If you use the `babel-loader`, it's recommended to use `write-references` mode to improve initial compilation time. If you use `ts-loader`, it's recommended to use `readonly` mode to not overwrite filed emitted by `ts-loader`. | +| `compilerOptions` | `object` | `{ skipLibCheck: true, skipDefaultLibCheck: true }` | These options will overwrite compiler options from the `tsconfig.json` file. | +| `diagnosticsOptions` | `object` | `{ syntactic: false, semantic: true, declaration: false, global: false }` | Settings to select which diagnostics do we want to perform. | +| `extensions` | `object` | `{}` | See [TypeScript extensions options](#typescript-extensions-options). | #### TypeScript extensions options diff --git a/src/ForkTsCheckerWebpackPluginOptions.json b/src/ForkTsCheckerWebpackPluginOptions.json index bba06df4..7b6a3f07 100644 --- a/src/ForkTsCheckerWebpackPluginOptions.json +++ b/src/ForkTsCheckerWebpackPluginOptions.json @@ -121,6 +121,11 @@ "type": "boolean", "description": "The equivalent of the `--build` flag from the `tsc`." }, + "mode": { + "type": "string", + "enum": ["readonly", "write-tsbuildinfo", "write-references"], + "description": "`readonly` keeps all emitted files in memory, `write-tsbuildinfo` which writes only .tsbuildinfo files and `write-references` which writes both .tsbuildinfo and referenced projects output" + }, "incremental": { "type": "boolean", "description": "The equivalent of the `--incremental` flag from the `tsc`." diff --git a/src/typescript-reporter/TypeScriptReporterConfiguration.ts b/src/typescript-reporter/TypeScriptReporterConfiguration.ts index 67c13821..a8aef0a2 100644 --- a/src/typescript-reporter/TypeScriptReporterConfiguration.ts +++ b/src/typescript-reporter/TypeScriptReporterConfiguration.ts @@ -14,6 +14,7 @@ interface TypeScriptReporterConfiguration { memoryLimit: number; tsconfig: string; build: boolean; + mode: 'readonly' | 'write-tsbuildinfo' | 'write-references'; compilerOptions: Partial; diagnosticOptions: TypeScriptDiagnosticsOptions; extensions: { @@ -53,6 +54,7 @@ function createTypeScriptReporterConfiguration( enabled: options !== false, memoryLimit: 2048, build: false, + mode: 'readonly', ...(typeof options === 'object' ? options : {}), tsconfig: tsconfig, compilerOptions: compilerOptions, diff --git a/src/typescript-reporter/TypeScriptReporterOptions.ts b/src/typescript-reporter/TypeScriptReporterOptions.ts index 67530a6f..54372880 100644 --- a/src/typescript-reporter/TypeScriptReporterOptions.ts +++ b/src/typescript-reporter/TypeScriptReporterOptions.ts @@ -8,6 +8,7 @@ type TypeScriptReporterOptions = memoryLimit?: number; tsconfig?: string; build?: boolean; + mode?: 'readonly' | 'write-tsbuildinfo' | 'write-references'; compilerOptions?: object; diagnosticOptions?: Partial; extensions?: { diff --git a/src/typescript-reporter/file-system/FileSystem.ts b/src/typescript-reporter/file-system/FileSystem.ts new file mode 100644 index 00000000..cf45bf5e --- /dev/null +++ b/src/typescript-reporter/file-system/FileSystem.ts @@ -0,0 +1,26 @@ +// eslint-disable-next-line node/no-unsupported-features/node-builtins +import { Dirent, Stats } from 'fs'; + +/** + * Interface to abstract file system implementation details. + */ +interface FileSystem { + // read + exists(path: string): boolean; + readFile(path: string, encoding?: string): string | undefined; + readDir(path: string): Dirent[]; + readStats(path: string): Stats | undefined; + realPath(path: string): string; + normalizePath(path: string): string; + + // write + writeFile(path: string, data: string): void; + deleteFile(path: string): void; + createDir(path: string): void; + updateTimes(path: string, atime: Date, mtime: Date): void; + + // cache + clearCache(): void; +} + +export { FileSystem }; diff --git a/src/typescript-reporter/file-system/PassiveFileSystem.ts b/src/typescript-reporter/file-system/PassiveFileSystem.ts new file mode 100644 index 00000000..1061f5e2 --- /dev/null +++ b/src/typescript-reporter/file-system/PassiveFileSystem.ts @@ -0,0 +1,145 @@ +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 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 { + if (memExists(path)) { + return mem + .readFileSync(normalizePath(path), { encoding: encoding as BufferEncoding }) + .toString(); + } + } + + function memReadDir(path: string): Dirent[] { + if (memExists(path)) { + return mem.readdirSync(normalizePath(path), { withFileTypes: true }) as Dirent[]; + } + + return []; + } + + function exists(path: string) { + return realFileSystem.exists(path) || memExists(path); + } + + function readFile(path: string, encoding?: string) { + const fsStats = realFileSystem.readStats(path); + const memStats = memReadStats(path); + + if (fsStats && memStats) { + return fsStats.mtimeMs > memStats.mtimeMs + ? realFileSystem.readFile(path, encoding) + : memReadFile(path, encoding); + } else if (fsStats) { + return realFileSystem.readFile(path, encoding); + } else if (memStats) { + return memReadFile(path, encoding); + } + } + + function readDir(path: string) { + const fsDirents = realFileSystem.readDir(path); + const memDirents = memReadDir(path); + + // merge list of dirents from fs and mem + return fsDirents + .filter((fsDirent) => !memDirents.some((memDirent) => memDirent.name === fsDirent.name)) + .concat(memDirents); + } + + function readStats(path: string) { + const fsStats = realFileSystem.readStats(path); + const memStats = memReadStats(path); + + if (fsStats && memStats) { + return fsStats.mtimeMs > memStats.mtimeMs ? fsStats : memStats; + } else if (fsStats) { + return fsStats; + } else if (memStats) { + return memStats; + } + } + + 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 { + 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)); + }, + 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(); + }, + }; +} + +export { createPassiveFileSystem }; diff --git a/src/typescript-reporter/file-system/RealFileSystem.ts b/src/typescript-reporter/file-system/RealFileSystem.ts new file mode 100644 index 00000000..0e27b2cc --- /dev/null +++ b/src/typescript-reporter/file-system/RealFileSystem.ts @@ -0,0 +1,207 @@ +import { dirname, basename, join, normalize } from 'path'; +import fs from 'fs-extra'; +import { FileSystem } from './FileSystem'; +// eslint-disable-next-line node/no-unsupported-features/node-builtins +import { Dirent, Stats } from 'fs'; + +/** + * It's an implementation of the FileSystem interface which reads and writes directly to the real file system. + * + * @param caseSensitive + */ +function createRealFileSystem(caseSensitive = false): FileSystem { + // read cache + const existsCache = new Map(); + const readStatsCache = new Map(); + const readFileCache = new Map(); + const readDirCache = new Map(); + const realPathCache = new Map(); + + function normalizePath(path: string): string { + return caseSensitive ? normalize(path) : normalize(path).toLowerCase(); + } + + // read methods + function exists(path: string): boolean { + const normalizedPath = normalizePath(path); + + if (!existsCache.has(normalizedPath)) { + existsCache.set(normalizedPath, fs.existsSync(normalizedPath)); + } + + return !!existsCache.get(normalizedPath); + } + + function readStats(path: string): Stats | undefined { + const normalizedPath = normalizePath(path); + + if (!readStatsCache.has(normalizedPath)) { + if (exists(normalizedPath)) { + readStatsCache.set(normalizedPath, fs.statSync(normalizedPath)); + } + } + + return readStatsCache.get(normalizedPath); + } + + function readFile(path: string, encoding?: string): string | undefined { + const normalizedPath = normalizePath(path); + + if (!readFileCache.has(normalizedPath)) { + if (exists(normalizedPath)) { + readFileCache.set(normalizedPath, fs.readFileSync(normalizedPath, { encoding }).toString()); + } else { + readFileCache.set(normalizedPath, undefined); + } + } + + return readFileCache.get(normalizedPath); + } + + function readDir(path: string): Dirent[] { + const normalizedPath = normalizePath(path); + + if (!readDirCache.has(normalizedPath)) { + if (exists(normalizedPath)) { + readDirCache.set(normalizedPath, fs.readdirSync(normalizedPath, { withFileTypes: true })); + } else { + readDirCache.set(normalizedPath, []); + } + } + + return readDirCache.get(normalizedPath) || []; + } + + function getRealPath(path: string) { + const normalizedPath = normalizePath(path); + + if (!realPathCache.has(normalizedPath)) { + let base = normalizedPath; + let nested = ''; + + while (base !== dirname(base)) { + if (exists(base)) { + realPathCache.set(normalizedPath, normalizePath(join(fs.realpathSync(base), nested))); + break; + } + + nested = join(basename(base), nested); + base = dirname(base); + } + } + + return realPathCache.get(normalizedPath) || normalizedPath; + } + + function createDir(path: string) { + const normalizedPath = normalizePath(path); + + fs.mkdirSync(normalizedPath, { recursive: true }); + + // update cache + existsCache.set(normalizedPath, true); + if (readDirCache.has(dirname(normalizedPath))) { + readDirCache.delete(dirname(normalizedPath)); + } + if (readStatsCache.has(normalizedPath)) { + readStatsCache.delete(normalizedPath); + } + } + + function writeFile(path: string, data: string) { + const normalizedPath = normalizePath(path); + + if (!exists(dirname(normalizedPath))) { + createDir(dirname(normalizedPath)); + } + + fs.writeFileSync(normalizedPath, data); + + // update cache + existsCache.set(normalizedPath, true); + if (readDirCache.has(dirname(normalizedPath))) { + readDirCache.delete(dirname(normalizedPath)); + } + if (readStatsCache.has(normalizedPath)) { + readStatsCache.delete(normalizedPath); + } + if (readFileCache.has(normalizedPath)) { + readFileCache.delete(normalizedPath); + } + } + + function deleteFile(path: string) { + if (exists(path)) { + const normalizedPath = normalizePath(path); + + fs.unlinkSync(normalizedPath); + + // update cache + existsCache.set(normalizedPath, false); + if (readDirCache.has(dirname(normalizedPath))) { + readDirCache.delete(dirname(normalizedPath)); + } + if (readStatsCache.has(normalizedPath)) { + readStatsCache.delete(normalizedPath); + } + if (readFileCache.has(normalizedPath)) { + readFileCache.delete(normalizedPath); + } + } + } + + function updateTimes(path: string, atime: Date, mtime: Date) { + if (exists(path)) { + const normalizedPath = normalizePath(path); + + fs.utimesSync(normalizePath(path), atime, mtime); + + // update cache + if (readStatsCache.has(normalizedPath)) { + readStatsCache.delete(normalizedPath); + } + } + } + + return { + exists(path: string) { + return exists(getRealPath(path)); + }, + readFile(path: string, encoding?: string) { + return readFile(getRealPath(path), encoding); + }, + readDir(path: string) { + return readDir(getRealPath(path)); + }, + readStats(path: string) { + return readStats(getRealPath(path)); + }, + realPath(path: string) { + return getRealPath(path); + }, + normalizePath(path: string) { + return normalizePath(path); + }, + writeFile(path: string, data: string) { + writeFile(getRealPath(path), data); + }, + deleteFile(path: string) { + deleteFile(getRealPath(path)); + }, + createDir(path: string) { + createDir(getRealPath(path)); + }, + updateTimes(path: string, atime: Date, mtime: Date) { + updateTimes(getRealPath(path), atime, mtime); + }, + clearCache() { + existsCache.clear(); + readStatsCache.clear(); + readFileCache.clear(); + readDirCache.clear(); + realPathCache.clear(); + }, + }; +} + +export { createRealFileSystem }; diff --git a/src/typescript-reporter/reporter/ControlledTypeScriptSystem.ts b/src/typescript-reporter/reporter/ControlledTypeScriptSystem.ts index 4802491a..dfaab09f 100644 --- a/src/typescript-reporter/reporter/ControlledTypeScriptSystem.ts +++ b/src/typescript-reporter/reporter/ControlledTypeScriptSystem.ts @@ -1,7 +1,8 @@ import * as ts from 'typescript'; import { dirname } from 'path'; -import { createPassiveFileSystem } from './PassiveFileSystem'; +import { createPassiveFileSystem } from '../file-system/PassiveFileSystem'; import normalizeSlash from '../../utils/path/normalizeSlash'; +import { createRealFileSystem } from '../file-system/RealFileSystem'; interface ControlledTypeScriptSystem extends ts.System { // control watcher @@ -34,7 +35,11 @@ interface ControlledTypeScriptSystem extends ts.System { waitForQueued(): Promise; } -function createControlledTypeScriptSystem(): ControlledTypeScriptSystem { +type FileSystemMode = 'readonly' | 'write-tsbuildinfo' | 'write-references'; + +function createControlledTypeScriptSystem( + mode: FileSystemMode = 'readonly' +): ControlledTypeScriptSystem { // watchers const fileWatchersMap = new Map(); const directoryWatchersMap = new Map(); @@ -43,14 +48,15 @@ function createControlledTypeScriptSystem(): ControlledTypeScriptSystem { // eslint-disable-next-line @typescript-eslint/no-explicit-any const timeoutCallbacks = new Set(); const caseSensitive = ts.sys.useCaseSensitiveFileNames; - const fileSystem = createPassiveFileSystem(caseSensitive); + const realFileSystem = createRealFileSystem(caseSensitive); + const passiveFileSystem = createPassiveFileSystem(caseSensitive, realFileSystem); function createWatcher( watchersMap: Map, path: string, callback: TCallback ) { - const normalizedPath = fileSystem.normalizePath(path); + const normalizedPath = realFileSystem.normalizePath(path); const watchers = watchersMap.get(normalizedPath) || []; const nextWatchers = [...watchers, callback]; @@ -70,18 +76,18 @@ function createControlledTypeScriptSystem(): ControlledTypeScriptSystem { }; } - const invokeFileWatchers = (path: string, event: ts.FileWatcherEventKind) => { - const normalizedPath = fileSystem.normalizePath(path); + function invokeFileWatchers(path: string, event: ts.FileWatcherEventKind) { + const normalizedPath = realFileSystem.normalizePath(path); const fileWatchers = fileWatchersMap.get(normalizedPath); if (fileWatchers) { // typescript expects normalized paths with posix forward slash fileWatchers.forEach((fileWatcher) => fileWatcher(normalizeSlash(normalizedPath), event)); } - }; + } - const invokeDirectoryWatchers = (path: string) => { - const normalizedPath = fileSystem.normalizePath(path); + function invokeDirectoryWatchers(path: string) { + const normalizedPath = realFileSystem.normalizePath(path); let directory = dirname(normalizedPath); const directoryWatchers = directoryWatchersMap.get(directory); @@ -101,58 +107,66 @@ function createControlledTypeScriptSystem(): ControlledTypeScriptSystem { directory = dirname(directory); } - }; + } + + function getWriteFileSystem(path: string) { + if (mode === 'readonly' || (mode === 'write-tsbuildinfo' && !path.endsWith('.tsbuildinfo'))) { + return passiveFileSystem; + } else { + return realFileSystem; + } + } const controlledSystem: ControlledTypeScriptSystem = { ...ts.sys, useCaseSensitiveFileNames: caseSensitive, fileExists(path: string): boolean { - const stats = fileSystem.readStats(path); + const stats = passiveFileSystem.readStats(path); return !!stats && stats.isFile(); }, readFile(path: string, encoding?: string): string | undefined { - return fileSystem.readFile(path, encoding); + return passiveFileSystem.readFile(path, encoding); }, getFileSize(path: string): number { - const stats = fileSystem.readStats(path); + const stats = passiveFileSystem.readStats(path); return stats ? stats.size : 0; }, writeFile(path: string, data: string): void { - fileSystem.writeFile(path, data); + getWriteFileSystem(path).writeFile(path, data); controlledSystem.invokeFileChanged(path); }, deleteFile(path: string): void { - fileSystem.deleteFile(path); + getWriteFileSystem(path).deleteFile(path); controlledSystem.invokeFileDeleted(path); }, directoryExists(path: string): boolean { - const stats = fileSystem.readStats(path); + const stats = passiveFileSystem.readStats(path); return !!stats && stats.isDirectory(); }, createDirectory(path: string): void { - fileSystem.createDir(path); + getWriteFileSystem(path).createDir(path); invokeDirectoryWatchers(path); }, getDirectories(path: string): string[] { - const dirents = fileSystem.readDir(path); + const dirents = passiveFileSystem.readDir(path); return dirents.filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name); }, getModifiedTime(path: string): Date | undefined { - const stats = fileSystem.readStats(path); + const stats = passiveFileSystem.readStats(path); if (stats) { return stats.mtime; } }, setModifiedTime(path: string, date: Date): void { - fileSystem.updateTimes(path, date, date); + getWriteFileSystem(path).updateTimes(path, date, date); invokeDirectoryWatchers(path); invokeFileWatchers(path, ts.FileWatcherEventKind.Changed); @@ -191,7 +205,7 @@ function createControlledTypeScriptSystem(): ControlledTypeScriptSystem { } }, invokeFileChanged(path: string) { - const normalizedPath = fileSystem.normalizePath(path); + const normalizedPath = realFileSystem.normalizePath(path); invokeDirectoryWatchers(normalizedPath); @@ -203,7 +217,7 @@ function createControlledTypeScriptSystem(): ControlledTypeScriptSystem { } }, invokeFileDeleted(path: string) { - const normalizedPath = fileSystem.normalizePath(path); + const normalizedPath = realFileSystem.normalizePath(path); if (!deletedFiles.get(normalizedPath)) { invokeDirectoryWatchers(path); @@ -213,7 +227,8 @@ function createControlledTypeScriptSystem(): ControlledTypeScriptSystem { } }, clearCache() { - fileSystem.clearCache(); + passiveFileSystem.clearCache(); + realFileSystem.clearCache(); }, }; diff --git a/src/typescript-reporter/reporter/PassiveFileSystem.ts b/src/typescript-reporter/reporter/PassiveFileSystem.ts deleted file mode 100644 index 306bc81f..00000000 --- a/src/typescript-reporter/reporter/PassiveFileSystem.ts +++ /dev/null @@ -1,244 +0,0 @@ -import { dirname, basename, join, normalize } from 'path'; -import fs from 'fs-extra'; -import { fs as mem } from 'memfs'; -// eslint-disable-next-line node/no-unsupported-features/node-builtins -import { Stats, Dirent } from 'fs'; - -interface PassiveFileSystem { - // read - exists(path: string): boolean; - readFile(path: string, encoding?: string): string | undefined; - readDir(path: string): Dirent[]; - readStats(path: string): Stats | undefined; - realPath(path: string): string; - normalizePath(path: string): string; - - // write - writeFile(path: string, data: string): void; - deleteFile(path: string): void; - createDir(path: string): void; - updateTimes(path: string, atime: Date, mtime: Date): void; - - // cache - clearCache(): void; -} - -function createPassiveFileSystem(caseSensitive = false): PassiveFileSystem { - // read cache - const fsExistsCache = new Map(); - const fsReadStatsCache = new Map(); - const fsReadFileCache = new Map(); - const fsReadDirCache = new Map(); - const fsRealPathCache = new Map(); - - function normalizePath(path: string): string { - return caseSensitive ? normalize(path) : normalize(path).toLowerCase(); - } - - // read methods - function fsExists(path: string): boolean { - const normalizedPath = normalizePath(path); - - if (!fsExistsCache.has(normalizedPath)) { - fsExistsCache.set(normalizedPath, fs.existsSync(normalizedPath)); - } - - return !!fsExistsCache.get(normalizedPath); - } - - function memExists(path: string): boolean { - return mem.existsSync(normalizePath(path)); - } - - function fsReadStats(path: string): Stats | undefined { - const normalizedPath = normalizePath(path); - - if (!fsReadStatsCache.has(normalizedPath)) { - if (fsExists(normalizedPath)) { - fsReadStatsCache.set(normalizedPath, fs.statSync(normalizedPath)); - } - } - - return fsReadStatsCache.get(normalizedPath); - } - - function memReadStats(path: string): Stats | undefined { - return memExists(path) ? mem.statSync(normalizePath(path)) : undefined; - } - - function fsReadFile(path: string, encoding?: string): string | undefined { - const normalizedPath = normalizePath(path); - - if (!fsReadFileCache.has(normalizedPath)) { - if (fsExists(normalizedPath)) { - fsReadFileCache.set( - normalizedPath, - fs.readFileSync(normalizedPath, { encoding }).toString() - ); - } else { - fsReadFileCache.set(normalizedPath, undefined); - } - } - - return fsReadFileCache.get(normalizedPath); - } - - function memReadFile(path: string, encoding?: string): string | undefined { - if (memExists(path)) { - return mem - .readFileSync(normalizePath(path), { encoding: encoding as BufferEncoding }) - .toString(); - } - } - - function fsReadDir(path: string): Dirent[] { - const normalizedPath = normalizePath(path); - - if (!fsReadDirCache.has(normalizedPath)) { - if (fsExists(normalizedPath)) { - fsReadDirCache.set(normalizedPath, fs.readdirSync(normalizedPath, { withFileTypes: true })); - } else { - fsReadDirCache.set(normalizedPath, []); - } - } - - return fsReadDirCache.get(normalizedPath) || []; - } - - function memReadDir(path: string): Dirent[] { - if (memExists(path)) { - return mem.readdirSync(normalizePath(path), { withFileTypes: true }) as Dirent[]; - } - - return []; - } - - function exists(path: string) { - return fsExists(path) || memExists(path); - } - - function readFile(path: string, encoding?: string) { - const fsStats = fsReadStats(path); - const memStats = memReadStats(path); - - if (fsStats && memStats) { - return fsStats.mtimeMs > memStats.mtimeMs - ? fsReadFile(path, encoding) - : memReadFile(path, encoding); - } else if (fsStats) { - return fsReadFile(path, encoding); - } else if (memStats) { - return memReadFile(path, encoding); - } - } - - function readDir(path: string) { - const fsDirents = fsReadDir(path); - const memDirents = memReadDir(path); - - // merge list of dirents from fs and mem - return fsDirents - .filter((fsDirent) => !memDirents.some((memDirent) => memDirent.name === fsDirent.name)) - .concat(memDirents); - } - - function readStats(path: string) { - const fsStats = fsReadStats(path); - const memStats = memReadStats(path); - - if (fsStats && memStats) { - return fsStats.mtimeMs > memStats.mtimeMs ? fsStats : memStats; - } else if (fsStats) { - return fsStats; - } else if (memStats) { - return memStats; - } - } - - function getRealPath(path: string) { - const normalizedPath = normalizePath(path); - - if (!fsRealPathCache.has(normalizedPath)) { - let base = normalizedPath; - let nested = ''; - - while (base !== dirname(base)) { - if (fsExists(base)) { - fsRealPathCache.set(normalizedPath, normalizePath(join(fs.realpathSync(base), nested))); - break; - } - - nested = join(basename(base), nested); - base = dirname(base); - } - } - - return fsRealPathCache.get(normalizedPath) || normalizedPath; - } - - 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 { - exists(path: string) { - return exists(getRealPath(path)); - }, - readFile(path: string, encoding?: string) { - return readFile(getRealPath(path), encoding); - }, - readDir(path: string) { - return readDir(getRealPath(path)); - }, - readStats(path: string) { - return readStats(getRealPath(path)); - }, - realPath(path: string) { - return getRealPath(path); - }, - normalizePath(path: string) { - return normalizePath(path); - }, - writeFile(path: string, data: string) { - writeFile(getRealPath(path), data); - }, - deleteFile(path: string) { - deleteFile(getRealPath(path)); - }, - createDir(path: string) { - createDir(getRealPath(path)); - }, - updateTimes(path: string, atime: Date, mtime: Date) { - updateTimes(getRealPath(path), atime, mtime); - }, - clearCache() { - fsExistsCache.clear(); - fsReadStatsCache.clear(); - fsReadFileCache.clear(); - fsReadDirCache.clear(); - fsRealPathCache.clear(); - }, - }; -} - -export { createPassiveFileSystem, PassiveFileSystem }; diff --git a/src/typescript-reporter/reporter/TypeScriptReporter.ts b/src/typescript-reporter/reporter/TypeScriptReporter.ts index 00a9c5fc..3626d76b 100644 --- a/src/typescript-reporter/reporter/TypeScriptReporter.ts +++ b/src/typescript-reporter/reporter/TypeScriptReporter.ts @@ -63,7 +63,7 @@ function createTypeScriptReporter(configuration: TypeScriptReporterConfiguration return { getReport: async ({ changedFiles = [], deletedFiles = [] }: FilesChange) => { if (!system) { - system = createControlledTypeScriptSystem(); + system = createControlledTypeScriptSystem(configuration.mode); } // clear cache to be ready for next iteration and to free memory diff --git a/test/e2e/TypeScriptSolutionBuilderApi.spec.ts b/test/e2e/TypeScriptSolutionBuilderApi.spec.ts index 162ca439..266db88b 100644 --- a/test/e2e/TypeScriptSolutionBuilderApi.spec.ts +++ b/test/e2e/TypeScriptSolutionBuilderApi.spec.ts @@ -23,20 +23,21 @@ describe('TypeScript SolutionBuilder API', () => { }); it.each([ - { async: false, typescript: '~3.6.0' }, - { async: true, typescript: '~3.8.0' }, - ])('reports semantic error for %p', async ({ async, typescript }) => { + { async: false, typescript: '~3.6.0', mode: 'readonly' }, + { async: true, typescript: '~3.8.0', mode: 'write-tsbuildinfo' }, + { async: false, typescript: '~3.8.0', mode: 'write-references' }, + ])('reports semantic error for %p', async ({ async, typescript, mode }) => { await sandbox.load([ await readFixture(join(__dirname, 'fixtures/environment/typescript-monorepo.fixture'), { FORK_TS_CHECKER_WEBPACK_PLUGIN_VERSION: JSON.stringify( FORK_TS_CHECKER_WEBPACK_PLUGIN_VERSION ), - TS_LOADER_VERSION: JSON.stringify('^7.0.1'), TYPESCRIPT_VERSION: JSON.stringify(typescript), WEBPACK_VERSION: JSON.stringify('^4.0.0'), WEBPACK_CLI_VERSION: JSON.stringify(WEBPACK_CLI_VERSION), WEBPACK_DEV_SERVER_VERSION: JSON.stringify(WEBPACK_DEV_SERVER_VERSION), ASYNC: JSON.stringify(async), + MODE: JSON.stringify(mode), }), await readFixture(join(__dirname, 'fixtures/implementation/typescript-monorepo.fixture')), ]); @@ -99,5 +100,44 @@ describe('TypeScript SolutionBuilder API', () => { // this compilation should be successful await driver.waitForNoErrors(); + + switch (mode) { + case 'readonly': + expect(await sandbox.exists('packages/shared/tsconfig.tsbuildinfo')).toEqual(false); + expect(await sandbox.exists('packages/client/tsconfig.tsbuildinfo')).toEqual(false); + expect(await sandbox.exists('packages/shared/lib')).toEqual(false); + expect(await sandbox.exists('packages/client/lib')).toEqual(false); + break; + + case 'write-tsbuildinfo': + expect(await sandbox.exists('packages/shared/tsconfig.tsbuildinfo')).toEqual(true); + expect(await sandbox.exists('packages/client/tsconfig.tsbuildinfo')).toEqual(true); + expect(await sandbox.exists('packages/shared/lib')).toEqual(false); + expect(await sandbox.exists('packages/client/lib')).toEqual(false); + + expect(await sandbox.read('packages/shared/tsconfig.tsbuildinfo')).not.toEqual(''); + expect(await sandbox.read('packages/client/tsconfig.tsbuildinfo')).not.toEqual(''); + + await sandbox.remove('packages/shared/tsconfig.tsbuildinfo'); + await sandbox.remove('packages/client/tsconfig.tsbuildinfo'); + break; + + case 'write-references': + expect(await sandbox.exists('packages/shared/tsconfig.tsbuildinfo')).toEqual(true); + expect(await sandbox.exists('packages/client/tsconfig.tsbuildinfo')).toEqual(true); + expect(await sandbox.exists('packages/shared/lib')).toEqual(true); + expect(await sandbox.exists('packages/client/lib')).toEqual(true); + expect(await sandbox.exists('packages/shared/lib/index.js')).toEqual(true); + expect(await sandbox.exists('packages/client/lib/index.js')).toEqual(true); + + expect(await sandbox.read('packages/shared/tsconfig.tsbuildinfo')).not.toEqual(''); + expect(await sandbox.read('packages/client/tsconfig.tsbuildinfo')).not.toEqual(''); + + await sandbox.remove('packages/shared/tsconfig.tsbuildinfo'); + await sandbox.remove('packages/client/tsconfig.tsbuildinfo'); + await sandbox.remove('packages/shared/lib'); + await sandbox.remove('packages/client/lib'); + break; + } }); }); diff --git a/test/e2e/fixtures/environment/typescript-monorepo.fixture b/test/e2e/fixtures/environment/typescript-monorepo.fixture index d169d2cd..a5109388 100644 --- a/test/e2e/fixtures/environment/typescript-monorepo.fixture +++ b/test/e2e/fixtures/environment/typescript-monorepo.fixture @@ -9,9 +9,12 @@ "webpack-dev-server": "webpack-dev-server" }, "devDependencies": { + "@babel/core": "^7.10.0", + "@babel/preset-env": "^7.10.0", + "@babel/preset-typescript": "^7.9.0", + "babel-loader": "^8.1.0", "lerna": "^3.0.0", "fork-ts-checker-webpack-plugin": ${FORK_TS_CHECKER_WEBPACK_PLUGIN_VERSION}, - "ts-loader": ${TS_LOADER_VERSION}, "typescript": ${TYPESCRIPT_VERSION}, "webpack": ${WEBPACK_VERSION}, "webpack-cli": ${WEBPACK_CLI_VERSION}, @@ -66,20 +69,22 @@ module.exports = { module: { rules: [ { - test: /\.tsx?$/, - loader: 'ts-loader', + test: /\.ts$/, exclude: /node_modules/, + loader: "babel-loader", options: { - transpileOnly: true, - projectReferences: true - } + presets: [ + "@babel/preset-env", + "@babel/preset-typescript", + ], + }, }, ], }, resolve: { - extensions: ['.tsx', '.ts', '.js'], - // as ts-loader doesn't fully support project references we put an alias to resolve package - // and to not pollute output with ts-loader errors + extensions: ['.ts', '.js'], + // as babel-loader doesn't support project references we put an alias to resolve package + // and to not pollute output with babel-loader errors alias: { "@project-references-fixture/shared": path.resolve(__dirname, "packages/shared/src") } @@ -88,7 +93,11 @@ module.exports = { new ForkTsCheckerWebpackPlugin({ async: ${ASYNC}, typescript: { - build: true + build: true, + mode: ${MODE} + }, + logger: { + infrastructure: 'console' } }) ] diff --git a/test/unit/typescript-reporter/TypeScriptReporterConfiguration.spec.ts b/test/unit/typescript-reporter/TypeScriptReporterConfiguration.spec.ts index 49f76940..e15ffa23 100644 --- a/test/unit/typescript-reporter/TypeScriptReporterConfiguration.spec.ts +++ b/test/unit/typescript-reporter/TypeScriptReporterConfiguration.spec.ts @@ -12,6 +12,7 @@ describe('typescript-reporter/TypeScriptsReporterConfiguration', () => { memoryLimit: 2048, tsconfig: '/webpack/context/tsconfig.json', build: false, + mode: 'readonly', compilerOptions: { skipDefaultLibCheck: true, skipLibCheck: true, @@ -60,6 +61,8 @@ describe('typescript-reporter/TypeScriptsReporterConfiguration', () => { { ...configuration, tsconfig: '/webpack/context/tsconfig.another.json' }, ], [{ build: true }, { ...configuration, build: true }], + [{ mode: 'write-tsbuildinfo' }, { ...configuration, mode: 'write-tsbuildinfo' }], + [{ mode: 'write-references' }, { ...configuration, mode: 'write-references' }], [ { compilerOptions: { strict: true } }, { diff --git a/test/unit/typescript-reporter/TypeScriptSupport.spec.ts b/test/unit/typescript-reporter/TypeScriptSupport.spec.ts index 7bf3721c..30ee3f9e 100644 --- a/test/unit/typescript-reporter/TypeScriptSupport.spec.ts +++ b/test/unit/typescript-reporter/TypeScriptSupport.spec.ts @@ -11,6 +11,7 @@ describe('typescript-reporter/TypeScriptSupport', () => { tsconfig: './tsconfig.json', compilerOptions: {}, build: false, + mode: 'readonly', diagnosticOptions: { declaration: false, global: true,