2018-10-23 13:22:44 +08:00
|
|
|
/*
|
|
|
|
MIT License http://www.opensource.org/licenses/mit-license.php
|
|
|
|
Author Tobias Koppers @sokra
|
|
|
|
*/
|
|
|
|
|
|
|
|
"use strict";
|
|
|
|
|
2019-11-01 19:21:55 +08:00
|
|
|
const LazySet = require("../util/LazySet");
|
|
|
|
|
2018-10-23 13:22:44 +08:00
|
|
|
/** @typedef {import("enhanced-resolve/lib/Resolver")} Resolver */
|
|
|
|
/** @typedef {import("../Compiler")} Compiler */
|
|
|
|
/** @typedef {import("../FileSystemInfo")} FileSystemInfo */
|
2019-07-18 05:35:05 +08:00
|
|
|
/** @typedef {import("../FileSystemInfo").Snapshot} Snapshot */
|
2019-08-07 15:54:43 +08:00
|
|
|
/** @template T @typedef {import("../util/LazySet")<T>} LazySet<T> */
|
2018-10-23 13:22:44 +08:00
|
|
|
|
2019-07-18 05:35:05 +08:00
|
|
|
/**
|
|
|
|
* @typedef {Object} CacheEntry
|
|
|
|
* @property {Object} result
|
2019-11-01 19:21:55 +08:00
|
|
|
* @property {LazySet<string>} fileDependencies
|
|
|
|
* @property {LazySet<string>} contextDependencies
|
|
|
|
* @property {LazySet<string>} missingDependencies
|
2019-07-18 05:35:05 +08:00
|
|
|
* @property {Snapshot} snapshot
|
|
|
|
*/
|
|
|
|
|
2019-08-07 15:54:43 +08:00
|
|
|
/**
|
|
|
|
* @template T
|
|
|
|
* @param {Set<T> | LazySet<T>} set set to add items to
|
2019-11-01 19:21:55 +08:00
|
|
|
* @param {Set<T> | LazySet<T>} otherSet set to add items from
|
2019-08-07 15:54:43 +08:00
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
const addAllToSet = (set, otherSet) => {
|
|
|
|
if ("addAll" in set) {
|
|
|
|
set.addAll(otherSet);
|
|
|
|
} else {
|
|
|
|
for (const item of otherSet) {
|
|
|
|
set.add(item);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-07-18 05:35:05 +08:00
|
|
|
/**
|
|
|
|
* @param {Object} request a request
|
2019-11-07 23:33:29 +08:00
|
|
|
* @param {boolean} excludeContext if true, context is not included in string
|
2019-07-18 05:35:05 +08:00
|
|
|
* @returns {string} stringified version
|
|
|
|
*/
|
2019-11-07 23:33:29 +08:00
|
|
|
const requestToString = (request, excludeContext) => {
|
2019-01-05 04:09:36 +08:00
|
|
|
let str = "";
|
|
|
|
for (const key in request) {
|
2019-11-07 23:33:29 +08:00
|
|
|
if (excludeContext && key === "context") continue;
|
2019-01-05 04:09:36 +08:00
|
|
|
const value = request[key];
|
|
|
|
if (typeof value === "object" && value !== null) {
|
2019-11-07 23:33:29 +08:00
|
|
|
str += `/${key}={${requestToString(value, false)}}`;
|
2019-01-05 04:09:36 +08:00
|
|
|
} else {
|
|
|
|
str += `/${key}=${value}`;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return str;
|
|
|
|
};
|
|
|
|
|
2018-10-23 13:22:44 +08:00
|
|
|
class ResolverCachePlugin {
|
|
|
|
/**
|
|
|
|
* @param {Compiler} compiler Webpack compiler
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
apply(compiler) {
|
|
|
|
const cache = compiler.cache;
|
|
|
|
/** @type {FileSystemInfo} */
|
|
|
|
let fileSystemInfo;
|
2019-07-26 16:42:57 +08:00
|
|
|
let realResolves = 0;
|
|
|
|
let cachedResolves = 0;
|
|
|
|
let cacheInvalidResolves = 0;
|
2018-10-23 13:22:44 +08:00
|
|
|
compiler.hooks.thisCompilation.tap("ResolverCachePlugin", compilation => {
|
|
|
|
fileSystemInfo = compilation.fileSystemInfo;
|
2019-07-26 16:42:57 +08:00
|
|
|
compilation.hooks.finishModules.tap("ResolverCachePlugin", () => {
|
2019-11-01 19:17:35 +08:00
|
|
|
if (realResolves + cachedResolves > 0) {
|
|
|
|
const logger = compilation.getLogger("webpack.ResolverCachePlugin");
|
|
|
|
logger.debug(
|
|
|
|
`${Math.round(
|
|
|
|
(100 * realResolves) / (realResolves + cachedResolves)
|
|
|
|
)}% really resolved (${realResolves} real resolves, ${cachedResolves} cached, ${cacheInvalidResolves} cached but invalid)`
|
|
|
|
);
|
|
|
|
realResolves = 0;
|
|
|
|
cachedResolves = 0;
|
|
|
|
cacheInvalidResolves = 0;
|
|
|
|
}
|
2019-07-26 16:42:57 +08:00
|
|
|
});
|
2018-10-23 13:22:44 +08:00
|
|
|
});
|
2019-07-18 05:35:05 +08:00
|
|
|
/**
|
|
|
|
* @param {string} identifier cache key
|
|
|
|
* @param {string} type resolver type
|
|
|
|
* @param {Resolver} resolver the resolver
|
|
|
|
* @param {Object} resolveContext context for resolving meta info
|
|
|
|
* @param {Object} request the request info object
|
|
|
|
* @param {function(Error=, Object=): void} callback callback function
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
2018-10-23 13:22:44 +08:00
|
|
|
const doRealResolve = (
|
|
|
|
identifier,
|
|
|
|
type,
|
|
|
|
resolver,
|
|
|
|
resolveContext,
|
|
|
|
request,
|
|
|
|
callback
|
|
|
|
) => {
|
2019-07-26 16:42:57 +08:00
|
|
|
realResolves++;
|
2019-06-19 19:16:05 +08:00
|
|
|
const newRequest = {
|
|
|
|
_ResolverCachePluginCacheMiss: true,
|
|
|
|
...request
|
|
|
|
};
|
|
|
|
const newResolveContext = {
|
|
|
|
...resolveContext,
|
2018-10-23 13:22:44 +08:00
|
|
|
stack: new Set(),
|
2019-11-01 19:21:55 +08:00
|
|
|
missingDependencies: new LazySet(),
|
|
|
|
fileDependencies: new LazySet(),
|
|
|
|
contextDependencies: new LazySet()
|
2019-06-19 19:16:05 +08:00
|
|
|
};
|
2018-10-23 13:22:44 +08:00
|
|
|
const propagate = key => {
|
|
|
|
if (resolveContext[key]) {
|
2019-11-01 19:21:55 +08:00
|
|
|
addAllToSet(resolveContext[key], newResolveContext[key]);
|
2018-10-23 13:22:44 +08:00
|
|
|
}
|
|
|
|
};
|
|
|
|
const resolveTime = Date.now();
|
|
|
|
resolver.doResolve(
|
|
|
|
resolver.hooks.resolve,
|
|
|
|
newRequest,
|
|
|
|
"Cache miss",
|
|
|
|
newResolveContext,
|
|
|
|
(err, result) => {
|
|
|
|
propagate("fileDependencies");
|
|
|
|
propagate("contextDependencies");
|
2019-07-05 06:41:30 +08:00
|
|
|
propagate("missingDependencies");
|
2018-10-23 13:22:44 +08:00
|
|
|
if (err) return callback(err);
|
2019-01-05 21:58:06 +08:00
|
|
|
const fileDependencies = newResolveContext.fileDependencies;
|
|
|
|
const contextDependencies = newResolveContext.contextDependencies;
|
2019-07-05 06:41:30 +08:00
|
|
|
const missingDependencies = newResolveContext.missingDependencies;
|
2019-01-05 21:58:06 +08:00
|
|
|
fileSystemInfo.createSnapshot(
|
|
|
|
resolveTime,
|
|
|
|
fileDependencies,
|
|
|
|
contextDependencies,
|
|
|
|
missingDependencies,
|
|
|
|
null,
|
|
|
|
(err, snapshot) => {
|
2018-10-23 13:22:44 +08:00
|
|
|
if (err) return callback(err);
|
2019-01-05 21:58:06 +08:00
|
|
|
cache.store(
|
|
|
|
identifier,
|
|
|
|
null,
|
2019-07-18 05:35:05 +08:00
|
|
|
/** @type {CacheEntry} */ {
|
2019-01-05 21:58:06 +08:00
|
|
|
result,
|
|
|
|
fileDependencies: newResolveContext.fileDependencies,
|
|
|
|
contextDependencies: newResolveContext.contextDependencies,
|
2019-07-05 06:41:30 +08:00
|
|
|
missingDependencies: newResolveContext.missingDependencies,
|
2019-01-05 21:58:06 +08:00
|
|
|
snapshot
|
|
|
|
},
|
|
|
|
storeErr => {
|
|
|
|
if (storeErr) return callback(storeErr);
|
|
|
|
if (result) return callback(null, result);
|
|
|
|
callback();
|
|
|
|
}
|
|
|
|
);
|
2018-10-23 13:22:44 +08:00
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
};
|
|
|
|
compiler.resolverFactory.hooks.resolver.intercept({
|
|
|
|
factory(type, hook) {
|
|
|
|
hook.tap(
|
|
|
|
"ResolverCachePlugin",
|
|
|
|
/**
|
|
|
|
* @param {Resolver} resolver the resolver
|
|
|
|
* @param {Object} options resolve options
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
(resolver, options) => {
|
2018-10-31 20:53:38 +08:00
|
|
|
if (options.cache !== true) return;
|
2019-11-07 23:33:29 +08:00
|
|
|
const cacheWithContext =
|
|
|
|
options.cacheWithContext !== undefined
|
|
|
|
? options.cacheWithContext
|
|
|
|
: false;
|
2018-10-23 13:22:44 +08:00
|
|
|
resolver.hooks.resolve.tapAsync(
|
|
|
|
{
|
|
|
|
name: "ResolverCachePlugin",
|
|
|
|
stage: -100
|
|
|
|
},
|
|
|
|
(request, resolveContext, callback) => {
|
|
|
|
if (request._ResolverCachePluginCacheMiss || !fileSystemInfo) {
|
|
|
|
return callback();
|
|
|
|
}
|
2019-01-05 04:09:36 +08:00
|
|
|
const identifier = `/resolve/${type}${requestToString(
|
2019-11-07 23:33:29 +08:00
|
|
|
request,
|
|
|
|
cacheWithContext
|
2018-10-23 13:22:44 +08:00
|
|
|
)}`;
|
2019-07-18 05:35:05 +08:00
|
|
|
/**
|
|
|
|
* @param {Error=} err error if any
|
|
|
|
* @param {CacheEntry=} cacheEntry cache entry
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
2019-01-05 21:58:06 +08:00
|
|
|
const processCacheResult = (err, cacheEntry) => {
|
2018-10-23 13:22:44 +08:00
|
|
|
if (err) return callback(err);
|
|
|
|
|
|
|
|
if (cacheEntry) {
|
2019-01-05 21:58:06 +08:00
|
|
|
fileSystemInfo.checkSnapshotValid(
|
|
|
|
cacheEntry.snapshot,
|
|
|
|
(err, valid) => {
|
|
|
|
if (err || !valid) {
|
2019-07-26 16:42:57 +08:00
|
|
|
cacheInvalidResolves++;
|
2018-10-23 13:22:44 +08:00
|
|
|
return doRealResolve(
|
|
|
|
identifier,
|
|
|
|
type,
|
|
|
|
resolver,
|
|
|
|
resolveContext,
|
|
|
|
request,
|
|
|
|
callback
|
|
|
|
);
|
|
|
|
}
|
2019-07-26 16:42:57 +08:00
|
|
|
cachedResolves++;
|
2019-07-05 06:41:30 +08:00
|
|
|
if (resolveContext.missingDependencies) {
|
2019-08-07 15:54:43 +08:00
|
|
|
addAllToSet(
|
|
|
|
resolveContext.missingDependencies,
|
|
|
|
cacheEntry.missingDependencies
|
|
|
|
);
|
2019-01-05 21:58:06 +08:00
|
|
|
}
|
|
|
|
if (resolveContext.fileDependencies) {
|
2019-08-07 15:54:43 +08:00
|
|
|
addAllToSet(
|
|
|
|
resolveContext.fileDependencies,
|
|
|
|
cacheEntry.fileDependencies
|
|
|
|
);
|
2019-01-05 21:58:06 +08:00
|
|
|
}
|
|
|
|
if (resolveContext.contextDependencies) {
|
2019-08-07 15:54:43 +08:00
|
|
|
addAllToSet(
|
|
|
|
resolveContext.contextDependencies,
|
|
|
|
cacheEntry.contextDependencies
|
|
|
|
);
|
2019-01-05 21:58:06 +08:00
|
|
|
}
|
|
|
|
callback(null, cacheEntry.result);
|
2018-10-23 13:22:44 +08:00
|
|
|
}
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
doRealResolve(
|
|
|
|
identifier,
|
|
|
|
type,
|
|
|
|
resolver,
|
|
|
|
resolveContext,
|
|
|
|
request,
|
|
|
|
callback
|
|
|
|
);
|
|
|
|
}
|
2019-01-05 21:58:06 +08:00
|
|
|
};
|
|
|
|
cache.get(identifier, null, processCacheResult);
|
2018-10-23 13:22:44 +08:00
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
return hook;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = ResolverCachePlugin;
|