Skip to content

Commit

Permalink
refactor: rewrite implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
alexander-akait committed Apr 17, 2023
1 parent 5b87555 commit aaec883
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 134 deletions.
235 changes: 107 additions & 128 deletions lib/util/entrypoints.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ Conditional mapping nested in another conditional mapping is called nested mappi
*/

const { fileURLToPath } = require("url");
const slashCode = "/".charCodeAt(0);
const dotCode = ".".charCodeAt(0);
const hashCode = "#".charCodeAt(0);
Expand All @@ -93,6 +94,7 @@ module.exports.processExportsField = function processExportsField(
) {
return createFieldProcessor(
buildExportsFieldPathTree(exportsField),
exportsField,
assertExportsFieldRequest,
assertExportTarget
);
Expand All @@ -107,22 +109,24 @@ module.exports.processImportsField = function processImportsField(
) {
return createFieldProcessor(
buildImportsFieldPathTree(importsField),
importsField,
assertImportsFieldRequest,
assertImportTarget
);
};

/**
* @param {PathTreeNode} treeRoot root
* @param {ExportsField | ImportsField} field exports or import field
* @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(treeRoot, field, assertRequest, assertTarget) {
return function fieldProcessor(request, conditionNames) {
request = assertRequest(request);

const match = findMatch(request, treeRoot);
const match = findMatch(request, field);

if (match === null) return [];

Expand Down Expand Up @@ -244,128 +248,110 @@ function assertImportTarget(imp, expectFolder) {
}
}

function getRemainingRequest(index, request) {
return index === request.length + 1
? undefined
: index < 0
? request.slice(-index - 1)
: request.slice(index);
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;
}

function isConditionalExportsMainSugar(exports) {
if (typeof exports === "string" || Array.isArray(exports)) return true;
if (typeof exports !== "object" || exports === null) return false;

const keys = Object.getOwnPropertyNames(exports);
let isConditionalSugar = false;
let i = 0;
for (let j = 0; j < keys.length; j++) {
const key = keys[j];
const curIsConditionalSugar = key === "" || key[0] !== ".";
if (i++ === 0) {
isConditionalSugar = curIsConditionalSugar;
} else if (isConditionalSugar !== curIsConditionalSugar) {
// TODO
}
}
return isConditionalSugar;
}

/**
* Trying to match request to field
* @param {string} request request
* @param {PathTreeNode} treeRoot path tree root
* @param {ExportsField | ImportsField} field exports or import field
* @returns {[MappingValue, string, 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, getRemainingRequest(1, request), false] : null;
function findMatch(request, field) {
if (isConditionalExportsMainSugar(field)) {
field = { ".": field };
}

if (
treeRoot.children === null &&
treeRoot.folder === null &&
treeRoot.wildcards === null
(Object.prototype.hasOwnProperty.call(field, "./" + request) ||
Object.prototype.hasOwnProperty.call(field, "." + request)) &&
!request.includes("*") &&
!request.endsWith("/")
) {
const value = treeRoot.files.get(request);
const target = field["./" + request] || field["." + request];

return value
? [value, getRemainingRequest(request.length + 1, request), false]
: null;
}
if (target === "./") return null;

let node = treeRoot;
let lastNonSlashIndex = 0;
let slashIndex = request.indexOf("/", 0);
const isDirectory =
typeof target === "string" ? target.endsWith("/") : false;

/** @type {[MappingValue, string, boolean]|null} */
let lastFolderMatch = null;
return [target, "", isDirectory];
}

const applyFolderMapping = () => {
const folderMapping = node.folder;
let bestMatch = "";
let bestMatchSubpath;

if (folderMapping) {
if (lastFolderMatch) {
lastFolderMatch[0] = folderMapping;
lastFolderMatch[1] = getRemainingRequest(
-lastNonSlashIndex - 1,
request
);
lastFolderMatch[2] = -lastNonSlashIndex - 1 < 0;
} else {
lastFolderMatch = [
folderMapping,
getRemainingRequest(-lastNonSlashIndex - 1, request),
-lastNonSlashIndex - 1 < 0
];
}
}
};
const keys = Object.getOwnPropertyNames(field);

