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 aaec883 commit 50d5cfe
Showing 1 changed file with 22 additions and 151 deletions.
173 changes: 22 additions & 151 deletions lib/util/entrypoints.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,6 @@
/** @typedef {Record<string, MappingValue>|ConditionalMapping|DirectMapping} ExportsField */
/** @typedef {Record<string, MappingValue>} ImportsField */

/**
* @typedef {Object} PathTreeNode
* @property {Map<string, PathTreeNode>|null} children
* @property {MappingValue} folder
* @property {Map<string, MappingValue>|null} wildcards
* @property {Map<string, MappingValue>} files
*/

/**
* Processing exports/imports field
* @callback FieldProcessor
Expand Down Expand Up @@ -80,7 +72,6 @@ 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 @@ -94,7 +85,6 @@ module.exports.processExportsField = function processExportsField(
) {
return createFieldProcessor(
buildExportsFieldPathTree(exportsField),
exportsField,
assertExportsFieldRequest,
assertExportTarget
);
Expand All @@ -109,20 +99,18 @@ 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 {ExportsField | ImportsField} field root
* @param {(s: string) => string} assertRequest assertRequest
* @param {(s: string, f: boolean) => void} assertTarget assertTarget
* @returns {FieldProcessor} field processor
*/
function createFieldProcessor(treeRoot, field, assertRequest, assertTarget) {
function createFieldProcessor(field, assertRequest, assertTarget) {
return function fieldProcessor(request, conditionNames) {
request = assertRequest(request);

Expand Down Expand Up @@ -264,46 +252,19 @@ function patternKeyCompare(a, b) {
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 {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, field) {
if (isConditionalExportsMainSugar(field)) {
field = { ".": field };
}

if (
(Object.prototype.hasOwnProperty.call(field, "./" + request) ||
Object.prototype.hasOwnProperty.call(field, "." + request)) &&
Object.prototype.hasOwnProperty.call(field, request) &&
!request.includes("*") &&
!request.endsWith("/")
) {
const target = field["./" + request] || field["." + request];

if (target === "./") return null;

const target = field[request];
const isDirectory =
typeof target === "string" ? target.endsWith("/") : false;

Expand All @@ -316,8 +277,7 @@ function findMatch(request, field) {
const keys = Object.getOwnPropertyNames(field);

for (let i = 0; i < keys.length; i++) {
const originalKey = keys[i];
const key = keys[i].slice(2);
const key = keys[i];
const patternIndex = key.indexOf("*");

if (patternIndex !== -1 && request.startsWith(key.slice(0, patternIndex))) {
Expand All @@ -336,19 +296,19 @@ function findMatch(request, field) {
);
}
} else if (
originalKey[originalKey.length - 1] === "/" &&
// For `./foo/` or `./`
key[key.length - 1] === "/" &&
request.startsWith(key) &&
patternKeyCompare(bestMatch, key) === 1
) {
bestMatch = originalKey;
bestMatch = key;
bestMatchSubpath = request.slice(key.length);
}
}

if (bestMatch === "") return null;

const target =
field[bestMatch.startsWith("./") ? bestMatch : "./" + bestMatch];
const target = field[bestMatch];
const pattern = bestMatch.endsWith("/");

return [target, /** @type {string} */ (bestMatchSubpath), pattern];
Expand Down Expand Up @@ -494,105 +454,17 @@ 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 look 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 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 {
// 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 = /** @type {PathTreeNode} */ (newNode);
lastNonSlashIndex = slashIndex + 1;
slashIndex = path.indexOf("/", lastNonSlashIndex);
}

if (lastNonSlashIndex >= path.length) {
node.folder = target;
} else {
const file = lastNonSlashIndex > 0 ? path.slice(lastNonSlashIndex) : path;
const wildcardsIndex = file.indexOf("*");
if (wildcardsIndex !== -1) {
if (node.wildcards === null) node.wildcards = new Map();
node.wildcards.set(file.slice(0, wildcardsIndex), 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();

// 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 newField = /** @type {ExportsField} */ ({});
const keys = Object.keys(field);

for (let i = 0; i < keys.length; i++) {
Expand All @@ -613,8 +485,7 @@ function buildExportsFieldPathTree(field) {
i++;
}

root.files.set("", field);
return root;
return { "": field };
}

throw new Error(
Expand All @@ -625,7 +496,8 @@ function buildExportsFieldPathTree(field) {
}

if (key.length === 1) {
root.files.set("", field[key]);
newField[""] = field[key];

continue;
}

Expand All @@ -637,20 +509,19 @@ function buildExportsFieldPathTree(field) {
);
}

walkPath(root, key.slice(2), field[key]);
newField[key.slice(2)] = field[key];
}

return root;
return newField;
}

/**
* @param {ImportsField} field imports field
* @returns {PathTreeNode} root
* @returns {ImportsField} normalized imports field
*/
function buildImportsFieldPathTree(field) {
const root = createNode();

const keys = Object.keys(field);
const newField = /** @type {ImportsField} */ ({});

for (let i = 0; i < keys.length; i++) {
const key = keys[i];
Expand All @@ -677,8 +548,8 @@ function buildImportsFieldPathTree(field) {
);
}

walkPath(root, key.slice(1), field[key]);
newField[key.slice(1)] = field[key];
}

return root;
return newField;
}

0 comments on commit 50d5cfe

Please sign in to comment.