diff --git a/lib/PnpPlugin.js b/lib/PnpPlugin.js index e418d717..8f151774 100644 --- a/lib/PnpPlugin.js +++ b/lib/PnpPlugin.js @@ -51,6 +51,19 @@ module.exports = class PnpPlugin { }); } } catch (error) { + if ( + error.code === "MODULE_NOT_FOUND" && + error.pnpCode === "UNDECLARED_DEPENDENCY" + ) { + // This is not a PnP managed dependency. + // Try to continue resolving with our alternatives + if (resolveContext.log) { + resolveContext.log(`request is not managed by the pnpapi`); + for (const line of error.message.split("\n").filter(Boolean)) + resolveContext.log(` ${line}`); + } + return callback(); + } return callback(error); } diff --git a/lib/ResolverFactory.js b/lib/ResolverFactory.js index b92ba5ea..2f981e34 100644 --- a/lib/ResolverFactory.js +++ b/lib/ResolverFactory.js @@ -375,15 +375,17 @@ exports.createResolver = function(options) { new SelfReferencePlugin("raw-module", exportsField, "resolve-as-module") ); }); - if (pnpApi) { - plugins.push(new PnpPlugin("raw-module", pnpApi, "relative")); - } modules.forEach(item => { - if (Array.isArray(item)) + if (Array.isArray(item)) { plugins.push( new ModulesInHierachicDirectoriesPlugin("raw-module", item, "module") ); - else plugins.push(new ModulesInRootPlugin("raw-module", item, "module")); + if (item.includes("node_modules") && pnpApi) { + plugins.push(new PnpPlugin("raw-module", pnpApi, "relative")); + } + } else { + plugins.push(new ModulesInRootPlugin("raw-module", item, "module")); + } }); // module diff --git a/test/fixtures/pnp-a/m2/a.js b/test/fixtures/pnp-a/m2/a.js new file mode 100644 index 00000000..997c1051 --- /dev/null +++ b/test/fixtures/pnp-a/m2/a.js @@ -0,0 +1,3 @@ +module.exports = function a() { + return "This is nested m1/a"; +}; diff --git a/test/pnp.js b/test/pnp.js index cf537c6e..98ce9b19 100644 --- a/test/pnp.js +++ b/test/pnp.js @@ -42,7 +42,10 @@ describe("pnp", () => { if (pnpApi.mocks.has(request)) { return pnpApi.mocks.get(request); } else { - throw new Error(`No way`); + const err = /** @type {any} */ (new Error(`No way`)); + err.code = "MODULE_NOT_FOUND"; + err.pnpCode = "UNDECLARED_DEPENDENCY"; + throw err; } } }); @@ -50,7 +53,11 @@ describe("pnp", () => { extensions: [".ts", ".js"], aliasFields: ["browser"], fileSystem: nodeFileSystem, - pnpApi + alias: { + alias: path.resolve(fixture, "pkg") + }, + pnpApi, + modules: ["node_modules", path.resolve(fixture, "../pnp-a")] }); }); it("should resolve by going through the pnp api", done => { @@ -145,7 +152,7 @@ describe("pnp", () => { done(); }); }); - it("should skip normal modules when pnp resolves", done => { + it("should prefer normal modules over pnp resolves", done => { pnpApi.mocks.set("m1/a.js", path.resolve(fixture, "pkg/a.js")); resolver.resolve( {}, @@ -153,7 +160,52 @@ describe("pnp", () => { "m1/a.js", {}, (err, result) => { - if (!err) return done(new Error("Resolving should fail")); + if (err) return done(err); + result.should.equal(path.resolve(fixture, "../node_modules/m1/a.js")); + done(); + } + ); + }); + it("should prefer alias over pnp resolves", done => { + pnpApi.mocks.set( + "alias/index.js", + path.resolve(fixture, "pkg/dir/index.js") + ); + resolver.resolve( + {}, + path.resolve(__dirname, "fixtures"), + "alias/index.js", + {}, + (err, result) => { + if (err) return done(err); + result.should.equal(path.resolve(fixture, "pkg/index.js")); + done(); + } + ); + }); + it("should prefer pnp over modules after node_modules", done => { + pnpApi.mocks.set("m2/a.js", path.resolve(fixture, "pkg/index.js")); + resolver.resolve( + {}, + path.resolve(__dirname, "fixtures"), + "m2/a.js", + {}, + (err, result) => { + if (err) return done(err); + result.should.equal(path.resolve(fixture, "pkg/index.js")); + done(); + } + ); + }); + it("should fallback to alternatives when pnp resolving fails", done => { + resolver.resolve( + {}, + path.resolve(__dirname, "fixtures"), + "m2/a.js", + {}, + (err, result) => { + if (err) return done(err); + result.should.equal(path.resolve(fixture, "../pnp-a/m2/a.js")); done(); } );