Description
Sorry if this was posted, I did search quite a few things before posting :-P
So just came across this today, https://nodejs.org/dist/latest-v14.x/docs/api/esm.html#esm_packages - not sure how i missed it for so long but it appears that the internal imports
mapping was just released in a recent .x
version of v14.
Basically it allows node to resolve situations like Typescript paths
would. There are a few cases here that seem like they are fairly advanced to handle properly, and it doesn't seem like it works at the moment, although just initial testing so far.
While I know that there are import resolvers that would allow defining the rules manually, since this is an official node feature already launched in the LTS version of node, it probably makes sense to support it with the default node resolver?
{
"name": "example",
"type": "module",
"exports": {
".": "./src/index.js",
"./quick": "./src/quick.js"
},
"imports": {
"#alias": "./alias.js",
"#tryPeer": ["some-peer-dep", "./src/peer-not-found.js"],
"#conditionally": {
"node": "./src/node.js",
"require": "./src/required.js",
"import": "./src/imported.js",
"default": "./src/defaulted.js"
}
}
}
Given the above, I can run the following in node (no feature flags needed)
// src/test.js
// loads src/quick.js due to exports "./quick" mapping pointing to "./src/quick.js"
import * as mods from 'example/quick';
// tries to load some-peer-dep package but if it fails it will load ./src/peer-not-found.js instead
import peer from '#tryPeer';
// if node env loads ./src/node,
// if (not node) and required with require('example') loads ./src/required.js
// if (not node) and imported with import 'example' or import('example') loads ./src/imported.js
// if no match (dont think its possible here) , loads './src/defaulted.js'
import conditional from '#conditionally'
mods.runMain()
mods.runAlias()
// src/quick.js
export * from '#alias'; // exports all modules from './alias.js' due to internal imports mapping for #alias
export * from 'example'; // exports all modules from './src/index.js' due to exports "." mapping to "./src/index.js"
// src/index.js
export function runMain() {
console.log('runs');
}
// ./alias.js
export function runAlias() {
console.log('alias runs')
}
There is additional support for conditional handling which makes things a bit more complicated, but should potentially be considered.
With the import maps I believe it also allows importing subpaths if supported so that if you have
#alias
resolve toeslint
then you could doimport subpath from '#alias/lib/someFile.js'
I (think?) this is actually pretty similar to how
deno
handles module resolution mapping?
Running things with some flags to provide helper fns (no require.resolve by default) we do see:
node --experimental-import-meta-resolve --experimental-top-level-await ./src/test.js
// src/test.js
console.log(
await import.meta.resolve('example'),
await import.meta.resolve('example/quick'),
await import.meta.resolve('#alias'),
);
// file:///.../example/src/index.js
// file:///.../example/src/quick.js
// file:///.../example/alias.js
Also just realized this even works if not using "type": "module"
for standard require-based modules, although with require.resolve it doesnt return file path:
console.log(
require.resolve('example'),
require.resolve('example/quick'),
require.resolve('#alias'),
);
// /.../example/src/index.js
// /.../example/src/quick.js
// /.../example/alias.js