Version selection with typesVersions

Feedback from our community, as well as our own experience, has shown us that leveraging the newest TypeScript features while also accomodating users on the older versions are difficult.TypeScript introduces a new feature called typesVersions to help accomodate these scenarios.

When using Node module resolution in TypeScript 3.1, when TypeScript cracks open a package.json file to figure out which files it needs to read, it first looks at a new field called typesVersions.A package.json with a typesVersions field might look like this:

  1. {
  2. "name": "package-name",
  3. "version": "1.0",
  4. "types": "./index.d.ts",
  5. "typesVersions": {
  6. ">=3.1": { "*": ["ts3.1/*"] }
  7. }
  8. }

This package.json tells TypeScript to check whether the current version of TypeScript is running.If it’s 3.1 or later, it figures out the path you’ve imported relative to the package, and reads from the package’s ts3.1 folder.That’s what that { "": ["ts3.1/"] } means - if you’re familiar with path mapping today, it works exactly like that.

So in the above example, if we’re importing from "package-name", we’ll try to resolve from […]/node_modules/package-name/ts3.1/index.d.ts (and other relevant paths) when running in TypeScript 3.1.If we import from package-name/foo, we’ll try to look for […]/node_modules/package-name/ts3.1/foo.d.ts and […]/node_modules/package-name/ts3.1/foo/index.d.ts.

What if we’re not running in TypeScript 3.1 in this example?Well, if none of the fields in typesVersions get matched, TypeScript falls back to the types field, so here TypeScript 3.0 and earlier will be redirected to […]/node_modules/package-name/index.d.ts.

Matching behavior

The way that TypeScript decides on whether a version of the compiler & language matches is by using Node’s semver ranges.

Multiple fields

typesVersions can support multiple fields where each field name is specified by the range to match on.

  1. {
  2. "name": "package-name",
  3. "version": "1.0",
  4. "types": "./index.d.ts",
  5. "typesVersions": {
  6. ">=3.2": { "*": ["ts3.2/*"] },
  7. ">=3.1": { "*": ["ts3.1/*"] }
  8. }
  9. }

Since ranges have the potential to overlap, determining which redirect applies is order-specific.That means in the above example, even though both the >=3.2 and the >=3.1 matchers support TypeScript 3.2 and above, reversing the order could have different behavior, so the above sample would not be equivalent to the following.

  1. {
  2. "name": "package-name",
  3. "version": "1.0",
  4. "types": "./index.d.ts",
  5. "typesVersions": {
  6. // NOTE: this doesn't work!
  7. ">=3.1": { "*": ["ts3.1/*"] },
  8. ">=3.2": { "*": ["ts3.2/*"] }
  9. }
  10. }