Import Maps and Subpaths

Import Maps and Internal Subpaths

"exports" faces outward; "imports" faces inward. It maps specifiers beginning with # to files inside your own package, visible only to code in that package -- the supported replacement for the ../../../ chains that grow in every Express 24,430 project, with no bundler, tsconfig path alias or NODE_PATH hack.

package.json for an API server using internal subpathsJSON
{
  "name": "@shop/api", "type": "module",
  "exports": { "./config": "./src/config.js" },
  "imports": {
    "#db": { "test": "./src/db/memory.js", "default": "./src/db/mongo.js" },
    "#lib/*": "./src/*.js"
  }
}
src/server.js -- internal imports and a self-referenceJavaScript
import { store } from '#db';
import { port } from '#lib/config';
import { port as viaSelf } from '@shop/api/config';
console.log(store, port, viaSelf === port);
Output
MongoDB driver 3000 true

Running node -C test src/server.js prints in-memory stub 3000 true. One flag swaps the data layer for the whole process, with no dependency-injection container and no mocking library.

Two details make "imports" more capable than it looks. It accepts every condition from Export Maps, and a target may be a bare specifier rather than a file -- "#logger": "pino 18,227 " in production, "#logger": "./src/noop.js" under test. The keys must start with #, which guarantees no outside package can address them.

The third import is a self-reference: a package may import itself by its own "name", provided it declares "exports". That is how you write tests that consume your package exactly as a user will, export map included, without publishing it or linking it into node_modules.