Repository: webpack/enhanced-resolve (js)

Issue:
Bug: linked package incorrectly uses node_modules instead of pnpapi
enhanced-resolve is incorrectly escaping from the pnpapi context.

### Context

Encountered this via webpack initially using Yarn 2's pnpapi.

### With Webpack:

Repro project: https://github.com/noahnu/yarn-repro-linking-non-pnp, after cloning the project, `cd yarn2-pnp-project && yarn build` then search for `yarn1-nm-project/node_modules/react-dom/index.js` in the `dist/bundle.js` file. It's from node_modules.

And without webpack, purely using enhanced-resolve: 

### Repro:

Clone: https://github.com/noahnu/yarn-repro-linking-non-pnp/tree/pure_enhance

and run the bash script: `./run-repro.sh`.

```js
const assert = require('assert')
const resolve = require('enhanced-resolve')

assert.equal(
  resolve.sync(require.resolve('my-linked-package'), 'react-dom'),
  require.resolve('react-dom', {
    paths: [require.resolve('my-linked-package')],
  })
)
```

### Expectation

I expect the assertion to pass, as both should be going through pnp.


Pull request:
fix: give precedence to pnp over node_modules, close #288
Fixes https://github.com/webpack/enhanced-resolve/issues/288.

Ideally, node_modules should not even be considered if the pnp api is enabled, however it looks like that's intentional according to https://github.com/webpack/enhanced-resolve/issues/273

Patch:
diff --git a/lib/ResolverFactory.js b/lib/ResolverFactory.js
index 3f12653..6cbbe12 100644
--- a/lib/ResolverFactory.js
+++ b/lib/ResolverFactory.js
@@ -399,13 +399,21 @@ exports.createResolver = function (options) {
 	});
 	modules.forEach(item => {
 		if (Array.isArray(item)) {
-			plugins.push(
-				new ModulesInHierachicDirectoriesPlugin("raw-module", item, "module")
-			);
 			if (item.includes("node_modules") && pnpApi) {
+				plugins.push(
+					new ModulesInHierachicDirectoriesPlugin(
+						"raw-module",
+						item.filter(i => i !== "node_modules"),
+						"module"
+					)
+				);
 				plugins.push(
 					new PnpPlugin("raw-module", pnpApi, "undescribed-resolve-in-package")
 				);
+			} else {
+				plugins.push(
+					new ModulesInHierachicDirectoriesPlugin("raw-module", item, "module")
+				);
 			}
 		} else {
 			plugins.push(new ModulesInRootPlugin("raw-module", item, "module"));


Test patch:
diff --git a/test/fixtures/prefer-pnp/alternative-modules/m1/b.js b/test/fixtures/prefer-pnp/alternative-modules/m1/b.js
new file mode 100644
index 0000000..e69de29
diff --git a/test/pnp.js b/test/pnp.js
index 5894676..b4aa42d 100644
--- a/test/pnp.js
+++ b/test/pnp.js
@@ -68,7 +68,11 @@ describe("pnp", () => {
 				alias: path.resolve(fixture, "pkg")
 			},
 			pnpApi,
-			modules: ["node_modules", path.resolve(fixture, "../pnp-a")]
+			modules: [
+				"alternative-modules",
+				"node_modules",
+				path.resolve(fixture, "../pnp-a")
+			]
 		});
 	});
 	it("should resolve by going through the pnp api", done => {
@@ -172,16 +176,35 @@ describe("pnp", () => {
 			}
 		);
 	});
-	it("should prefer normal modules over pnp resolves", done => {
-		pnpApi.mocks.set("m1", path.resolve(fixture, "pkg"));
+	it("should prefer pnp resolves over normal modules", done => {
+		pnpApi.mocks.set("m1", path.resolve(fixture, "../node_modules/m2"));
 		resolver.resolve(
 			{},
 			path.resolve(__dirname, "fixtures"),
-			"m1/a.js",
+			"m1/b.js",
 			{},
 			(err, result) => {
 				if (err) return done(err);
-				result.should.equal(path.resolve(fixture, "../node_modules/m1/a.js"));
+				result.should.equal(path.resolve(fixture, "../node_modules/m2/b.js"));
+				done();
+			}
+		);
+	});
+	it("should prefer alternative module directories over pnp", done => {
+		pnpApi.mocks.set("m1", path.resolve(fixture, "../node_modules/m2"));
+		resolver.resolve(
+			{},
+			path.resolve(__dirname, "fixtures/prefer-pnp"),
+			"m1/b.js",
+			{},
+			(err, result) => {
+				if (err) return done(err);
+				result.should.equal(
+					path.resolve(
+						__dirname,
+						"fixtures/prefer-pnp/alternative-modules/m1/b.js"
+					)
+				);
 				done();
 			}
 		);