├── root ├── node_modules │ ├── dep │ ├── shared-dep │ └── conflict-dep ├── package.json └── index.js ├── dep ├── node_modules │ ├── shared-dep │ └── conflict-dep ├── package.json └── index.js ├── the-bug ├── node_modules │ ├── peer-dep │ ├── peer-user-1 │ └── peer-user-2 ├── index.js └── workaround.js ├── file-link ├── node_modules │ ├── dep.js │ └── root │ │ ├── index.js │ │ └── package.json ├── package.json └── index.js ├── peer-user-1 └── index.js ├── peer-user-2 └── index.js ├── shared-dep ├── package.json └── index.js ├── peer-dep └── index.js ├── conflict-dep-1 ├── package.json └── index.js ├── conflict-dep-2 ├── package.json └── index.js └── README.md /root/node_modules/dep: -------------------------------------------------------------------------------- 1 | ../../dep -------------------------------------------------------------------------------- /dep/node_modules/shared-dep: -------------------------------------------------------------------------------- 1 | ../../shared-dep -------------------------------------------------------------------------------- /the-bug/node_modules/peer-dep: -------------------------------------------------------------------------------- 1 | ../../peer-dep -------------------------------------------------------------------------------- /file-link/node_modules/dep.js: -------------------------------------------------------------------------------- 1 | ../../dep/index.js -------------------------------------------------------------------------------- /root/node_modules/shared-dep: -------------------------------------------------------------------------------- 1 | ../../shared-dep -------------------------------------------------------------------------------- /dep/node_modules/conflict-dep: -------------------------------------------------------------------------------- 1 | ../../conflict-dep-2 -------------------------------------------------------------------------------- /root/node_modules/conflict-dep: -------------------------------------------------------------------------------- 1 | ../../conflict-dep-1 -------------------------------------------------------------------------------- /the-bug/node_modules/peer-user-1: -------------------------------------------------------------------------------- 1 | ../../peer-user-1 -------------------------------------------------------------------------------- /the-bug/node_modules/peer-user-2: -------------------------------------------------------------------------------- 1 | ../../peer-user-2 -------------------------------------------------------------------------------- /file-link/node_modules/root/index.js: -------------------------------------------------------------------------------- 1 | ../../../root/index.js -------------------------------------------------------------------------------- /file-link/node_modules/root/package.json: -------------------------------------------------------------------------------- 1 | ../../../root/package.json -------------------------------------------------------------------------------- /file-link/package.json: -------------------------------------------------------------------------------- 1 | {"name":"file-link", 2 | "version":"1.0.0"} 3 | -------------------------------------------------------------------------------- /peer-user-1/index.js: -------------------------------------------------------------------------------- 1 | require('peer-dep').one = module.paths.slice(0) 2 | -------------------------------------------------------------------------------- /peer-user-2/index.js: -------------------------------------------------------------------------------- 1 | require('peer-dep').two = module.paths.slice(0) 2 | -------------------------------------------------------------------------------- /shared-dep/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shared-dep", 3 | "version": "1.0.0" 4 | } 5 | -------------------------------------------------------------------------------- /peer-dep/index.js: -------------------------------------------------------------------------------- 1 | exports.filename = __filename 2 | exports.paths = module.paths.slice(0) 3 | -------------------------------------------------------------------------------- /conflict-dep-1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "conflict-dep", 3 | "version": "1.0.0" 4 | } 5 | -------------------------------------------------------------------------------- /conflict-dep-2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "conflict-dep", 3 | "version": "2.0.0" 4 | } 5 | -------------------------------------------------------------------------------- /shared-dep/index.js: -------------------------------------------------------------------------------- 1 | console.log('shared-dep', require('./package.json').version, __filename) 2 | -------------------------------------------------------------------------------- /conflict-dep-1/index.js: -------------------------------------------------------------------------------- 1 | console.log('conflict-dep', require('./package.json').version, __filename) 2 | -------------------------------------------------------------------------------- /conflict-dep-2/index.js: -------------------------------------------------------------------------------- 1 | console.log('conflict-dep', require('./package.json').version, __filename) 2 | -------------------------------------------------------------------------------- /the-bug/index.js: -------------------------------------------------------------------------------- 1 | require('peer-user-1') 2 | require('peer-user-2') 3 | console.log(require('peer-dep')) 4 | -------------------------------------------------------------------------------- /file-link/index.js: -------------------------------------------------------------------------------- 1 | console.log('file-link', require('./package.json').version, __filename) 2 | require('root') 3 | require('dep') 4 | -------------------------------------------------------------------------------- /dep/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dep", 3 | "version": "1.0.0", 4 | "dependencies": { 5 | "shared-dep": "1", 6 | "conflict-dep": "2" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /dep/index.js: -------------------------------------------------------------------------------- 1 | console.log('dep', require('./package.json').version, __filename) 2 | var conflict = require('conflict-dep') 3 | var shared = require('shared-dep') 4 | -------------------------------------------------------------------------------- /the-bug/workaround.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_PATH = require.main.paths.join(require('path').delimiter) 2 | 3 | console.log(process.env.NODE_PATH) 4 | 5 | require('./index.js') 6 | -------------------------------------------------------------------------------- /root/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "version": "1.0.0", 4 | "dependencies": { 5 | "shared-dep": "1", 6 | "conflict-dep": "1", 7 | "dep":"1" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /root/index.js: -------------------------------------------------------------------------------- 1 | console.log(process.version) 2 | console.log('root', require('./package.json').version, __filename) 3 | var dep = require('dep') 4 | var conflict = require('conflict-dep') 5 | var shared = require('shared-dep') 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Node v6.0 and 6.1 have a subtly different module system. 2 | 3 | I found this surprising. 4 | 5 | I'm told that this is behind a flag in "the next minor", so I guess 6 | it'll be back to normal in 6.2, and hopefully this will go the way of 7 | `NODE_MODULE_CONTEXTS`. (That is, ignored until it's broken by 8 | accident and eventually removed.) 9 | 10 | Here's how it changed. 11 | 12 | ``` 13 | $ node root 14 | v5.6.0 15 | root 1.0.0 /Users/isaacs/dev/js/node6-module-system-change/root/index.js 16 | dep 1.0.0 /Users/isaacs/dev/js/node6-module-system-change/dep/index.js 17 | conflict-dep 2.0.0 /Users/isaacs/dev/js/node6-module-system-change/conflict-dep-2/index.js 18 | shared-dep 1.0.0 /Users/isaacs/dev/js/node6-module-system-change/shared-dep/index.js 19 | conflict-dep 1.0.0 /Users/isaacs/dev/js/node6-module-system-change/conflict-dep-1/index.js 20 | 21 | $ node file-link/ 22 | file-link 1.0.0 /Users/isaacs/dev/js/node6-module-system-change/file-link/index.js 23 | v5.6.0 24 | root 1.0.0 /Users/isaacs/dev/js/node6-module-system-change/root/index.js 25 | dep 1.0.0 /Users/isaacs/dev/js/node6-module-system-change/dep/index.js 26 | conflict-dep 2.0.0 /Users/isaacs/dev/js/node6-module-system-change/conflict-dep-2/index.js 27 | shared-dep 1.0.0 /Users/isaacs/dev/js/node6-module-system-change/shared-dep/index.js 28 | conflict-dep 1.0.0 /Users/isaacs/dev/js/node6-module-system-change/conflict-dep-1/index.js 29 | 30 | $ nave use 6.0 31 | 32 | $ node root 33 | v6.0.0 34 | root 1.0.0 /Users/isaacs/dev/js/node6-module-system-change/root/index.js 35 | dep 1.0.0 /Users/isaacs/dev/js/node6-module-system-change/root/node_modules/dep/index.js 36 | conflict-dep 2.0.0 /Users/isaacs/dev/js/node6-module-system-change/root/node_modules/dep/node_modules/conflict-dep/index.js 37 | shared-dep 1.0.0 /Users/isaacs/dev/js/node6-module-system-change/root/node_modules/dep/node_modules/shared-dep/index.js 38 | conflict-dep 1.0.0 /Users/isaacs/dev/js/node6-module-system-change/root/node_modules/conflict-dep/index.js 39 | shared-dep 1.0.0 /Users/isaacs/dev/js/node6-module-system-change/root/node_modules/shared-dep/index.js 40 | 41 | $ node file-link/ 42 | file-link 1.0.0 /Users/isaacs/dev/js/node6-module-system-change/file-link/index.js 43 | v6.0.0 44 | root 1.0.0 /Users/isaacs/dev/js/node6-module-system-change/file-link/node_modules/root/index.js 45 | module.js:440 46 | throw err; 47 | ^ 48 | 49 | Error: Cannot find module './package.json' 50 | at Function.Module._resolveFilename (module.js:438:15) 51 | at Function.Module._load (module.js:386:25) 52 | at Module.require (module.js:466:17) 53 | at require (internal/module.js:20:19) 54 | at Object. (/Users/isaacs/dev/js/node6-module-system-change/file-link/node_modules/dep.js:1:82) 55 | at Module._compile (module.js:541:32) 56 | at Object.Module._extensions..js (module.js:550:10) 57 | at Module.load (module.js:456:32) 58 | at tryModuleLoad (module.js:415:12) 59 | at Function.Module._load (module.js:407:3) 60 | ``` 61 | 62 | Symbolic links are no longer resolved to their `realpath` in the 63 | cache. The `main` module is still resolved to its real path, though. 64 | 65 | So, in first test, where `root` is the main module, it loads files 66 | with a different `__filename` value, and loads modules multiple times 67 | if they are symlinked to multiple places. This is a significant 68 | behavior change, and very subtle, but ?probably? will "only" result in 69 | programs being less efficient. (I would not bet on this changing 70 | having no impact on the correctness of programs, however.) 71 | 72 | In the second case, when a *file* is symlinked into the `node_modules` 73 | folder, this breaks entirely where it used to work. 74 | 75 | ## The bug being fixed 76 | 77 | The "bug" being addressed by the change to the module system is this: 78 | 79 | If a module is symbolically linked into a `node_modules` folder along 80 | with one or more peer dependencies, then they will not be able to find 81 | the "root" module to attach onto. 82 | 83 | You can demonstrate this by running: 84 | 85 | ``` 86 | $ node the-bug 87 | module.js:341 88 | throw err; 89 | ^ 90 | 91 | Error: Cannot find module 'peer-dep' 92 | at Function.Module._resolveFilename (module.js:339:15) 93 | at Function.Module._load (module.js:290:25) 94 | at Module.require (module.js:367:17) 95 | at require (internal/module.js:16:19) 96 | at Object. (/Users/isaacs/dev/js/node6-module-system-change/peer-user-1/index.js:1:63) 97 | at Module._compile (module.js:413:34) 98 | at Object.Module._extensions..js (module.js:422:10) 99 | at Module.load (module.js:357:32) 100 | at Function.Module._load (module.js:314:12) 101 | at Module.require (module.js:367:17) 102 | 103 | $ nave use 6 104 | 105 | $ node the-bug 106 | { filename: '/Users/isaacs/dev/js/node6-module-system-change/the-bug/node_modules/peer-dep/index.js', 107 | one: 1, 108 | two: 2 } 109 | ``` 110 | 111 | I am not convinced that this is a bug, rather than merely a limitation 112 | of what the node module system can do. Nevertheless, it can be worked 113 | around easily, and fixed in a less hazardous manner. 114 | 115 | ### Workaround: `NODE_PATH` environment variable 116 | 117 | ``` 118 | $ NODE_PATH=$PWD/the-bug/node_modules node the-bug 119 | { filename: '/Users/isaacs/dev/js/node6-module-system-change/peer-dep/index.js', 120 | one: 1, 121 | two: 2 } 122 | ``` 123 | 124 | ### Proposal: Add `require.main.paths` to all module lookup paths 125 | 126 | This patch should do it: 127 | 128 | ```diff 129 | diff --git a/lib/module.js b/lib/module.js 130 | index 82b1971..45d8e66 100644 131 | --- a/lib/module.js 132 | +++ b/lib/module.js 133 | @@ -230,6 +230,11 @@ Module._resolveLookupPaths = function(request, parent) { 134 | paths = parent.paths.concat(paths); 135 | } 136 | 137 | + if (process.mainModule && parent !== process.mainModule) { 138 | + if (!process.mainModule.paths) process.mainModule.paths = []; 139 | + paths = paths.concat(process.mainModule.paths); 140 | + } 141 | + 142 | // Maintain backwards compat with certain broken uses of require('.') 143 | // by putting the module's directory in front of the lookup paths. 144 | if (request === '.') { 145 | 146 | ``` 147 | 148 | I'm not sure if this is a good idea! It might be bad! It could 149 | load things that you don't want loaded, but at least it'd only happen 150 | as a *lower* priority lookup than the other established behavior that 151 | the module ecosystem has come to rely on. 152 | 153 | ### Proposal: Add `module.parent.paths` to module lookup paths 154 | 155 | Even shorter patch: 156 | 157 | ```diff 158 | diff --git a/lib/module.js b/lib/module.js 159 | index 82b1971..f2d82a0 100644 160 | --- a/lib/module.js 161 | +++ b/lib/module.js 162 | @@ -230,6 +230,10 @@ Module._resolveLookupPaths = function(request, parent) { 163 | paths = parent.paths.concat(paths); 164 | } 165 | 166 | + if (parent && parent.parent && parent.parent.paths) { 167 | + paths = paths.concat(parent.parent.paths); 168 | + } 169 | + 170 | // Maintain backwards compat with certain broken uses of require('.') 171 | // by putting the module's directory in front of the lookup paths. 172 | if (request === '.') { 173 | 174 | ``` 175 | 176 | This would handle the case where a module with some peer dependencies 177 | is not installed in the `node_modules` of the `main` module's root. 178 | So, for example, if a module loaded `the-bug` as a dep, deep in a 179 | `node_modules` heirarchy (which would require a very *interesting* 180 | hybrid package management strategy!), then this would ensure that 181 | modules always have their parent's lookup paths. 182 | 183 | **UPDATE**: Actually the package layout strategy wouldn't have to be 184 | all that interesting. If you have a pnpm/ied-style symlink-based 185 | system, then a nested peer-dep needs to have a lookup path that has 186 | paths not in the main module's set. 187 | 188 | However, this gets pretty long pretty fast! Probably you'd want to do 189 | some de-duping, so that you don't have a 100,000 item list of folders 190 | to search, most of which would be the same folders. 191 | 192 | Longer patch: 193 | 194 | ```diff 195 | diff --git a/lib/module.js b/lib/module.js 196 | index 82b1971..1fb6a1f 100644 197 | --- a/lib/module.js 198 | +++ b/lib/module.js 199 | @@ -230,6 +230,14 @@ Module._resolveLookupPaths = function(request, parent) { 200 | paths = parent.paths.concat(paths); 201 | } 202 | 203 | + if (parent && parent.parent && parent.parent.paths) { 204 | + for (var p = 0; p < parent.parent.paths.length; p++) { 205 | + if (paths.indexOf(parent.parent.paths[p]) === -1) { 206 | + paths.push(parent.parent.paths[p]); 207 | + } 208 | + } 209 | + } 210 | + 211 | // Maintain backwards compat with certain broken uses of require('.') 212 | // by putting the module's directory in front of the lookup paths. 213 | if (request === '.') { 214 | 215 | ``` 216 | 217 | ### Use parent paths, secondary (introspectable) proposal 218 | 219 | Here's another way to do that, which is inspectable: 220 | 221 | ```diff 222 | diff --git a/lib/module.js b/lib/module.js 223 | index ccbb5ba..fb6a831 100644 224 | --- a/lib/module.js 225 | +++ b/lib/module.js 226 | @@ -453,6 +453,15 @@ Module.prototype.load = function(filename) { 227 | this.filename = filename; 228 | this.paths = Module._nodeModulePaths(path.dirname(filename)); 229 | 230 | + if (this.parent && this.parent.paths) { 231 | + for (let p = 0; p < this.parent.paths.length; p++) { 232 | + if (this.paths.indexOf(this.parent.paths[p]) === -1) { 233 | + this.paths.push(this.parent.paths[p]); 234 | + } 235 | + } 236 | + } 237 | + 238 | + 239 | var extension = path.extname(filename) || '.js'; 240 | if (!Module._extensions[extension]) extension = '.js'; 241 | Module._extensions[extension](this, filename); 242 | 243 | ``` 244 | 245 | The result: 246 | 247 | ``` 248 | $ node the-bug 249 | { filename: '/Users/isaacs/dev/js/node6-module-system-change/peer-dep/index.js', 250 | paths: 251 | [ '/Users/isaacs/dev/js/node6-module-system-change/peer-dep/node_modules', 252 | '/Users/isaacs/dev/js/node6-module-system-change/node_modules', 253 | '/Users/isaacs/dev/js/node_modules', 254 | '/Users/isaacs/dev/node_modules', 255 | '/Users/isaacs/node_modules', 256 | '/Users/node_modules', 257 | '/Users/isaacs/dev/js/node6-module-system-change/peer-user-1/node_modules', 258 | '/Users/isaacs/dev/js/node6-module-system-change/the-bug/node_modules' ], 259 | one: 260 | [ '/Users/isaacs/dev/js/node6-module-system-change/peer-user-1/node_modules', 261 | '/Users/isaacs/dev/js/node6-module-system-change/node_modules', 262 | '/Users/isaacs/dev/js/node_modules', 263 | '/Users/isaacs/dev/node_modules', 264 | '/Users/isaacs/node_modules', 265 | '/Users/node_modules', 266 | '/Users/isaacs/dev/js/node6-module-system-change/the-bug/node_modules' ], 267 | two: 268 | [ '/Users/isaacs/dev/js/node6-module-system-change/peer-user-2/node_modules', 269 | '/Users/isaacs/dev/js/node6-module-system-change/node_modules', 270 | '/Users/isaacs/dev/js/node_modules', 271 | '/Users/isaacs/dev/node_modules', 272 | '/Users/isaacs/node_modules', 273 | '/Users/node_modules', 274 | '/Users/isaacs/dev/js/node6-module-system-change/the-bug/node_modules' ] } 275 | ``` 276 | 277 | I believe that this is the best way to support peer dependencies 278 | without breaking the node module ecosystem. 279 | --------------------------------------------------------------------------------