Page Menu
Home
c4science
Search
Configure Global Search
Log In
Files
F102589601
PackFileCacheStrategy.js
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Subscribers
None
File Metadata
Details
File Info
Storage
Attached
Created
Sat, Feb 22, 07:22
Size
30 KB
Mime Type
text/x-c++
Expires
Mon, Feb 24, 07:22 (2 d)
Engine
blob
Format
Raw Data
Handle
24345040
Attached To
rOACCT Open Access Compliance Check Tool (OACCT)
PackFileCacheStrategy.js
View Options
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict"
;
const
FileSystemInfo
=
require
(
"../FileSystemInfo"
);
const
ProgressPlugin
=
require
(
"../ProgressPlugin"
);
const
{
formatSize
}
=
require
(
"../SizeFormatHelpers"
);
const
LazySet
=
require
(
"../util/LazySet"
);
const
makeSerializable
=
require
(
"../util/makeSerializable"
);
const
memorize
=
require
(
"../util/memorize"
);
const
{
createFileSerializer
}
=
require
(
"../util/serialization"
);
/** @typedef {import("../../declarations/WebpackOptions").SnapshotOptions} SnapshotOptions */
/** @typedef {import("../Cache").Etag} Etag */
/** @typedef {import("../Compiler")} Compiler */
/** @typedef {import("../FileSystemInfo").Snapshot} Snapshot */
/** @typedef {import("../logging/Logger").Logger} Logger */
/** @typedef {import("../util/fs").IntermediateFileSystem} IntermediateFileSystem */
class
PackContainer
{
/**
* @param {Object} data stored data
* @param {string} version version identifier
* @param {Snapshot} buildSnapshot snapshot of all build dependencies
* @param {Set<string>} buildDependencies list of all unresolved build dependencies captured
* @param {Map<string, string>} resolveResults result of the resolved build dependencies
* @param {Snapshot} resolveBuildDependenciesSnapshot snapshot of the dependencies of the build dependencies resolving
*/
constructor
(
data
,
version
,
buildSnapshot
,
buildDependencies
,
resolveResults
,
resolveBuildDependenciesSnapshot
)
{
this
.
data
=
data
;
this
.
version
=
version
;
this
.
buildSnapshot
=
buildSnapshot
;
this
.
buildDependencies
=
buildDependencies
;
this
.
resolveResults
=
resolveResults
;
this
.
resolveBuildDependenciesSnapshot
=
resolveBuildDependenciesSnapshot
;
}
serialize
({
write
,
writeLazy
})
{
write
(
this
.
version
);
write
(
this
.
buildSnapshot
);
write
(
this
.
buildDependencies
);
write
(
this
.
resolveResults
);
write
(
this
.
resolveBuildDependenciesSnapshot
);
writeLazy
(
this
.
data
);
}
deserialize
({
read
})
{
this
.
version
=
read
();
this
.
buildSnapshot
=
read
();
this
.
buildDependencies
=
read
();
this
.
resolveResults
=
read
();
this
.
resolveBuildDependenciesSnapshot
=
read
();
this
.
data
=
read
();
}
}
makeSerializable
(
PackContainer
,
"webpack/lib/cache/PackFileCacheStrategy"
,
"PackContainer"
);
const
MIN_CONTENT_SIZE
=
1024
*
1024
;
// 1 MB
const
CONTENT_COUNT_TO_MERGE
=
10
;
const
MAX_AGE
=
1000
*
60
*
60
*
24
*
60
;
// 1 month
const
MAX_ITEMS_IN_FRESH_PACK
=
50000
;
class
PackItemInfo
{
/**
* @param {string} identifier identifier of item
* @param {string | null} etag etag of item
* @param {any} value fresh value of item
*/
constructor
(
identifier
,
etag
,
value
)
{
this
.
identifier
=
identifier
;
this
.
etag
=
etag
;
this
.
location
=
-
1
;
this
.
lastAccess
=
Date
.
now
();
this
.
freshValue
=
value
;
}
}
class
Pack
{
constructor
(
logger
)
{
/** @type {Map<string, PackItemInfo>} */
this
.
itemInfo
=
new
Map
();
/** @type {string[]} */
this
.
requests
=
[];
/** @type {Map<string, PackItemInfo>} */
this
.
freshContent
=
new
Map
();
/** @type {(undefined | PackContent)[]} */
this
.
content
=
[];
this
.
invalid
=
false
;
this
.
logger
=
logger
;
}
/**
* @param {string} identifier unique name for the resource
* @param {string | null} etag etag of the resource
* @returns {any} cached content
*/
get
(
identifier
,
etag
)
{
const
info
=
this
.
itemInfo
.
get
(
identifier
);
this
.
requests
.
push
(
identifier
);
if
(
info
===
undefined
)
{
return
undefined
;
}
if
(
info
.
etag
!==
etag
)
return
null
;
info
.
lastAccess
=
Date
.
now
();
const
loc
=
info
.
location
;
if
(
loc
===
-
1
)
{
return
info
.
freshValue
;
}
else
{
if
(
!
this
.
content
[
loc
])
{
return
undefined
;
}
return
this
.
content
[
loc
].
get
(
identifier
);
}
}
/**
* @param {string} identifier unique name for the resource
* @param {string | null} etag etag of the resource
* @param {any} data cached content
* @returns {void}
*/
set
(
identifier
,
etag
,
data
)
{
if
(
!
this
.
invalid
)
{
this
.
invalid
=
true
;
this
.
logger
.
log
(
`
Pack
got
invalid
because
of
write
to
:
$
{
identifier
}
`
);
}
const
info
=
this
.
itemInfo
.
get
(
identifier
);
if
(
info
===
undefined
)
{
const
newInfo
=
new
PackItemInfo
(
identifier
,
etag
,
data
);
this
.
itemInfo
.
set
(
identifier
,
newInfo
);
this
.
requests
.
push
(
identifier
);
this
.
freshContent
.
set
(
identifier
,
newInfo
);
}
else
{
const
loc
=
info
.
location
;
if
(
loc
>=
0
)
{
this
.
requests
.
push
(
identifier
);
this
.
freshContent
.
set
(
identifier
,
info
);
const
content
=
this
.
content
[
loc
];
content
.
delete
(
identifier
);
if
(
content
.
items
.
size
===
0
)
{
this
.
content
[
loc
]
=
undefined
;
this
.
logger
.
debug
(
"Pack %d got empty and is removed"
,
loc
);
}
}
info
.
freshValue
=
data
;
info
.
lastAccess
=
Date
.
now
();
info
.
etag
=
etag
;
info
.
location
=
-
1
;
}
}
/**
* @returns {number} new location of data entries
*/
_findLocation
()
{
let
i
;
for
(
i
=
0
;
i
<
this
.
content
.
length
&&
this
.
content
[
i
]
!==
undefined
;
i
++
);
return
i
;
}
_gcAndUpdateLocation
(
items
,
usedItems
,
newLoc
)
{
let
count
=
0
;
let
lastGC
;
const
now
=
Date
.
now
();
for
(
const
identifier
of
items
)
{
const
info
=
this
.
itemInfo
.
get
(
identifier
);
if
(
now
-
info
.
lastAccess
>
MAX_AGE
)
{
this
.
itemInfo
.
delete
(
identifier
);
items
.
delete
(
identifier
);
usedItems
.
delete
(
identifier
);
count
++
;
lastGC
=
identifier
;
}
else
{
info
.
location
=
newLoc
;
}
}
if
(
count
>
0
)
{
this
.
logger
.
log
(
"Garbage Collected %d old items at pack %d e. g. %s"
,
count
,
newLoc
,
lastGC
);
}
}
_persistFreshContent
()
{
if
(
this
.
freshContent
.
size
>
0
)
{
const
packCount
=
Math
.
ceil
(
this
.
freshContent
.
size
/
MAX_ITEMS_IN_FRESH_PACK
);
const
itemsPerPack
=
Math
.
ceil
(
this
.
freshContent
.
size
/
packCount
);
this
.
logger
.
log
(
`
$
{
this
.
freshContent
.
size
}
fresh
items
in
cache
`
);
const
packs
=
Array
.
from
({
length
:
packCount
},
()
=>
{
const
loc
=
this
.
_findLocation
();
this
.
content
[
loc
]
=
null
;
// reserve
return
{
/** @type {Set<string>} */
items
:
new
Set
(),
/** @type {Map<string, any>} */
map
:
new
Map
(),
loc
};
});
let
i
=
0
;
let
pack
=
packs
[
0
];
let
packIndex
=
0
;
for
(
const
identifier
of
this
.
requests
)
{
const
info
=
this
.
freshContent
.
get
(
identifier
);
if
(
info
===
undefined
)
continue
;
pack
.
items
.
add
(
identifier
);
pack
.
map
.
set
(
identifier
,
info
.
freshValue
);
info
.
location
=
pack
.
loc
;
info
.
freshValue
=
undefined
;
this
.
freshContent
.
delete
(
identifier
);
if
(
++
i
>
itemsPerPack
)
{
i
=
0
;
pack
=
packs
[
++
packIndex
];
}
}
for
(
const
pack
of
packs
)
{
this
.
content
[
pack
.
loc
]
=
new
PackContent
(
pack
.
items
,
new
Set
(
pack
.
items
),
new
PackContentItems
(
pack
.
map
)
);
}
}
}
/**
* Merges small content files to a single content file
*/
_optimizeSmallContent
()
{
// 1. Find all small content files
// Treat unused content files separately to avoid
// a merge-split cycle
/** @type {number[]} */
const
smallUsedContents
=
[];
/** @type {number} */
let
smallUsedContentSize
=
0
;
/** @type {number[]} */
const
smallUnusedContents
=
[];
/** @type {number} */
let
smallUnusedContentSize
=
0
;
for
(
let
i
=
0
;
i
<
this
.
content
.
length
;
i
++
)
{
const
content
=
this
.
content
[
i
];
if
(
content
===
undefined
)
continue
;
if
(
content
.
outdated
)
continue
;
const
size
=
content
.
getSize
();
if
(
size
<
0
||
size
>
MIN_CONTENT_SIZE
)
continue
;
if
(
content
.
used
.
size
>
0
)
{
smallUsedContents
.
push
(
i
);
smallUsedContentSize
+=
size
;
}
else
{
smallUnusedContents
.
push
(
i
);
smallUnusedContentSize
+=
size
;
}
}
// 2. Check if minimum number is reached
let
mergedIndices
;
if
(
smallUsedContents
.
length
>=
CONTENT_COUNT_TO_MERGE
||
smallUsedContentSize
>
MIN_CONTENT_SIZE
)
{
mergedIndices
=
smallUsedContents
;
}
else
if
(
smallUnusedContents
.
length
>=
CONTENT_COUNT_TO_MERGE
||
smallUnusedContentSize
>
MIN_CONTENT_SIZE
)
{
mergedIndices
=
smallUnusedContents
;
}
else
return
;
const
mergedContent
=
[];
// 3. Remove old content entries
for
(
const
i
of
mergedIndices
)
{
mergedContent
.
push
(
this
.
content
[
i
]);
this
.
content
[
i
]
=
undefined
;
}
// 4. Determine merged items
/** @type {Set<string>} */
const
mergedItems
=
new
Set
();
/** @type {Set<string>} */
const
mergedUsedItems
=
new
Set
();
/** @type {(function(Map<string, any>): Promise)[]} */
const
addToMergedMap
=
[];
for
(
const
content
of
mergedContent
)
{
for
(
const
identifier
of
content
.
items
)
{
mergedItems
.
add
(
identifier
);
}
for
(
const
identifer
of
content
.
used
)
{
mergedUsedItems
.
add
(
identifer
);
}
addToMergedMap
.
push
(
async
map
=>
{
// unpack existing content
// after that values are accessible in .content
await
content
.
unpack
();
for
(
const
[
identifier
,
value
]
of
content
.
content
)
{
map
.
set
(
identifier
,
value
);
}
});
}
// 5. GC and update location of merged items
const
newLoc
=
this
.
_findLocation
();
this
.
_gcAndUpdateLocation
(
mergedItems
,
mergedUsedItems
,
newLoc
);
// 6. If not empty, store content somewhere
if
(
mergedItems
.
size
>
0
)
{
this
.
content
[
newLoc
]
=
new
PackContent
(
mergedItems
,
mergedUsedItems
,
memorize
(
async
()
=>
{
/** @type {Map<string, any>} */
const
map
=
new
Map
();
await
Promise
.
all
(
addToMergedMap
.
map
(
fn
=>
fn
(
map
)));
return
new
PackContentItems
(
map
);
})
);
this
.
logger
.
log
(
"Merged %d small files with %d cache items into pack %d"
,
mergedContent
.
length
,
mergedItems
.
size
,
newLoc
);
}
}
/**
* Split large content files with used and unused items
* into two parts to separate used from unused items
*/
_optimizeUnusedContent
()
{
// 1. Find a large content file with used and unused items
for
(
let
i
=
0
;
i
<
this
.
content
.
length
;
i
++
)
{
const
content
=
this
.
content
[
i
];
if
(
content
===
undefined
)
continue
;
const
size
=
content
.
getSize
();
if
(
size
<
MIN_CONTENT_SIZE
)
continue
;
const
used
=
content
.
used
.
size
;
const
total
=
content
.
items
.
size
;
if
(
used
>
0
&&
used
<
total
)
{
// 2. Remove this content
this
.
content
[
i
]
=
undefined
;
// 3. Determine items for the used content file
const
usedItems
=
new
Set
(
content
.
used
);
const
newLoc
=
this
.
_findLocation
();
this
.
_gcAndUpdateLocation
(
usedItems
,
usedItems
,
newLoc
);
// 4. Create content file for used items
if
(
usedItems
.
size
>
0
)
{
this
.
content
[
newLoc
]
=
new
PackContent
(
usedItems
,
new
Set
(
usedItems
),
async
()
=>
{
await
content
.
unpack
();
const
map
=
new
Map
();
for
(
const
identifier
of
usedItems
)
{
map
.
set
(
identifier
,
content
.
content
.
get
(
identifier
));
}
return
new
PackContentItems
(
map
);
}
);
}
// 5. Determine items for the unused content file
const
unusedItems
=
new
Set
(
content
.
items
);
const
usedOfUnusedItems
=
new
Set
();
for
(
const
identifier
of
usedItems
)
{
unusedItems
.
delete
(
identifier
);
}
const
newUnusedLoc
=
this
.
_findLocation
();
this
.
_gcAndUpdateLocation
(
unusedItems
,
usedOfUnusedItems
,
newUnusedLoc
);
// 6. Create content file for unused items
if
(
unusedItems
.
size
>
0
)
{
this
.
content
[
newUnusedLoc
]
=
new
PackContent
(
unusedItems
,
usedOfUnusedItems
,
async
()
=>
{
await
content
.
unpack
();
const
map
=
new
Map
();
for
(
const
identifier
of
unusedItems
)
{
map
.
set
(
identifier
,
content
.
content
.
get
(
identifier
));
}
return
new
PackContentItems
(
map
);
}
);
}
this
.
logger
.
log
(
"Split pack %d into pack %d with %d used items and pack %d with %d unused items"
,
i
,
newLoc
,
usedItems
.
size
,
newUnusedLoc
,
unusedItems
.
size
);
// optimizing only one of them is good enough and
// reduces the amount of serialization needed
return
;
}
}
}
serialize
({
write
,
writeSeparate
})
{
this
.
_persistFreshContent
();
this
.
_optimizeSmallContent
();
this
.
_optimizeUnusedContent
();
for
(
const
identifier
of
this
.
itemInfo
.
keys
())
{
write
(
identifier
);
}
write
(
null
);
// null as marker of the end of keys
for
(
const
info
of
this
.
itemInfo
.
values
())
{
write
(
info
.
etag
);
}
for
(
const
info
of
this
.
itemInfo
.
values
())
{
write
(
info
.
lastAccess
);
}
for
(
let
i
=
0
;
i
<
this
.
content
.
length
;
i
++
)
{
const
content
=
this
.
content
[
i
];
if
(
content
!==
undefined
)
{
write
(
content
.
items
);
writeSeparate
(
content
.
getLazyContentItems
(),
{
name
:
`
$
{
i
}
`
});
}
else
{
write
(
undefined
);
// undefined marks an empty content slot
}
}
write
(
null
);
// null as marker of the end of items
}
deserialize
({
read
,
logger
})
{
this
.
logger
=
logger
;
{
const
items
=
[];
let
item
=
read
();
while
(
item
!==
null
)
{
items
.
push
(
item
);
item
=
read
();
}
this
.
itemInfo
.
clear
();
const
infoItems
=
items
.
map
(
identifier
=>
{
const
info
=
new
PackItemInfo
(
identifier
,
undefined
,
undefined
);
this
.
itemInfo
.
set
(
identifier
,
info
);
return
info
;
});
for
(
const
info
of
infoItems
)
{
info
.
etag
=
read
();
}
for
(
const
info
of
infoItems
)
{
info
.
lastAccess
=
read
();
}
}
this
.
content
.
length
=
0
;
let
items
=
read
();
while
(
items
!==
null
)
{
if
(
items
===
undefined
)
{
this
.
content
.
push
(
items
);
}
else
{
const
idx
=
this
.
content
.
length
;
const
lazy
=
read
();
this
.
content
.
push
(
new
PackContent
(
items
,
new
Set
(),
lazy
,
logger
,
`
$
{
this
.
content
.
length
}
`
)
);
for
(
const
identifier
of
items
)
{
this
.
itemInfo
.
get
(
identifier
).
location
=
idx
;
}
}
items
=
read
();
}
}
}
makeSerializable
(
Pack
,
"webpack/lib/cache/PackFileCacheStrategy"
,
"Pack"
);
class
PackContentItems
{
/**
* @param {Map<string, any>} map items
*/
constructor
(
map
)
{
this
.
map
=
map
;
}
serialize
({
write
,
snapshot
,
rollback
,
logger
})
{
// Try to serialize all at once
const
s
=
snapshot
();
try
{
write
(
true
);
write
(
this
.
map
);
}
catch
(
e
)
{
rollback
(
s
);
// Try to serialize each item on it's own
write
(
false
);
for
(
const
[
key
,
value
]
of
this
.
map
)
{
const
s
=
snapshot
();
try
{
write
(
key
);
write
(
value
);
}
catch
(
e
)
{
rollback
(
s
);
logger
.
warn
(
`
Skipped
not
serializable
cache
item
'${key}'
:
$
{
e
.
message
}
`
);
logger
.
debug
(
e
.
stack
);
}
}
write
(
null
);
}
}
deserialize
({
read
})
{
if
(
read
())
{
this
.
map
=
read
();
}
else
{
const
map
=
new
Map
();
let
key
=
read
();
while
(
key
!==
null
)
{
map
.
set
(
key
,
read
());
key
=
read
();
}
this
.
map
=
map
;
}
}
}
makeSerializable
(
PackContentItems
,
"webpack/lib/cache/PackFileCacheStrategy"
,
"PackContentItems"
);
class
PackContent
{
/**
* @param {Set<string>} items keys
* @param {Set<string>} usedItems used keys
* @param {PackContentItems | function(): Promise<PackContentItems>} dataOrFn sync or async content
* @param {Logger=} logger logger for logging
* @param {string=} lazyName name of dataOrFn for logging
*/
constructor
(
items
,
usedItems
,
dataOrFn
,
logger
,
lazyName
)
{
this
.
items
=
items
;
/** @type {function(): PackContentItems | Promise<PackContentItems>} */
this
.
lazy
=
typeof
dataOrFn
===
"function"
?
dataOrFn
:
undefined
;
/** @type {Map<string, any>} */
this
.
content
=
typeof
dataOrFn
===
"function"
?
undefined
:
dataOrFn
.
map
;
this
.
outdated
=
false
;
this
.
used
=
usedItems
;
this
.
logger
=
logger
;
this
.
lazyName
=
lazyName
;
}
get
(
identifier
)
{
this
.
used
.
add
(
identifier
);
if
(
this
.
content
)
{
return
this
.
content
.
get
(
identifier
);
}
const
{
lazyName
}
=
this
;
let
timeMessage
;
if
(
lazyName
)
{
// only log once
this
.
lazyName
=
undefined
;
timeMessage
=
`
restore
cache
content
$
{
lazyName
}
(
$
{
formatSize
(
this
.
getSize
()
)})
`
;
this
.
logger
.
log
(
`
starting
to
restore
cache
content
$
{
lazyName
}
(
$
{
formatSize
(
this
.
getSize
()
)})
because
of
request
to
:
$
{
identifier
}
`
);
this
.
logger
.
time
(
timeMessage
);
}
const
value
=
this
.
lazy
();
if
(
value
instanceof
Promise
)
{
return
value
.
then
(
data
=>
{
const
map
=
data
.
map
;
if
(
timeMessage
)
{
this
.
logger
.
timeEnd
(
timeMessage
);
}
this
.
content
=
map
;
return
map
.
get
(
identifier
);
});
}
else
{
const
map
=
value
.
map
;
if
(
timeMessage
)
{
this
.
logger
.
timeEnd
(
timeMessage
);
}
this
.
content
=
map
;
return
map
.
get
(
identifier
);
}
}
/**
* @returns {void | Promise} maybe a promise if lazy
*/
unpack
()
{
if
(
this
.
content
)
return
;
if
(
this
.
lazy
)
{
const
{
lazyName
}
=
this
;
let
timeMessage
;
if
(
lazyName
)
{
// only log once
this
.
lazyName
=
undefined
;
timeMessage
=
`
unpack
cache
content
$
{
lazyName
}
(
$
{
formatSize
(
this
.
getSize
()
)})
`
;
this
.
logger
.
time
(
timeMessage
);
}
const
value
=
this
.
lazy
();
if
(
value
instanceof
Promise
)
{
return
value
.
then
(
data
=>
{
if
(
timeMessage
)
{
this
.
logger
.
timeEnd
(
timeMessage
);
}
this
.
content
=
data
.
map
;
});
}
else
{
if
(
timeMessage
)
{
this
.
logger
.
timeEnd
(
timeMessage
);
}
this
.
content
=
value
.
map
;
}
}
}
/**
* @returns {number} size of the content or -1 if not known
*/
getSize
()
{
if
(
!
this
.
lazy
)
return
-
1
;
const
options
=
/** @type {any} */
(
this
.
lazy
).
options
;
if
(
!
options
)
return
-
1
;
const
size
=
options
.
size
;
if
(
typeof
size
!==
"number"
)
return
-
1
;
return
size
;
}
delete
(
identifier
)
{
this
.
items
.
delete
(
identifier
);
this
.
used
.
delete
(
identifier
);
this
.
outdated
=
true
;
}
/**
* @returns {function(): PackContentItems | Promise<PackContentItems>} lazy content items
*/
getLazyContentItems
()
{
if
(
!
this
.
outdated
&&
this
.
lazy
)
return
this
.
lazy
;
if
(
!
this
.
outdated
&&
this
.
content
)
{
const
map
=
new
Map
(
this
.
content
);
return
(
this
.
lazy
=
memorize
(()
=>
new
PackContentItems
(
map
)));
}
this
.
outdated
=
false
;
if
(
this
.
content
)
{
return
(
this
.
lazy
=
memorize
(()
=>
{
/** @type {Map<string, any>} */
const
map
=
new
Map
();
for
(
const
item
of
this
.
items
)
{
map
.
set
(
item
,
this
.
content
.
get
(
item
));
}
return
new
PackContentItems
(
map
);
}));
}
const
lazy
=
this
.
lazy
;
return
(
this
.
lazy
=
()
=>
{
const
value
=
lazy
();
if
(
value
instanceof
Promise
)
{
return
value
.
then
(
data
=>
{
const
oldMap
=
data
.
map
;
/** @type {Map<string, any>} */
const
map
=
new
Map
();
for
(
const
item
of
this
.
items
)
{
map
.
set
(
item
,
oldMap
.
get
(
item
));
}
return
new
PackContentItems
(
map
);
});
}
else
{
const
oldMap
=
value
.
map
;
/** @type {Map<string, any>} */
const
map
=
new
Map
();
for
(
const
item
of
this
.
items
)
{
map
.
set
(
item
,
oldMap
.
get
(
item
));
}
return
new
PackContentItems
(
map
);
}
});
}
}
class
PackFileCacheStrategy
{
/**
* @param {Object} options options
* @param {Compiler} options.compiler the compiler
* @param {IntermediateFileSystem} options.fs the filesystem
* @param {string} options.context the context directory
* @param {string} options.cacheLocation the location of the cache data
* @param {string} options.version version identifier
* @param {Logger} options.logger a logger
* @param {SnapshotOptions} options.snapshot options regarding snapshotting
*/
constructor
({
compiler
,
fs
,
context
,
cacheLocation
,
version
,
logger
,
snapshot
})
{
this
.
fileSerializer
=
createFileSerializer
(
fs
);
this
.
fileSystemInfo
=
new
FileSystemInfo
(
fs
,
{
managedPaths
:
snapshot
.
managedPaths
,
immutablePaths
:
snapshot
.
immutablePaths
,
logger
:
logger
.
getChildLogger
(
"webpack.FileSystemInfo"
)
});
this
.
compiler
=
compiler
;
this
.
context
=
context
;
this
.
cacheLocation
=
cacheLocation
;
this
.
version
=
version
;
this
.
logger
=
logger
;
this
.
snapshot
=
snapshot
;
/** @type {Set<string>} */
this
.
buildDependencies
=
new
Set
();
/** @type {LazySet<string>} */
this
.
newBuildDependencies
=
new
LazySet
();
/** @type {Snapshot} */
this
.
resolveBuildDependenciesSnapshot
=
undefined
;
/** @type {Map<string, string>} */
this
.
resolveResults
=
undefined
;
/** @type {Snapshot} */
this
.
buildSnapshot
=
undefined
;
/** @type {Promise<Pack>} */
this
.
packPromise
=
this
.
_openPack
();
}
/**
* @returns {Promise<Pack>} the pack
*/
_openPack
()
{
const
{
logger
,
cacheLocation
,
version
}
=
this
;
/** @type {Snapshot} */
let
buildSnapshot
;
/** @type {Set<string>} */
let
buildDependencies
;
/** @type {Set<string>} */
let
newBuildDependencies
;
/** @type {Snapshot} */
let
resolveBuildDependenciesSnapshot
;
/** @type {Map<string, string>} */
let
resolveResults
;
logger
.
time
(
"restore cache container"
);
return
this
.
fileSerializer
.
deserialize
(
null
,
{
filename
:
`
$
{
cacheLocation
}
/
index
.
pack
`
,
extension
:
".pack"
,
logger
})
.
catch
(
err
=>
{
if
(
err
.
code
!==
"ENOENT"
)
{
logger
.
warn
(
`
Restoring
pack
failed
from
$
{
cacheLocation
}.
pack
:
$
{
err
}
`
);
logger
.
debug
(
err
.
stack
);
}
else
{
logger
.
debug
(
`
No
pack
exists
at
$
{
cacheLocation
}.
pack
:
$
{
err
}
`
);
}
return
undefined
;
})
.
then
(
packContainer
=>
{
logger
.
timeEnd
(
"restore cache container"
);
if
(
!
packContainer
)
return
undefined
;
if
(
!
(
packContainer
instanceof
PackContainer
))
{
logger
.
warn
(
`
Restored
pack
from
$
{
cacheLocation
}.
pack
,
but
contained
content
is
unexpected
.
`
,
packContainer
);
return
undefined
;
}
if
(
packContainer
.
version
!==
version
)
{
logger
.
log
(
`
Restored
pack
from
$
{
cacheLocation
}.
pack
,
but
version
doesn
't match.`
);
return undefined;
}
logger.time("check build dependencies");
return Promise.all([
new Promise((resolve, reject) => {
this.fileSystemInfo.checkSnapshotValid(
packContainer.buildSnapshot,
(err, valid) => {
if (err) {
logger.log(
`Restored pack from ${cacheLocation}.pack, but checking snapshot of build dependencies errored: ${err}.`
);
logger.debug(err.stack);
return resolve(false);
}
if (!valid) {
logger.log(
`Restored pack from ${cacheLocation}.pack, but build dependencies have changed.`
);
return resolve(false);
}
buildSnapshot = packContainer.buildSnapshot;
return resolve(true);
}
);
}),
new Promise((resolve, reject) => {
this.fileSystemInfo.checkSnapshotValid(
packContainer.resolveBuildDependenciesSnapshot,
(err, valid) => {
if (err) {
logger.log(
`Restored pack from ${cacheLocation}.pack, but checking snapshot of resolving of build dependencies errored: ${err}.`
);
logger.debug(err.stack);
return resolve(false);
}
if (valid) {
resolveBuildDependenciesSnapshot =
packContainer.resolveBuildDependenciesSnapshot;
buildDependencies = packContainer.buildDependencies;
resolveResults = packContainer.resolveResults;
return resolve(true);
}
logger.log(
"resolving of build dependencies is invalid, will re-resolve build dependencies"
);
this.fileSystemInfo.checkResolveResultsValid(
packContainer.resolveResults,
(err, valid) => {
if (err) {
logger.log(
`Restored pack from ${cacheLocation}.pack, but resolving of build dependencies errored: ${err}.`
);
logger.debug(err.stack);
return resolve(false);
}
if (valid) {
newBuildDependencies = packContainer.buildDependencies;
resolveResults = packContainer.resolveResults;
return resolve(true);
}
logger.log(
`Restored pack from ${cacheLocation}.pack, but build dependencies resolve to different locations.`
);
return resolve(false);
}
);
}
);
})
])
.catch(err => {
logger.timeEnd("check build dependencies");
throw err;
})
.then(([buildSnapshotValid, resolveValid]) => {
logger.timeEnd("check build dependencies");
if (buildSnapshotValid && resolveValid) {
logger.time("restore cache content metadata");
const d = packContainer.data();
logger.timeEnd("restore cache content metadata");
return d;
}
return undefined;
});
})
.then(pack => {
if (pack) {
this.buildSnapshot = buildSnapshot;
if (buildDependencies) this.buildDependencies = buildDependencies;
if (newBuildDependencies)
this.newBuildDependencies.addAll(newBuildDependencies);
this.resolveResults = resolveResults;
this.resolveBuildDependenciesSnapshot = resolveBuildDependenciesSnapshot;
return pack;
}
return new Pack(logger);
})
.catch(err => {
this.logger.warn(
`Restoring pack from ${cacheLocation}.pack failed: ${err}`
);
this.logger.debug(err.stack);
return new Pack(logger);
});
}
/**
* @param {string} identifier unique name for the resource
* @param {Etag | null} etag etag of the resource
* @param {any} data cached content
* @returns {Promise<void>} promise
*/
store(identifier, etag, data) {
return this.packPromise.then(pack => {
pack.set(identifier, etag === null ? null : etag.toString(), data);
});
}
/**
* @param {string} identifier unique name for the resource
* @param {Etag | null} etag etag of the resource
* @returns {Promise<any>} promise to the cached content
*/
restore(identifier, etag) {
return this.packPromise
.then(pack =>
pack.get(identifier, etag === null ? null : etag.toString())
)
.catch(err => {
if (err && err.code !== "ENOENT") {
this.logger.warn(
`Restoring failed for ${identifier} from pack: ${err}`
);
this.logger.debug(err.stack);
}
});
}
storeBuildDependencies(dependencies) {
this.newBuildDependencies.addAll(dependencies);
}
afterAllStored() {
const reportProgress = ProgressPlugin.getReporter(this.compiler);
return this.packPromise
.then(pack => {
if (!pack.invalid) return;
this.logger.log(`Storing pack...`);
let promise;
const newBuildDependencies = new Set();
for (const dep of this.newBuildDependencies) {
if (!this.buildDependencies.has(dep)) {
newBuildDependencies.add(dep);
}
}
if (newBuildDependencies.size > 0 || !this.buildSnapshot) {
if (reportProgress) reportProgress(0.5, "resolve build dependencies");
this.logger.debug(
`Capturing build dependencies... (${Array.from(
newBuildDependencies
).join(", ")})`
);
promise = new Promise((resolve, reject) => {
this.logger.time("resolve build dependencies");
this.fileSystemInfo.resolveBuildDependencies(
this.context,
newBuildDependencies,
(err, result) => {
this.logger.timeEnd("resolve build dependencies");
if (err) return reject(err);
this.logger.time("snapshot build dependencies");
const {
files,
directories,
missing,
resolveResults,
resolveDependencies
} = result;
if (this.resolveResults) {
for (const [key, value] of resolveResults) {
this.resolveResults.set(key, value);
}
} else {
this.resolveResults = resolveResults;
}
if (reportProgress) {
reportProgress(
0.6,
"snapshot build dependencies",
"resolving"
);
}
this.fileSystemInfo.createSnapshot(
undefined,
resolveDependencies.files,
resolveDependencies.directories,
resolveDependencies.missing,
this.snapshot.resolveBuildDependencies,
(err, snapshot) => {
if (err) {
this.logger.timeEnd("snapshot build dependencies");
return reject(err);
}
if (!snapshot) {
this.logger.timeEnd("snapshot build dependencies");
return reject(
new Error("Unable to snapshot resolve dependencies")
);
}
if (this.resolveBuildDependenciesSnapshot) {
this.resolveBuildDependenciesSnapshot = this.fileSystemInfo.mergeSnapshots(
this.resolveBuildDependenciesSnapshot,
snapshot
);
} else {
this.resolveBuildDependenciesSnapshot = snapshot;
}
if (reportProgress) {
reportProgress(
0.7,
"snapshot build dependencies",
"modules"
);
}
this.fileSystemInfo.createSnapshot(
undefined,
files,
directories,
missing,
this.snapshot.buildDependencies,
(err, snapshot) => {
this.logger.timeEnd("snapshot build dependencies");
if (err) return reject(err);
if (!snapshot) {
return reject(
new Error("Unable to snapshot build dependencies")
);
}
this.logger.debug("Captured build dependencies");
if (this.buildSnapshot) {
this.buildSnapshot = this.fileSystemInfo.mergeSnapshots(
this.buildSnapshot,
snapshot
);
} else {
this.buildSnapshot = snapshot;
}
resolve();
}
);
}
);
}
);
});
} else {
promise = Promise.resolve();
}
return promise.then(() => {
if (reportProgress) reportProgress(0.8, "serialize pack");
this.logger.time(`store pack`);
const content = new PackContainer(
pack,
this.version,
this.buildSnapshot,
this.buildDependencies,
this.resolveResults,
this.resolveBuildDependenciesSnapshot
);
// 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
return
this
.
fileSerializer
.
serialize
(
content
,
{
filename
:
`
$
{
this
.
cacheLocation
}
/
index
.
pack
`
,
extension
:
".pack"
,
logger
:
this
.
logger
})
.
then
(()
=>
{
for
(
const
dep
of
newBuildDependencies
)
{
this
.
buildDependencies
.
add
(
dep
);
}
this
.
newBuildDependencies
.
clear
();
this
.
logger
.
timeEnd
(
`
store
pack
`
);
this
.
logger
.
log
(
`
Stored
pack
`
);
})
.
catch
(
err
=>
{
this
.
logger
.
timeEnd
(
`
store
pack
`
);
this
.
logger
.
warn
(
`
Caching
failed
for
pack
:
$
{
err
}
`
);
this
.
logger
.
debug
(
err
.
stack
);
});
});
})
.
catch
(
err
=>
{
this
.
logger
.
warn
(
`
Caching
failed
for
pack
:
$
{
err
}
`
);
this
.
logger
.
debug
(
err
.
stack
);
});
}
}
module
.
exports
=
PackFileCacheStrategy
;
Event Timeline
Log In to Comment