Support TypeScript 7.1 API - #1704
Conversation
|
@copilot update ts-loader/.github/workflows/push.yml Line 52 in 53082fb ts-loader/.github/workflows/push.yml Line 84 in 53082fb We expect execution-tests to pass after this change |
Updated in |
|
@copilot great work! We would like the following to succeed:
Remember backwards compatibility is not required - feel free to make changes |
Verified on |
|
@copilot we have explictly removed the historic typescript version from dependencies and moved to use 7.1 directly. This has broken compilation. Please fix it. You will likely need to use new APIs in TypeScript 7 to achieve this. No cheating. |
Updated in |
|
@copilot make |
Addressed in |
The tsgo-backed native API resolves compiler options purely from the on-disk tsconfig.json, with no per-loader-instance override hook, so compilerOptions has been inert since the 10.0.0 rewrite. Remove it as a valid loader option (matching how onlyCompileBundledFiles/context were removed) instead of leaving it silently accepted but ignored. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…t removal
The comparison-test harness injected compilerOptions: { newLine: 'LF' }
into every test's loader options via its own default options object,
independent of each fixture's own webpack.config.js. Removing
compilerOptions as a valid loader option meant that default had to go
too (test/comparison-tests/create-and-execute-test.js), which shifts
the computed options-hash for every test that doesn't set an explicit
instance name. A number of snapshots embed that hash literally as
ts-loader-default_<hash>; regenerate them via --save-output.
Verified the full diff contains only options-hash changes, dist/index.js
stack-line normalization noise, and the couple-byte bundle size shift
from the shortened validateLoaderOptionNames error message - no actual
compiled-output changes.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
relativeSpecifierExtensions (used by resolveRelativeSpecifier to match a relative import specifier against the program's source files for webpack dependency registration and transitive-dependant rechecking) only tried .ts/.tsx/.d.ts/.js/.jsx, unlike the neighbouring toDeclarationFileName which already handles .mts/.cts. A relative import resolving to a .mts/.cts file was silently treated as unresolved, so editing it neither registered a webpack dependency on the importer nor triggered a diagnostics recheck of it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
updateSnapshot passed { invalidateAll: true } unconditionally on every
single file compiled, forcing the native API to discard its caches and
rescan disk once per file rather than once per build - real watch-mode
wiring (a hook tied to webpack's own build/rebuild boundary) didn't
exist anywhere in the tsgo rewrite.
Add TypeScriptInstance.pendingInvalidation, consumed by the first
updateSnapshot call of a build and re-armed via a compiler.hooks.compile
tap (fires once per build/watch-rebuild in both webpack 4 and 5), so
only that first call forces the rescan; every other file compiled in
the same build reuses the snapshot it already refreshed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…heck findTransitiveDependants rebuilt a fresh O(n) Set of every project file's name inside getDirectResolvedImports on every single call, and called it once per project file for every file compiled - roughly O(n^3) for a full build. The currently-compiling file's own imports were also recomputed a second time (once via registerResolvedImportDependencies, again inside the BFS). - comparableSourceFileNames is now built once per file compiled (passed down to callers) instead of once per candidate file inside the BFS. - Each project file's resolved-imports list is memoized in a new TypeScriptInstance.directImportsCache, reused across every file compiled in the same build via getCachedDirectResolvedImports. The currently-compiling file always recomputes fresh (never trusts a stale entry) and writes its result back, so other files' dependant searches - and the file's own subsequent BFS lookup - see it too, incidentally also fixing the double computation. - The cache is cleared whenever pendingInvalidation forces a real rescan (see the prior watch-mode fix), keeping it correct across builds. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
registerResolvedImportDependencies wrote cache entries keyed by apiFileName (OS-native, from webpack), while findTransitiveDependants/getCachedDirectResolvedImports read/wrote entries keyed by program.getSourceFileNames() (forward-slash- normalized). On Windows those are different strings for the same file, so an entry written by one path could never be found by the other - silently defeating the memoization exactly where it mattered (the currently-compiled file's own entry). Key by FilePathKey via each instance's ResolvedPathCache instead, matching how instance.files/pendingDiagnostics/pendingDeclarationFiles already canonicalize path spelling and case-sensitivity. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
registerTypeScriptDependencies iterated program.getSourceFileNames() (every file in the program) on every single compiled file just to find .d.ts files to register as dependencies - O(n) per file, O(n^2) over a build, even though that set only changes when the program's file set actually does. Add getProjectDtsFileNames, memoized per project (primary or a synthetic one-off project for an orphan file) in a new TypeScriptInstance.projectDtsFileNamesCache, cleared alongside directImportsCache whenever pendingInvalidation forces a real rescan. Keyed by FilePathKey (via ResolvedPathCache) for consistency with directImportsCache, though project config paths are already stable, single-sourced strings in this codebase. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
ensureSyntheticConfigForFile created and permanently tracked one synthetic tsconfig + open project per distinct orphan file (e.g. every allowTsInNodeModules file ever compiled), with no eviction - each held its own ref-counted project open on the API side, so a long watch session touching many distinct orphan files leaked memory/state proportional to all of them, unbounded. Cap it at 20 (maxOrphanFileProjects), evicting the least-recently-used entry - tracked via Map insertion order, bumped on reuse - once the cap is hit. The evicted project's closeProjects is threaded through updateSnapshot to actually release its ref-counted open on the API side, and removed from openedProjectPaths so revisiting that file later reopens it fresh rather than treating it as still-open. Verified with a standalone scratch reproduction (22 distinct orphan files under allowTsInNodeModules): exactly 2 evictions occurred against the cap of 20, the build succeeded with no errors, and editing a previously-evicted file was correctly picked up on the next rebuild. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Two real bugs, same class as the earlier directImportsCache fix: - syntheticConfigContents's readFile/fileExists lookups used toComparablePath (slash-only normalization), while the sibling files Map right next to it already used resolvedPathCache (slash *and* case normalization) - a case-insensitive-filesystem spelling mismatch would silently miss. - syntheticConfigFiles was keyed by raw fileName. Two importers spelling/casing the same orphan file differently would be treated as distinct files, each spawning its own redundant synthetic project - undermining the LRU cap added previously. configFilePath and openedProjectPaths are converted too for consistency, though their values were already single-sourced and stable in practice. Added TypeScriptInstance.resolvedPathCache so functions that only receive TypeScriptInstance (not the outer TSInstance) - openPrimaryProject, prepareSnapshotForFile, ensureSyntheticConfigForFile, updateSnapshot - can canonicalize a path without threading it through as a separate parameter. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Use interface instead of type for the plain object shape TypeScriptApiModule, matching AGENTS.md's convention. - Extract reportConfigFileParsingErrors, deduplicating the ~20-line broken-tsconfig error-reporting block shared by getTypeScriptEmit and getTranspileOnlyEmit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
TypeScriptInstance.resolvedFilePathCache duplicated the exact same function reference already stored on the owning TSInstance's own resolvedPathCache field. Drop the stored field; thread it as an explicit parameter through prepareSnapshotForFile/ensureSyntheticConfigForFile instead, sourced from instance.resolvedPathCache at the getTypeScriptEmit call site - matching the convention already used by getProjectDtsFileNames, registerResolvedImportDependencies, getCachedDirectResolvedImports, and findTransitiveDependants. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Copilot didn't write this - I did! The below can also be found in the CHANGELOG.md.
This is a ground-up rewrite of ts-loader's compilation engine. Instead of driving TypeScript's classic
LanguageService/Program/ watch APIs, ts-loader now compiles exclusively through TypeScript's new nativetypescript/unstable/syncAPI (the tsgo-powered engine) - the legacy compiler API integration has been removed entirely.Not supported yet
getCustomTransformers,resolveModuleNameandresolveTypeReferenceDirectiveare still accepted for backwards compatibility but are now inert - the native API doesn't expose equivalent extension points, so custom transformers and custom module/type-reference resolution are no longer applied. It is possible that the API will support these in future, and so the options have been left in place for now, but they will be removed if the API never exposes them.Breaking changes:
nextprerelease that exposes this native API ahead of a stable 7.1 release.compileroption must now resolve to a package exposing a<compiler>/unstable/syncentry point (the TypeScript native API). Drop-in classic-API compilers (e.g.ttypescript) are no longer supported.compilerOptionsloader option; the native API resolves a project's compiler options purely from its on-disk tsconfig.json, with no per-loader-instance override hook. Set compiler options intsconfig.jsoninstead.contextloader option; the native API always resolves relative paths against the config file's own directory, with no basePath override exposed to let a tsconfig live outside the project root.happyPackModeloader option - HappyPack / thread-loader based parallelisation is no longer supported this way.experimentalFileCachingloader option; there's no equivalent under the native API's own caching model.experimentalWatchApiloader option, now that the native API supersedes TypeScript's classic watch API.onlyCompileBundledFilesloader option; the native API always resolves a project's own root files and offers no hook to restrict them to what webpack actually bundles.errorFormatter'scolorsargument is now a smallpicocolors-backed helper object instead of achalkinstance;chalkhas been dropped as a runtime dependency in favour ofpicocolors.