vite-esbuild-typescript-checker
Version:
* Speeds up [TypeScript](https://github.com/Microsoft/TypeScript) type checking * Supports [Vue Single File Component](https://vuejs.org/v2/guide/single-file-components.html) * Displays nice error messages with the [code frame](https://babeljs.io/docs/en/
225 lines (224 loc) • 8.77 kB
JavaScript
import { dirname, join } from 'path';
import ts from 'typescript';
import { realFileSystem } from './filesystem/real-file-system.js';
import { memFileSystem } from './filesystem/mem-file-system.js';
import { passiveFileSystem } from './filesystem/passive-file-system.js';
import { getState } from './state.js';
import { forwardSlash } from './functions.js';
const state = getState();
const mode = state.mode;
let artifacts = {
files: [],
dirs: [],
excluded: [],
extensions: []
};
let isInitialRun = true;
// watchers
const fileWatcherCallbacksMap = new Map();
const directoryWatcherCallbacksMap = new Map();
const recursiveDirectoryWatcherCallbacksMap = new Map();
const deletedFiles = new Map();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const timeoutCallbacks = new Set();
// based on the ts.ignorePaths
const ignoredPaths = [
'/node_modules/.',
'/.git',
'/.#'
];
export const system = {
...ts.sys,
useCaseSensitiveFileNames: true,
realpath (path) {
return getReadFileSystem(path).realPath(path);
},
fileExists (path) {
const stats = getReadFileSystem(path).readStats(path);
return !!stats && stats.isFile();
},
readFile (path, encoding) {
// if (path.endsWith('main.ts')) {
// console.log(path)
// console.log(getReadFileSystem(path).readFile(path, encoding))
// }
return getReadFileSystem(path).readFile(path, encoding);
},
getFileSize (path) {
const stats = getReadFileSystem(path).readStats(path);
return stats ? stats.size : 0;
},
writeFile (path, data) {
getWriteFileSystem(path).writeFile(path, data);
system.invokeFileChanged(path);
},
deleteFile (path) {
getWriteFileSystem(path).deleteFile(path);
system.invokeFileDeleted(path);
},
directoryExists (path) {
return Boolean(getReadFileSystem(path).readStats(path)?.isDirectory());
},
createDirectory (path) {
getWriteFileSystem(path).createDir(path);
invokeDirectoryWatchers(path);
},
getDirectories (path) {
const dirents = getReadFileSystem(path).readDir(path);
return dirents.filter((dirent)=>dirent.isDirectory() || dirent.isSymbolicLink() && system.directoryExists(join(path, dirent.name))).map((dirent)=>dirent.name);
},
getModifiedTime (path) {
const stats = getReadFileSystem(path).readStats(path);
if (stats) {
return stats.mtime;
}
},
setModifiedTime (path, date) {
getWriteFileSystem(path).updateTimes(path, date, date);
invokeDirectoryWatchers(path);
invokeFileWatchers(path, ts.FileWatcherEventKind.Changed);
},
watchFile (path, callback) {
return createWatcher(fileWatcherCallbacksMap, path, callback);
},
watchDirectory (path, callback, recursive = false) {
return createWatcher(recursive ? recursiveDirectoryWatcherCallbacksMap : directoryWatcherCallbacksMap, path, callback);
},
// use immediate instead of timeout to avoid waiting 250ms for batching files changes
setTimeout: (callback, timeout, ...args)=>{
const timeoutId = setImmediate(()=>{
callback(...args);
timeoutCallbacks.delete(timeoutId);
});
timeoutCallbacks.add(timeoutId);
return timeoutId;
},
clearTimeout: (timeoutId)=>{
clearImmediate(timeoutId);
timeoutCallbacks.delete(timeoutId);
},
async waitForQueued () {
while(timeoutCallbacks.size > 0){
await new Promise((resolve)=>setImmediate(resolve));
}
isInitialRun = false;
},
invokeFileCreated (path) {
const normalizedPath = normalizeAndResolvePath(path);
invokeFileWatchers(path, ts.FileWatcherEventKind.Created);
invokeDirectoryWatchers(normalizedPath);
deletedFiles.set(normalizedPath, false);
},
invokeFileChanged (path) {
const normalizedPath = normalizeAndResolvePath(path);
if (deletedFiles.get(normalizedPath) || !fileWatcherCallbacksMap.has(normalizedPath)) {
invokeFileWatchers(path, ts.FileWatcherEventKind.Created);
invokeDirectoryWatchers(normalizedPath);
deletedFiles.set(normalizedPath, false);
} else {
invokeFileWatchers(path, ts.FileWatcherEventKind.Changed);
}
},
invokeFileDeleted (path) {
const normalizedPath = normalizeAndResolvePath(path);
if (!deletedFiles.get(normalizedPath)) {
invokeFileWatchers(path, ts.FileWatcherEventKind.Deleted);
invokeDirectoryWatchers(path);
deletedFiles.set(normalizedPath, true);
}
},
invalidateCache () {
memFileSystem.clearCache();
},
setArtifacts (nextArtifacts) {
artifacts = nextArtifacts;
}
};
function createWatcher(watchersMap, path, callback) {
const normalizedPath = normalizeAndResolvePath(path);
const watchers = watchersMap.get(normalizedPath) || [];
const nextWatchers = [
...watchers,
callback
];
watchersMap.set(normalizedPath, nextWatchers);
return {
close: ()=>{
const watchers = watchersMap.get(normalizedPath) || [];
const nextWatchers = watchers.filter((watcher)=>watcher !== callback);
if (nextWatchers.length > 0) {
watchersMap.set(normalizedPath, nextWatchers);
} else {
watchersMap.delete(normalizedPath);
}
}
};
}
function invokeFileWatchers(path, event) {
const normalizedPath = normalizeAndResolvePath(path);
if (normalizedPath.endsWith('.js')) {
// trigger relevant .d.ts file watcher - handles the case, when we have webpack watcher
// that points to a symlinked package
invokeFileWatchers(normalizedPath.slice(0, -3) + '.d.ts', event);
}
const fileWatcherCallbacks = fileWatcherCallbacksMap.get(normalizedPath);
if (fileWatcherCallbacks) {
// typescript expects normalized paths with posix forward slash
fileWatcherCallbacks.forEach((fileWatcherCallback)=>fileWatcherCallback(forwardSlash(normalizedPath), event));
}
}
function invokeDirectoryWatchers(path) {
const normalizedPath = normalizeAndResolvePath(path);
const directory = dirname(normalizedPath);
if (ignoredPaths.some((ignoredPath)=>forwardSlash(normalizedPath).includes(ignoredPath))) {
return;
}
const directoryWatcherCallbacks = directoryWatcherCallbacksMap.get(directory);
if (directoryWatcherCallbacks) {
directoryWatcherCallbacks.forEach((directoryWatcherCallback)=>directoryWatcherCallback(forwardSlash(normalizedPath)));
}
recursiveDirectoryWatcherCallbacksMap.forEach((recursiveDirectoryWatcherCallbacks, watchedDirectory)=>{
if (watchedDirectory === directory || directory.startsWith(watchedDirectory) && forwardSlash(directory)[watchedDirectory.length] === '/') {
recursiveDirectoryWatcherCallbacks.forEach((recursiveDirectoryWatcherCallback)=>recursiveDirectoryWatcherCallback(forwardSlash(normalizedPath)));
}
});
}
function normalizeAndResolvePath(path) {
let normalizedPath = realFileSystem.normalizePath(path);
try {
normalizedPath = realFileSystem.realPath(normalizedPath);
} catch (error) {
// ignore error - maybe file doesn't exist
}
return normalizedPath;
}
function isArtifact(path) {
return (artifacts.dirs.some((dir)=>path.includes(dir)) || artifacts.files.some((file)=>path === file)) && artifacts.extensions.some((extension)=>path.endsWith(extension));
}
function getReadFileSystem(path) {
if ((mode === 'readonly' || mode === 'write-tsbuildinfo') && isArtifact(path)) {
if (isInitialRun && !memFileSystem.exists(path) && passiveFileSystem.exists(path)) {
// copy file to memory on initial run
const stats = passiveFileSystem.readStats(path);
if (stats?.isFile()) {
const content = passiveFileSystem.readFile(path);
if (content) {
memFileSystem.writeFile(path, content);
memFileSystem.updateTimes(path, stats.atime, stats.mtime);
}
}
return memFileSystem;
}
}
return passiveFileSystem;
}
function getWriteFileSystem(path) {
if (mode === 'write-references' || mode === 'write-tsbuildinfo' && path.endsWith('.tsbuildinfo') || mode === 'write-dts' && [
'.tsbuildinfo',
'.d.ts',
'.d.ts.map'
].some((suffix)=>path.endsWith(suffix))) {
return realFileSystem;
}
return passiveFileSystem;
}