2019-01-26 02:21:45 +08:00
|
|
|
/*
|
|
|
|
MIT License http://www.opensource.org/licenses/mit-license.php
|
|
|
|
Author Tobias Koppers @sokra
|
|
|
|
*/
|
|
|
|
|
|
|
|
"use strict";
|
|
|
|
|
2019-08-13 04:59:09 +08:00
|
|
|
const FileSystemInfo = require("../FileSystemInfo");
|
2019-01-26 02:21:45 +08:00
|
|
|
const makeSerializable = require("../util/makeSerializable");
|
|
|
|
const {
|
2019-06-11 19:09:42 +08:00
|
|
|
createFileSerializer,
|
2019-01-26 02:21:45 +08:00
|
|
|
NOT_SERIALIZABLE,
|
|
|
|
MEASURE_START_OPERATION,
|
|
|
|
MEASURE_END_OPERATION
|
|
|
|
} = require("../util/serialization");
|
|
|
|
|
|
|
|
const MAX_INLINE_SIZE = 20000;
|
|
|
|
|
2019-08-13 22:11:40 +08:00
|
|
|
class DataWithBuildSnapshot {
|
|
|
|
constructor(data, buildSnapshot) {
|
|
|
|
this.data = data;
|
|
|
|
this.buildSnapshot = buildSnapshot;
|
|
|
|
}
|
|
|
|
|
|
|
|
serialize({ write }) {
|
|
|
|
write(this.buildSnapshot);
|
|
|
|
write(this.data);
|
|
|
|
}
|
|
|
|
|
|
|
|
deserialize({ read }) {
|
|
|
|
this.buildSnapshot = read();
|
|
|
|
this.data = read();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
makeSerializable(
|
|
|
|
DataWithBuildSnapshot,
|
|
|
|
"webpack/lib/cache/PackFileCacheStrategy",
|
|
|
|
"DataWithBuildSnapshot"
|
|
|
|
);
|
|
|
|
|
2019-01-26 02:21:45 +08:00
|
|
|
class Pack {
|
2019-08-12 19:41:23 +08:00
|
|
|
constructor(version, logger) {
|
2019-01-26 02:21:45 +08:00
|
|
|
this.version = version;
|
|
|
|
this.etags = new Map();
|
|
|
|
/** @type {Map<string, any | (() => Promise<PackEntry>)>} */
|
|
|
|
this.content = new Map();
|
|
|
|
this.lastAccess = new Map();
|
|
|
|
this.lastSizes = new Map();
|
|
|
|
this.unserializable = new Set();
|
|
|
|
this.used = new Set();
|
|
|
|
this.invalid = false;
|
2019-08-12 19:41:23 +08:00
|
|
|
this.logger = logger;
|
2019-01-26 02:21:45 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
get(identifier, etag) {
|
|
|
|
const etagInCache = this.etags.get(identifier);
|
|
|
|
if (etagInCache === undefined) return undefined;
|
|
|
|
if (etagInCache !== etag) return undefined;
|
|
|
|
this.used.add(identifier);
|
|
|
|
const content = this.content.get(identifier);
|
|
|
|
if (typeof content === "function") {
|
|
|
|
return Promise.resolve(content()).then(entry =>
|
|
|
|
this._unpack(identifier, entry, false)
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
return content;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
set(identifier, etag, data) {
|
|
|
|
if (this.unserializable.has(identifier)) return;
|
|
|
|
this.used.add(identifier);
|
2019-08-14 00:53:41 +08:00
|
|
|
if (!this.invalid) {
|
|
|
|
if (
|
|
|
|
this.content.get(identifier) === data &&
|
|
|
|
this.etags.get(identifier) === etag
|
|
|
|
) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.invalid = true;
|
|
|
|
this.logger.debug(`Pack got invalid because of ${identifier}`);
|
|
|
|
}
|
2019-01-26 02:21:45 +08:00
|
|
|
this.etags.set(identifier, etag);
|
|
|
|
return this.content.set(identifier, data);
|
|
|
|
}
|
|
|
|
|
|
|
|
collectGarbage(maxAge) {
|
|
|
|
this._updateLastAccess();
|
|
|
|
const now = Date.now();
|
|
|
|
for (const [identifier, lastAccess] of this.lastAccess) {
|
|
|
|
if (now - lastAccess > maxAge) {
|
|
|
|
this.lastAccess.delete(identifier);
|
|
|
|
this.etags.delete(identifier);
|
|
|
|
this.content.delete(identifier);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_updateLastAccess() {
|
|
|
|
const now = Date.now();
|
|
|
|
for (const identifier of this.used) {
|
|
|
|
this.lastAccess.set(identifier, now);
|
|
|
|
}
|
|
|
|
this.used.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
serialize({ write }) {
|
|
|
|
this._updateLastAccess();
|
|
|
|
write(this.version);
|
|
|
|
write(this.etags);
|
|
|
|
write(this.unserializable);
|
|
|
|
write(this.lastAccess);
|
|
|
|
for (const [identifier, data] of this.content) {
|
|
|
|
write(identifier);
|
|
|
|
if (typeof data === "function") {
|
|
|
|
write(data);
|
|
|
|
} else {
|
2019-08-12 19:41:23 +08:00
|
|
|
const packEntry = new PackEntry(data, identifier);
|
2019-01-26 02:21:45 +08:00
|
|
|
const lastSize = this.lastSizes.get(identifier);
|
|
|
|
if (lastSize > MAX_INLINE_SIZE) {
|
|
|
|
write(() => packEntry);
|
|
|
|
} else {
|
|
|
|
write(packEntry);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
write(null);
|
|
|
|
}
|
|
|
|
|
2019-08-12 19:41:23 +08:00
|
|
|
deserialize({ read, logger }) {
|
|
|
|
this.logger = logger;
|
2019-01-26 02:21:45 +08:00
|
|
|
this.version = read();
|
|
|
|
this.etags = read();
|
|
|
|
this.unserializable = read();
|
|
|
|
this.lastAccess = read();
|
|
|
|
this.content = new Map();
|
|
|
|
let identifier = read();
|
|
|
|
while (identifier !== null) {
|
|
|
|
const entry = read();
|
|
|
|
if (typeof entry === "function") {
|
|
|
|
this.content.set(identifier, entry);
|
|
|
|
} else {
|
|
|
|
this.content.set(identifier, this._unpack(identifier, entry, true));
|
|
|
|
}
|
|
|
|
identifier = read();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_unpack(identifier, entry, currentlyInline) {
|
|
|
|
const { data, size } = entry;
|
|
|
|
if (data === undefined) {
|
|
|
|
this.unserializable.add(identifier);
|
|
|
|
this.lastSizes.delete(identifier);
|
|
|
|
return undefined;
|
|
|
|
} else {
|
|
|
|
this.lastSizes.set(identifier, size);
|
|
|
|
if (currentlyInline) {
|
|
|
|
if (size > MAX_INLINE_SIZE) {
|
|
|
|
this.invalid = true;
|
2019-08-12 19:41:23 +08:00
|
|
|
this.logger.log(
|
|
|
|
`Moved ${identifier} from inline to lazy section for better performance.`
|
|
|
|
);
|
2019-01-26 02:21:45 +08:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (size <= MAX_INLINE_SIZE) {
|
|
|
|
this.content.set(identifier, data);
|
|
|
|
this.invalid = true;
|
2019-08-12 19:41:23 +08:00
|
|
|
this.logger.log(
|
|
|
|
`Moved ${identifier} from lazy to inline section for better performance.`
|
|
|
|
);
|
2019-01-26 02:21:45 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
makeSerializable(Pack, "webpack/lib/cache/PackFileCacheStrategy", "Pack");
|
|
|
|
|
|
|
|
class PackEntry {
|
2019-08-12 19:41:23 +08:00
|
|
|
constructor(data, identifier) {
|
2019-01-26 02:21:45 +08:00
|
|
|
this.data = data;
|
|
|
|
this.size = undefined;
|
|
|
|
this.identifier = identifier;
|
|
|
|
}
|
|
|
|
|
2019-08-12 19:41:23 +08:00
|
|
|
serialize({ write, snapshot, rollback, logger }) {
|
2019-01-26 02:21:45 +08:00
|
|
|
const s = snapshot();
|
|
|
|
try {
|
|
|
|
write(true);
|
|
|
|
if (this.size === undefined) {
|
|
|
|
write(MEASURE_START_OPERATION);
|
|
|
|
write(this.data);
|
|
|
|
write(MEASURE_END_OPERATION);
|
|
|
|
} else {
|
|
|
|
write(this.data);
|
|
|
|
write(this.size);
|
|
|
|
}
|
|
|
|
} catch (err) {
|
2019-08-12 19:41:23 +08:00
|
|
|
if (err !== NOT_SERIALIZABLE) {
|
|
|
|
logger.warn(
|
|
|
|
`Caching failed for ${this.identifier}: ${err}\nWe will not try to cache this entry again until the cache file is deleted.`
|
2019-01-26 02:21:45 +08:00
|
|
|
);
|
2019-08-12 19:41:23 +08:00
|
|
|
logger.debug(err.stack);
|
2019-01-26 02:21:45 +08:00
|
|
|
}
|
|
|
|
rollback(s);
|
|
|
|
write(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
deserialize({ read }) {
|
|
|
|
if (read()) {
|
|
|
|
this.data = read();
|
|
|
|
this.size = read();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
makeSerializable(
|
|
|
|
PackEntry,
|
|
|
|
"webpack/lib/cache/PackFileCacheStrategy",
|
|
|
|
"PackEntry"
|
|
|
|
);
|
|
|
|
|
|
|
|
class PackFileCacheStrategy {
|
2019-08-13 04:59:09 +08:00
|
|
|
constructor({ fs, context, cacheLocation, version, logger, managedPaths }) {
|
2019-06-11 19:09:42 +08:00
|
|
|
this.fileSerializer = createFileSerializer(fs);
|
2019-08-13 04:59:09 +08:00
|
|
|
this.fileSystemInfo = new FileSystemInfo(fs, { managedPaths });
|
|
|
|
this.context = context;
|
2019-01-26 02:21:45 +08:00
|
|
|
this.cacheLocation = cacheLocation;
|
2019-08-12 19:41:23 +08:00
|
|
|
this.logger = logger;
|
2019-08-13 22:11:40 +08:00
|
|
|
this.buildSnapshot = undefined;
|
|
|
|
let buildSnapshot;
|
2019-08-12 19:41:23 +08:00
|
|
|
logger.time("restore pack");
|
2019-06-11 19:09:42 +08:00
|
|
|
this.packPromise = this.fileSerializer
|
2019-08-12 19:41:23 +08:00
|
|
|
.deserialize({ filename: `${cacheLocation}.pack`, logger })
|
2019-01-26 02:21:45 +08:00
|
|
|
.then(cacheEntry => {
|
2019-08-13 22:11:40 +08:00
|
|
|
if (cacheEntry instanceof DataWithBuildSnapshot) {
|
|
|
|
logger.timeEnd("restore pack");
|
2019-08-13 04:59:09 +08:00
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
logger.time("check build dependencies");
|
|
|
|
this.fileSystemInfo.checkSnapshotValid(
|
|
|
|
cacheEntry.buildSnapshot,
|
|
|
|
(err, valid) => {
|
|
|
|
if (err) return reject(err);
|
|
|
|
logger.timeEnd("check build dependencies");
|
|
|
|
if (!valid) {
|
|
|
|
logger.log(
|
|
|
|
`Restored pack from ${cacheLocation}.pack, but build dependencies have changed.`
|
|
|
|
);
|
2019-08-13 22:11:40 +08:00
|
|
|
return resolve(undefined);
|
2019-08-13 04:59:09 +08:00
|
|
|
}
|
2019-08-13 22:11:40 +08:00
|
|
|
buildSnapshot = cacheEntry.buildSnapshot;
|
|
|
|
logger.time("restore pack content");
|
|
|
|
return resolve(
|
|
|
|
cacheEntry.data().then(d => {
|
|
|
|
logger.timeEnd("restore pack content");
|
|
|
|
return d;
|
|
|
|
})
|
|
|
|
);
|
2019-08-13 04:59:09 +08:00
|
|
|
}
|
|
|
|
);
|
|
|
|
});
|
2019-01-26 02:21:45 +08:00
|
|
|
}
|
2019-08-13 22:11:40 +08:00
|
|
|
return cacheEntry;
|
|
|
|
})
|
|
|
|
.then(cacheEntry => {
|
|
|
|
if (cacheEntry) {
|
|
|
|
if (!(cacheEntry instanceof Pack)) {
|
|
|
|
logger.warn(
|
|
|
|
`Restored from ${cacheLocation}.pack, but is not a Pack.`
|
|
|
|
);
|
|
|
|
return new Pack(version, logger);
|
|
|
|
}
|
|
|
|
if (cacheEntry.version !== version) {
|
|
|
|
logger.log(
|
|
|
|
`Restored pack from ${cacheLocation}.pack, but version doesn't match.`
|
|
|
|
);
|
|
|
|
return new Pack(version, logger);
|
|
|
|
}
|
|
|
|
this.buildSnapshot = buildSnapshot;
|
|
|
|
return cacheEntry;
|
|
|
|
}
|
2019-08-12 19:41:23 +08:00
|
|
|
return new Pack(version, logger);
|
2019-01-26 02:21:45 +08:00
|
|
|
})
|
|
|
|
.catch(err => {
|
2019-08-12 19:41:23 +08:00
|
|
|
if (err && err.code !== "ENOENT") {
|
|
|
|
logger.warn(
|
|
|
|
`Restoring pack failed from ${cacheLocation}.pack: ${err}`
|
2019-01-26 02:21:45 +08:00
|
|
|
);
|
2019-08-12 19:41:23 +08:00
|
|
|
logger.debug(err.stack);
|
2019-01-26 02:21:45 +08:00
|
|
|
}
|
2019-08-12 19:41:23 +08:00
|
|
|
return new Pack(version, logger);
|
2019-01-26 02:21:45 +08:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
store(identifier, etag, data, idleTasks) {
|
|
|
|
return this.packPromise.then(pack => {
|
2019-08-12 19:41:23 +08:00
|
|
|
this.logger.debug(`Cached ${identifier} to pack.`);
|
2019-01-26 02:21:45 +08:00
|
|
|
pack.set(identifier, etag, data);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
restore(identifier, etag) {
|
|
|
|
return this.packPromise
|
|
|
|
.then(pack => pack.get(identifier, etag))
|
|
|
|
.catch(err => {
|
2019-08-12 19:41:23 +08:00
|
|
|
if (err && err.code !== "ENOENT") {
|
|
|
|
this.logger.warn(
|
|
|
|
`Restoring failed for ${identifier} from pack: ${err}`
|
2019-01-26 02:21:45 +08:00
|
|
|
);
|
2019-08-12 19:41:23 +08:00
|
|
|
this.logger.debug(err.stack);
|
2019-01-26 02:21:45 +08:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-08-13 04:59:09 +08:00
|
|
|
storeBuildDependencies(dependencies) {
|
|
|
|
this.logger.debug("Storing build dependencies...");
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
this.logger.time("resolve build dependencies");
|
|
|
|
this.fileSystemInfo.resolveBuildDependencies(
|
|
|
|
this.context,
|
|
|
|
dependencies,
|
|
|
|
(err, result) => {
|
|
|
|
if (err) return reject(err);
|
|
|
|
this.logger.timeEnd("resolve build dependencies");
|
|
|
|
|
|
|
|
this.logger.time("snapshot build dependencies");
|
|
|
|
const { files, directories, missing } = result;
|
|
|
|
this.fileSystemInfo.createSnapshot(
|
|
|
|
undefined,
|
|
|
|
files,
|
|
|
|
directories,
|
|
|
|
missing,
|
|
|
|
{ hash: true },
|
|
|
|
(err, snapshot) => {
|
|
|
|
if (err) return reject(err);
|
|
|
|
this.logger.timeEnd("snapshot build dependencies");
|
|
|
|
this.logger.debug("Stored build dependencies");
|
|
|
|
|
|
|
|
resolve(
|
|
|
|
this.packPromise.then(pack => {
|
2019-08-13 22:11:40 +08:00
|
|
|
if (this.buildSnapshot) {
|
|
|
|
this.buildSnapshot = this.fileSystemInfo.mergeSnapshots(
|
|
|
|
this.buildSnapshot,
|
2019-08-13 04:59:09 +08:00
|
|
|
snapshot
|
|
|
|
);
|
|
|
|
} else {
|
2019-08-13 22:11:40 +08:00
|
|
|
this.buildSnapshot = snapshot;
|
2019-08-13 04:59:09 +08:00
|
|
|
}
|
|
|
|
})
|
|
|
|
);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-01-26 02:21:45 +08:00
|
|
|
afterAllStored() {
|
|
|
|
return this.packPromise.then(pack => {
|
|
|
|
if (!pack.invalid) return;
|
2019-08-12 19:41:23 +08:00
|
|
|
this.logger.log(`Storing pack...`);
|
|
|
|
this.logger.time(`store pack`);
|
2019-01-26 02:21:45 +08:00
|
|
|
pack.collectGarbage(1000 * 60 * 60 * 24 * 2);
|
2019-08-13 22:11:40 +08:00
|
|
|
const content = this.buildSnapshot
|
|
|
|
? new DataWithBuildSnapshot(() => pack, this.buildSnapshot)
|
|
|
|
: pack;
|
2019-01-26 02:21:45 +08:00
|
|
|
// You might think this breaks all access to the existing pack
|
|
|
|
// which are still referenced, but serializing the pack memorizes
|
|
|
|
// all data in the pack and makes it no longer need the backing file
|
|
|
|
// So it's safe to replace the pack file
|
2019-06-11 19:09:42 +08:00
|
|
|
return this.fileSerializer
|
2019-08-13 22:11:40 +08:00
|
|
|
.serialize(content, {
|
2019-08-12 19:41:23 +08:00
|
|
|
filename: `${this.cacheLocation}.pack`,
|
|
|
|
logger: this.logger
|
2019-01-26 02:21:45 +08:00
|
|
|
})
|
|
|
|
.then(() => {
|
2019-08-12 19:41:23 +08:00
|
|
|
this.logger.timeEnd(`store pack`);
|
|
|
|
this.logger.log(`Stored pack`);
|
2019-01-26 02:21:45 +08:00
|
|
|
})
|
|
|
|
.catch(err => {
|
2019-08-12 19:41:23 +08:00
|
|
|
this.logger.timeEnd(`store pack`);
|
|
|
|
this.logger.warn(`Caching failed for pack: ${err}`);
|
|
|
|
this.logger.debug(err.stack);
|
2019-01-26 02:21:45 +08:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = PackFileCacheStrategy;
|