/* MIT License http://www.opensource.org/licenses/mit-license.php Author Tobias Koppers @sokra */ "use strict"; const forEachBail = require("./forEachBail"); const { PathType, getType } = require("./util/path"); /** @typedef {import("./Resolver")} Resolver */ /** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */ /** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */ /** @typedef {string | Array | false} Alias */ /** @typedef {{alias: Alias, name: string, onlyModule?: boolean}} AliasOption */ module.exports = class AliasPlugin { /** * @param {string | ResolveStepHook} source source * @param {AliasOption | Array} options options * @param {string | ResolveStepHook} target target */ constructor(source, options, target) { this.source = source; this.options = Array.isArray(options) ? options : [options]; this.target = target; } /** * @param {Resolver} resolver the resolver * @returns {void} */ apply(resolver) { const target = resolver.ensureHook(this.target); /** * @param {string} maybeAbsolutePath path * @returns {null|string} absolute path with slash ending */ const getAbsolutePathWithSlashEnding = maybeAbsolutePath => { const type = getType(maybeAbsolutePath); if (type === PathType.AbsolutePosix || type === PathType.AbsoluteWin) { return resolver.join(maybeAbsolutePath, "_").slice(0, -1); } return null; }; /** * @param {string} path path * @param {string} maybeSubPath sub path * @returns {boolean} true, if path is sub path */ const isSubPath = (path, maybeSubPath) => { const absolutePath = getAbsolutePathWithSlashEnding(maybeSubPath); if (!absolutePath) return false; return path.startsWith(absolutePath); }; resolver .getHook(this.source) .tapAsync("AliasPlugin", (request, resolveContext, callback) => { const innerRequest = request.request || request.path; if (!innerRequest) return callback(); forEachBail( this.options, (item, callback) => { /** @type {boolean} */ let shouldStop = false; if ( innerRequest === item.name || (!item.onlyModule && (request.request ? innerRequest.startsWith(`${item.name}/`) : isSubPath(innerRequest, item.name))) ) { /** @type {string} */ const remainingRequest = innerRequest.slice(item.name.length); /** * @param {Alias} alias alias * @param {(err?: null|Error, result?: null|ResolveRequest) => void} callback callback * @returns {void} */ const resolveWithAlias = (alias, callback) => { if (alias === false) { /** @type {ResolveRequest} */ const ignoreObj = { ...request, path: false }; if (typeof resolveContext.yield === "function") { resolveContext.yield(ignoreObj); return callback(null, null); } return callback(null, ignoreObj); } if ( innerRequest !== alias && !innerRequest.startsWith(alias + "/") ) { shouldStop = true; const newRequestStr = alias + remainingRequest; /** @type {ResolveRequest} */ const obj = { ...request, request: newRequestStr, fullySpecified: false }; return resolver.doResolve( target, obj, "aliased with mapping '" + item.name + "': '" + alias + "' to '" + newRequestStr + "'", resolveContext, (err, result) => { if (err) return callback(err); if (result) return callback(null, result); return callback(); } ); } return callback(); }; /** * @param {null|Error} [err] error * @param {null|ResolveRequest} [result] result * @returns {void} */ const stoppingCallback = (err, result) => { if (err) return callback(err); if (result) return callback(null, result); // Don't allow other aliasing or raw request if (shouldStop) return callback(null, null); return callback(); }; if (Array.isArray(item.alias)) { return forEachBail( item.alias, resolveWithAlias, stoppingCallback ); } else { return resolveWithAlias(item.alias, stoppingCallback); } } return callback(); }, callback ); }); } };