├── .gitignore ├── README.md ├── examples ├── node11-resolution │ ├── README.md │ ├── main.ts │ ├── package.json │ └── tsconfig.json ├── node16-resolution │ ├── README.md │ ├── main.cts │ ├── main.mts │ ├── package.json │ └── tsconfig.json └── node_modules │ ├── extensionless │ ├── README.md │ ├── index.d.mts │ ├── index.d.ts │ ├── index.js │ ├── index.mjs │ ├── one.d.mts │ ├── one.d.ts │ ├── one.js │ ├── one.mjs │ ├── package.json │ └── two │ │ ├── index.d.mts │ │ ├── index.d.ts │ │ ├── index.js │ │ └── index.mjs │ ├── package-json-redirects │ ├── README.md │ ├── cjs │ │ ├── index.js │ │ ├── one.js │ │ └── two.js │ ├── esm │ │ ├── index.js │ │ ├── one.js │ │ ├── package.json │ │ └── two.js │ ├── one │ │ └── package.json │ ├── package.json │ ├── three │ │ ├── one.js │ │ │ └── package.json │ │ ├── package.json │ │ └── two.js │ │ │ └── package.json │ ├── two │ │ └── package.json │ └── types │ │ ├── index.d.ts │ │ ├── one.d.ts │ │ └── two.d.ts │ └── types-versions-wildcards │ ├── README.md │ ├── dist │ ├── index.js │ ├── one.js │ └── two.js │ ├── package.json │ └── types │ ├── index.d.ts │ ├── one.d.ts │ └── two.d.ts ├── package-lock.json └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | !examples/node_modules 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TypeScript-friendly strategies for package.json subpath exports compatibility 2 | 3 | Many npm libraries are eager to use [subpath exports](https://nodejs.org/api/packages.html#subpath-exports), but support is not universal. This repository demonstrates three strategies for libraries that wish to use subpath exports while providing fallbacks for module resolvers that don’t support them. Each is compatible with TypeScript under `--moduleResolution node` and `--moduleResolution node16` (as well as `nodenext` at the time of writing). 4 | 5 | ## Why care about old module resolvers? 6 | 7 | Support for subpath exports is included in Node versions 12 and later. While many library authors are fine with dropping support for Node 11 and earlier, many TypeScript users on more recent versions of Node have not yet updated their `moduleResolution` setting, and are still type-checking their projects as if it will run on Node 11 or earlier. 8 | 9 | Moreover, bundler users who write TypeScript are typically unable to use `--moduleResolution node16` due to significant differences between bundler resolution and Node 16 resolution, and an appropriate `moduleResolution` option for bundlers does [not exist yet](https://github.com/microsoft/TypeScript/issues/50152). 10 | 11 | TypeScript is working on these problems, but library authors can ease a lot of compatibility pain by employing one of these strategies in the meantime. 12 | 13 | Finally, at least two still-widely-used bundlers, Parcel and Browserify, do not support subpath exports, so any library that relies on them without a fallback strategy will be unusable by users of these bundlers, whether they use TypeScript or not. 14 | 15 | ## How this repository is structured 16 | 17 | The [`examples`](./examples) directory contains two small TypeScript projects, compiled under different `moduleResolution` settings, that consume three hand-written libraries in [`examples/node_modules`](./examples/node_modules). Each of these three libraries is named for the subpath exports fallback strategy it uses, and has a nested README that discusses the strategy in detail: 18 | 19 | - [`extensionless`](./examples/node_modules/extensionless) 20 | - [`package-json-redirects`](./examples/node_modules/package-json-redirects) 21 | - [`types-versions-wildcards`](./examples/node_modules/types-versions-wildcards) 22 | 23 | Each of the two TypeScript projects successfully type checks with `npm run build`, demonstrating which types are found for each import. The output can be run in Node with `npm start`, where a series of assertions prove which implementation files Node finds. These projects also have nested READMEs: 24 | 25 | - [`node11-resolution`](./examples/node11-resolution) 26 | - [`node16-resolution`](./examples/node16-resolution) 27 | 28 | ## Strategy support matrix 29 | 30 | Note that the [`types-versions-wildcard`](./examples/node_modules/types-versions-wildcards) fallback strategy is only fallback for TypeScript, so it does not help users who are on Node 11 or other runtimes/bundlers that lack `exports` support. It is included because it is the only method that offers an analog to `*` wildcards in subpath `exports`. 31 | 32 | | | [`extensionless`](./examples/node_modules/extensionless) | [`package-json-redirects`](./examples/node_modules/package-json-redirects) | [`types-versions-wildcards`](./examples/node_modules/types-versions-wildcards) | 33 | |----------------------------------------|------------------|-----------------|------------------| 34 | | TypeScript `--moduleResolution node16` | ✅ via `exports` | ✅ via `exports` | ✅ via `exports` | 35 | | TypeScript `--moduleResolution node` | ✅ via fallback | ✅ via fallback | ✅ via fallback | 36 | | Node 12+ | ✅ via `exports` | ✅ via `exports` | ✅ via `exports` | 37 | | Node 11 | ✅ via fallback | ✅ via fallback | ❌ | 38 | | Most bundlers | ✅ via `exports` | ✅ via `exports` | ✅ via `exports` | 39 | | Parcel, Browserify | ✅ via fallback | ✅ via fallback | ❌ | 40 | -------------------------------------------------------------------------------- /examples/node11-resolution/README.md: -------------------------------------------------------------------------------- 1 | # node11-resolution 2 | 3 | This example project compiles under `--moduleResolution node`, which reflects the resolution algorithm present in Node 11 (and will possibly be renamed or aliased to `node11` in a future version of TypeScript). 4 | 5 | Volta users can automatically run this test in Node 11. It will fail because the [`types-versions-wildcards`](../node_modules/types-versions-wildcards) strategy is intentionally incompatible with Node 11. Also note that type checking with `npm run build` verifies that the assertions are correct (or possibly correct, in the case where type definitions are shared between ESM and CJS entrypoints). You can experiment with changing the assertions such that TypeScript issues an error: 6 | 7 | ```ts 8 | import extensionless from "extensionless"; 9 | assert(extensionless === "extensionless/index.js"); // ok 10 | assert(extensionless === "boop!"); 11 | // ^^^^^^^^^^^^^^^^^^^^^^^^^ 12 | // ts(2367): This comparison appears to be unintentional because the 13 | // types '"extensionless/index.js"' and '"boop!"' have no overlap. 14 | ``` 15 | -------------------------------------------------------------------------------- /examples/node11-resolution/main.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import extensionless from "extensionless"; 3 | import extensionlessOne from "extensionless/one"; 4 | import extensionlessTwo from "extensionless/two"; 5 | assert(extensionless === "extensionless/index.js"); 6 | assert(extensionlessOne === "extensionless/one.js"); 7 | assert(extensionlessTwo === "extensionless/two/index.js"); 8 | 9 | import { index as pjr } from "package-json-redirects"; 10 | import { one as pjrOne } from "package-json-redirects/one"; 11 | import { two as pjrTwo } from "package-json-redirects/two"; 12 | import { one as pjrWcOne } from "package-json-redirects/three/one.js"; 13 | import { two as pjrWcTwo } from "package-json-redirects/three/two.js"; 14 | 15 | assert(pjr === "package-json-redirects/cjs/index.js"); 16 | assert(pjrOne === "package-json-redirects/cjs/one.js"); 17 | assert(pjrTwo === "package-json-redirects/cjs/two.js"); 18 | assert(pjrWcOne === "package-json-redirects/cjs/one.js"); 19 | assert(pjrWcTwo === "package-json-redirects/cjs/two.js"); 20 | 21 | // These fail in Node 11 because only TypeScript follows `typesVersions` 22 | import tvw from "types-versions-wildcards"; 23 | import tvwOne from "types-versions-wildcards/one"; 24 | import tvwTwo from "types-versions-wildcards/two"; 25 | assert(tvw === "types-versions-wildcards/dist/index.js"); 26 | assert(tvwOne === "types-versions-wildcards/dist/one.js"); 27 | assert(tvwTwo === "types-versions-wildcards/dist/two.js"); 28 | -------------------------------------------------------------------------------- /examples/node11-resolution/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "build": "tsc", 4 | "prestart": "npm run build", 5 | "start": "node out/main.js" 6 | }, 7 | "volta": { 8 | "node": "11.15.0" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/node11-resolution/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "moduleResolution": "node", 5 | "esModuleInterop": true, 6 | "noImplicitAny": true, 7 | "outDir": "out" 8 | } 9 | } -------------------------------------------------------------------------------- /examples/node16-resolution/README.md: -------------------------------------------------------------------------------- 1 | # node16-resolution 2 | 3 | This example project compiles under `--moduleResolution node16`, which reflects the resolution algorithm present in Node 16. (At the time of writing, `nodenext` is identical to `node16`, but will be updated in future versions of TypeScript if Node introduces module resolution changes into later versions of Node.) 4 | 5 | Both a `.mts` and `.cts` file are included to show that the correct entrypoints are found in the libraries that ship separate CJS and ESM files, but this does not actually impact how subpath exports work. 6 | 7 | Volta users can automatically run this test in Node 16. Also note that type checking with `npm run build` verifies that the assertions are correct (or possibly correct, in the case where type definitions are shared between ESM and CJS entrypoints). You can experiment with changing the assertions such that TypeScript issues an error: 8 | 9 | ```ts 10 | import extensionless from "extensionless"; 11 | assert(extensionless === "extensionless/index.js"); // ok 12 | assert(extensionless === "boop!"); 13 | // ^^^^^^^^^^^^^^^^^^^^^^^^^ 14 | // ts(2367): This comparison appears to be unintentional because the 15 | // types '"extensionless/index.js"' and '"boop!"' have no overlap. 16 | ``` -------------------------------------------------------------------------------- /examples/node16-resolution/main.cts: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import extensionless from "extensionless"; 3 | import extensionlessOne from "extensionless/one"; 4 | import extensionlessTwo from "extensionless/two"; 5 | assert(extensionless === "extensionless/index.js"); 6 | assert(extensionlessOne === "extensionless/one.js"); 7 | assert(extensionlessTwo === "extensionless/two/index.js"); 8 | 9 | import { index as pjr } from "package-json-redirects"; 10 | import { one as pjrOne } from "package-json-redirects/one"; 11 | import { two as pjrTwo } from "package-json-redirects/two"; 12 | import { one as pjrWcOne } from "package-json-redirects/three/one.js"; 13 | import { two as pjrWcTwo } from "package-json-redirects/three/two.js"; 14 | assert(pjr === "package-json-redirects/cjs/index.js"); 15 | assert(pjrOne === "package-json-redirects/cjs/one.js"); 16 | assert(pjrTwo === "package-json-redirects/cjs/two.js"); 17 | assert(pjrWcOne === "package-json-redirects/cjs/one.js"); 18 | assert(pjrWcTwo === "package-json-redirects/cjs/two.js"); 19 | 20 | import tvw from "types-versions-wildcards"; 21 | import tvwOne from "types-versions-wildcards/one"; 22 | import tvwTwo from "types-versions-wildcards/two"; 23 | assert(tvw === "types-versions-wildcards/dist/index.js"); 24 | assert(tvwOne === "types-versions-wildcards/dist/one.js"); 25 | assert(tvwTwo === "types-versions-wildcards/dist/two.js"); 26 | -------------------------------------------------------------------------------- /examples/node16-resolution/main.mts: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import extensionless from "extensionless"; 3 | import extensionlessOne from "extensionless/one"; 4 | import extensionlessTwo from "extensionless/two"; 5 | assert(extensionless === "extensionless/index.mjs"); 6 | assert(extensionlessOne === "extensionless/one.mjs"); 7 | assert(extensionlessTwo === "extensionless/two/index.mjs"); 8 | 9 | import { index as pjr } from "package-json-redirects"; 10 | import { one as pjrOne } from "package-json-redirects/one"; 11 | import { two as pjrTwo } from "package-json-redirects/two"; 12 | import { one as pjrWcOne } from "package-json-redirects/three/one.js"; 13 | import { two as pjrWcTwo } from "package-json-redirects/three/two.js"; 14 | assert(pjr === "package-json-redirects/esm/index.js"); 15 | assert(pjrOne === "package-json-redirects/esm/one.js"); 16 | assert(pjrTwo === "package-json-redirects/esm/two.js"); 17 | assert(pjrWcOne === "package-json-redirects/esm/one.js"); 18 | assert(pjrWcTwo === "package-json-redirects/esm/two.js"); 19 | 20 | import tvw from "types-versions-wildcards"; 21 | import tvwOne from "types-versions-wildcards/one"; 22 | import tvwTwo from "types-versions-wildcards/two"; 23 | assert(tvw === "types-versions-wildcards/dist/index.js"); 24 | assert(tvwOne === "types-versions-wildcards/dist/one.js"); 25 | assert(tvwTwo === "types-versions-wildcards/dist/two.js"); 26 | 27 | import "./main.cjs"; 28 | -------------------------------------------------------------------------------- /examples/node16-resolution/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "build": "tsc", 4 | "prestart": "npm run build", 5 | "start": "node out/main.mjs" 6 | }, 7 | "volta": { 8 | "node": "16.17.0" 9 | } 10 | } -------------------------------------------------------------------------------- /examples/node16-resolution/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "node16", 4 | "noImplicitAny": true, 5 | "outDir": "out" 6 | } 7 | } -------------------------------------------------------------------------------- /examples/node_modules/extensionless/README.md: -------------------------------------------------------------------------------- 1 | # extensionless 2 | 3 | ## Mode of operation 4 | 5 | This is the simplest fallback strategy of the three. It relies only on stripping `.js` and `/index.js` (and corresponding `.d.ts` and `/index.d.ts`) suffixes when a non-`exports`-supporting resolver attempts to resolve a subpath: 6 | 7 | ```ts 8 | import "extensionless/one"; 9 | ``` 10 | 11 | An `exports`-supporting resolver will look up `"./one"` in the package.json `exports`, which in turn points to `"./one.js"`. A non-`exports`-supporting resolver will look for `./one` (with no file extension) on the file system, and assuming it is not found, will try `./one.js`, arriving at the same resolution through different means. (TypeScript’s non-`exports`-supporting `moduleResolution` modes will do the same, but trying `.ts` and `.d.ts` extensions instead of `.js`.) 12 | 13 | ```ts 14 | import "extensionless/two"; 15 | ``` 16 | 17 | Likewise, a non-`exports`-supporting resolver will try `./two`, `./two.js`, and this time move onto `./two/index.js`. 18 | 19 | ## Considerations 20 | 21 | Pros: 22 | 23 | - Well-supported 24 | - Simple 25 | - No configuration update needed when adding additional subpaths 26 | 27 | Cons: 28 | 29 | - Cannot accommodate a `dist` folder 30 | - Cannot support mappings that do something besides extension/index removal 31 | -------------------------------------------------------------------------------- /examples/node_modules/extensionless/index.d.mts: -------------------------------------------------------------------------------- 1 | declare const index: "extensionless/index.mjs"; 2 | export default index; 3 | -------------------------------------------------------------------------------- /examples/node_modules/extensionless/index.d.ts: -------------------------------------------------------------------------------- 1 | declare const index: "extensionless/index.js"; 2 | export = index; 3 | -------------------------------------------------------------------------------- /examples/node_modules/extensionless/index.js: -------------------------------------------------------------------------------- 1 | module.exports = "extensionless/index.js"; 2 | -------------------------------------------------------------------------------- /examples/node_modules/extensionless/index.mjs: -------------------------------------------------------------------------------- 1 | export default "extensionless/index.mjs"; -------------------------------------------------------------------------------- /examples/node_modules/extensionless/one.d.mts: -------------------------------------------------------------------------------- 1 | declare const one: "extensionless/one.mjs"; 2 | export default one; 3 | -------------------------------------------------------------------------------- /examples/node_modules/extensionless/one.d.ts: -------------------------------------------------------------------------------- 1 | declare const one: "extensionless/one.js"; 2 | export = one; 3 | -------------------------------------------------------------------------------- /examples/node_modules/extensionless/one.js: -------------------------------------------------------------------------------- 1 | module.exports = "extensionless/one.js"; 2 | -------------------------------------------------------------------------------- /examples/node_modules/extensionless/one.mjs: -------------------------------------------------------------------------------- 1 | export default "extensionless/one.mjs"; 2 | -------------------------------------------------------------------------------- /examples/node_modules/extensionless/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "extensionless", 3 | "version": "1.0.0", 4 | "main": "./index.js", 5 | "exports": { 6 | ".": { 7 | "import": "./index.mjs", 8 | "default": "./index.js" 9 | }, 10 | "./one": { 11 | "import": "./one.mjs", 12 | "default": "./one.js" 13 | }, 14 | "./two": { 15 | "import": "./two/index.mjs", 16 | "default": "./two/index.js" 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /examples/node_modules/extensionless/two/index.d.mts: -------------------------------------------------------------------------------- 1 | declare const two: "extensionless/two/index.mjs"; 2 | export default two; 3 | -------------------------------------------------------------------------------- /examples/node_modules/extensionless/two/index.d.ts: -------------------------------------------------------------------------------- 1 | declare const two: "extensionless/two/index.js"; 2 | export = two; 3 | -------------------------------------------------------------------------------- /examples/node_modules/extensionless/two/index.js: -------------------------------------------------------------------------------- 1 | module.exports = "extensionless/two/index.js"; -------------------------------------------------------------------------------- /examples/node_modules/extensionless/two/index.mjs: -------------------------------------------------------------------------------- 1 | export default "extensionless/two/index.mjs"; 2 | -------------------------------------------------------------------------------- /examples/node_modules/package-json-redirects/README.md: -------------------------------------------------------------------------------- 1 | # package-json-redirects 2 | 3 | ## Mode of operation 4 | 5 | This strategy is similar to [`extensionless`](../extensionless), but uses directories with package.json stubs that redirect to another file via `main` (and `types`) instead of requiring that the implementation (and types) files actually be at the file-system-apparent location. 6 | 7 | ```ts 8 | import "package-json-redirects/one"; 9 | ``` 10 | 11 | An `exports`-supporting resolver will look up `"./one"` in the package.json `exports`, which in turn points either to `./cjs/one.js` or `./esm/one.js`, depending on the conditions presented by the resolver. A non-`exports`-supporting resolver will instead find [`./one/package.json`](./one/package.json): 12 | 13 | ```json 14 | { 15 | "main": "../cjs/one.js", 16 | "types": "../types/one.d.ts" 17 | } 18 | ``` 19 | 20 | And reroute accordingly. The package.json stub chooses to route the non-`exports`-supporting resolver to the CommonJS files, since Node 11 does not support ESM-format files. 21 | 22 | Note that the separate handling of ESM and CJS, as well as the decision to present a unified set of types for the two, is orthogonal to the subpath exports demonstration. (Including separate types for CJS and ESM files, as [`extensionless`](../extensionless) does, is often a better choice, and is compatible with this strategy.) 23 | 24 | ## Considerations 25 | 26 | Pros: 27 | 28 | - Well-supported 29 | - Supports putting implementation and types files in subfolders 30 | - Can model subpaths that map to a file with a different name than the subpath 31 | 32 | Cons: 33 | 34 | - Lots of files and folders (aesthetic) 35 | - Need to add more every time a new subpath is added 36 | - Cannot support `*` wildcards; need to create a `package.json` for all possible matches 37 | -------------------------------------------------------------------------------- /examples/node_modules/package-json-redirects/cjs/index.js: -------------------------------------------------------------------------------- 1 | module.exports.index = "package-json-redirects/cjs/index.js"; 2 | -------------------------------------------------------------------------------- /examples/node_modules/package-json-redirects/cjs/one.js: -------------------------------------------------------------------------------- 1 | module.exports.one = "package-json-redirects/cjs/one.js"; 2 | -------------------------------------------------------------------------------- /examples/node_modules/package-json-redirects/cjs/two.js: -------------------------------------------------------------------------------- 1 | module.exports.two = "package-json-redirects/cjs/two.js"; 2 | -------------------------------------------------------------------------------- /examples/node_modules/package-json-redirects/esm/index.js: -------------------------------------------------------------------------------- 1 | export const index = "package-json-redirects/esm/index.js"; 2 | -------------------------------------------------------------------------------- /examples/node_modules/package-json-redirects/esm/one.js: -------------------------------------------------------------------------------- 1 | export const one = "package-json-redirects/esm/one.js"; 2 | -------------------------------------------------------------------------------- /examples/node_modules/package-json-redirects/esm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module" 3 | } -------------------------------------------------------------------------------- /examples/node_modules/package-json-redirects/esm/two.js: -------------------------------------------------------------------------------- 1 | export const two = "package-json-redirects/esm/two.js"; 2 | -------------------------------------------------------------------------------- /examples/node_modules/package-json-redirects/one/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "../cjs/one.js", 3 | "types": "../types/one.d.ts" 4 | } -------------------------------------------------------------------------------- /examples/node_modules/package-json-redirects/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "package-json-redirects", 3 | "version": "1.0.0", 4 | "main": "cjs/index.js", 5 | "types": "types/index.d.ts", 6 | "exports": { 7 | ".": { 8 | "types": "./types/index.d.ts", 9 | "import": "./esm/index.js", 10 | "default": "./cjs/index.js" 11 | }, 12 | "./one": { 13 | "types": "./types/one.d.ts", 14 | "import": "./esm/one.js", 15 | "default": "./cjs/one.js" 16 | }, 17 | "./two": { 18 | "types": "./types/two.d.ts", 19 | "import": "./esm/two.js", 20 | "default": "./cjs/two.js" 21 | }, 22 | "./three/*.js": { 23 | "types": "./types/*.d.ts", 24 | "import": "./esm/*.js", 25 | "default": "./cjs/*.js" 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /examples/node_modules/package-json-redirects/three/one.js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "../../cjs/one.js", 3 | "types": "../../types/one.d.ts" 4 | } -------------------------------------------------------------------------------- /examples/node_modules/package-json-redirects/three/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "../cjs/index.js", 3 | "types": "../types/one.d.ts" 4 | } -------------------------------------------------------------------------------- /examples/node_modules/package-json-redirects/three/two.js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "../../cjs/two.js", 3 | "types": "../../types/two.d.ts" 4 | } -------------------------------------------------------------------------------- /examples/node_modules/package-json-redirects/two/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "../cjs/two.js", 3 | "types": "../types/two.d.ts" 4 | } -------------------------------------------------------------------------------- /examples/node_modules/package-json-redirects/types/index.d.ts: -------------------------------------------------------------------------------- 1 | export const index: "package-json-redirects/cjs/index.js" | "package-json-redirects/esm/index.js"; 2 | -------------------------------------------------------------------------------- /examples/node_modules/package-json-redirects/types/one.d.ts: -------------------------------------------------------------------------------- 1 | export const one: "package-json-redirects/cjs/one.js" | "package-json-redirects/esm/one.js"; -------------------------------------------------------------------------------- /examples/node_modules/package-json-redirects/types/two.d.ts: -------------------------------------------------------------------------------- 1 | export const two: "package-json-redirects/cjs/two.js" | "package-json-redirects/esm/two.js"; 2 | -------------------------------------------------------------------------------- /examples/node_modules/types-versions-wildcards/README.md: -------------------------------------------------------------------------------- 1 | # types-versions-wildcards 2 | 3 | ## Mode of operation 4 | 5 | This strategy works very differently than the other two; rather than relying on file-system-based resolution features, it leverages `typesVersions` to reroute TypeScript’s `--moduleResolution node` resolution process in the same way that `exports` reroutes `exports`-supporting resolvers’ resolution process. 6 | 7 | ```ts 8 | import "types-versions-wildcards/one"; 9 | ``` 10 | 11 | An `exports`-supporting resolver will look up `"./one"` in the package.json `exports`, which in turn points to `./dist/one.js` (or `./types/one.d.ts` for TypeScript). Most non-`exports`-supporting resolvers will fail, but TypeScript under `--moduleResolution node` (which does not support `exports`) will look at the `typesVersions` map in package.json: 12 | 13 | ```json 14 | { 15 | "//": "...", 16 | 17 | "types": "index.d.ts", 18 | "typesVersions": { 19 | "*": { 20 | "*": ["types/*"] 21 | } 22 | } 23 | } 24 | ``` 25 | 26 | The first `*` matches the TypeScript version, and the second matches (and captures) the package subpath, resulting in the substitution `types/one`, which is resolved relative to the package root and ultimately resolves to `./types/one.d.ts`. 27 | 28 | This has the same effect _on TypeScript_ that the `exports` would have had on any resolver that used it, but `typesVersions` _only_ affects TypeScript. **This strategy does not help Node 11, Parcel, or Browserify users.** This strategy should only be used by libraries that do not intend to support these users. This repository is intended to help library authors make informed decisions, not prescribe support. 29 | 30 | ## Discussion 31 | 32 | Pros: 33 | 34 | - Simple 35 | - Supports wildcards 36 | - Can model subpaths that map to a file with a different name than the subpath 37 | - Configuration maintenance is 1:1 with `exports` 38 | 39 | Cons: 40 | 41 | - Doesn’t support runtimes/bundlers that don’t support `exports` 42 | -------------------------------------------------------------------------------- /examples/node_modules/types-versions-wildcards/dist/index.js: -------------------------------------------------------------------------------- 1 | module.exports = "types-versions-wildcards/dist/index.js"; 2 | -------------------------------------------------------------------------------- /examples/node_modules/types-versions-wildcards/dist/one.js: -------------------------------------------------------------------------------- 1 | module.exports = "types-versions-wildcards/dist/one.js"; 2 | -------------------------------------------------------------------------------- /examples/node_modules/types-versions-wildcards/dist/two.js: -------------------------------------------------------------------------------- 1 | module.exports = "types-versions-wildcards/dist/two.js"; 2 | -------------------------------------------------------------------------------- /examples/node_modules/types-versions-wildcards/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "types-versions-redirects", 3 | "version": "1.0.0", 4 | "main": "dist/index.js", 5 | "exports": { 6 | ".": { 7 | "types": "./types/index.d.ts", 8 | "default": "./dist/index.js" 9 | }, 10 | "./*": { 11 | "types": "./types/*.d.ts", 12 | "default": "./dist/*.js" 13 | } 14 | }, 15 | "types": "index.d.ts", 16 | "typesVersions": { 17 | "*": { 18 | "*": ["types/*"] 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /examples/node_modules/types-versions-wildcards/types/index.d.ts: -------------------------------------------------------------------------------- 1 | declare const index: "types-versions-wildcards/dist/index.js"; 2 | export = index; 3 | -------------------------------------------------------------------------------- /examples/node_modules/types-versions-wildcards/types/one.d.ts: -------------------------------------------------------------------------------- 1 | declare const one: "types-versions-wildcards/dist/one.js"; 2 | export = one; 3 | -------------------------------------------------------------------------------- /examples/node_modules/types-versions-wildcards/types/two.d.ts: -------------------------------------------------------------------------------- 1 | declare const two: "types-versions-wildcards/dist/two.js"; 2 | export = two; 3 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-subpath-exports-ts-compat", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "example-subpath-exports-ts-compat", 9 | "version": "1.0.0", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "@types/node": "^18.7.18", 13 | "typescript": "^4.8.3" 14 | } 15 | }, 16 | "node_modules/@types/node": { 17 | "version": "18.7.18", 18 | "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.18.tgz", 19 | "integrity": "sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg==", 20 | "dev": true 21 | }, 22 | "node_modules/typescript": { 23 | "version": "4.8.3", 24 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz", 25 | "integrity": "sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==", 26 | "dev": true, 27 | "bin": { 28 | "tsc": "bin/tsc", 29 | "tsserver": "bin/tsserver" 30 | }, 31 | "engines": { 32 | "node": ">=4.2.0" 33 | } 34 | } 35 | }, 36 | "dependencies": { 37 | "@types/node": { 38 | "version": "18.7.18", 39 | "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.18.tgz", 40 | "integrity": "sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg==", 41 | "dev": true 42 | }, 43 | "typescript": { 44 | "version": "4.8.3", 45 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz", 46 | "integrity": "sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==", 47 | "dev": true 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-subpath-exports-ts-compat", 3 | "version": "1.0.0", 4 | "description": "", 5 | "private": true, 6 | "main": "index.js", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "Andrew Branch", 11 | "license": "MIT", 12 | "devDependencies": { 13 | "@types/node": "^18.7.18", 14 | "typescript": "^4.8.3" 15 | } 16 | } 17 | --------------------------------------------------------------------------------