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:
import "extensionless/one";
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
.)
import "extensionless/two";
Likewise, a non-exports
-supporting resolver will try ./two
, ./two.js
, and this time move onto ./two/index.js
.
This is the only strategy that supports dual ESM/CJS type declarations, by using sibling lookup. It's important when moduleResolution
is set to node16
or nodenext
. The compiler will select appropriate vairant of the type declarations depending on the type of the file (ESM or commonjs) which is importing the library. Other strategies which are using explicit types
fields can provide only single type declarations for both ESM and commonjs users, and that can be problematic in some cases:
-
sometimes, as has been warned here for example, it's necessary to have different declarations for EMS and commonjs to handle exports correctly
-
if the user code is commonjs, and type declarations are ESM, it will not compile - commonjs code can not use ESM imports (this applies only when
moduleResolution
isnodenext
ornode16
)
Pros:
- Well-supported
- Simple
- No configuration update needed when adding additional subpaths
- Supports dual ESM/CJS type declarations
Cons:
- Cannot accommodate a
dist
folder - Cannot support mappings that do something besides extension/index removal