@@ -947,6 +947,171 @@ $ node other.js
947947
948948See [ the package examples repository] [ ] for details.
949949
950+ ## Package maps
951+
952+ <!-- YAML
953+ added: REPLACEME
954+ -->
955+
956+ > Stability: 1 - Experimental
957+
958+ Package maps provide a mechanism to control package resolution without relying
959+ on the ` node_modules ` folder structure. When enabled via the
960+ [ ` --experimental-package-map ` ] [ ] flag, Node.js uses a JSON configuration file
961+ to determine how bare specifiers are resolved.
962+
963+ This feature is useful for:
964+
965+ * ** Monorepos** : Define explicit dependency relationships between workspace
966+ packages without symlinks or hoisting complexities.
967+ * ** Dependency isolation** : Prevent packages from accessing undeclared
968+ dependencies (phantom dependencies).
969+ * ** Multiple versions** : Allow different packages to depend on different
970+ versions of the same dependency.
971+
972+ ### Enabling package maps
973+
974+ Package maps are enabled by passing the ` --experimental-package-map ` flag
975+ with a path to the configuration file:
976+
977+ ``` bash
978+ node --experimental-package-map=./package-map.json app.js
979+ ```
980+
981+ ### Configuration file format
982+
983+ The package map configuration file is a JSON file with a ` packages ` object.
984+ Each key in ` packages ` is a unique identifier for a package entry:
985+
986+ ``` json
987+ {
988+ "packages" : {
989+ "app" : {
990+ "name" : " my-app" ,
991+ "path" : " ./packages/app" ,
992+ "dependencies" : [" utils" , " ui-lib" ]
993+ },
994+ "utils" : {
995+ "name" : " @myorg/utils" ,
996+ "path" : " ./packages/utils" ,
997+ "dependencies" : []
998+ },
999+ "ui-lib" : {
1000+ "name" : " @myorg/ui-lib" ,
1001+ "path" : " ./packages/ui-lib" ,
1002+ "dependencies" : [" utils" ]
1003+ }
1004+ }
1005+ }
1006+ ```
1007+
1008+ Each package entry has the following fields:
1009+
1010+ * ` path ` {string} ** Required.** Relative path from the configuration file to
1011+ the package directory.
1012+ * ` name ` {string} The package name used in import specifiers. If omitted, the
1013+ package cannot be imported by name but can still import its dependencies.
1014+ * ` dependencies ` {string\[ ] } Array of package keys that this package is allowed
1015+ to import. Defaults to an empty array.
1016+
1017+ ### Resolution algorithm
1018+
1019+ When a bare specifier is encountered:
1020+
1021+ 1 . Node.js determines which package contains the importing file by checking
1022+ if the file path is within any package's ` path ` .
1023+ 2 . If the importing file is not within any mapped package, standard
1024+ ` node_modules ` resolution is used.
1025+ 3 . Node.js searches the importing package's ` dependencies ` array for an entry
1026+ whose ` name ` matches the specifier's package name.
1027+ 4 . If found, the specifier resolves to that dependency's ` path ` .
1028+ 5 . If the package exists in the map but is not in ` dependencies ` , an
1029+ [ ` ERR_PACKAGE_MAP_ACCESS_DENIED ` ] [ ] error is thrown.
1030+ 6 . If the package does not exist in the map at all, standard ` node_modules `
1031+ resolution is used as a fallback.
1032+
1033+ ### Subpath resolution
1034+
1035+ Package maps support importing subpaths. Given the configuration above:
1036+
1037+ ``` js
1038+ // In packages/app/index.js
1039+ import { helper } from ' @myorg/utils' ; // Resolves to ./packages/utils
1040+ import { format } from ' @myorg/utils/format' ; // Resolves to ./packages/utils/format
1041+ ```
1042+
1043+ The subpath portion of the specifier is preserved and appended to the resolved
1044+ package path. The target package's ` package.json ` [ ` "exports" ` ] [ ] field is
1045+ then used to resolve the final file path.
1046+
1047+ ### Multiple package versions
1048+
1049+ Different packages can depend on different versions of the same package by
1050+ using distinct keys:
1051+
1052+ ``` json
1053+ {
1054+ "packages" : {
1055+ "app" : {
1056+ "name" : " app" ,
1057+ "path" : " ./app" ,
1058+ "dependencies" : [" component-v2" ]
1059+ },
1060+ "legacy" : {
1061+ "name" : " legacy" ,
1062+ "path" : " ./legacy" ,
1063+ "dependencies" : [" component-v1" ]
1064+ },
1065+ "component-v1" : {
1066+ "name" : " component" ,
1067+ "path" : " ./vendor/component-1.0.0" ,
1068+ "dependencies" : []
1069+ },
1070+ "component-v2" : {
1071+ "name" : " component" ,
1072+ "path" : " ./vendor/component-2.0.0" ,
1073+ "dependencies" : []
1074+ }
1075+ }
1076+ }
1077+ ```
1078+
1079+ Both ` app ` and ` legacy ` can ` import 'component' ` , but they resolve to
1080+ different paths based on their declared dependencies.
1081+
1082+ ### CommonJS and ES modules
1083+
1084+ Package maps work with both CommonJS (` require() ` ) and ES modules (` import ` ).
1085+ The resolution behavior is identical for both module systems.
1086+
1087+ ``` cjs
1088+ // CommonJS
1089+ const utils = require (' @myorg/utils' );
1090+ ```
1091+
1092+ ``` mjs
1093+ // ES modules
1094+ import utils from ' @myorg/utils' ;
1095+ ```
1096+
1097+ ### Fallback behavior
1098+
1099+ Package maps do not replace ` node_modules ` resolution entirely. Resolution
1100+ falls back to standard behavior when:
1101+
1102+ * The importing file is not within any package defined in the map.
1103+ * The specifier's package name is not found in any package's ` name ` field.
1104+ * The specifier is a relative path (` ./ ` or ` ../ ` ).
1105+ * The specifier is an absolute path or URL.
1106+ * The specifier refers to a Node.js builtin module (` node:fs ` , etc.).
1107+
1108+ ### Limitations
1109+
1110+ * Package maps must be a single static file; dynamic configuration is not
1111+ supported.
1112+ * Circular dependency detection is not performed by the package map resolver.
1113+ * The package map file is loaded synchronously at startup.
1114+
9501115## Node.js ` package.json ` field definitions
9511116
9521117This section describes the fields used by the Node.js runtime. Other tools (such
@@ -1177,7 +1342,9 @@ This field defines [subpath imports][] for the current package.
11771342[ `"type"` ] : #type
11781343[ `--conditions` / `-C` flag ] : #resolving-user-conditions
11791344[ `--experimental-addon-modules` ] : cli.md#--experimental-addon-modules
1345+ [ `--experimental-package-map` ] : cli.md#--experimental-package-mappath
11801346[ `--no-addons` flag ] : cli.md#--no-addons
1347+ [ `ERR_PACKAGE_MAP_ACCESS_DENIED` ] : errors.md#err_package_map_access_denied
11811348[ `ERR_PACKAGE_PATH_NOT_EXPORTED` ] : errors.md#err_package_path_not_exported
11821349[ `ERR_UNKNOWN_FILE_EXTENSION` ] : errors.md#err_unknown_file_extension
11831350[ `package.json` ] : #nodejs-packagejson-field-definitions
0 commit comments