Repository: webpack/enhanced-resolve
Pull request #279 (2021-01-11 20:27:37)

add support for wildcard exports/imports mapping
fix a few bugs in exports/imports handling


Patch:
diff --git a/lib/util/entrypoints.js b/lib/util/entrypoints.js
index ed9c69c..b925d62 100644
--- a/lib/util/entrypoints.js
+++ b/lib/util/entrypoints.js
@@ -15,6 +15,7 @@
  * @typedef {Object} PathTreeNode
  * @property {Map<string, PathTreeNode>|null} children
  * @property {MappingValue} folder
+ * @property {Map<string, MappingValue>|null} wildcards
  * @property {Map<string, MappingValue>} files
  */
 
@@ -113,21 +114,22 @@ module.exports.processImportsField = function processImportsField(
 
 /**
  * @param {PathTreeNode} treeRoot root
- * @param {(s: string) => void} assertRequest assertRequest
+ * @param {(s: string) => string} assertRequest assertRequest
  * @param {(s: string, f: boolean) => void} assertTarget assertTarget
  * @returns {FieldProcessor} field processor
  */
 function createFieldProcessor(treeRoot, assertRequest, assertTarget) {
 	return function fieldProcessor(request, conditionNames) {
-		assertRequest(request);
+		request = assertRequest(request);
 
 		const match = findMatch(request, treeRoot);
 
 		if (match === null) return [];
 
+		const [mapping, remainRequestIndex] = match;
+
 		/** @type {DirectMapping|null} */
 		let direct = null;
-		const [mapping, remainRequestIndex] = match;
 
 		if (isConditionalMapping(mapping)) {
 			direct = conditionalMapping(
@@ -142,12 +144,15 @@ function createFieldProcessor(treeRoot, assertRequest, assertTarget) {
 		}
 
 		const remainingRequest =
-			remainRequestIndex !== request.length
-				? request.slice(remainRequestIndex)
-				: undefined;
+			remainRequestIndex === request.length + 1
+				? undefined
+				: remainRequestIndex < 0
+				? request.slice(-remainRequestIndex - 1)
+				: request.slice(remainRequestIndex);
 
 		return directMapping(
 			remainingRequest,
+			remainRequestIndex < 0,
 			direct,
 			conditionNames,
 			assertTarget
@@ -157,22 +162,26 @@ function createFieldProcessor(treeRoot, assertRequest, assertTarget) {
 
 /**
  * @param {string} request request
+ * @returns {string} updated request
  */
 function assertExportsFieldRequest(request) {
 	if (request.charCodeAt(0) !== dotCode) {
 		throw new Error('Request should be relative path and start with "."');
 	}
-	if (request.length === 1) return;
+	if (request.length === 1) return "";
 	if (request.charCodeAt(1) !== slashCode) {
 		throw new Error('Request should be relative path and start with "./"');
 	}
 	if (request.charCodeAt(request.length - 1) === slashCode) {
 		throw new Error("Only requesting file allowed");
 	}
+
+	return request.slice(2);
 }
 
 /**
  * @param {string} request request
+ * @returns {string} updated request
  */
 function assertImportsFieldRequest(request) {
 	if (request.charCodeAt(0) !== hashCode) {
@@ -187,6 +196,8 @@ function assertImportsFieldRequest(request) {
 	if (request.charCodeAt(request.length - 1) === slashCode) {
 		throw new Error("Only requesting file allowed");
 	}
+
+	return request.slice(1);
 }
 
 /**
@@ -244,49 +255,76 @@ function assertImportTarget(imp, expectFolder) {
  * Trying to match request to field
  * @param {string} request request
  * @param {PathTreeNode} treeRoot path tree root
- * @returns {[MappingValue, number]|null} match or null
+ * @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
  */
 function findMatch(request, treeRoot) {
-	if (request.length === 1) {
-		const value = treeRoot.files.get("*root*");
+	if (request.length === 0) {
+		const value = treeRoot.files.get("");
 
 		return value ? [value, 1] : null;
 	}
 
-	if (treeRoot.children === null && treeRoot.folder === null) {
+	if (
+		treeRoot.children === null &&
+		treeRoot.folder === null &&
+		treeRoot.wildcards === null
+	) {
 		const value = treeRoot.files.get(request);
 
-		return value ? [value, request.length] : null;
+		return value ? [value, request.length + 1] : null;
 	}
 
 	let node = treeRoot;
 	let lastNonSlashIndex = 0;
-	let slashIndex = request.indexOf("/", 2);
+	let slashIndex = request.indexOf("/", 0);
 
 	/** @type {[MappingValue, number]|null} */
 	let lastFolderMatch = null;
 
-	while (slashIndex !== -1) {
-		const folder = request.slice(lastNonSlashIndex, slashIndex);
-
+	const applyFolderMapping = () => {
 		const folderMapping = node.folder;
 		if (folderMapping) {
 			if (lastFolderMatch) {
 				lastFolderMatch[0] = folderMapping;
-				lastFolderMatch[1] = lastNonSlashIndex;
+				lastFolderMatch[1] = -lastNonSlashIndex - 1;
 			} else {
-				lastFolderMatch = [folderMapping, lastNonSlashIndex || 2];
+				lastFolderMatch = [folderMapping, -lastNonSlashIndex - 1];
 			}
 		}
+	};
+
+	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;
+					}
+				}
+			}
+		}
+	};
+
+	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) {
-			const value = node.folder;
-
-			return value ? [value, lastNonSlashIndex] : null;
+			return lastFolderMatch;
 		}
 
 		node = newNode;
@@ -294,18 +332,18 @@ function findMatch(request, treeRoot) {
 		slashIndex = request.indexOf("/", lastNonSlashIndex);
 	}
 
-	const value = node.files.get(
-		lastNonSlashIndex > 0 ? request.slice(lastNonSlashIndex) : request
-	);
+	const remainingRequest =
+		lastNonSlashIndex > 0 ? request.slice(lastNonSlashIndex) : request;
+
+	const value = node.files.get(remainingRequest);
 
 	if (value) {
-		return [value, request.length];
+		return [value, request.length + 1];
 	}
 
-	const folderMapping = node.folder;
-	if (folderMapping) {
-		return [folderMapping, lastNonSlashIndex || 2];
-	}
+	applyFolderMapping();
+
+	applyWildcardMappings(node.wildcards, remainingRequest);
 
 	return lastFolderMatch;
 }
@@ -322,35 +360,34 @@ function isConditionalMapping(mapping) {
 
 /**
  * @param {string|undefined} remainingRequest remaining request when folder mapping, undefined for file mappings
- * @param {DirectMapping|null} dirrectMapping_ direct export
+ * @param {boolean} subpathMapping true, for subpath mappings
+ * @param {DirectMapping|null} mappingTarget direct export
  * @param {Set<string>} conditionNames condition names
  * @param {(d: string, f: boolean) => void} assert asserting direct value
  * @returns {string[]} mapping result
  */
 function directMapping(
 	remainingRequest,
-	dirrectMapping_,
+	subpathMapping,
+	mappingTarget,
 	conditionNames,
 	assert
 ) {
-	if (dirrectMapping_ === null) return [];
-
-	const expectFolder = remainingRequest !== undefined;
+	if (mappingTarget === null) return [];
 
-	if (typeof dirrectMapping_ === "string") {
-		assert(dirrectMapping_, expectFolder);
-
-		return expectFolder
-			? [`${dirrectMapping_}${remainingRequest}`]
-			: [dirrectMapping_];
+	if (typeof mappingTarget === "string") {
+		return [
+			targetMapping(remainingRequest, subpathMapping, mappingTarget, assert)
+		];
 	}
 
 	const targets = [];
 
-	for (const exp of dirrectMapping_) {
+	for (const exp of mappingTarget) {
 		if (typeof exp === "string") {
-			assert(exp, expectFolder);
-			targets.push(expectFolder ? `${exp}${remainingRequest}` : exp);
+			targets.push(
+				targetMapping(remainingRequest, subpathMapping, exp, assert)
+			);
 			continue;
 		}
 
@@ -358,6 +395,7 @@ function directMapping(
 		if (!mapping) continue;
 		const innerExports = directMapping(
 			remainingRequest,
+			subpathMapping,
 			mapping,
 			conditionNames,
 			assert
@@ -370,6 +408,31 @@ function directMapping(
 	return targets;
 }
 
+/**
+ * @param {string|undefined} remainingRequest remaining request when folder mapping, undefined for file mappings
+ * @param {boolean} subpathMapping 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,
+	mappingTarget,
+	assert
+) {
+	if (remainingRequest === undefined) {
+		assert(mappingTarget, false);
+		return mappingTarget;
+	}
+	if (subpathMapping) {
+		assert(mappingTarget, true);
+		return mappingTarget + remainingRequest;
+	}
+	assert(mappingTarget, false);
+	return mappingTarget.replace(/\*/g, remainingRequest.replace(/\$/g, "$$"));
+}
+
 /**
  * @param {ConditionalMapping} conditionalMapping_ conditional mapping
  * @param {Set<string>} conditionNames condition names
@@ -433,6 +496,7 @@ function createNode() {
 	return {
 		children: null,
 		folder: null,
+		wildcards: null,
 		files: new Map()
 	};
 }
@@ -444,25 +508,20 @@ function createNode() {
  * @param {MappingValue} target target
  */
 function walkPath(root, path, target) {
-	if (path.length === 2 && path === "./") {
+	if (path.length === 0) {
 		root.folder = target;
 		return;
 	}
 
 	let node = root;
-	// It is safe to store # and ./ as a part of file
-	// because mapping works like string concatenation
-	// so typical path tree can looks like
+	// Typical path tree can looks like
 	// root
-	// - files: ["./a.js", "./b.js"]
+	// - files: ["a.js", "b.js"]
 	// - children:
 	//    node1:
 	//    - files: ["a.js", "b.js"]
 	let lastNonSlashIndex = 0;
-	// This is safe for "imports" field
-	// since specifiers "#" and "#/" are disallowed and
-	// should be asserted before "walking"
-	let slashIndex = path.indexOf("/", 2);
+	let slashIndex = path.indexOf("/", 0);
 
 	while (slashIndex !== -1) {
 		const folder = path.slice(lastNonSlashIndex, slashIndex);
@@ -486,13 +545,16 @@ function walkPath(root, path, target) {
 		slashIndex = path.indexOf("/", lastNonSlashIndex);
 	}
 
-	if (lastNonSlashIndex < path.length) {
-		node.files.set(
-			lastNonSlashIndex > 0 ? path.slice(lastNonSlashIndex) : path,
-			target
-		);
-	} else {
+	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);
+		}
 	}
 }
 
@@ -505,11 +567,11 @@ function buildExportsFieldPathTree(field) {
 
 	// handle syntax sugar, if exports field is direct mapping for "."
 	if (typeof field === "string") {
-		root.files.set("*root*", field);
+		root.files.set("", field);
 
 		return root;
 	} else if (Array.isArray(field)) {
-		root.files.set("*root*", field.slice());
+		root.files.set("", field.slice());
 
 		return root;
 	}
@@ -534,7 +596,7 @@ function buildExportsFieldPathTree(field) {
 					i++;
 				}
 
-				root.files.set("*root*", field);
+				root.files.set("", field);
 				return root;
 			}
 
@@ -546,7 +608,7 @@ function buildExportsFieldPathTree(field) {
 		}
 
 		if (key.length === 1) {
-			root.files.set("*root*", field[key]);
+			root.files.set("", field[key]);
 			continue;
 		}
 
@@ -558,7 +620,7 @@ function buildExportsFieldPathTree(field) {
 			);
 		}
 
-		walkPath(root, key, field[key]);
+		walkPath(root, key.slice(2), field[key]);
 	}
 
 	return root;
@@ -598,7 +660,7 @@ function buildImportsFieldPathTree(field) {
 			);
 		}
 
-		walkPath(root, key, field[key]);
+		walkPath(root, key.slice(1), field[key]);
 	}
 
 	return root;


Test patch:
diff --git a/test/exportsField.js b/test/exportsField.js
index c83b40d..03f62b8 100644
--- a/test/exportsField.js
+++ b/test/exportsField.js
@@ -30,6 +30,21 @@ describe("Process exports field", function exportsField() {
 				["import", "webpack"]
 			]
 		},
+		{
+			name: "sample #1 (wildcard)",
+			expect: ["./dist/test/file.js", "./src/test/file.js"],
+			suite: [
+				{
+					"./foo/*": {
+						import: ["./dist/*", "./src/*"],
+						webpack: "./wp/*"
+					},
+					".": "./main.js"
+				},
+				"./foo/test/file.js",
+				["import", "webpack"]
+			]
+		},
 		{
 			name: "sample #2",
 			expect: ["./data/timezones/pdt.mjs"],
@@ -41,6 +56,17 @@ describe("Process exports field", function exportsField() {
 				[]
 			]
 		},
+		{
+			name: "sample #2 (wildcard)",
+			expect: ["./data/timezones/pdt.mjs"],
+			suite: [
+				{
+					"./timezones/*": "./data/timezones/*"
+				},
+				"./timezones/pdt.mjs",
+				[]
+			]
+		},
 		{
 			name: "sample #3",
 			// mapping works like concatenating strings not file paths
@@ -53,6 +79,18 @@ describe("Process exports field", function exportsField() {
 				[]
 			]
 		},
+		{
+			name: "sample #3 (wildcard)",
+			// mapping works like concatenating strings not file paths
+			expect: ["./data/timezones/timezones/pdt.mjs"],
+			suite: [
+				{
+					"./*": "./data/timezones/*.mjs"
+				},
+				"./timezones/pdt",
+				[]
+			]
+		},
 		{
 			name: "sample #4",
 			expect: [],
@@ -69,6 +107,22 @@ describe("Process exports field", function exportsField() {
 				["browser"]
 			]
 		},
+		{
+			name: "sample #4 (wildcard)",
+			expect: [],
+			suite: [
+				{
+					"./lib/*": {
+						browser: ["./browser/*"]
+					},
+					"./dist/index.js": {
+						node: "./index.js"
+					}
+				},
+				"./dist/index.js",
+				["browser"]
+			]
+		},
 		{
 			name: "sample #5",
 			expect: ["./browser/index.js"], // default condition used
@@ -86,6 +140,23 @@ describe("Process exports field", function exportsField() {
 				["browser"]
 			]
 		},
+		{
+			name: "sample #5 (wildcard)",
+			expect: ["./browser/index.js"], // default condition used
+			suite: [
+				{
+					"./lib/*": {
+						browser: ["./browser/*"]
+					},
+					"./dist/index.js": {
+						node: "./index.js",
+						default: "./browser/index.js"
+					}
+				},
+				"./dist/index.js",
+				["browser"]
+			]
+		},
 		{
 			name: "sample #6",
 			expect: [],
@@ -108,6 +179,17 @@ describe("Process exports field", function exportsField() {
 				[]
 			]
 		},
+		{
+			name: "sample #7 (wildcard)",
+			expect: [],
+			suite: [
+				{
+					"./dist/a/a/*": "./dist/index.js"
+				},
+				"./dist/a/a",
+				[]
+			]
+		},
 		{
 			name: "sample #8",
 			expect: [],
@@ -140,7 +222,8 @@ describe("Process exports field", function exportsField() {
 					"./🎉": "./ok.js",
 					"./%F0%9F%8E%89": "./other.js",
 					"./bar#foo": "./ok.js",
-					"./#zapp/": "./"
+					"./#zapp/": "./",
+					"./#zipp*": "./zzz*"
 				},
 				"./#foo",
 				[]
@@ -156,7 +239,8 @@ describe("Process exports field", function exportsField() {
 					"./🎉": "./ok.js",
 					"./%F0%9F%8E%89": "./other.js",
 					"./bar#foo": "./ok.js",
-					"./#zapp/": "./"
+					"./#zapp/": "./",
+					"./#zipp*": "./zzz*"
 				},
 				"./bar#foo",
 				[]
@@ -172,7 +256,25 @@ describe("Process exports field", function exportsField() {
 					"./🎉": "./ok.js",
 					"./%F0%9F%8E%89": "./other.js",
 					"./bar#foo": "./ok.js",
-					"./#zapp/": "./"
+					"./#zapp/": "./",
+					"./#zipp*": "./zzz*"
+				},
+				"./#zapp/ok.js#abc",
+				[]
+			]
+		},
+		{
+			name: "sample #12",
+			expect: ["./ok.js#abc"],
+			suite: [
+				{
+					"./#foo": "./ok.js",
+					"./module": "./ok.js",
+					"./🎉": "./ok.js",
+					"./%F0%9F%8E%89": "./other.js",
+					"./bar#foo": "./ok.js",
+					"./#zapp/": "./",
+					"./#zipp*": "./zzz*"
 				},
 				"./#zapp/ok.js#abc",
 				[]
@@ -188,7 +290,8 @@ describe("Process exports field", function exportsField() {
 					"./🎉": "./ok.js",
 					"./%F0%9F%8E%89": "./other.js",
 					"./bar#foo": "./ok.js",
-					"./#zapp/": "./"
+					"./#zapp/": "./",
+					"./#zipp*": "./zzz*"
 				},
 				"./#zapp/ok.js?abc",
 				[]
@@ -204,7 +307,8 @@ describe("Process exports field", function exportsField() {
 					"./🎉": "./ok.js",
 					"./%F0%9F%8E%89": "./other.js",
 					"./bar#foo": "./ok.js",
-					"./#zapp/": "./"
+					"./#zapp/": "./",
+					"./#zipp*": "./zzz*"
 				},
 				"./#zapp/🎉.js",
 				[]
@@ -220,7 +324,8 @@ describe("Process exports field", function exportsField() {
 					"./🎉": "./ok.js",
 					"./%F0%9F%8E%89": "./other.js",
 					"./bar#foo": "./ok.js",
-					"./#zapp/": "./"
+					"./#zapp/": "./",
+					"./#zipp*": "./zzz*"
 				},
 				// "🎉" percent encoded
 				"./#zapp/%F0%9F%8E%89.js",
@@ -237,7 +342,8 @@ describe("Process exports field", function exportsField() {
 					"./🎉": "./ok.js",
 					"./%F0%9F%8E%89": "./other.js",
 					"./bar#foo": "./ok.js",
-					"./#zapp/": "./"
+					"./#zapp/": "./",
+					"./#zipp*": "./zzz*"
 				},
 				"./🎉",
 				[]
@@ -253,7 +359,8 @@ describe("Process exports field", function exportsField() {
 					"./🎉": "./ok.js",
 					"./%F0%9F%8E%89": "./other.js",
 					"./bar#foo": "./ok.js",
-					"./#zapp/": "./"
+					"./#zapp/": "./",
+					"./#zipp*": "./zzz*"
 				},
 				"./%F0%9F%8E%89",
 				[]
@@ -269,7 +376,8 @@ describe("Process exports field", function exportsField() {
 					"./🎉": "./ok.js",
 					"./%F0%9F%8E%89": "./other.js",
 					"./bar#foo": "./ok.js",
-					"./#zapp/": "./"
+					"./#zapp/": "./",
+					"./#zipp*": "./zzz*"
 				},
 				"./module",
 				[]
@@ -285,7 +393,8 @@ describe("Process exports field", function exportsField() {
 					"./🎉": "./ok.js",
 					"./%F0%9F%8E%89": "./other.js",
 					"./bar#foo": "./ok.js",
-					"./#zapp/": "./"
+					"./#zapp/": "./",
+					"./#zipp*": "./zzz*"
 				},
 				"./module#foo",
 				[]
@@ -301,7 +410,8 @@ describe("Process exports field", function exportsField() {
 					"./🎉": "./ok.js",
 					"./%F0%9F%8E%89": "./other.js",
 					"./bar#foo": "./ok.js",
-					"./#zapp/": "./"
+					"./#zapp/": "./",
+					"./#zipp*": "./zzz*"
 				},
 				"./module?foo",
 				[]
@@ -309,6 +419,23 @@ describe("Process exports field", function exportsField() {
 		},
 		{
 			name: "sample #21",
+			expect: ["./zizizi"],
+			suite: [
+				{
+					"./#foo": "./ok.js",
+					"./module": "./ok.js",
+					"./🎉": "./ok.js",
+					"./%F0%9F%8E%89": "./other.js",
+					"./bar#foo": "./ok.js",
+					"./#zapp/": "./",
+					"./#zipp*": "./z*z*z*"
+				},
+				"./#zippi",
+				[]
+			]
+		},
+		{
+			name: "sample #22",
 			expect: ["./d?e?f"],
 			suite: [
 				{
@@ -338,6 +465,7 @@ describe("Process exports field", function exportsField() {
 			suite: [
 				{
 					"./": "./",
+					"./*": "./*",
 					"./dist/index.js": "./dist/index.js"
 				},
 				".",
@@ -350,6 +478,8 @@ describe("Process exports field", function exportsField() {
 			suite: [
 				{
 					"./dist/": "./dist/",
+					"./dist/*": "./dist/*",
+					"./dist*": "./dist*",
 					"./dist/index.js": "./dist/a.js"
 				},
 				"./dist/index.js",
@@ -364,6 +494,9 @@ describe("Process exports field", function exportsField() {
 					"./": {
 						browser: ["./browser/"]
 					},
+					"./*": {
+						browser: ["./browser/*"]
+					},
 					"./dist/index.js": {
 						browser: "./index.js"
 					}
@@ -382,6 +515,9 @@ describe("Process exports field", function exportsField() {
 					"./": {
 						browser: ["./browser/"]
 					},
+					"./*": {
+						browser: ["./browser/*"]
+					},
 					"./dist/index.js": {
 						node: "./node.js"
 					}
@@ -556,6 +692,17 @@ describe("Process exports field", function exportsField() {
 				[]
 			]
 		},
+		{
+			name: "mapping to a folder root #3 (wildcard)",
+			expect: ["./data/timezones/pdt/index.mjs"],
+			suite: [
+				{
+					"./timezones/pdt/*": "./data/timezones/pdt/*"
+				},
+				"./timezones/pdt/index.mjs",
+				[]
+			]
+		},
 		{
 			name: "mapping to a folder root #4",
 			expect: ["./timezones/pdt.mjs"],
@@ -567,6 +714,17 @@ describe("Process exports field", function exportsField() {
 				[]
 			]
 		},
+		{
+			name: "mapping to a folder root #4 (wildcard)",
+			expect: ["./timezones/pdt.mjs"],
+			suite: [
+				{
+					"./*": "./timezones/*"
+				},
+				"./pdt.mjs",
+				[]
+			]
+		},
 		{
 			name: "mapping to a folder root #5",
 			expect: ["./timezones/pdt.mjs"],
@@ -578,6 +736,17 @@ describe("Process exports field", function exportsField() {
 				[]
 			]
 		},
+		{
+			name: "mapping to a folder root #5 (wildcard)",
+			expect: ["./timezones/pdt.mjs"],
+			suite: [
+				{
+					"./*": "./*"
+				},
+				"./timezones/pdt.mjs",
+				[]
+			]
+		},
 		{
 			name: "mapping to a folder root #6",
 			expect: new Error(), // not a folder mapping
@@ -589,6 +758,17 @@ describe("Process exports field", function exportsField() {
 				[]
 			]
 		},
+		{
+			name: "mapping to a folder root #6 (wildcard)",
+			expect: new Error(), // not a valid mapping
+			suite: [
+				{
+					"./*": "."
+				},
+				"./timezones/pdt.mjs",
+				[]
+			]
+		},
 		{
 			name: "mapping to a folder root #7",
 			expect: [], // incorrect export field, but value did not processed
@@ -600,6 +780,17 @@ describe("Process exports field", function exportsField() {
 				[]
 			]
 		},
+		{
+			name: "mapping to a folder root #7 (wildcard)",
+			expect: [], // incorrect export field, but value did not processed
+			suite: [
+				{
+					".": "./*"
+				},
+				"./timezones/pdt.mjs",
+				[]
+			]
+		},
 		//#endregion
 
 		//#region The longest matching path prefix is prioritized
@@ -617,6 +808,20 @@ describe("Process exports field", function exportsField() {
 				[]
 			]
 		},
+		{
+			name: "the longest matching path prefix is prioritized #1 (wildcard)",
+			// it does not work same as conditional mapping,
+			// so there is no match for ./dist/index.mjs
+			expect: ["./lib/index.mjs"],
+			suite: [
+				{
+					"./*": "./*",
+					"./dist/*": "./lib/*"
+				},
+				"./dist/index.mjs",
+				[]
+			]
+		},
 		{
 			name: "the longest matching path prefix is prioritized #2",
 			expect: ["./dist/utils/index.js"],
@@ -629,6 +834,18 @@ describe("Process exports field", function exportsField() {
 				[]
 			]
 		},
+		{
+			name: "the longest matching path prefix is prioritized #2 (wildcard)",
+			expect: ["./dist/utils/index.js"],
+			suite: [
+				{
+					"./dist/utils/*": "./dist/utils/*",
+					"./dist/*": "./lib/*"
+				},
+				"./dist/utils/index.js",
+				[]
+			]
+		},
 		{
 			name: "the longest matching path prefix is prioritized #3",
 			// direct mapping is prioritize
@@ -645,6 +862,22 @@ describe("Process exports field", function exportsField() {
 				[]
 			]
 		},
+		{
+			name: "the longest matching path prefix is prioritized #3 (wildcard)",
+			// direct mapping is prioritize
+			// it does not work same as conditional mapping,
+			// so there is no match for ./dist/utils/index.mjs
+			expect: ["./dist/utils/index.js"],
+			suite: [
+				{
+					"./dist/utils/index.js": "./dist/utils/index.js",
+					"./dist/utils/*": "./dist/utils/index.mjs",
+					"./dist/*": "./lib/*"
+				},
+				"./dist/utils/index.js",
+				[]
+			]
+		},
 		{
 			name: "the longest matching path prefix is prioritized #4",
 			// it does not work same as conditional mapping,
@@ -662,6 +895,23 @@ describe("Process exports field", function exportsField() {
 				["browser"]
 			]
 		},
+		{
+			name: "the longest matching path prefix is prioritized #4 (wildcard)",
+			// it does not work same as conditional mapping,
+			// even if right side is a conditional mapping,
+			// so there is no match for ./browser/dist/index.mjs
+			expect: ["./lib/index.mjs"],
+			suite: [
+				{
+					"./*": {
+						browser: "./browser/*"
+					},
+					"./dist/*": "./lib/*"
+				},
+				"./dist/index.mjs",
+				["browser"]
+			]
+		},
 		//#endregion
 
 		//#region Conditional mapping folder
@@ -679,6 +929,20 @@ describe("Process exports field", function exportsField() {
 				["browser"]
 			]
 		},
+		{
+			name: "conditional mapping folder #1 (wildcard)",
+			expect: ["lodash/index.js", "./utils/index.js"],
+			suite: [
+				{
+					"./utils/*": {
+						browser: ["lodash/*", "./utils/*"],
+						node: ["./utils-node/*"]
+					}
+				},
+				"./utils/index.js",
+				["browser"]
+			]
+		},
 		{
 			name: "conditional mapping folder #2",
 			expect: [], // no condition names
@@ -694,6 +958,21 @@ describe("Process exports field", function exportsField() {
 				[]
 			]
 		},
+		{
+			name: "conditional mapping folder #2 (wildcard)",
+			expect: [], // no condition names
+			suite: [
+				{
+					"./utils/*": {
+						webpack: "./wpk/*",
+						browser: ["lodash/*", "./utils/*"],
+						node: ["./node/*"]
+					}
+				},
+				"./utils/index.mjs",
+				[]
+			]
+		},
 		{
 			name: "conditional mapping folder #3",
 			expect: ["./wpk/index.mjs"],
@@ -709,6 +988,21 @@ describe("Process exports field", function exportsField() {
 				["browser", "webpack"]
 			]
 		},
+		{
+			name: "conditional mapping folder #3 (wildcard)",
+			expect: ["./wpk/index.mjs"],
+			suite: [
+				{
+					"./utils/*": {
+						webpack: "./wpk/*",
+						browser: ["lodash/*", "./utils/*"],
+						node: ["./utils/*"]
+					}
+				},
+				"./utils/index.mjs",
+				["browser", "webpack"]
+			]
+		},
 		//#endregion
 
 		//#region Incorrect exports field definition
@@ -762,6 +1056,20 @@ describe("Process exports field", function exportsField() {
 				["browser"]
 			]
 		},
+		{
+			name: "incorrect exports field #4 (wildcard)",
+			expect: new Error(),
+			suite: [
+				{
+					"./utils/*": {
+						browser: "/a/",
+						default: "/b/"
+					}
+				},
+				"./utils/index.mjs",
+				["browser"]
+			]
+		},
 		{
 			name: "incorrect exports field #5",
 			expect: [],
@@ -883,6 +1191,17 @@ describe("Process exports field", function exportsField() {
 				[]
 			]
 		},
+		{
+			name: "backtracking package base #1 (wildcard)",
+			expect: ["./dist/index"], // we don't handle backtracking here
+			suite: [
+				{
+					"./../../utils/*": "./dist/*"
+				},
+				"./../../utils/index",
+				[]
+			]
+		},
 		{
 			name: "backtracking package base #2",
 			expect: new Error(),
@@ -894,6 +1213,17 @@ describe("Process exports field", function exportsField() {
 				[]
 			]
 		},
+		{
+			name: "backtracking package base #2 (wildcard)",
+			expect: new Error(),
+			suite: [
+				{
+					"../../utils/*": "./dist/*"
+				},
+				"../../utils/index",
+				[]
+			]
+		},
 		{
 			name: "backtracking package base #3",
 			expect: new Error(),
@@ -905,6 +1235,17 @@ describe("Process exports field", function exportsField() {
 				[]
 			]
 		},
+		{
+			name: "backtracking package base #3 (wildcard)",
+			expect: new Error(),
+			suite: [
+				{
+					"./utils/*": "../src/*"
+				},
+				"./utils/index",
+				[]
+			]
+		},
 		{
 			name: "backtracking package base #4",
 			expect: ["./../src/index"], // we don't handle backtracking here
@@ -916,6 +1257,17 @@ describe("Process exports field", function exportsField() {
 				[]
 			]
 		},
+		{
+			name: "backtracking package base #4 (wildcard)",
+			expect: ["./../src/index"], // we don't handle backtracking here
+			suite: [
+				{
+					"./utils/*": "./../src/*"
+				},
+				"./utils/index",
+				[]
+			]
+		},
 		{
 			name: "backtracking package base #5",
 			// fits package base depth
@@ -955,6 +1307,20 @@ describe("Process exports field", function exportsField() {
 				["browser"]
 			]
 		},
+		{
+			name: "backtracking package base #7",
+			// enhanced-resolve don't know is this same package or not
+			expect: new Error(),
+			suite: [
+				{
+					"./utils/*": {
+						browser: "../this/*"
+					}
+				},
+				"./utils/index",
+				["browser"]
+			]
+		},
 		{
 			name: "backtracking package base #8",
 			expect: ["./utils/../index"],
@@ -968,6 +1334,19 @@ describe("Process exports field", function exportsField() {
 				["browser"]
 			]
 		},
+		{
+			name: "backtracking package base #8 (wildcard)",
+			expect: ["./utils/../index"],
+			suite: [
+				{
+					"./utils/*": {
+						browser: "./utils/../*"
+					}
+				},
+				"./utils/index",
+				["browser"]
+			]
+		},
 		{
 			name: "backtracking package base #9",
 			expect: ["./dist/index"],
@@ -980,6 +1359,18 @@ describe("Process exports field", function exportsField() {
 				["browser"]
 			]
 		},
+		{
+			name: "backtracking package base #9 (wildcard)",
+			expect: ["./dist/index"],
+			suite: [
+				{
+					"./*": "./src/../../*",
+					"./dist/*": "./dist/*"
+				},
+				"./dist/index",
+				["browser"]
+			]
+		},
 		//#endregion
 
 		//#region Directory exports subpaths may not backtrack above the target folder
@@ -994,6 +1385,17 @@ describe("Process exports field", function exportsField() {
 				[]
 			]
 		},
+		{
+			name: "backtracking target folder #1 (wildcard)",
+			expect: ["./dist/timezone/../../index"],
+			suite: [
+				{
+					"./utils/*": "./dist/*"
+				},
+				"./utils/timezone/../../index",
+				[]
+			]
+		},
 		{
 			name: "backtracking target folder #2",
 			expect: ["./dist/timezone/../index"],
@@ -1005,6 +1407,17 @@ describe("Process exports field", function exportsField() {
 				[]
 			]
 		},
+		{
+			name: "backtracking target folder #2 (wildcard)",
+			expect: ["./dist/timezone/../index"],
+			suite: [
+				{
+					"./utils/*": "./dist/*"
+				},
+				"./utils/timezone/../index",
+				[]
+			]
+		},
 		{
 			name: "backtracking target folder #3",
 			expect: ["./dist/target/../../index"],
@@ -1016,6 +1429,17 @@ describe("Process exports field", function exportsField() {
 				[]
 			]
 		},
+		{
+			name: "backtracking target folder #3 (wildcard)",
+			expect: ["./dist/target/../../index"],
+			suite: [
+				{
+					"./utils/*": "./dist/target/*"
+				},
+				"./utils/../../index",
+				[]
+			]
+		},
 		//#endregion
 
 		//#region Exports targets cannot map into a nested node_modules path
@@ -1032,6 +1456,19 @@ describe("Process exports field", function exportsField() {
 				["browser"]
 			]
 		},
+		{
+			name: "nested node_modules path #1 (wildcard)",
+			expect: ["./node_modules/lodash/dist/index.js"], // we don't handle node_modules here
+			suite: [
+				{
+					"./utils/*": {
+						browser: "./node_modules/*"
+					}
+				},
+				"./utils/lodash/dist/index.js",
+				["browser"]
+			]
+		},
 		{
 			name: "nested node_modules path #2",
 			expect: ["./utils/../node_modules/lodash/dist/index.js"],
@@ -1043,6 +1480,17 @@ describe("Process exports field", function exportsField() {
 				[]
 			]
 		},
+		{
+			name: "nested node_modules path #2 (wildcard)",
+			expect: ["./utils/../node_modules/lodash/dist/index.js"],
+			suite: [
+				{
+					"./utils/*": "./utils/../node_modules/*"
+				},
+				"./utils/lodash/dist/index.js",
+				[]
+			]
+		},
 		//#endregion
 
 		//#region Nested mapping
@@ -1064,6 +1512,24 @@ describe("Process exports field", function exportsField() {
 				["browser"]
 			]
 		},
+		{
+			name: "nested mapping #1 (wildcard)",
+			expect: [],
+			suite: [
+				{
+					"./utils/*": {
+						browser: {
+							webpack: "./*",
+							default: {
+								node: "./node/*"
+							}
+						}
+					}
+				},
+				"./utils/index.js",
+				["browser"]
+			]
+		},
 		{
 			name: "nested mapping #2",
 			expect: ["./index.js", "./node/index.js"],
@@ -1082,6 +1548,24 @@ describe("Process exports field", function exportsField() {
 				["browser", "webpack"]
 			]
 		},
+		{
+			name: "nested mapping #2 (wildcard)",
+			expect: ["./index.js", "./node/index.js"],
+			suite: [
+				{
+					"./utils/*": {
+						browser: {
+							webpack: ["./*", "./node/*"],
+							default: {
+								node: "./node/*"
+							}
+						}
+					}
+				},
+				"./utils/index.js",
+				["browser", "webpack"]
+			]
+		},
 		{
 			name: "nested mapping #3",
 			expect: [], // no browser condition name
@@ -1100,6 +1584,24 @@ describe("Process exports field", function exportsField() {
 				["webpack"]
 			]
 		},
+		{
+			name: "nested mapping #3 (wildcard)",
+			expect: [], // no browser condition name
+			suite: [
+				{
+					"./utils/*": {
+						browser: {
+							webpack: ["./*", "./node/*"],
+							default: {
+								node: "./node/*"
+							}
+						}
+					}
+				},
+				"./utils/index.js",
+				["webpack"]
+			]
+		},
 		{
 			name: "nested mapping #4",
 			expect: ["./node/index.js"],
@@ -1118,6 +1620,24 @@ describe("Process exports field", function exportsField() {
 				["node", "browser"]
 			]
 		},
+		{
+			name: "nested mapping #4 (wildcard)",
+			expect: ["./node/index.js"],
+			suite: [
+				{
+					"./utils/*": {
+						browser: {
+							webpack: ["./*", "./node/*"],
+							default: {
+								node: "./node/*"
+							}
+						}
+					}
+				},
+				"./utils/index.js",
+				["node", "browser"]
+			]
+		},
 		{
 			name: "nested mapping #5",
 			expect: [],
@@ -1138,6 +1658,26 @@ describe("Process exports field", function exportsField() {
 				["browser", "node"]
 			]
 		},
+		{
+			name: "nested mapping #5 (wildcard)",
+			expect: [],
+			suite: [
+				{
+					"./utils/*": {
+						browser: {
+							webpack: ["./*", "./node/*"],
+							default: {
+								node: {
+									webpack: ["./wpck/*"]
+								}
+							}
+						}
+					}
+				},
+				"./utils/index.js",
+				["browser", "node"]
+			]
+		},
 		{
 			name: "nested mapping #6",
 			expect: ["./index.js", "./node/index.js"],
@@ -1158,6 +1698,26 @@ describe("Process exports field", function exportsField() {
 				["browser", "node", "webpack"]
 			]
 		},
+		{
+			name: "nested mapping #6 (wildcard)",
+			expect: ["./index.js", "./node/index.js"],
+			suite: [
+				{
+					"./utils/*": {
+						browser: {
+							webpack: ["./*", "./node/*"],
+							default: {
+								node: {
+									webpack: ["./wpck/*"]
+								}
+							}
+						}
+					}
+				},
+				"./utils/index.js",
+				["browser", "node", "webpack"]
+			]
+		},
 		{
 			name: "nested mapping #7",
 			expect: ["./y.js"],
@@ -1265,6 +1825,62 @@ describe("Process exports field", function exportsField() {
 		},
 		//#endregion
 
+		//#region Wildcards
+		{
+			name: "wildcard longest #1",
+			expect: ["./abc/d"],
+			suite: [
+				{
+					"./ab*": "./ab/*",
+					"./abc*": "./abc/*",
+					"./a*": "./a/*"
+				},
+				"./abcd",
+				["browser"]
+			]
+		},
+		{
+			name: "wildcard longest #2",
+			expect: ["./abc/d/e"],
+			suite: [
+				{
+					"./ab*": "./ab/*",
+					"./abc*": "./abc/*",
+					"./a*": "./a/*"
+				},
+				"./abcd/e",
+				["browser"]
+			]
+		},
+		{
+			name: "wildcard longest #3",
+			expect: ["./abc/d"],
+			suite: [
+				{
+					"./x/ab*": "./ab/*",
+					"./x/abc*": "./abc/*",
+					"./x/a*": "./a/*"
+				},
+				"./x/abcd",
+				["browser"]
+			]
+		},
+		{
+			name: "wildcard longest #4",
+			expect: ["./abc/d/e"],
+			suite: [
+				{
+					"./x/ab*": "./ab/*",
+					"./x/abc*": "./abc/*",
+					"./x/a*": "./a/*"
+				},
+				"./x/abcd/e",
+				["browser"]
+			]
+		},
+
+		//#endregion
+
 		{
 			name: "path tree edge case #1",
 			expect: ["./A/b/d.js"],
@@ -1277,6 +1893,18 @@ describe("Process exports field", function exportsField() {
 				[]
 			]
 		},
+		{
+			name: "path tree edge case #1 (wildcard)",
+			expect: ["./A/b/d.js"],
+			suite: [
+				{
+					"./a/*": "./A/*",
+					"./a/b/c": "./c.js"
+				},
+				"./a/b/d.js",
+				[]
+			]
+		},
 		{
 			name: "path tree edge case #2",
 			expect: ["./A/c.js"],
@@ -1288,6 +1916,42 @@ describe("Process exports field", function exportsField() {
 				"./a/c.js",
 				[]
 			]
+		},
+		{
+			name: "path tree edge case #2 (wildcard)",
+			expect: ["./A/c.js"],
+			suite: [
+				{
+					"./a/*": "./A/*",
+					"./a/b": "./b.js"
+				},
+				"./a/c.js",
+				[]
+			]
+		},
+		{
+			name: "path tree edge case #3",
+			expect: ["./A/b/d/c.js"],
+			suite: [
+				{
+					"./a/": "./A/",
+					"./a/b/c/d": "./c.js"
+				},
+				"./a/b/d/c.js",
+				[]
+			]
+		},
+		{
+			name: "path tree edge case #3 (wildcard)",
+			expect: ["./A/b/d/c.js"],
+			suite: [
+				{
+					"./a/*": "./A/*",
+					"./a/b/c/d": "./c.js"
+				},
+				"./a/b/d/c.js",
+				[]
+			]
 		}
 	];