diff --git a/lib/util/entrypoints.js b/lib/util/entrypoints.js index b925d626..36e335e5 100644 --- a/lib/util/entrypoints.js +++ b/lib/util/entrypoints.js @@ -11,14 +11,6 @@ /** @typedef {Record|ConditionalMapping|DirectMapping} ExportsField */ /** @typedef {Record} ImportsField */ -/** - * @typedef {Object} PathTreeNode - * @property {Map|null} children - * @property {MappingValue} folder - * @property {Map|null} wildcards - * @property {Map} files - */ - /** * Processing exports/imports field * @callback FieldProcessor @@ -83,6 +75,7 @@ Conditional mapping nested in another conditional mapping is called nested mappi const slashCode = "/".charCodeAt(0); const dotCode = ".".charCodeAt(0); const hashCode = "#".charCodeAt(0); +const patternRegEx = /\*/g; /** * @param {ExportsField} exportsField the exports field @@ -92,7 +85,8 @@ module.exports.processExportsField = function processExportsField( exportsField ) { return createFieldProcessor( - buildExportsFieldPathTree(exportsField), + buildExportsField(exportsField), + request => (request.length === 0 ? "." : "./" + request), assertExportsFieldRequest, assertExportTarget ); @@ -106,27 +100,34 @@ module.exports.processImportsField = function processImportsField( importsField ) { return createFieldProcessor( - buildImportsFieldPathTree(importsField), + buildImportsField(importsField), + request => "#" + request, assertImportsFieldRequest, assertImportTarget ); }; /** - * @param {PathTreeNode} treeRoot root + * @param {ExportsField | ImportsField} field root + * @param {(s: string) => string} normalizeRequest Normalize request, for `imports` field it adds `#`, for `exports` field it adds `.` or `./` * @param {(s: string) => string} assertRequest assertRequest * @param {(s: string, f: boolean) => void} assertTarget assertTarget * @returns {FieldProcessor} field processor */ -function createFieldProcessor(treeRoot, assertRequest, assertTarget) { +function createFieldProcessor( + field, + normalizeRequest, + assertRequest, + assertTarget +) { return function fieldProcessor(request, conditionNames) { request = assertRequest(request); - const match = findMatch(request, treeRoot); + const match = findMatch(normalizeRequest(request), field); if (match === null) return []; - const [mapping, remainRequestIndex] = match; + const [mapping, remainingRequest, isSubpathMapping, isPattern] = match; /** @type {DirectMapping|null} */ let direct = null; @@ -143,16 +144,10 @@ function createFieldProcessor(treeRoot, assertRequest, assertTarget) { direct = /** @type {DirectMapping} */ (mapping); } - const remainingRequest = - remainRequestIndex === request.length + 1 - ? undefined - : remainRequestIndex < 0 - ? request.slice(-remainRequestIndex - 1) - : request.slice(remainRequestIndex); - return directMapping( remainingRequest, - remainRequestIndex < 0, + isPattern, + isSubpathMapping, direct, conditionNames, assertTarget @@ -251,101 +246,87 @@ function assertImportTarget(imp, expectFolder) { } } +function patternKeyCompare(a, b) { + const aPatternIndex = a.indexOf("*"); + const bPatternIndex = b.indexOf("*"); + const baseLenA = aPatternIndex === -1 ? a.length : aPatternIndex + 1; + const baseLenB = bPatternIndex === -1 ? b.length : bPatternIndex + 1; + + if (baseLenA > baseLenB) return -1; + if (baseLenB > baseLenA) return 1; + if (aPatternIndex === -1) return 1; + if (bPatternIndex === -1) return -1; + if (a.length > b.length) return -1; + if (b.length > a.length) return 1; + + return 0; +} + /** * Trying to match request to field * @param {string} request request - * @param {PathTreeNode} treeRoot path tree root - * @returns {[MappingValue, number]|null} match or null, number is negative and one less when it's a folder mapping, number is request.length + 1 for direct mappings + * @param {ExportsField | ImportsField} field exports or import field + * @returns {[MappingValue, string, boolean, boolean]|null} match or null, number is negative and one less when it's a folder mapping, number is request.length + 1 for direct mappings */ -function findMatch(request, treeRoot) { - if (request.length === 0) { - const value = treeRoot.files.get(""); - - return value ? [value, 1] : null; - } - +function findMatch(request, field) { if ( - treeRoot.children === null && - treeRoot.folder === null && - treeRoot.wildcards === null + Object.prototype.hasOwnProperty.call(field, request) && + !request.includes("*") && + !request.endsWith("/") ) { - const value = treeRoot.files.get(request); + const target = field[request]; - return value ? [value, request.length + 1] : null; + return [target, "", false, false]; } - let node = treeRoot; - let lastNonSlashIndex = 0; - let slashIndex = request.indexOf("/", 0); - - /** @type {[MappingValue, number]|null} */ - let lastFolderMatch = null; - - const applyFolderMapping = () => { - const folderMapping = node.folder; - if (folderMapping) { - if (lastFolderMatch) { - lastFolderMatch[0] = folderMapping; - lastFolderMatch[1] = -lastNonSlashIndex - 1; - } else { - lastFolderMatch = [folderMapping, -lastNonSlashIndex - 1]; - } - } - }; + let bestMatch = ""; + let bestMatchSubpath; - const applyWildcardMappings = (wildcardMappings, remainingRequest) => { - if (wildcardMappings) { - for (const [key, target] of wildcardMappings) { - if (remainingRequest.startsWith(key)) { - if (!lastFolderMatch) { - lastFolderMatch = [target, lastNonSlashIndex + key.length]; - } else if (lastFolderMatch[1] < lastNonSlashIndex + key.length) { - lastFolderMatch[0] = target; - lastFolderMatch[1] = lastNonSlashIndex + key.length; - } - } + const keys = Object.getOwnPropertyNames(field); + + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const patternIndex = key.indexOf("*"); + + if (patternIndex !== -1 && request.startsWith(key.slice(0, patternIndex))) { + const patternTrailer = key.slice(patternIndex + 1); + + if ( + request.length >= key.length && + request.endsWith(patternTrailer) && + patternKeyCompare(bestMatch, key) === 1 && + key.lastIndexOf("*") === patternIndex + ) { + bestMatch = key; + bestMatchSubpath = request.slice( + patternIndex, + request.length - patternTrailer.length + ); } } - }; - - while (slashIndex !== -1) { - applyFolderMapping(); - - const wildcardMappings = node.wildcards; - - if (!wildcardMappings && node.children === null) return lastFolderMatch; - - const folder = request.slice(lastNonSlashIndex, slashIndex); - - applyWildcardMappings(wildcardMappings, folder); - - if (node.children === null) return lastFolderMatch; - - const newNode = node.children.get(folder); - - if (!newNode) { - return lastFolderMatch; + // For legacy `./foo/` + else if ( + key[key.length - 1] === "/" && + request.startsWith(key) && + patternKeyCompare(bestMatch, key) === 1 + ) { + bestMatch = key; + bestMatchSubpath = request.slice(key.length); } - - node = newNode; - lastNonSlashIndex = slashIndex + 1; - slashIndex = request.indexOf("/", lastNonSlashIndex); - } - - const remainingRequest = - lastNonSlashIndex > 0 ? request.slice(lastNonSlashIndex) : request; - - const value = node.files.get(remainingRequest); - - if (value) { - return [value, request.length + 1]; } - applyFolderMapping(); + if (bestMatch === "") return null; - applyWildcardMappings(node.wildcards, remainingRequest); + const target = field[bestMatch]; + const isSubpathMapping = bestMatch.endsWith("/"); + const isPattern = bestMatch.includes("*"); - return lastFolderMatch; + return [ + target, + /** @type {string} */ (bestMatchSubpath), + isSubpathMapping, + isPattern + ]; } /** @@ -360,7 +341,8 @@ function isConditionalMapping(mapping) { /** * @param {string|undefined} remainingRequest remaining request when folder mapping, undefined for file mappings - * @param {boolean} subpathMapping true, for subpath mappings + * @param {boolean} isPattern true, if mapping is a pattern (contains "*") + * @param {boolean} isSubpathMapping true, for subpath mappings * @param {DirectMapping|null} mappingTarget direct export * @param {Set} conditionNames condition names * @param {(d: string, f: boolean) => void} assert asserting direct value @@ -368,7 +350,8 @@ function isConditionalMapping(mapping) { */ function directMapping( remainingRequest, - subpathMapping, + isPattern, + isSubpathMapping, mappingTarget, conditionNames, assert @@ -377,7 +360,13 @@ function directMapping( if (typeof mappingTarget === "string") { return [ - targetMapping(remainingRequest, subpathMapping, mappingTarget, assert) + targetMapping( + remainingRequest, + isPattern, + isSubpathMapping, + mappingTarget, + assert + ) ]; } @@ -386,7 +375,13 @@ function directMapping( for (const exp of mappingTarget) { if (typeof exp === "string") { targets.push( - targetMapping(remainingRequest, subpathMapping, exp, assert) + targetMapping( + remainingRequest, + isPattern, + isSubpathMapping, + exp, + assert + ) ); continue; } @@ -395,7 +390,8 @@ function directMapping( if (!mapping) continue; const innerExports = directMapping( remainingRequest, - subpathMapping, + isPattern, + isSubpathMapping, mapping, conditionNames, assert @@ -410,27 +406,43 @@ function directMapping( /** * @param {string|undefined} remainingRequest remaining request when folder mapping, undefined for file mappings - * @param {boolean} subpathMapping true, for subpath mappings + * @param {boolean} isPattern true, if mapping is a pattern (contains "*") + * @param {boolean} isSubpathMapping true, for subpath mappings * @param {string} mappingTarget direct export * @param {(d: string, f: boolean) => void} assert asserting direct value * @returns {string} mapping result */ function targetMapping( remainingRequest, - subpathMapping, + isPattern, + isSubpathMapping, mappingTarget, assert ) { if (remainingRequest === undefined) { assert(mappingTarget, false); + return mappingTarget; } - if (subpathMapping) { + + if (isSubpathMapping) { assert(mappingTarget, true); + return mappingTarget + remainingRequest; } + assert(mappingTarget, false); - return mappingTarget.replace(/\*/g, remainingRequest.replace(/\$/g, "$$")); + + let result = mappingTarget; + + if (isPattern) { + result = result.replace( + patternRegEx, + remainingRequest.replace(/\$/g, "$$") + ); + } + + return result; } /** @@ -487,93 +499,14 @@ function conditionalMapping(conditionalMapping_, conditionNames) { return null; } -/** - * Internal helper to create path tree node - * to ensure that each node gets the same hidden class - * @returns {PathTreeNode} node - */ -function createNode() { - return { - children: null, - folder: null, - wildcards: null, - files: new Map() - }; -} - -/** - * Internal helper for building path tree - * @param {PathTreeNode} root root - * @param {string} path path - * @param {MappingValue} target target - */ -function walkPath(root, path, target) { - if (path.length === 0) { - root.folder = target; - return; - } - - let node = root; - // Typical path tree can looks like - // root - // - files: ["a.js", "b.js"] - // - children: - // node1: - // - files: ["a.js", "b.js"] - let lastNonSlashIndex = 0; - let slashIndex = path.indexOf("/", 0); - - while (slashIndex !== -1) { - const folder = path.slice(lastNonSlashIndex, slashIndex); - let newNode; - - if (node.children === null) { - newNode = createNode(); - node.children = new Map(); - node.children.set(folder, newNode); - } else { - newNode = node.children.get(folder); - - if (!newNode) { - newNode = createNode(); - node.children.set(folder, newNode); - } - } - - node = newNode; - lastNonSlashIndex = slashIndex + 1; - slashIndex = path.indexOf("/", lastNonSlashIndex); - } - - if (lastNonSlashIndex >= path.length) { - node.folder = target; - } else { - const file = lastNonSlashIndex > 0 ? path.slice(lastNonSlashIndex) : path; - if (file.endsWith("*")) { - if (node.wildcards === null) node.wildcards = new Map(); - node.wildcards.set(file.slice(0, -1), target); - } else { - node.files.set(file, target); - } - } -} - /** * @param {ExportsField} field exports field - * @returns {PathTreeNode} tree root + * @returns {ExportsField} normalized exports field */ -function buildExportsFieldPathTree(field) { - const root = createNode(); - +function buildExportsField(field) { // handle syntax sugar, if exports field is direct mapping for "." - if (typeof field === "string") { - root.files.set("", field); - - return root; - } else if (Array.isArray(field)) { - root.files.set("", field.slice()); - - return root; + if (typeof field === "string" || Array.isArray(field)) { + return { ".": field }; } const keys = Object.keys(field); @@ -596,8 +529,7 @@ function buildExportsFieldPathTree(field) { i++; } - root.files.set("", field); - return root; + return { ".": field }; } throw new Error( @@ -608,7 +540,6 @@ function buildExportsFieldPathTree(field) { } if (key.length === 1) { - root.files.set("", field[key]); continue; } @@ -619,20 +550,16 @@ function buildExportsFieldPathTree(field) { )})` ); } - - walkPath(root, key.slice(2), field[key]); } - return root; + return field; } /** * @param {ImportsField} field imports field - * @returns {PathTreeNode} root + * @returns {ImportsField} normalized imports field */ -function buildImportsFieldPathTree(field) { - const root = createNode(); - +function buildImportsField(field) { const keys = Object.keys(field); for (let i = 0; i < keys.length; i++) { @@ -659,9 +586,7 @@ function buildImportsFieldPathTree(field) { )})` ); } - - walkPath(root, key.slice(1), field[key]); } - return root; + return field; } diff --git a/test/exportsField.js b/test/exportsField.js index d2f2d685..035fc759 100644 --- a/test/exportsField.js +++ b/test/exportsField.js @@ -594,6 +594,56 @@ describe("Process exports field", function exportsField() { [] ] }, + { + name: "Direct mapping #11", + expect: ["./foo.js"], + suite: [ + { + "./": "./", + "./*": "./*", + "./dist/index.js": "./dist/index.js" + }, + "./foo.js", + [] + ] + }, + { + name: "Direct mapping #12", + expect: ["./foo/bar/baz.js"], + suite: [ + { + "./": "./", + "./*": "./*", + "./dist/index.js": "./dist/index.js" + }, + "./foo/bar/baz.js", + [] + ] + }, + { + name: "Direct mapping #13", + expect: ["./foo/bar/baz.js"], + suite: [ + { + "./": "./", + "./dist/index.js": "./dist/index.js" + }, + "./foo/bar/baz.js", + [] + ] + }, + { + name: "Direct mapping #14", + expect: ["./foo/bar/baz.js"], + suite: [ + { + "./*": "./*", + "./dist/index.js": "./dist/index.js" + }, + "./foo/bar/baz.js", + [] + ] + }, //#endregion //#region Direct and conditional mapping @@ -1953,6 +2003,212 @@ describe("Process exports field", function exportsField() { "./a/b/d/c.js", [] ] + }, + { + name: "wildcard pattern #1", + expect: ["./A/b.js"], + suite: [ + { + "./a/*.js": "./A/*.js" + }, + "./a/b.js", + [] + ] + }, + { + name: "wildcard pattern #2", + expect: ["./A/b/c.js"], + suite: [ + { + "./a/*.js": "./A/*.js" + }, + "./a/b/c.js", + [] + ] + }, + { + name: "wildcard pattern #3", + expect: ["./A/b/c.js"], + suite: [ + { + "./a/*/c.js": "./A/*/c.js" + }, + "./a/b/c.js", + [] + ] + }, + { + name: "wildcard pattern #4", + expect: ["./A/b/b.js"], + suite: [ + { + "./a/*/c.js": "./A/*/*.js" + }, + "./a/b/c.js", + [] + ] + }, + { + name: "wildcard pattern #5", + expect: ["./browser/index.js"], + suite: [ + { + "./lib/*": { + browser: ["./browser/*"] + }, + "./dist/*.js": { + node: "./*.js", + default: "./browser/*.js" + } + }, + "./dist/index.js", + ["browser"] + ] + }, + { + name: "wildcard pattern #5", + expect: ["./browser/index.js"], + suite: [ + { + "./lib/*": { + browser: ["./browser/*"] + }, + "./dist/*.js": { + node: "./*.js", + default: "./browser/*.js" + } + }, + "./lib/index.js", + ["browser"] + ] + }, + { + name: "wildcard pattern #6", + expect: ["./browser/foo/bar.js"], + suite: [ + { + "./lib/*/bar.js": { + browser: ["./browser/*/bar.js"] + }, + "./dist/*/bar.js": { + node: "./*.js", + default: "./browser/*.js" + } + }, + "./lib/foo/bar.js", + ["browser"] + ] + }, + { + name: "wildcard pattern #6", + expect: ["./browser/foo.js"], + suite: [ + { + "./lib/*/bar.js": { + browser: ["./browser/*/bar.js"] + }, + "./dist/*/bar.js": { + node: "./*.js", + default: "./browser/*.js" + } + }, + "./dist/foo/bar.js", + ["browser"] + ] + }, + { + name: "wildcard pattern #7", + expect: ["./browser/foo/default.js"], + suite: [ + { + "./lib/*/bar.js": { + browser: ["./browser/*/bar.js"] + }, + "./dist/*/bar.js": { + node: "./*.js", + default: "./browser/*/default.js" + } + }, + "./dist/foo/bar.js", + ["default"] + ] + }, + { + name: "wildcard pattern #8", + expect: ["./A/b/b/b.js"], + suite: [ + { + "./a/*/c.js": "./A/*/*/*.js" + }, + "./a/b/c.js", + [] + ] + }, + { + name: "wildcard pattern #9", + expect: ["./A/b/b/b.js", "./B/b/b/b.js"], + suite: [ + { + "./a/*/c.js": ["./A/*/*/*.js", "./B/*/*/*.js"] + }, + "./a/b/c.js", + [] + ] + }, + { + name: "wildcard pattern #10", + expect: ["./A/b/b/b.js"], + suite: [ + { + "./a/foo-*/c.js": "./A/*/*/*.js" + }, + "./a/foo-b/c.js", + [] + ] + }, + { + name: "wildcard pattern #11", + expect: ["./A/b/b/b.js"], + suite: [ + { + "./a/*-foo/c.js": "./A/*/*/*.js" + }, + "./a/b-foo/c.js", + [] + ] + }, + { + name: "wildcard pattern #12", + expect: ["./A/b/b/b.js"], + suite: [ + { + "./a/foo-*-foo/c.js": "./A/*/*/*.js" + }, + "./a/foo-b-foo/c.js", + [] + ] + }, + { + name: "wildcard pattern #13", + expect: ["./A/b/c/d.js"], + suite: [ + { + "./a/foo-*-foo/c.js": "./A/b/c/d.js" + }, + "./a/foo-b-foo/c.js", + [] + ] + }, + { + name: "wildcard pattern #13", + expect: ["./A/b/c/*.js"], + suite: [ + { + "./a/foo-foo/c.js": "./A/b/c/*.js" + }, + "./a/foo-foo/c.js", + [] + ] } ]; @@ -2452,4 +2708,207 @@ describe("ExportsFieldPlugin", () => { } ); }); + + it("should resolve with wildcard pattern #1", done => { + const fixture = path.resolve( + __dirname, + "./fixtures/imports-exports-wildcard/" + ); + + resolver.resolve({}, fixture, "m/features/f.js", {}, (err, result) => { + if (err) return done(err); + if (!result) throw new Error("No result"); + result.should.equal( + path.resolve(fixture, "./node_modules/m/src/features/f.js") + ); + done(); + }); + }); + + it("should resolve with wildcard pattern #2", done => { + const fixture = path.resolve( + __dirname, + "./fixtures/imports-exports-wildcard/" + ); + + resolver.resolve({}, fixture, "m/features/y/y.js", {}, (err, result) => { + if (err) return done(err); + if (!result) throw new Error("No result"); + result.should.equal( + path.resolve(fixture, "./node_modules/m/src/features/y/y.js") + ); + done(); + }); + }); + + it("should resolve with wildcard pattern #2", done => { + const fixture = path.resolve( + __dirname, + "./fixtures/imports-exports-wildcard/" + ); + + resolver.resolve({}, fixture, "m/features/y/y.js", {}, (err, result) => { + if (err) return done(err); + if (!result) throw new Error("No result"); + result.should.equal( + path.resolve(fixture, "./node_modules/m/src/features/y/y.js") + ); + done(); + }); + }); + + it("should resolve with wildcard pattern #3", done => { + const fixture = path.resolve( + __dirname, + "./fixtures/imports-exports-wildcard/" + ); + + resolver.resolve( + {}, + fixture, + "m/features-no-ext/y/y.js", + {}, + (err, result) => { + if (err) return done(err); + if (!result) throw new Error("No result"); + result.should.equal( + path.resolve(fixture, "./node_modules/m/src/features/y/y.js") + ); + done(); + } + ); + }); + + it("should resolve with wildcard pattern #4", done => { + const fixture = path.resolve( + __dirname, + "./fixtures/imports-exports-wildcard/" + ); + + resolver.resolve({}, fixture, "m/middle/nested/f.js", {}, (err, result) => { + if (err) return done(err); + if (!result) throw new Error("No result"); + result.should.equal( + path.resolve(fixture, "./node_modules/m/src/middle/nested/f.js") + ); + done(); + }); + }); + + it("should resolve with wildcard pattern #5", done => { + const fixture = path.resolve( + __dirname, + "./fixtures/imports-exports-wildcard/" + ); + + resolver.resolve( + {}, + fixture, + "m/middle-1/nested/f.js", + {}, + (err, result) => { + if (err) return done(err); + if (!result) throw new Error("No result"); + result.should.equal( + path.resolve(fixture, "./node_modules/m/src/middle-1/nested/f.js") + ); + done(); + } + ); + }); + + it("should resolve with wildcard pattern #6", done => { + const fixture = path.resolve( + __dirname, + "./fixtures/imports-exports-wildcard/" + ); + + resolver.resolve( + {}, + fixture, + "m/middle-2/nested/f.js", + {}, + (err, result) => { + if (err) return done(err); + if (!result) throw new Error("No result"); + result.should.equal( + path.resolve(fixture, "./node_modules/m/src/middle-2/nested/f.js") + ); + done(); + } + ); + }); + + it("should resolve with wildcard pattern #7", done => { + const fixture = path.resolve( + __dirname, + "./fixtures/imports-exports-wildcard/" + ); + + resolver.resolve({}, fixture, "m/middle-3/nested/f", {}, (err, result) => { + if (err) return done(err); + if (!result) throw new Error("No result"); + result.should.equal( + path.resolve( + fixture, + "./node_modules/m/src/middle-3/nested/f/nested/f.js" + ) + ); + done(); + }); + }); + + it("should resolve with wildcard pattern #8", done => { + const fixture = path.resolve( + __dirname, + "./fixtures/imports-exports-wildcard/" + ); + + resolver.resolve({}, fixture, "m/middle-4/f/nested", {}, (err, result) => { + if (err) return done(err); + if (!result) throw new Error("No result"); + result.should.equal( + path.resolve(fixture, "./node_modules/m/src/middle-4/f/f.js") + ); + done(); + }); + }); + + it("should resolve with wildcard pattern #9", done => { + const fixture = path.resolve( + __dirname, + "./fixtures/imports-exports-wildcard/" + ); + + resolver.resolve({}, fixture, "m/middle-5/f$/$", {}, (err, result) => { + if (err) return done(err); + if (!result) throw new Error("No result"); + result.should.equal( + path.resolve(fixture, "./node_modules/m/src/middle-5/f$/$.js") + ); + done(); + }); + }); + + it("should throw error if target is 'null'", done => { + const fixture = path.resolve( + __dirname, + "./fixtures/imports-exports-wildcard/" + ); + + resolver.resolve( + {}, + fixture, + "m/features/internal/file.js", + {}, + (err, result) => { + if (!err) throw new Error(`expect error, got ${result}`); + err.should.be.instanceof(Error); + err.message.should.match( + /Package path \.\/features\/internal\/file\.js is not exported/ + ); + done(); + } + ); + }); }); diff --git a/test/fixtures/imports-exports-wildcard/node_modules/m/package.json b/test/fixtures/imports-exports-wildcard/node_modules/m/package.json new file mode 100644 index 00000000..aa2be496 --- /dev/null +++ b/test/fixtures/imports-exports-wildcard/node_modules/m/package.json @@ -0,0 +1,17 @@ +{ + "name": "m", + "exports": { + "./features-no-ext/*": "./src/features/*", + "./features/*.js": "./src/features/*.js", + "./features/internal/*": null, + "./middle/nested/f.js": "./src/middle/nested/f.js", + "./middle-1/nested/*.js": "./src/middle-1/nested/*.js", + "./middle-2/*/f.js": "./src/middle-2/*/f.js", + "./middle-3/*": "./src/middle-3/*/*.js", + "./middle-4/*/nested": "./src/middle-4/*/*.js", + "./middle-5/*/$": "./src/middle-5/*/$.js" + }, + "imports": { + "#internal/*.js": "./src/internal/*.js" + } +} diff --git a/test/fixtures/imports-exports-wildcard/node_modules/m/src/features/f.js b/test/fixtures/imports-exports-wildcard/node_modules/m/src/features/f.js new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/imports-exports-wildcard/node_modules/m/src/features/internal/file.js b/test/fixtures/imports-exports-wildcard/node_modules/m/src/features/internal/file.js new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/imports-exports-wildcard/node_modules/m/src/features/y/y.js b/test/fixtures/imports-exports-wildcard/node_modules/m/src/features/y/y.js new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/imports-exports-wildcard/node_modules/m/src/internal/i.js b/test/fixtures/imports-exports-wildcard/node_modules/m/src/internal/i.js new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/imports-exports-wildcard/node_modules/m/src/middle-1/f.js b/test/fixtures/imports-exports-wildcard/node_modules/m/src/middle-1/f.js new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/imports-exports-wildcard/node_modules/m/src/middle-1/nested/f.js b/test/fixtures/imports-exports-wildcard/node_modules/m/src/middle-1/nested/f.js new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/imports-exports-wildcard/node_modules/m/src/middle-2/nested/f.js b/test/fixtures/imports-exports-wildcard/node_modules/m/src/middle-2/nested/f.js new file mode 100644 index 00000000..e5b70a86 --- /dev/null +++ b/test/fixtures/imports-exports-wildcard/node_modules/m/src/middle-2/nested/f.js @@ -0,0 +1 @@ +module.exports = { nested: "nested" } diff --git a/test/fixtures/imports-exports-wildcard/node_modules/m/src/middle-3/nested/f/nested/f.js b/test/fixtures/imports-exports-wildcard/node_modules/m/src/middle-3/nested/f/nested/f.js new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/imports-exports-wildcard/node_modules/m/src/middle-4/f/f.js b/test/fixtures/imports-exports-wildcard/node_modules/m/src/middle-4/f/f.js new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/imports-exports-wildcard/node_modules/m/src/middle-5/f$/$.js b/test/fixtures/imports-exports-wildcard/node_modules/m/src/middle-5/f$/$.js new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/imports-exports-wildcard/node_modules/m/src/middle-5/f/$.js b/test/fixtures/imports-exports-wildcard/node_modules/m/src/middle-5/f/$.js new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/imports-exports-wildcard/node_modules/m/src/middle/f.js b/test/fixtures/imports-exports-wildcard/node_modules/m/src/middle/f.js new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/imports-exports-wildcard/node_modules/m/src/middle/nested/f.js b/test/fixtures/imports-exports-wildcard/node_modules/m/src/middle/nested/f.js new file mode 100644 index 00000000..e69de29b diff --git a/test/importsField.js b/test/importsField.js index 334cd87a..59eacba4 100644 --- a/test/importsField.js +++ b/test/importsField.js @@ -1357,4 +1357,17 @@ describe("ImportsFieldPlugin", () => { } ); }); + + it("should resolve with wildcard pattern", done => { + const fixture = path.resolve( + __dirname, + "./fixtures/imports-exports-wildcard/node_modules/m/" + ); + resolver.resolve({}, fixture, "#internal/i.js", {}, (err, result) => { + if (err) return done(err); + if (!result) throw new Error("No result"); + result.should.equal(path.resolve(fixture, "./src/internal/i.js")); + done(); + }); + }); });