How can I make a package built on top of TS 4.X patible with 3.X? For example, if I have a newer version, use new features, otherwise use any
or unknown
or whatever is supported in older version.
Is there any possibility to use directives for that purpose?
How can I make a package built on top of TS 4.X patible with 3.X? For example, if I have a newer version, use new features, otherwise use any
or unknown
or whatever is supported in older version.
Is there any possibility to use directives for that purpose?
Share Improve this question edited Feb 27, 2023 at 19:20 starball 54k35 gold badges236 silver badges931 bronze badges asked Jan 31, 2022 at 11:47 hypeofpipehypeofpipe 5135 silver badges16 bronze badges 7- TS 4 has some features which are not exists in TS 3 – captain-yossarian from Ukraine Commented Jan 31, 2022 at 13:58
-
Correct, but what if I want to conditionally ignore them? Like, if you have TS 4 -> use new features, else -> rollback to
any
or something – hypeofpipe Commented Jan 31, 2022 at 14:07 - You simply need to support two versions of type declarations for your project – captain-yossarian from Ukraine Commented Jan 31, 2022 at 14:30
- I thought about it, but it's more difficult from maintainability's point of view. I was wondering if there's another way. – hypeofpipe Commented Jan 31, 2022 at 21:51
- AFAIK, it is impossible. There are no feature flags – captain-yossarian from Ukraine Commented Jan 31, 2022 at 22:00
2 Answers
Reset to default 7Preface
This answer assumes the question is about taking source code (.ts) of a project written using TypeScript 4.x constructs and making type declaration files (.d.ts) emitted from them patible with a TypeScript 3.x piler for the benefit of the users of the package- as opposed to writing source code that uses 4.x constructs and somehow making it patible with a 3.x piler (the way the question is phrased is ambiguous with respect to this). I make this assumption because:
If you decide you want to use language features in your source code that aren't supported by older piler versions, you are (whether you realize it or not) making a decision to drop support for building the project using those older piler version. If you have a strong reason to want to support building the project using those older pilers, then I'm pretty sure you just have to not use those newer language features (or somehow convince the maintainers of the piler to backport those changes to older piler versions, which I think is pretty rare).
I'm not personally aware of any reasons not to upgrade your piler version unless you have very strict security policies and require your dependencies and build tooling to be audited. I'd wager that that's pretty rare in the JavaScript development scene where the landscape is known for changing rapidly.
downlevel-dts
Note: TypeScript has a feature to "downlevel" the JavaScript it emits to convert language constructs from later versions of the ECMA Script standard to constructs that work in older versions. (see the pileOptions.target
field of tsconfig).
As far as I know, TypeScript itself doesn't have such a feature to downlevel the typings files it emits (including triple-slash-directives at the time of this writing), but Nathan Sanders (a maintainer of Definitely Typed) maintains an open-source project, downlevel-dts
, to downlevel .d.ts files which can downlevel typings all the way down to typescript v3.4 syntax.
package.json.typesVersions and semver-ts
The "'Downleveling' Types" section of semver-ts explains how you can script downleveling types for each typescript version where new non-backwards-patible language constructs were introduced and how to tell a package-user's piler which version of the types to use and where to find them:
When a new version of TypeScript includes a backwards-inpatible change to emitted type definitions, as they did in 3.7, the strategy of changing the types directly may not work. However, it is still possible to provide backwards-patible types, using the bination of downlevel-dts and typesVersions. (In some cases, this may also require some manual tweaking of types, but this should be rare for most packages.)
The
downlevel-dts
tool allows you to take a.d.ts
file which is not valid for an earlier version of TypeScript (e.g. the changes to class field emit mentioned in Breaking Changes), and emit a version which is patible with that version. It supports targeting all TypeScript versions later than 3.4.TypeScript supports using the
typesVersions
key in apackage.json
file to specify a specific set of type definitions (which may consist of one or more.d.ts
files) which correspond to a specific TypeScript version.The remended flow would be as follows:
To avoid copying too much from off-site material (plagiarism), I'll summarize the steps in my own words (go read the source for the full steps with examples):
- Install
downlevel-dts
as a dev-dependency (and some other helper tools). - Call
downlevel-dts
to downlevel types to whichever older type declaration versions you want to support (this can be scripted). - Update your package.json to register call your script after generating types for the more recent type declaration verion.
- Register your generated older-version type declaration files in your package.json file using the
typesVersions
field. - Make sure the generated files are included with your package's files (update the
files
field, or whichever similar fields you are using).
limitations of downlevel-dts
Note that there are limitations. The following is a quote from downlevel-dts
's readme:
Note that not all features can be downlevelled. For example, TypeScript 4.0 allows spreading multiple tuple type variables, at any position in a tuple. This is not allowed in previous versions, but has no obvious downlevel emit, so downlevel-dts doesn't attempt to do anything. Be sure to test the output of downlevel-dts with the appropriate version of TypeScript.
Problematic aspects of other proposed solutions:
"Use an older typescript version to emit your typings"
- This assumes the asker of the question is using the TS piler to emit typings from .ts files and doesn't work if they are maintaining them by hand, which is often the case for very large projects that were first written in JS and don't have the bandwidth to migrate to TS.
- This probably requires you to make your .ts source code to not use TypeScript language constructs that were introduced in a version newer than the piler that you have to use to emit typings in the TypeScript language version you want to emit. This is not ideal because it may be much cleaner to write code using newer language constructs, or impossible to do something with just older constructs.
"Maintain typings for both TypeScript language version"
- This seems to make the opposite assumption: That the project maintains manual typings files for manually written JS code instead of being transpiled to .js and emitting .d.ts from .ts files.
- This is a big maintenance burden (it's already a big maintenance burden to manually maintain .d.ts files!). It may be acceptable for small projects, but not for libraries with a large or plicated API surface.
There is no way to switch between supported and unsupported features in TypeScript.
For instance, if you have a library which is built on top of TS 4.0 with variadic tuple types
there is no way to use it in a package where TS 3.0 is used.
However, you can maintain two versions of your typings: before TS4 and after TS4. For instance, take a look how lodash or react maintaince several versions of typings.