UNPKG

webpack

Version:

Packs ECMAScript/CommonJs/AMD modules for the browser. Allows you to split your codebase into multiple bundles, which can be loaded on demand. Supports loaders to preprocess files, i.e. json, jsx, es7, css, less, ... and your custom stuff.

140 lines (126 loc) 4.63 kB
/* MIT License http://www.opensource.org/licenses/mit-license.php Author Alexander Akait @alexander-akait */ "use strict"; const { dirname, join } = require("./fs"); /** @import { InputFileSystem } from "./fs" */ /** @typedef {[string, string]} CaseCorrection wrongly cased segment paired with its real name */ /** @typedef {{ corrections: CaseCorrection[], path: string }} CaseMismatch */ /** Bounds the walk for a path pointing far outside any existing directory. */ const MAX_MISSING_SEGMENTS = 20; /** * @param {InputFileSystem} fs input file system * @param {string} absolutePath absolute path * @returns {string} the last segment of the path, or an empty string at the root */ const basename = (fs, absolutePath) => { const parent = dirname(fs, absolutePath); if (parent === absolutePath) return ""; let start = parent.length; const charCode = absolutePath.charCodeAt(start); if (charCode === 47 /* "/" */ || charCode === 92 /* "\\" */) start++; return absolutePath.slice(start); }; /** * Finds an existing path that differs from `missingPath` only in the casing of * its segments, by listing the directories the path walks through. * @param {InputFileSystem} fs input file system * @param {string} missingPath absolute path that does not exist * @param {(mismatch?: CaseMismatch) => void} callback receives the real path and the wrongly cased segments, outermost first, or nothing when the path does not exist under any casing * @returns {void} */ const findCaseMismatch = (fs, missingPath, callback) => { /** @type {string[]} */ const segments = []; /** @type {CaseCorrection[]} */ const corrections = []; /** * @param {string} directory existing directory the remaining segments start from * @param {string[]} entries its entries * @param {number} index index into `segments` * @returns {void} */ const walkDown = (directory, entries, index) => { const name = segments[index]; let realName = name; if (!entries.includes(name)) { const lowerCasedName = name.toLowerCase(); const matches = entries.filter((e) => e.toLowerCase() === lowerCasedName); // More than one match means the real name is not knowable from the request alone if (matches.length !== 1) return callback(); realName = matches[0]; corrections.push([name, realName]); } const next = join(fs, directory, realName); if (index === segments.length - 1) { return callback( corrections.length > 0 ? { corrections, path: next } : undefined ); } fs.readdir(next, (err, nextEntries) => { if (err || !nextEntries) return callback(); walkDown(next, /** @type {string[]} */ (nextEntries), index + 1); }); }; let current = missingPath; /** * @returns {void} */ const walkUp = () => { const parent = dirname(fs, current); const name = basename(fs, current); if (parent === current || name === "") return callback(); segments.push(name); if (segments.length > MAX_MISSING_SEGMENTS) return callback(); fs.readdir(parent, (err, entries) => { if (err || !entries) { current = parent; return walkUp(); } segments.reverse(); walkDown(parent, /** @type {string[]} */ (entries), 0); }); }; walkUp(); }; /** * Rewrites a request with the real casing found on disk. Corrections are * case-only, so each one keeps the length and position of what it replaces — * a request that omitted the extension spells a prefix of the segment. * @param {string} request request as written * @param {CaseCorrection[]} corrections corrections, outermost first * @returns {string | undefined} the corrected request, or undefined when a correction is not spelled out in the request */ const applyCaseCorrections = (request, corrections) => { let result = request; let cursor = 0; for (const [wrongName, realName] of corrections) { let index = result.indexOf(wrongName, cursor); let length = wrongName.length; if (index === -1) { length = 0; for (let i = wrongName.length - 1; i > 0; i--) { // A prefix that is already correctly cased identifies nothing — the // request would come back unchanged and the hint would repeat it if ( result.endsWith(wrongName.slice(0, i)) && realName.slice(0, i) !== wrongName.slice(0, i) ) { index = result.length - i; length = i; break; } } if (length === 0) return undefined; } result = result.slice(0, index) + realName.slice(0, length) + result.slice(index + length); cursor = index + length; } return result; }; module.exports.applyCaseCorrections = applyCaseCorrections; module.exports.findCaseMismatch = findCaseMismatch;