| | |
| | | |
| | | "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<string> | false} Alias */ |
| | | /** @typedef {{alias: Alias, name: string, onlyModule?: boolean}} AliasOption */ |
| | | /** @typedef {string | string[] | false} Alias */ |
| | | /** @typedef {{ alias: Alias, name: string, onlyModule?: boolean }} AliasOption */ |
| | | |
| | | const { aliasResolveHandler } = require("./AliasUtils"); |
| | | |
| | | module.exports = class AliasPlugin { |
| | | /** |
| | | * @param {string | ResolveStepHook} source source |
| | | * @param {AliasOption | Array<AliasOption>} options options |
| | | * @param {AliasOption | AliasOption[]} options options |
| | | * @param {string | ResolveStepHook} target target |
| | | */ |
| | | constructor(source, options, target) { |
| | |
| | | */ |
| | | 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( |
| | | aliasResolveHandler( |
| | | resolver, |
| | | this.options, |
| | | (item, callback) => { |
| | | /** @type {boolean} */ |
| | | let shouldStop = false; |
| | | |
| | | const matchRequest = |
| | | innerRequest === item.name || |
| | | (!item.onlyModule && |
| | | (request.request |
| | | ? innerRequest.startsWith(`${item.name}/`) |
| | | : isSubPath(innerRequest, item.name))); |
| | | |
| | | const splitName = item.name.split("*"); |
| | | const matchWildcard = !item.onlyModule && splitName.length === 2; |
| | | |
| | | if (matchRequest || matchWildcard) { |
| | | /** |
| | | * @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); |
| | | } |
| | | |
| | | let newRequestStr; |
| | | |
| | | const [prefix, suffix] = splitName; |
| | | if ( |
| | | matchWildcard && |
| | | innerRequest.startsWith(prefix) && |
| | | innerRequest.endsWith(suffix) |
| | | ) { |
| | | const match = innerRequest.slice( |
| | | prefix.length, |
| | | innerRequest.length - suffix.length, |
| | | ); |
| | | newRequestStr = alias.toString().replace("*", match); |
| | | } |
| | | |
| | | if ( |
| | | matchRequest && |
| | | innerRequest !== alias && |
| | | !innerRequest.startsWith(`${alias}/`) |
| | | ) { |
| | | /** @type {string} */ |
| | | const remainingRequest = innerRequest.slice(item.name.length); |
| | | newRequestStr = alias + remainingRequest; |
| | | } |
| | | |
| | | if (newRequestStr !== undefined) { |
| | | shouldStop = true; |
| | | /** @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, |
| | | ); |
| | | } |
| | | return resolveWithAlias(item.alias, stoppingCallback); |
| | | } |
| | | |
| | | return callback(); |
| | | }, |
| | | target, |
| | | request, |
| | | resolveContext, |
| | | callback, |
| | | ); |
| | | }); |