| |
| |
| |
| |
| |
| "use strict"; |
|
|
| const { parseIdentifier } = require("./identifier"); |
|
|
| |
| |
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| const slashCode = "/".charCodeAt(0); |
| const dotCode = ".".charCodeAt(0); |
| const hashCode = "#".charCodeAt(0); |
| const patternRegEx = /\*/g; |
|
|
| |
| |
| |
| |
| |
| 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 findMatch(request, field) { |
| if ( |
| Object.prototype.hasOwnProperty.call(field, request) && |
| !request.includes("*") && |
| !request.endsWith("/") |
| ) { |
| const target = (field)[ |
| request |
| ]; |
|
|
| return [target, "", false, false, request]; |
| } |
|
|
| |
| let bestMatch = ""; |
| |
| let bestMatchSubpath; |
|
|
| 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, |
| ); |
| } |
| } |
| |
| else if ( |
| key[key.length - 1] === "/" && |
| request.startsWith(key) && |
| patternKeyCompare(bestMatch, key) === 1 |
| ) { |
| bestMatch = key; |
| bestMatchSubpath = request.slice(key.length); |
| } |
| } |
|
|
| if (bestMatch === "") return null; |
|
|
| const target = |
| |
| (field)[bestMatch]; |
| const isSubpathMapping = bestMatch.endsWith("/"); |
| const isPattern = bestMatch.includes("*"); |
|
|
| return [ |
| target, |
| (bestMatchSubpath), |
| isSubpathMapping, |
| isPattern, |
| bestMatch, |
| ]; |
| } |
|
|
| |
| |
| |
| |
| function isConditionalMapping(mapping) { |
| return ( |
| mapping !== null && typeof mapping === "object" && !Array.isArray(mapping) |
| ); |
| } |
|
|
| |
| |
| |
| |
| |
| function conditionalMapping(conditionalMapping_, conditionNames) { |
| |
| const lookup = [[conditionalMapping_, Object.keys(conditionalMapping_), 0]]; |
|
|
| loop: while (lookup.length > 0) { |
| const [mapping, conditions, j] = lookup[lookup.length - 1]; |
|
|
| for (let i = j; i < conditions.length; i++) { |
| const condition = conditions[i]; |
|
|
| if (condition === "default") { |
| const innerMapping = mapping[condition]; |
| |
| if (isConditionalMapping(innerMapping)) { |
| const conditionalMapping = ( |
| innerMapping |
| ); |
| lookup[lookup.length - 1][2] = i + 1; |
| lookup.push([conditionalMapping, Object.keys(conditionalMapping), 0]); |
| continue loop; |
| } |
|
|
| return (innerMapping); |
| } |
|
|
| if (conditionNames.has(condition)) { |
| const innerMapping = mapping[condition]; |
| |
| if (isConditionalMapping(innerMapping)) { |
| const conditionalMapping = ( |
| innerMapping |
| ); |
| lookup[lookup.length - 1][2] = i + 1; |
| lookup.push([conditionalMapping, Object.keys(conditionalMapping), 0]); |
| continue loop; |
| } |
|
|
| return (innerMapping); |
| } |
| } |
|
|
| lookup.pop(); |
| } |
|
|
| return null; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| function targetMapping( |
| remainingRequest, |
| isPattern, |
| isSubpathMapping, |
| mappingTarget, |
| assert, |
| ) { |
| if (remainingRequest === undefined) { |
| assert(mappingTarget, false); |
|
|
| return mappingTarget; |
| } |
|
|
| if (isSubpathMapping) { |
| assert(mappingTarget, true); |
|
|
| return mappingTarget + remainingRequest; |
| } |
|
|
| assert(mappingTarget, false); |
|
|
| let result = mappingTarget; |
|
|
| if (isPattern) { |
| result = result.replace( |
| patternRegEx, |
| remainingRequest.replace(/\$/g, "$$"), |
| ); |
| } |
|
|
| return result; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| function directMapping( |
| remainingRequest, |
| isPattern, |
| isSubpathMapping, |
| mappingTarget, |
| conditionNames, |
| assert, |
| ) { |
| if (mappingTarget === null) return []; |
|
|
| if (typeof mappingTarget === "string") { |
| return [ |
| targetMapping( |
| remainingRequest, |
| isPattern, |
| isSubpathMapping, |
| mappingTarget, |
| assert, |
| ), |
| ]; |
| } |
|
|
| |
| const targets = []; |
|
|
| for (const exp of mappingTarget) { |
| if (typeof exp === "string") { |
| targets.push( |
| targetMapping( |
| remainingRequest, |
| isPattern, |
| isSubpathMapping, |
| exp, |
| assert, |
| ), |
| ); |
| continue; |
| } |
|
|
| const mapping = conditionalMapping(exp, conditionNames); |
| if (!mapping) continue; |
| const innerExports = directMapping( |
| remainingRequest, |
| isPattern, |
| isSubpathMapping, |
| mapping, |
| conditionNames, |
| assert, |
| ); |
| for (const innerExport of innerExports) { |
| targets.push(innerExport); |
| } |
| } |
|
|
| return targets; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| function createFieldProcessor( |
| field, |
| normalizeRequest, |
| assertRequest, |
| assertTarget, |
| ) { |
| return function fieldProcessor(request, conditionNames) { |
| request = assertRequest(request); |
|
|
| const match = findMatch(normalizeRequest(request), field); |
|
|
| if (match === null) return [[], null]; |
|
|
| const [mapping, remainingRequest, isSubpathMapping, isPattern, usedField] = |
| match; |
|
|
| |
| let direct = null; |
|
|
| if (isConditionalMapping(mapping)) { |
| direct = conditionalMapping( |
| (mapping), |
| conditionNames, |
| ); |
|
|
| |
| if (direct === null) return [[], null]; |
| } else { |
| direct = (mapping); |
| } |
|
|
| return [ |
| directMapping( |
| remainingRequest, |
| isPattern, |
| isSubpathMapping, |
| direct, |
| conditionNames, |
| assertTarget, |
| ), |
| usedField, |
| ]; |
| }; |
| } |
|
|
| |
| |
| |
| |
| 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.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); |
| } |
|
|
| |
| |
| |
| |
| function buildExportsField(field) { |
| |
| if (typeof field === "string" || Array.isArray(field)) { |
| return { ".": field }; |
| } |
|
|
| const keys = Object.keys(field); |
|
|
| for (let i = 0; i < keys.length; i++) { |
| const key = keys[i]; |
|
|
| if (key.charCodeAt(0) !== dotCode) { |
| |
| if (i === 0) { |
| while (i < keys.length) { |
| const charCode = keys[i].charCodeAt(0); |
| if (charCode === dotCode || charCode === slashCode) { |
| throw new Error( |
| `Exports field key should be relative path and start with "." (key: ${JSON.stringify( |
| key, |
| )})`, |
| ); |
| } |
| i++; |
| } |
|
|
| return { ".": field }; |
| } |
|
|
| throw new Error( |
| `Exports field key should be relative path and start with "." (key: ${JSON.stringify( |
| key, |
| )})`, |
| ); |
| } |
|
|
| if (key.length === 1) { |
| continue; |
| } |
|
|
| if (key.charCodeAt(1) !== slashCode) { |
| throw new Error( |
| `Exports field key should be relative path and start with "./" (key: ${JSON.stringify( |
| key, |
| )})`, |
| ); |
| } |
| } |
|
|
| return field; |
| } |
|
|
| |
| |
| |
| |
| function assertExportTarget(exp, expectFolder) { |
| const parsedIdentifier = parseIdentifier(exp); |
|
|
| if (!parsedIdentifier) { |
| return; |
| } |
|
|
| const [relativePath] = parsedIdentifier; |
| const isFolder = |
| relativePath.charCodeAt(relativePath.length - 1) === slashCode; |
|
|
| if (isFolder !== expectFolder) { |
| throw new Error( |
| expectFolder |
| ? `Expecting folder to folder mapping. ${JSON.stringify( |
| exp, |
| )} should end with "/"` |
| : `Expecting file to file mapping. ${JSON.stringify( |
| exp, |
| )} should not end with "/"`, |
| ); |
| } |
| } |
|
|
| |
| |
| |
| |
| module.exports.processExportsField = function processExportsField( |
| exportsField, |
| ) { |
| return createFieldProcessor( |
| buildExportsField(exportsField), |
| (request) => (request.length === 0 ? "." : `./${request}`), |
| assertExportsFieldRequest, |
| assertExportTarget, |
| ); |
| }; |
|
|
| |
| |
| |
| |
| function assertImportsFieldRequest(request) { |
| if (request.charCodeAt(0) !== hashCode) { |
| throw new Error('Request should start with "#"'); |
| } |
| if (request.length === 1) { |
| throw new Error("Request should have at least 2 characters"); |
| } |
| |
| |
| if (request.charCodeAt(request.length - 1) === slashCode) { |
| throw new Error("Only requesting file allowed"); |
| } |
|
|
| return request.slice(1); |
| } |
|
|
| |
| |
| |
| |
| function assertImportTarget(imp, expectFolder) { |
| const parsedIdentifier = parseIdentifier(imp); |
|
|
| if (!parsedIdentifier) { |
| return; |
| } |
|
|
| const [relativePath] = parsedIdentifier; |
| const isFolder = |
| relativePath.charCodeAt(relativePath.length - 1) === slashCode; |
|
|
| if (isFolder !== expectFolder) { |
| throw new Error( |
| expectFolder |
| ? `Expecting folder to folder mapping. ${JSON.stringify( |
| imp, |
| )} should end with "/"` |
| : `Expecting file to file mapping. ${JSON.stringify( |
| imp, |
| )} should not end with "/"`, |
| ); |
| } |
| } |
|
|
| |
| |
| |
| |
| module.exports.processImportsField = function processImportsField( |
| importsField, |
| ) { |
| return createFieldProcessor( |
| importsField, |
| (request) => `#${request}`, |
| assertImportsFieldRequest, |
| assertImportTarget, |
| ); |
| }; |
|
|