webpack/lib/cache/FileCachePlugin.js

205 lines
5.5 KiB
JavaScript
Raw Normal View History

2018-10-09 20:30:59 +08:00
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const path = require("path");
const createHash = require("../util/createHash");
const serializer = require("../util/serializer");
/** @typedef {import("webpack-sources").Source} Source */
/** @typedef {import("../../declarations/WebpackOptions").FileCacheOptions} FileCacheOptions */
/** @typedef {import("../Compiler")} Compiler */
/** @typedef {import("../Module")} Module */
const memorize = fn => {
let result = undefined;
return () => {
if (result === undefined) result = fn();
return result;
};
};
const memoryCache = new Map();
class FileCachePlugin {
/**
* @param {FileCacheOptions} options options
*/
constructor(options) {
this.options = options;
}
2018-10-18 04:15:46 +08:00
static purgeMemoryCache() {
memoryCache.clear();
}
2018-10-09 20:30:59 +08:00
/**
* @param {Compiler} compiler Webpack compiler
* @returns {void}
*/
apply(compiler) {
const cacheDirectory = path.resolve(
this.options.cacheDirectory || "node_modules/.cache/webpack/",
2018-10-10 16:07:51 +08:00
this.options.name || "default"
2018-10-09 20:30:59 +08:00
);
const hashAlgorithm = this.options.hashAlgorithm || "md4";
const version = this.options.version || "";
2018-10-10 16:51:08 +08:00
const log = this.options.loglevel
? { debug: 3, info: 2, warning: 1 }[this.options.loglevel]
: 0;
2018-10-09 20:30:59 +08:00
const store = this.options.store || "idle";
let pendingPromiseFactories = new Map();
const toHash = str => {
const hash = createHash(hashAlgorithm);
hash.update(str);
const digest = hash.digest("hex");
return `${digest.slice(0, 2)}/${digest.slice(2)}`;
};
compiler.cache.hooks.store.tapPromise(
"FileCachePlugin",
(identifier, etag, data) => {
const entry = { identifier, data: () => data, etag, version };
const filename = path.join(
cacheDirectory,
toHash(identifier) + ".data"
);
memoryCache.set(filename, entry);
const promiseFactory = () =>
serializer
.serializeToFile(entry, filename)
.then(() => {
2018-10-10 16:51:08 +08:00
if (log >= 2) {
2018-10-09 20:30:59 +08:00
console.warn(`Cached ${identifier} to ${filename}.`);
}
})
.catch(err => {
2018-10-10 16:51:08 +08:00
if (log >= 1) {
2018-10-10 23:19:31 +08:00
console.warn(
`Caching failed for ${identifier}: ${
log >= 3 ? err.stack : err
}`
);
2018-10-09 20:30:59 +08:00
}
});
if (store === "instant") {
return promiseFactory();
} else if (store === "idle") {
pendingPromiseFactories.set(filename, promiseFactory);
return Promise.resolve();
} else if (store === "background") {
const promise = promiseFactory();
pendingPromiseFactories.set(filename, () => promise);
return Promise.resolve();
}
}
);
compiler.cache.hooks.get.tapPromise(
"FileCachePlugin",
(identifier, etag) => {
const filename = path.join(
cacheDirectory,
toHash(identifier) + ".data"
);
const memory = memoryCache.get(filename);
if (memory !== undefined) {
return Promise.resolve(
memory.etag === etag && memory.version === version
? memory.data()
: undefined
);
}
return serializer.deserializeFromFile(filename).then(
cacheEntry => {
cacheEntry = {
identifier: cacheEntry.identifier,
etag: cacheEntry.etag,
version: cacheEntry.version,
data: memorize(cacheEntry.data)
};
memoryCache.set(filename, cacheEntry);
if (cacheEntry === undefined) return;
if (cacheEntry.identifier !== identifier) {
2018-10-10 16:51:08 +08:00
if (log >= 2) {
2018-10-09 20:30:59 +08:00
console.warn(
`Restored ${identifier} from ${filename}, but identifier doesn't match.`
);
}
return;
}
if (cacheEntry.etag !== etag) {
2018-10-10 16:51:08 +08:00
if (log >= 2) {
2018-10-09 20:30:59 +08:00
console.warn(
`Restored ${etag} from ${filename}, but etag doesn't match.`
);
}
return;
}
if (cacheEntry.version !== version) {
2018-10-10 16:51:08 +08:00
if (log >= 2) {
2018-10-09 20:30:59 +08:00
console.warn(
`Restored ${version} from ${filename}, but version doesn't match.`
);
}
return;
}
2018-10-10 16:51:08 +08:00
if (log >= 2) {
2018-10-09 20:30:59 +08:00
console.warn(`Restored ${identifier} from ${filename}.`);
}
return cacheEntry.data();
},
err => {
2018-10-10 16:51:08 +08:00
if (log >= 1 && err && err.code !== "ENOENT") {
console.warn(
`Restoring failed for ${identifier} from ${filename}: ${
2018-10-10 16:51:08 +08:00
log >= 3 ? err.stack : err
}`
);
2018-10-09 20:30:59 +08:00
}
}
);
}
);
compiler.cache.hooks.shutdown.tapPromise("FileCachePlugin", () => {
isIdle = false;
const promises = Array.from(pendingPromiseFactories.values()).map(fn =>
fn()
);
pendingPromiseFactories.clear();
if (currentIdlePromise !== undefined) promises.push(currentIdlePromise);
return Promise.all(promises);
});
let currentIdlePromise;
let isIdle = false;
const processIdleTasks = () => {
if (isIdle && pendingPromiseFactories.size > 0) {
const promises = [];
const maxTime = Date.now() + 100;
let maxCount = 100;
for (const [filename, factory] of pendingPromiseFactories) {
pendingPromiseFactories.delete(filename);
promises.push(factory());
if (maxCount-- <= 0 || Date.now() > maxTime) break;
}
currentIdlePromise = Promise.all(promises).then(() => {
currentIdlePromise = undefined;
});
currentIdlePromise.then(processIdleTasks);
}
};
compiler.cache.hooks.beginIdle.tap("FileCachePlugin", () => {
isIdle = true;
Promise.resolve().then(processIdleTasks);
});
compiler.cache.hooks.endIdle.tap("FileCachePlugin", () => {
isIdle = false;
});
}
}
2018-10-18 04:15:46 +08:00
2018-10-09 20:30:59 +08:00
module.exports = FileCachePlugin;