Description
As a practical example, let's say that we have:
/node_modules/foo/package.json
{
"name": "foo",
"exports": {
"./utils": "./sources/utils"
}
}
/node_modules/foo/sources/utils.ts
// Content doesn't matter
/index.js
import 'foo/utils';
And let's say that we have a composed pipeline:
-
The PnP resolver
- will get
foo/utils
as input - will want to turn the
foo
segment into/node_modules/foo
(and leave the rest untouched)
- will get
-
The TS resolver
- will want to add the
.ts
extension to whateverfoo/utils
resolves into
- will want to add the
What should return the PnP resolver?
a. "/node_modules/foo/utils"
b. "/node_modules/foo/sources/utils"
c. "/node_modules/foo/sources/utils.js"
d. ["/node_modules/foo", "./utils"]
Option A is incorrect; the exports
field doesn't apply when a file is loaded through an absolute path:
It is not a strong encapsulation since a direct require of any absolute subpath of the package such as
require('/path/to/node_modules/pkg/subpath.js')
will still loadsubpath.js
.
Option B is good, but requires the PnP loader to not only resolve foo
into node_modules/foo
, but also ./util
into ./sources/utils
. To do that requires either to reimplement the exports
resolution (with the risks in terms of correctness that come with it), or Node to provide a import.meta.resolveExports('foo/utils', '/node_modules/foo')
utility that would do the job for us. This is what we currently do, by embedding a copy of resolve.exports
into our loader.
Option C essentially prevents the TS loader from working, since the extension is already resolved.
Option D (which isn't supported by Node at the moment) lets the PnP loader resolves the bare identifier part which Node would presumably process through the same import.meta.resolveExports
function described earlier, but keeping it internal.