TypeScript: 5.3.2 adds broken "reference" comment
🔎 Search Terms
reference types error
🕗 Version & Regression Information
- This changed between versions 5.2.2 and 5.3.2
⏯ Playground Link
No response
💻 Code
My code does a type import like:
import type { ModuleOptions } from '../../module/module';
and in 5.3.2 this adds a type reference in the compiled output:
/// <reference types="dist/module/module" />
which is an invalid path from the point of the user of this package and result in an error:
node_modules/foo-package/dist/lib/dir/file.d.ts:4:23 - error TS2688: Cannot find type definition file for 'dist/module/module'.
4 /// <reference types="dist/module/module" />
🙁 Actual behavior
Adds a broken reference:
/// <reference types="dist/module/module" />
🙂 Expected behavior
5.2.2 didn’t add a reference in this case.
Additional information about the issue
No response
About this issue
- Original URL
- State: closed
- Created 7 months ago
- Comments: 19 (12 by maintainers)
Commits related to this issue
- pin typescript in router package https://github.com/microsoft/TypeScript/issues/56571 — committed to embroider-build/embroider by ef4 7 months ago
- Fix parsing issue with certain declarations for the https://github.com/microsoft/TypeScript/issues/56571 issue — committed to NullVoxPopuli/fix-bad-declaration-output by NullVoxPopuli 5 months ago
- Fix parsing issue with certain declarations for the https://github.com/microsoft/TypeScript/issues/56571 issue Normalize some checking, expand test cases — committed to NullVoxPopuli/fix-bad-declaration-output by NullVoxPopuli 5 months ago
Just so everyone has all the relevant context (and we should be super clear that the emit issue here is almost certainly something wacky we are doing on the Ember side, not a TS bug!)—
The mechanics of how Ember’s stable types are currently published are (as has been very typical for us) doing horrible things to deal with Ember’s “We implemented ESM ourselves!” decision back in 2015–2016, which in turn our type publishing mechanics have to account for and try to “translate” to something resolveable by TS (ultimately: by the standard Node resolution algorithm). The key constraint here was: Ember users import from specifiers like
@ember/object
, but there is no@ember/object
package on disk. Ember users instead depend on theember-source
package, which bundles along the runtime and type dependencies for the@ember
packages, and Ember CLI is responsible to resolve the module specifiers during the build.[^future]To enable a phased transition out of DefinitelyTyped, we took a two-step process of providing first “preview” and then “stable” types. In each case, users get access to the types by writing a side-effect-style import:
or
The “preview” types were copied over from DefinitelyTyped, and written as a set of
declare module
declarations. The “stable” types are generated from Ember’s own source. The distinction was: preview types were subject to lots of churn and change as we identified issues both in Ember and across the Ember ecosystem, while the stable types are reliable because they come out of Ember and are therefore the real source of truth. Accordingly, they should only change in SemVer-compatible ways along with Ember’s public API. We used the same type tests for both, which allowed us to make incremental progress as we finished converting Ember’s internals to TS, and to backport fixes to earlier versions of the hand-authored types.We also began publishing types from source incrementally, and so recommended that libraries and apps use this as the way of getting both simultaneously:
Libraries depend on
ember-source
indevDependencies
(and lately alsopeerDependencies
), expecting the host application to provide a specific version for runtime.The contents of the
ember-source/types/preview/index.d.ts
file was a hand-authored set of side-effect imports to make thedeclare module
s visible (see e.g. the declaration for@ember/object
). The contents for the stable types are similar, though in that case using/// <reference path="./@ember/object/index.d.ts" >
instead ofimport "./@ember/object/index.d.ts";
.Stable types are published from Ember’s own source, using a custom build script which:
tsc
against the Ember codebase using thistsconfig.json
, ultimately using these compiler options to generatedeclare module
/// <reference path="..." >
into thetypes/stable/index.d.ts
fileThis is a very weird way to have to make things visible, and it is possible that it is not playing nicely with how
tsc
expects things to line up in terms of what is a global or not. It is also possible that it is specifically an interaction around different packages depending on the preview vs. the stable types via their imports, since in some cases both may be present in the module graph? Last but not least, as @dfreeman pointed out in the Ember Discord, it is very possible that there is something specifically about the types for@ember/object
or the mixin system and how they are being surfaced which is driving this.It is not clear to me how or why any specific one of those would be at issue, and as I am no longer actively contributing to Ember or its types, I will not be able to dig in further, but I am happy to answer questions, provide further info, etc. here to help as I can.
Since they are not available in the repo but exist only as part of the build:
The generated
index.d.ts
for stable typesThe emitted and rewritten
@ember/object/index.d.ts
There are plenty more; you can poke at the package on npm or clone
ember-source
, runpnpm install
, thenpnpm build:types
to get the output locally.[^future]: There are a bunch of ideas about how to solve this in the future, including “just” actually publishing those packages, using import maps, and other approachs, but for now this is how things are.
As of #57681, TS will no longer synthesize reference directives, nor preserve reference directives unless explicitly annotated with
preserve="true"
. I believe that this should fix this issue and negate the need for plugins to fixup declaration files.I was under the impression that those triple-slash paths don’t resolve at all because they were ignoring
typesVersions
, but it turns out they do resolve as long as a version ofember-source
is present that actually includes thetypes/preview
folder.typesVersions
is not the same asexports
. It does not block resolution of subpaths not explicitly listed. It could have achieved a blocking effect if it wanted to like this:I agree, there is no TypeScript bug here. I opened #56614 to address https://github.com/microsoft/TypeScript/issues/56571#issuecomment-1830869896. Closing this out.
@NullVoxPopuli again, the path is incorrect, indicating a bug, but the reference is necessary. If there’s no way to write a path to the globals you’re implicitly referencing during your own compilation of the library, we should be giving you an error during declaration emit. If you have a repro for this, please open a new issue.
@rchl yeah, I think we could explore skipping paths/baseUrl for these paths that get generated in TypeScript-only constructs like triple-slash references. But that kind of hides the underlying misconfiguration here—the reality is that you could have written
import "dist/module/module"
in your own source code and TS would have thought that was going to be valid, and consumers would then have an issue both at runtime and compile time.@andrewbranch if you want to investigate my case, even though it’s technically invalid, I’ve created this minimal repro: https://github.com/rchl/ts-reference-test (includes compiled
dist
to easily see how it looks).No, we don’t add options that intentionally break declaration emit 🤨
Just an extra note that TS is adding:
even though there also exists a related type import, importing from the same file:
So in that case at least the reference looks redundant.
While I don’t disagree that using
baseUrl
is wrong in this case, it feels like the breaking change could have been avoided if the resolved paths in references would be more consistent with actual type imports those were generated from/for.