UNPKG

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
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; }