const applyWildcardMappings = (wildcardMappings, remainingRequest) => {
if (wildcardMappings) {
for (const [key, target] of wildcardMappings) {
if (remainingRequest.startsWith(key)) {
if (!lastFolderMatch) {
lastFolderMatch = [
target,
request.slice(lastNonSlashIndex + key.length),
lastNonSlashIndex + key.length < 0
];
} else if (
lastFolderMatch[1].length >
getRemainingRequest(lastNonSlashIndex + key.length, request).length
) {
lastFolderMatch[0] = target;
lastFolderMatch[1] = request.slice(lastNonSlashIndex + key.length);
lastFolderMatch[2] = lastNonSlashIndex + key.length < 0;
}
}
for (let i = 0; i < keys.length; i++) {
const originalKey = keys[i];
const key = keys[i].slice(2);
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
);
}
} else if (
originalKey[originalKey.length - 1] === "/" &&
request.startsWith(key) &&
patternKeyCompare(bestMatch, key) === 1
) {
bestMatch = originalKey;
bestMatchSubpath = request.slice(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) {
return lastFolderMatch;
}

node = newNode;
lastNonSlashIndex = slashIndex + 1;
slashIndex = request.indexOf("/", lastNonSlashIndex);
}

const remainingRequest =
lastNonSlashIndex > 0 ? request.slice(lastNonSlashIndex) : request;
if (bestMatch === "") return null;

const value = node.files.get(remainingRequest);
const target =
field[bestMatch.startsWith("./") ? bestMatch : "./" + bestMatch];
const pattern = bestMatch.endsWith("/");

if (value) {
return [value, getRemainingRequest(request.length + 1, request), false];
}

applyFolderMapping();

applyWildcardMappings(node.wildcards, remainingRequest);

return lastFolderMatch;
return [target, /** @type {string} */ (bestMatchSubpath), pattern];
}

/**
Expand Down Expand Up @@ -450,24 +436,8 @@ function targetMapping(
return mappingTarget + remainingRequest;
}
assert(mappingTarget, false);
const wildcardIndex = mappingTarget.indexOf("*");

if (wildcardIndex !== -1) {
const maybeCommonSuffix = mappingTarget.slice(wildcardIndex + 1);
if (remainingRequest.endsWith(maybeCommonSuffix)) {
return mappingTarget.replace(
new RegExp(`\\*${maybeCommonSuffix}`, "g"),
remainingRequest.replace(/\$/g, "$$")
);
} else {
return mappingTarget.replace(
/\*/g,
remainingRequest.replace(/\$/g, "$$")
);
}
} else {
return mappingTarget;
}

return mappingTarget.replace(/\*/g, remainingRequest.replace(/\$/g, "$$"));
}

/**
Expand Down Expand Up @@ -551,7 +521,7 @@ function walkPath(root, path, target) {
}

let node = root;
// Typical path tree can looks like
// Typical path tree can look like
// root
// - files: ["a.js", "b.js"]
// - children:
Expand All @@ -564,20 +534,29 @@ function walkPath(root, path, target) {
const folder = path.slice(lastNonSlashIndex, slashIndex);
let newNode;

if (node.children === null) {
newNode = createNode();
node.children = new Map();
node.children.set(folder, newNode);
// If the folder is a wildcard, create a new wildcard node or get an existing one.
if (folder === "*") {
if (node.wildcards === null) {
newNode = createNode();
node.wildcards = new Map();
node.wildcards.set("", newNode);
} else {
newNode = node.wildcards.get(folder) || createNode();
node.wildcards.set("", newNode);
}
} else {
newNode = node.children.get(folder);

if (!newNode) {
// If the folder is not a wildcard, create a new child node or get an existing one.
if (node.children === null) {
newNode = createNode();
node.children = new Map();
node.children.set(folder, newNode);
} else {
newNode = node.children.get(folder) || createNode();
node.children.set(folder, newNode);
}
}

node = newNode;
node = /** @type {PathTreeNode} */ (newNode);
lastNonSlashIndex = slashIndex + 1;
slashIndex = path.indexOf("/", lastNonSlashIndex);
}
Expand Down
9 changes: 3 additions & 6 deletions test/exportsField.js
Original file line number Diff line number Diff line change
Expand Up @@ -2598,7 +2598,7 @@ describe("ExportsFieldPlugin", () => {
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")
path.resolve(fixture, "./node_modules/m/src/middle-2/nested/f.js")
);
done();
}
Expand All @@ -2624,7 +2624,7 @@ describe("ExportsFieldPlugin", () => {
});
});

it("should resolve with wildcard pattern #7", done => {
it("should resolve with wildcard pattern #8", done => {
const fixture = path.resolve(
__dirname,
"./fixtures/imports-exports-wildcard/"
Expand All @@ -2634,10 +2634,7 @@ describe("ExportsFieldPlugin", () => {
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"
)
path.resolve(fixture, "./node_modules/m/src/middle-4/f/f.js")
);
done();
});
Expand Down

0 comments on commit aaec883

Please sign in to comment.