Files

201 lines
5.7 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const { isRelativeRequest } = require("./util/path");
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
/** @typedef {import("./Resolver").ResolveContextYield} ResolveContextYield */
/** @typedef {{ [k: string]: undefined | ResolveRequest | ResolveRequest[] }} Cache */
/**
* @param {string} relativePath relative path from package root
* @param {string} request relative request
* @param {Resolver} resolver resolver instance
* @returns {string} normalized request with a preserved leading dot
*/
function joinRelativePreservingLeadingDot(relativePath, request, resolver) {
const normalized = resolver.join(relativePath, request);
return isRelativeRequest(normalized) ? normalized : `./${normalized}`;
}
/**
* @param {ResolveRequest} request request
* @returns {string | false | undefined} normalized path
*/
function getCachePath(request) {
if (request.descriptionFileRoot && !request.module) {
return request.descriptionFileRoot;
}
return request.path;
}
/**
* @param {ResolveRequest} request request
* @param {Resolver} resolver resolver instance
* @returns {string | undefined} normalized request string
*/
function getCacheRequest(request, resolver) {
const requestString = request.request;
if (
!requestString ||
!request.relativePath ||
!isRelativeRequest(requestString)
) {
return requestString;
}
return joinRelativePreservingLeadingDot(
request.relativePath,
requestString,
resolver,
);
}
// Cache-key separator: `\0` is safe because paths, requests, queries and
// fragments produced by `parseIdentifier` never contain a raw NUL (the
// \0-escape in identifier.js is decoded back to the original char), and the
// context, when included, is passed through `JSON.stringify`, which escapes
// any NUL to \u0000.
// const SEP = "\0";
/**
* Build the cache id for a request. Called on every `described-resolve`
* invocation when `unsafeCache` is on, so it's a hot path.
*
* Equivalent in meaning to the previous `JSON.stringify({ ... })` form, but
* ~35× faster since we avoid the object allocation and JSON serializer for
* the fields that are already plain strings.
* @param {string} type type of cache
* @param {ResolveRequest} request request
* @param {boolean} withContext cache with context?
* @param {Resolver} resolver resolver instance
* @returns {string} cache id
*/
function getCacheId(type, request, withContext, resolver) {
// TODO use it in the next major release, it is faster
// const contextPart = withContext ? JSON.stringify(request.context) : "";
// const path = getCachePath(request);
// const cacheRequest = getCacheRequest(request, resolver);
// return (
// type +
// SEP +
// contextPart +
// SEP +
// (path || "") +
// SEP +
// (request.query || "") +
// SEP +
// (request.fragment || "") +
// SEP +
// (cacheRequest || "")
// );
return JSON.stringify({
type,
context: withContext ? request.context : "",
path: getCachePath(request),
query: request.query,
fragment: request.fragment,
request: getCacheRequest(request, resolver),
});
}
module.exports = class UnsafeCachePlugin {
/**
* @param {string | ResolveStepHook} source source
* @param {(request: ResolveRequest) => boolean} filterPredicate filterPredicate
* @param {Cache} cache cache
* @param {boolean} withContext withContext
* @param {string | ResolveStepHook} target target
*/
constructor(source, filterPredicate, cache, withContext, target) {
this.source = source;
this.filterPredicate = filterPredicate;
this.withContext = withContext;
this.cache = cache;
this.target = target;
}
/**
* @param {Resolver} resolver the resolver
* @returns {void}
*/
apply(resolver) {
const target = resolver.ensureHook(this.target);
resolver
.getHook(this.source)
.tapAsync("UnsafeCachePlugin", (request, resolveContext, callback) => {
if (!this.filterPredicate(request)) {
return resolver.doResolve(
target,
request,
null,
resolveContext,
callback,
);
}
const isYield = typeof resolveContext.yield === "function";
const cacheId = getCacheId(
isYield ? "yield" : "default",
request,
this.withContext,
resolver,
);
const cacheEntry = this.cache[cacheId];
if (cacheEntry) {
if (isYield) {
const yield_ =
/** @type {ResolveContextYield} */
(resolveContext.yield);
if (Array.isArray(cacheEntry)) {
for (const result of cacheEntry) yield_(result);
} else {
yield_(cacheEntry);
}
return callback(null, null);
}
return callback(null, /** @type {ResolveRequest} */ (cacheEntry));
}
/** @type {ResolveContextYield | undefined} */
let yieldFn;
/** @type {ResolveContextYield | undefined} */
let yield_;
/** @type {ResolveRequest[]} */
const yieldResult = [];
if (isYield) {
yieldFn = resolveContext.yield;
yield_ = (result) => {
yieldResult.push(result);
};
}
resolver.doResolve(
target,
request,
null,
yield_ ? { ...resolveContext, yield: yield_ } : resolveContext,
(err, result) => {
if (err) return callback(err);
if (isYield) {
if (result) yieldResult.push(result);
for (const result of yieldResult) {
/** @type {ResolveContextYield} */
(yieldFn)(result);
}
this.cache[cacheId] = yieldResult;
return callback(null, null);
}
if (result) return callback(null, (this.cache[cacheId] = result));
callback();
},
);
});
}
};