2018-09-27 13:22:19 +08:00
/ *
MIT License http : //www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @ sokra
* /
"use strict" ;
2019-11-11 22:25:03 +08:00
const { create : createResolver } = require ( "enhanced-resolve" ) ;
2019-08-13 04:59:09 +08:00
const asyncLib = require ( "neo-async" ) ;
2018-09-27 13:22:19 +08:00
const AsyncQueue = require ( "./util/AsyncQueue" ) ;
2019-08-13 04:59:09 +08:00
const createHash = require ( "./util/createHash" ) ;
2019-08-20 19:50:14 +08:00
const { join , dirname , relative } = require ( "./util/fs" ) ;
2020-08-22 18:01:28 +08:00
const makeSerializable = require ( "./util/makeSerializable" ) ;
2021-01-15 17:50:02 +08:00
const processAsyncTree = require ( "./util/processAsyncTree" ) ;
2018-09-27 13:22:19 +08:00
2019-07-18 05:35:05 +08:00
/** @typedef {import("./WebpackError")} WebpackError */
2019-11-04 17:06:53 +08:00
/** @typedef {import("./logging/Logger").Logger} Logger */
2019-07-18 05:35:05 +08:00
/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */
2021-01-14 04:16:55 +08:00
const supportsEsm = + process . versions . modules >= 83 ;
2018-09-27 13:22:19 +08:00
let FS _ACCURACY = 2000 ;
2019-11-05 23:47:45 +08:00
const EMPTY _SET = new Set ( ) ;
2021-01-15 17:50:02 +08:00
const RBDT _RESOLVE _CJS = 0 ;
const RBDT _RESOLVE _ESM = 1 ;
const RBDT _RESOLVE _DIRECTORY = 2 ;
const RBDT _RESOLVE _CJS _FILE = 3 ;
const RBDT _RESOLVE _ESM _FILE = 4 ;
const RBDT _DIRECTORY = 5 ;
const RBDT _FILE = 6 ;
const RBDT _DIRECTORY _DEPENDENCIES = 7 ;
const RBDT _FILE _DEPENDENCIES = 8 ;
2019-08-13 23:21:19 +08:00
2019-08-20 03:57:49 +08:00
const INVALID = Symbol ( "invalid" ) ;
2018-09-27 13:22:19 +08:00
/ * *
* @ typedef { Object } FileSystemInfoEntry
* @ property { number } safeTime
2019-09-04 19:34:34 +08:00
* @ property { number = } timestamp
* @ property { string = } timestampHash
2018-09-27 13:22:19 +08:00
* /
2020-08-26 06:36:16 +08:00
/ * *
* @ typedef { Object } TimestampAndHash
* @ property { number } safeTime
* @ property { number = } timestamp
* @ property { string = } timestampHash
* @ property { string } hash
* /
2020-01-31 17:59:28 +08:00
/ * *
* @ typedef { Object } SnapshotOptimizationEntry
* @ property { Snapshot } snapshot
* @ property { number } shared
* @ property { Set < string > } snapshotContent
* @ property { Set < SnapshotOptimizationEntry > } children
2019-07-18 05:35:05 +08:00
* /
2019-09-04 19:34:34 +08:00
/ * *
* @ typedef { Object } ResolveBuildDependenciesResult
* @ property { Set < string > } files list of files
* @ property { Set < string > } directories list of directories
* @ property { Set < string > } missing list of missing entries
* @ property { Map < string , string > } resolveResults stored resolve results
* @ property { Object } resolveDependencies dependencies of the resolving
* @ property { Set < string > } resolveDependencies . files list of files
* @ property { Set < string > } resolveDependencies . directories list of directories
* @ property { Set < string > } resolveDependencies . missing list of missing entries
* /
2020-08-23 03:54:34 +08:00
const DONE _ITERATOR _RESULT = new Set ( ) . keys ( ) . next ( ) ;
2020-08-26 06:36:16 +08:00
// cspell:word tshs
// Tsh = Timestamp + Hash
// Tshs = Timestamp + Hash combinations
2020-08-22 18:01:28 +08:00
class Snapshot {
constructor ( ) {
this . _flags = 0 ;
/** @type {number | undefined} */
this . startTime = undefined ;
/** @type {Map<string, FileSystemInfoEntry> | undefined} */
this . fileTimestamps = undefined ;
/** @type {Map<string, string> | undefined} */
this . fileHashes = undefined ;
2020-08-26 06:36:16 +08:00
/** @type {Map<string, TimestampAndHash | string> | undefined} */
this . fileTshs = undefined ;
2020-08-22 18:01:28 +08:00
/** @type {Map<string, FileSystemInfoEntry> | undefined} */
this . contextTimestamps = undefined ;
/** @type {Map<string, string> | undefined} */
this . contextHashes = undefined ;
2020-08-26 06:36:16 +08:00
/** @type {Map<string, TimestampAndHash | string> | undefined} */
this . contextTshs = undefined ;
2020-08-22 18:01:28 +08:00
/** @type {Map<string, boolean> | undefined} */
this . missingExistence = undefined ;
/** @type {Map<string, string> | undefined} */
this . managedItemInfo = undefined ;
2020-08-23 03:54:34 +08:00
/** @type {Set<string> | undefined} */
this . managedFiles = undefined ;
/** @type {Set<string> | undefined} */
this . managedContexts = undefined ;
/** @type {Set<string> | undefined} */
this . managedMissing = undefined ;
2020-08-22 18:01:28 +08:00
/** @type {Set<Snapshot> | undefined} */
this . children = undefined ;
}
hasStartTime ( ) {
return ( this . _flags & 1 ) !== 0 ;
}
setStartTime ( value ) {
this . _flags = this . _flags | 1 ;
this . startTime = value ;
}
setMergedStartTime ( value , snapshot ) {
if ( value ) {
if ( snapshot . hasStartTime ( ) ) {
this . setStartTime ( Math . min ( value , snapshot . startTime ) ) ;
} else {
this . setStartTime ( value ) ;
}
} else {
if ( snapshot . hasStartTime ( ) ) this . setStartTime ( snapshot . startTime ) ;
}
}
hasFileTimestamps ( ) {
return ( this . _flags & 2 ) !== 0 ;
}
setFileTimestamps ( value ) {
this . _flags = this . _flags | 2 ;
this . fileTimestamps = value ;
}
hasFileHashes ( ) {
return ( this . _flags & 4 ) !== 0 ;
}
setFileHashes ( value ) {
this . _flags = this . _flags | 4 ;
this . fileHashes = value ;
}
2020-08-26 06:36:16 +08:00
hasFileTshs ( ) {
2020-08-22 18:01:28 +08:00
return ( this . _flags & 8 ) !== 0 ;
}
2020-08-26 06:36:16 +08:00
setFileTshs ( value ) {
2020-08-22 18:01:28 +08:00
this . _flags = this . _flags | 8 ;
2020-08-26 06:36:16 +08:00
this . fileTshs = value ;
}
hasContextTimestamps ( ) {
return ( this . _flags & 0x10 ) !== 0 ;
}
setContextTimestamps ( value ) {
this . _flags = this . _flags | 0x10 ;
2020-08-22 18:01:28 +08:00
this . contextTimestamps = value ;
}
hasContextHashes ( ) {
2020-08-26 06:36:16 +08:00
return ( this . _flags & 0x20 ) !== 0 ;
2020-08-22 18:01:28 +08:00
}
setContextHashes ( value ) {
2020-08-26 06:36:16 +08:00
this . _flags = this . _flags | 0x20 ;
2020-08-22 18:01:28 +08:00
this . contextHashes = value ;
}
2020-08-26 06:36:16 +08:00
hasContextTshs ( ) {
return ( this . _flags & 0x40 ) !== 0 ;
}
setContextTshs ( value ) {
this . _flags = this . _flags | 0x40 ;
this . contextTshs = value ;
}
2020-08-22 18:01:28 +08:00
hasMissingExistence ( ) {
2020-08-26 06:36:16 +08:00
return ( this . _flags & 0x80 ) !== 0 ;
2020-08-22 18:01:28 +08:00
}
setMissingExistence ( value ) {
2020-08-26 06:36:16 +08:00
this . _flags = this . _flags | 0x80 ;
2020-08-22 18:01:28 +08:00
this . missingExistence = value ;
}
hasManagedItemInfo ( ) {
2020-08-26 06:36:16 +08:00
return ( this . _flags & 0x100 ) !== 0 ;
2020-08-22 18:01:28 +08:00
}
setManagedItemInfo ( value ) {
2020-08-26 06:36:16 +08:00
this . _flags = this . _flags | 0x100 ;
2020-08-22 18:01:28 +08:00
this . managedItemInfo = value ;
}
2020-08-23 03:54:34 +08:00
hasManagedFiles ( ) {
2020-08-26 06:36:16 +08:00
return ( this . _flags & 0x200 ) !== 0 ;
2020-08-22 18:01:28 +08:00
}
2020-08-23 03:54:34 +08:00
setManagedFiles ( value ) {
2020-08-26 06:36:16 +08:00
this . _flags = this . _flags | 0x200 ;
2020-08-23 03:54:34 +08:00
this . managedFiles = value ;
}
hasManagedContexts ( ) {
2020-08-26 06:36:16 +08:00
return ( this . _flags & 0x400 ) !== 0 ;
2020-08-23 03:54:34 +08:00
}
setManagedContexts ( value ) {
2020-08-26 06:36:16 +08:00
this . _flags = this . _flags | 0x400 ;
2020-08-23 03:54:34 +08:00
this . managedContexts = value ;
}
hasManagedMissing ( ) {
2020-08-26 06:36:16 +08:00
return ( this . _flags & 0x800 ) !== 0 ;
2020-08-23 03:54:34 +08:00
}
setManagedMissing ( value ) {
2020-08-26 06:36:16 +08:00
this . _flags = this . _flags | 0x800 ;
2020-08-23 03:54:34 +08:00
this . managedMissing = value ;
}
hasChildren ( ) {
2020-08-26 06:36:16 +08:00
return ( this . _flags & 0x1000 ) !== 0 ;
2020-08-23 03:54:34 +08:00
}
setChildren ( value ) {
2020-08-26 06:36:16 +08:00
this . _flags = this . _flags | 0x1000 ;
2020-08-22 18:01:28 +08:00
this . children = value ;
}
addChild ( child ) {
if ( ! this . hasChildren ( ) ) {
this . setChildren ( new Set ( ) ) ;
}
this . children . add ( child ) ;
}
serialize ( { write } ) {
write ( this . _flags ) ;
if ( this . hasStartTime ( ) ) write ( this . startTime ) ;
if ( this . hasFileTimestamps ( ) ) write ( this . fileTimestamps ) ;
if ( this . hasFileHashes ( ) ) write ( this . fileHashes ) ;
2020-08-26 06:36:16 +08:00
if ( this . hasFileTshs ( ) ) write ( this . fileTshs ) ;
2020-08-22 18:01:28 +08:00
if ( this . hasContextTimestamps ( ) ) write ( this . contextTimestamps ) ;
if ( this . hasContextHashes ( ) ) write ( this . contextHashes ) ;
2020-08-26 06:36:16 +08:00
if ( this . hasContextTshs ( ) ) write ( this . contextTshs ) ;
2020-08-22 18:01:28 +08:00
if ( this . hasMissingExistence ( ) ) write ( this . missingExistence ) ;
if ( this . hasManagedItemInfo ( ) ) write ( this . managedItemInfo ) ;
2020-08-23 03:54:34 +08:00
if ( this . hasManagedFiles ( ) ) write ( this . managedFiles ) ;
if ( this . hasManagedContexts ( ) ) write ( this . managedContexts ) ;
if ( this . hasManagedMissing ( ) ) write ( this . managedMissing ) ;
2020-08-22 18:01:28 +08:00
if ( this . hasChildren ( ) ) write ( this . children ) ;
}
deserialize ( { read } ) {
this . _flags = read ( ) ;
if ( this . hasStartTime ( ) ) this . startTime = read ( ) ;
if ( this . hasFileTimestamps ( ) ) this . fileTimestamps = read ( ) ;
if ( this . hasFileHashes ( ) ) this . fileHashes = read ( ) ;
2020-08-26 06:36:16 +08:00
if ( this . hasFileTshs ( ) ) this . fileTshs = read ( ) ;
2020-08-22 18:01:28 +08:00
if ( this . hasContextTimestamps ( ) ) this . contextTimestamps = read ( ) ;
if ( this . hasContextHashes ( ) ) this . contextHashes = read ( ) ;
2020-08-26 06:36:16 +08:00
if ( this . hasContextTshs ( ) ) this . contextTshs = read ( ) ;
2020-08-22 18:01:28 +08:00
if ( this . hasMissingExistence ( ) ) this . missingExistence = read ( ) ;
if ( this . hasManagedItemInfo ( ) ) this . managedItemInfo = read ( ) ;
2020-08-23 03:54:34 +08:00
if ( this . hasManagedFiles ( ) ) this . managedFiles = read ( ) ;
if ( this . hasManagedContexts ( ) ) this . managedContexts = read ( ) ;
if ( this . hasManagedMissing ( ) ) this . managedMissing = read ( ) ;
2020-08-22 18:01:28 +08:00
if ( this . hasChildren ( ) ) this . children = read ( ) ;
}
2020-08-23 03:54:34 +08:00
/ * *
2020-08-26 06:36:16 +08:00
* @ param { function ( Snapshot ) : ( Map < string , any > | Set < string > ) [ ] } getMaps first
2020-08-23 03:54:34 +08:00
* @ returns { Iterable < string > } iterable
* /
2020-08-26 06:36:16 +08:00
_createIterable ( getMaps ) {
2020-08-23 03:54:34 +08:00
let snapshot = this ;
return {
[ Symbol . iterator ] ( ) {
let state = 0 ;
/** @type {IterableIterator<string>} */
let it ;
2020-08-26 06:36:16 +08:00
let maps = getMaps ( snapshot ) ;
2020-08-23 03:54:34 +08:00
const queue = [ ] ;
return {
next ( ) {
for ( ; ; ) {
switch ( state ) {
2020-08-26 06:36:16 +08:00
case 0 :
if ( maps . length > 0 ) {
const map = maps . pop ( ) ;
if ( map !== undefined ) {
it = map . keys ( ) ;
state = 1 ;
} else {
break ;
}
2020-08-23 03:54:34 +08:00
} else {
state = 2 ;
break ;
}
/* falls through */
case 1 : {
const result = it . next ( ) ;
if ( ! result . done ) return result ;
2020-08-26 06:36:16 +08:00
state = 0 ;
2020-09-19 03:47:46 +08:00
break ;
2020-08-23 03:54:34 +08:00
}
case 2 : {
const children = snapshot . children ;
if ( children !== undefined ) {
for ( const child of children ) {
queue . push ( child ) ;
}
}
if ( queue . length > 0 ) {
snapshot = queue . pop ( ) ;
2020-08-26 06:36:16 +08:00
maps = getMaps ( snapshot ) ;
2020-08-23 03:54:34 +08:00
state = 0 ;
break ;
} else {
2020-08-26 06:36:16 +08:00
state = 3 ;
2020-08-23 03:54:34 +08:00
}
}
/* falls through */
2020-08-26 06:36:16 +08:00
case 3 :
2020-08-23 03:54:34 +08:00
return DONE _ITERATOR _RESULT ;
}
}
}
} ;
}
} ;
}
/ * *
* @ returns { Iterable < string > } iterable
* /
getFileIterable ( ) {
2020-08-26 06:36:16 +08:00
return this . _createIterable ( s => [
s . fileTimestamps ,
s . fileHashes ,
s . fileTshs ,
s . managedFiles
] ) ;
2020-08-23 03:54:34 +08:00
}
/ * *
* @ returns { Iterable < string > } iterable
* /
getContextIterable ( ) {
2020-08-26 06:36:16 +08:00
return this . _createIterable ( s => [
s . contextTimestamps ,
s . contextHashes ,
s . contextTshs ,
s . managedContexts
] ) ;
2020-08-23 03:54:34 +08:00
}
/ * *
* @ returns { Iterable < string > } iterable
* /
getMissingIterable ( ) {
2020-08-26 06:36:16 +08:00
return this . _createIterable ( s => [ s . missingExistence , s . managedMissing ] ) ;
2020-08-23 03:54:34 +08:00
}
2020-08-22 18:01:28 +08:00
}
makeSerializable ( Snapshot , "webpack/lib/FileSystemInfo" , "Snapshot" ) ;
2020-08-21 03:57:44 +08:00
const MIN _COMMON _SNAPSHOT _SIZE = 3 ;
2020-08-22 18:01:28 +08:00
/ * *
* @ template T
* /
2020-08-20 17:17:55 +08:00
class SnapshotOptimization {
2020-08-22 18:01:28 +08:00
/ * *
* @ param { function ( Snapshot ) : boolean } has has value
2020-08-23 03:54:34 +08:00
* @ param { function ( Snapshot ) : Map < string , T > | Set < string > } get get value
* @ param { function ( Snapshot , Map < string , T > | Set < string > ) : void } set set value
* @ param { boolean = } isSet value is an Set instead of a Map
2020-08-22 18:01:28 +08:00
* /
2020-08-23 03:54:34 +08:00
constructor ( has , get , set , isSet = false ) {
2020-08-22 18:01:28 +08:00
this . _has = has ;
this . _get = get ;
this . _set = set ;
2020-08-23 03:54:34 +08:00
this . _isSet = isSet ;
2020-08-20 17:17:55 +08:00
/** @type {Map<string, SnapshotOptimizationEntry>} */
this . _map = new Map ( ) ;
2020-08-21 03:57:44 +08:00
this . _statItemsShared = 0 ;
this . _statItemsUnshared = 0 ;
this . _statSharedSnapshots = 0 ;
2020-08-22 17:15:07 +08:00
this . _statReusedSharedSnapshots = 0 ;
2020-08-21 03:57:44 +08:00
}
getStatisticMessage ( ) {
const total = this . _statItemsShared + this . _statItemsUnshared ;
2020-08-23 03:54:34 +08:00
if ( total === 0 ) return undefined ;
2020-08-21 03:57:44 +08:00
return ` ${
this . _statItemsShared && Math . round ( ( this . _statItemsShared * 100 ) / total )
} % ( $ { this . _statItemsShared } / $ { total } ) entries shared via $ {
this . _statSharedSnapshots
2020-08-22 17:15:07 +08:00
} shared snapshots ( $ {
this . _statReusedSharedSnapshots + this . _statSharedSnapshots
} times referenced ) ` ;
2020-08-20 17:17:55 +08:00
}
2021-03-25 23:35:19 +08:00
clear ( ) {
this . _map . clear ( ) ;
this . _statItemsShared = 0 ;
this . _statItemsUnshared = 0 ;
this . _statSharedSnapshots = 0 ;
this . _statReusedSharedSnapshots = 0 ;
}
2020-08-20 17:17:55 +08:00
storeUnsharedSnapshot ( snapshot , locations ) {
if ( locations === undefined ) return ;
const optimizationEntry = {
snapshot ,
shared : 0 ,
snapshotContent : undefined ,
children : undefined
} ;
for ( const path of locations ) {
this . _map . set ( path , optimizationEntry ) ;
}
}
optimize ( capturedFiles , startTime , children ) {
/** @type {Set<string>} */
const unsetOptimizationEntries = new Set ( ) ;
/** @type {Set<SnapshotOptimizationEntry>} */
const checkedOptimizationEntries = new Set ( ) ;
/ * *
* @ param { SnapshotOptimizationEntry } entry optimization entry
* @ returns { void }
* /
const increaseSharedAndStoreOptimizationEntry = entry => {
if ( entry . children !== undefined ) {
entry . children . forEach ( increaseSharedAndStoreOptimizationEntry ) ;
}
entry . shared ++ ;
storeOptimizationEntry ( entry ) ;
} ;
/ * *
* @ param { SnapshotOptimizationEntry } entry optimization entry
* @ returns { void }
* /
const storeOptimizationEntry = entry => {
for ( const path of entry . snapshotContent ) {
const old = this . _map . get ( path ) ;
if ( old . shared < entry . shared ) {
this . _map . set ( path , entry ) ;
}
capturedFiles . delete ( path ) ;
}
} ;
2020-08-21 03:57:44 +08:00
const capturedFilesSize = capturedFiles . size ;
2020-08-20 17:17:55 +08:00
capturedFiles : for ( const path of capturedFiles ) {
const optimizationEntry = this . _map . get ( path ) ;
if ( optimizationEntry === undefined ) {
unsetOptimizationEntries . add ( path ) ;
continue ;
}
if ( checkedOptimizationEntries . has ( optimizationEntry ) ) continue ;
const snapshot = optimizationEntry . snapshot ;
if ( optimizationEntry . shared > 0 ) {
// It's a shared snapshot
// We can't change it, so we can only use it when all files match
// and startTime is compatible
if (
startTime &&
( ! snapshot . startTime || snapshot . startTime > startTime )
) {
continue ;
}
const nonSharedFiles = new Set ( ) ;
2020-08-21 03:57:44 +08:00
const snapshotContent = optimizationEntry . snapshotContent ;
2020-08-22 18:01:28 +08:00
const snapshotEntries = this . _get ( snapshot ) ;
2020-08-21 03:57:44 +08:00
for ( const path of snapshotContent ) {
2020-08-20 17:17:55 +08:00
if ( ! capturedFiles . has ( path ) ) {
2020-08-22 18:01:28 +08:00
if ( ! snapshotEntries . has ( path ) ) {
2020-08-20 17:17:55 +08:00
// File is not shared and can't be removed from the snapshot
// because it's in a child of the snapshot
checkedOptimizationEntries . add ( optimizationEntry ) ;
continue capturedFiles ;
}
nonSharedFiles . add ( path ) ;
continue ;
}
}
if ( nonSharedFiles . size === 0 ) {
// The complete snapshot is shared
// add it as child
children . add ( snapshot ) ;
increaseSharedAndStoreOptimizationEntry ( optimizationEntry ) ;
2020-08-22 17:15:07 +08:00
this . _statReusedSharedSnapshots ++ ;
2020-08-20 17:17:55 +08:00
} else {
// Only a part of the snapshot is shared
2020-08-21 03:57:44 +08:00
const sharedCount = snapshotContent . size - nonSharedFiles . size ;
if ( sharedCount < MIN _COMMON _SNAPSHOT _SIZE ) {
// Common part it too small
checkedOptimizationEntries . add ( optimizationEntry ) ;
continue capturedFiles ;
}
2020-08-20 17:17:55 +08:00
// Extract common timestamps from both snapshots
2020-08-23 03:54:34 +08:00
let commonMap ;
if ( this . _isSet ) {
commonMap = new Set ( ) ;
for ( const path of /** @type {Set<string>} */ ( snapshotEntries ) ) {
if ( nonSharedFiles . has ( path ) ) continue ;
commonMap . add ( path ) ;
snapshotEntries . delete ( path ) ;
}
} else {
commonMap = new Map ( ) ;
const map = /** @type {Map<string, T>} */ ( snapshotEntries ) ;
for ( const [ path , value ] of map ) {
if ( nonSharedFiles . has ( path ) ) continue ;
commonMap . set ( path , value ) ;
snapshotEntries . delete ( path ) ;
}
2020-08-20 17:17:55 +08:00
}
// Create and attach snapshot
2020-08-22 18:01:28 +08:00
const commonSnapshot = new Snapshot ( ) ;
commonSnapshot . setMergedStartTime ( startTime , snapshot ) ;
this . _set ( commonSnapshot , commonMap ) ;
2020-08-20 17:17:55 +08:00
children . add ( commonSnapshot ) ;
2020-08-22 18:01:28 +08:00
snapshot . addChild ( commonSnapshot ) ;
2020-08-20 17:17:55 +08:00
// Create optimization entry
const newEntry = {
snapshot : commonSnapshot ,
shared : optimizationEntry . shared + 1 ,
snapshotContent : new Set ( commonMap . keys ( ) ) ,
children : undefined
} ;
if ( optimizationEntry . children === undefined )
optimizationEntry . children = new Set ( ) ;
optimizationEntry . children . add ( newEntry ) ;
storeOptimizationEntry ( newEntry ) ;
2020-08-21 03:57:44 +08:00
this . _statSharedSnapshots ++ ;
2020-08-20 17:17:55 +08:00
}
} else {
// It's a unshared snapshot
// We can extract a common shared snapshot
// with all common files
2020-08-22 18:01:28 +08:00
const snapshotEntries = this . _get ( snapshot ) ;
2020-08-23 03:54:34 +08:00
let commonMap ;
if ( this . _isSet ) {
commonMap = new Set ( ) ;
const set = /** @type {Set<string>} */ ( snapshotEntries ) ;
if ( capturedFiles . size < set . size ) {
for ( const path of capturedFiles ) {
if ( set . has ( path ) ) commonMap . add ( path ) ;
}
} else {
for ( const path of set ) {
if ( capturedFiles . has ( path ) ) commonMap . add ( path ) ;
}
}
} else {
commonMap = new Map ( ) ;
const map = /** @type {Map<string, T>} */ ( snapshotEntries ) ;
for ( const path of capturedFiles ) {
const ts = map . get ( path ) ;
if ( ts === undefined ) continue ;
commonMap . set ( path , ts ) ;
}
2020-08-20 17:17:55 +08:00
}
2020-08-23 03:54:34 +08:00
2020-08-21 03:57:44 +08:00
if ( commonMap . size < MIN _COMMON _SNAPSHOT _SIZE ) {
2020-08-20 17:17:55 +08:00
// Common part it too small
2020-08-21 03:57:44 +08:00
checkedOptimizationEntries . add ( optimizationEntry ) ;
2020-08-20 17:17:55 +08:00
continue capturedFiles ;
}
// Create and attach snapshot
2020-08-22 18:01:28 +08:00
const commonSnapshot = new Snapshot ( ) ;
commonSnapshot . setMergedStartTime ( startTime , snapshot ) ;
this . _set ( commonSnapshot , commonMap ) ;
2020-08-20 17:17:55 +08:00
children . add ( commonSnapshot ) ;
2020-08-22 18:01:28 +08:00
snapshot . addChild ( commonSnapshot ) ;
2020-08-20 17:17:55 +08:00
// Remove files from snapshot
2020-08-22 18:01:28 +08:00
for ( const path of commonMap . keys ( ) ) snapshotEntries . delete ( path ) ;
2020-08-21 03:57:44 +08:00
const sharedCount = commonMap . size ;
this . _statItemsUnshared -= sharedCount ;
this . _statItemsShared += sharedCount ;
2020-08-20 17:17:55 +08:00
// Create optimization entry
storeOptimizationEntry ( {
snapshot : commonSnapshot ,
shared : 2 ,
snapshotContent : new Set ( commonMap . keys ( ) ) ,
children : undefined
} ) ;
2020-08-21 03:57:44 +08:00
this . _statSharedSnapshots ++ ;
2020-08-20 17:17:55 +08:00
}
2020-08-21 03:57:44 +08:00
checkedOptimizationEntries . add ( optimizationEntry ) ;
2020-08-20 17:17:55 +08:00
}
2020-08-21 03:57:44 +08:00
const unshared = capturedFiles . size ;
this . _statItemsUnshared += unshared ;
this . _statItemsShared += capturedFilesSize - unshared ;
2020-08-20 17:17:55 +08:00
return unsetOptimizationEntries ;
}
}
2020-04-23 16:48:36 +08:00
/* istanbul ignore next */
2019-10-20 17:35:36 +08:00
/ * *
* @ param { number } mtime mtime
* /
2018-09-27 13:22:19 +08:00
const applyMtime = mtime => {
if ( FS _ACCURACY > 1 && mtime % 2 !== 0 ) FS _ACCURACY = 1 ;
else if ( FS _ACCURACY > 10 && mtime % 20 !== 0 ) FS _ACCURACY = 10 ;
else if ( FS _ACCURACY > 100 && mtime % 200 !== 0 ) FS _ACCURACY = 100 ;
else if ( FS _ACCURACY > 1000 && mtime % 2000 !== 0 ) FS _ACCURACY = 1000 ;
} ;
2019-10-20 17:35:36 +08:00
/ * *
* @ template T
* @ template K
* @ param { Map < T , K > } a source map
* @ param { Map < T , K > } b joining map
* @ returns { Map < T , K > } joined map
* /
2019-08-13 04:59:09 +08:00
const mergeMaps = ( a , b ) => {
2019-08-14 00:48:15 +08:00
if ( ! b || b . size === 0 ) return a ;
if ( ! a || a . size === 0 ) return b ;
2019-08-13 04:59:09 +08:00
const map = new Map ( a ) ;
for ( const [ key , value ] of b ) {
map . set ( key , value ) ;
}
return map ;
} ;
2020-08-26 06:36:16 +08:00
/ * *
* @ template T
* @ template K
* @ param { Set < T , K > } a source map
* @ param { Set < T , K > } b joining map
* @ returns { Set < T , K > } joined map
* /
const mergeSets = ( a , b ) => {
if ( ! b || b . size === 0 ) return a ;
if ( ! a || a . size === 0 ) return b ;
const map = new Set ( a ) ;
for ( const item of b ) {
map . add ( item ) ;
}
return map ;
} ;
2019-10-20 17:35:36 +08:00
/ * *
* Finding file or directory to manage
* @ param { string } managedPath path that is managing by { @ link FileSystemInfo }
* @ param { string } path path to file or directory
* @ returns { string | null } managed item
* @ example
* getManagedItem (
2019-10-20 20:53:17 +08:00
* '/Users/user/my-project/node_modules/' ,
2019-10-20 17:35:36 +08:00
* '/Users/user/my-project/node_modules/package/index.js'
* ) === '/Users/user/my-project/node_modules/package'
* getManagedItem (
2019-10-20 20:53:17 +08:00
* '/Users/user/my-project/node_modules/' ,
* '/Users/user/my-project/node_modules/package1/node_modules/package2'
* ) === '/Users/user/my-project/node_modules/package1/node_modules/package2'
* getManagedItem (
* '/Users/user/my-project/node_modules/' ,
* '/Users/user/my-project/node_modules/.bin/script.js'
2019-10-20 17:35:36 +08:00
* ) === null // hidden files are disallowed as managed items
* getManagedItem (
2019-10-20 20:53:17 +08:00
* '/Users/user/my-project/node_modules/' ,
* '/Users/user/my-project/node_modules/package'
* ) === '/Users/user/my-project/node_modules/package'
2019-10-20 17:35:36 +08:00
* /
2019-08-13 04:59:09 +08:00
const getManagedItem = ( managedPath , path ) => {
2019-08-13 23:15:47 +08:00
let i = managedPath . length ;
2019-08-13 23:58:51 +08:00
let slashes = 1 ;
2019-09-08 21:26:40 +08:00
let startingPosition = true ;
2019-08-13 23:15:47 +08:00
loop : while ( i < path . length ) {
switch ( path . charCodeAt ( i ) ) {
2019-08-13 04:59:09 +08:00
case 47 : // slash
case 92 : // backslash
if ( -- slashes === 0 ) break loop ;
2019-09-08 21:26:40 +08:00
startingPosition = true ;
break ;
case 46 : // .
// hidden files are disallowed as managed items
// it's probably .yarn-integrity or .cache
if ( startingPosition ) return null ;
2019-08-13 04:59:09 +08:00
break ;
case 64 : // @
2019-09-08 21:26:40 +08:00
if ( ! startingPosition ) return null ;
2019-08-13 04:59:09 +08:00
slashes ++ ;
break ;
2019-09-08 21:26:40 +08:00
default :
startingPosition = false ;
break ;
2019-08-13 04:59:09 +08:00
}
i ++ ;
}
2019-09-04 19:34:34 +08:00
if ( i === path . length ) slashes -- ;
// return null when path is incomplete
if ( slashes !== 0 ) return null ;
2019-08-13 23:58:51 +08:00
// if (path.slice(i + 1, i + 13) === "node_modules")
if (
2019-11-05 23:47:45 +08:00
path . length >= i + 13 &&
2019-08-13 23:58:51 +08:00
path . charCodeAt ( i + 1 ) === 110 &&
path . charCodeAt ( i + 2 ) === 111 &&
path . charCodeAt ( i + 3 ) === 100 &&
path . charCodeAt ( i + 4 ) === 101 &&
path . charCodeAt ( i + 5 ) === 95 &&
path . charCodeAt ( i + 6 ) === 109 &&
path . charCodeAt ( i + 7 ) === 111 &&
path . charCodeAt ( i + 8 ) === 100 &&
path . charCodeAt ( i + 9 ) === 117 &&
path . charCodeAt ( i + 10 ) === 108 &&
path . charCodeAt ( i + 11 ) === 101 &&
path . charCodeAt ( i + 12 ) === 115
) {
2019-11-05 23:47:45 +08:00
// if this is the end of the path
if ( path . length === i + 13 ) {
// return the node_modules directory
// it's special
return path ;
}
2019-08-13 23:58:51 +08:00
const c = path . charCodeAt ( i + 13 ) ;
2019-10-20 17:35:36 +08:00
// if next symbol is slash or backslash
2019-08-13 23:58:51 +08:00
if ( c === 47 || c === 92 ) {
// Managed subpath
return getManagedItem ( path . slice ( 0 , i + 14 ) , path ) ;
}
}
2019-08-13 23:15:47 +08:00
return path . slice ( 0 , i ) ;
2019-08-13 04:59:09 +08:00
} ;
2019-11-14 23:49:45 +08:00
/ * *
2020-01-28 21:01:19 +08:00
* @ param { FileSystemInfoEntry } entry file system info entry
2020-03-10 09:59:46 +08:00
* @ returns { boolean } existence flag
2019-11-14 23:49:45 +08:00
* /
2020-03-13 19:09:14 +08:00
const toExistence = entry => {
2019-11-14 23:49:45 +08:00
return Boolean ( entry ) ;
} ;
2019-10-20 17:35:36 +08:00
/ * *
* Used to access information about the filesystem in a cached way
* /
2018-09-27 13:22:19 +08:00
class FileSystemInfo {
2019-07-18 05:35:05 +08:00
/ * *
* @ param { InputFileSystem } fs file system
2019-08-13 04:59:09 +08:00
* @ param { Object } options options
* @ param { Iterable < string >= } options . managedPaths paths that are only managed by a package manager
2019-08-16 17:55:10 +08:00
* @ param { Iterable < string >= } options . immutablePaths paths that are immutable
2019-11-04 17:06:53 +08:00
* @ param { Logger = } options . logger logger used to log invalid snapshots
2019-07-18 05:35:05 +08:00
* /
2019-11-04 17:06:53 +08:00
constructor ( fs , { managedPaths = [ ] , immutablePaths = [ ] , logger } = { } ) {
2018-09-27 13:22:19 +08:00
this . fs = fs ;
2019-11-04 17:06:53 +08:00
this . logger = logger ;
this . _remainingLogs = logger ? 40 : 0 ;
this . _loggedPaths = logger ? new Set ( ) : undefined ;
2019-10-27 14:38:06 +08:00
/** @type {WeakMap<Snapshot, boolean | (function(WebpackError=, boolean=): void)[]>} */
this . _snapshotCache = new WeakMap ( ) ;
2020-08-20 17:17:55 +08:00
this . _fileTimestampsOptimization = new SnapshotOptimization (
2020-08-22 18:01:28 +08:00
s => s . hasFileTimestamps ( ) ,
s => s . fileTimestamps ,
( s , v ) => s . setFileTimestamps ( v )
) ;
this . _fileHashesOptimization = new SnapshotOptimization (
s => s . hasFileHashes ( ) ,
s => s . fileHashes ,
( s , v ) => s . setFileHashes ( v )
2020-08-20 17:17:55 +08:00
) ;
2020-08-26 06:36:16 +08:00
this . _fileTshsOptimization = new SnapshotOptimization (
s => s . hasFileTshs ( ) ,
s => s . fileTshs ,
( s , v ) => s . setFileTshs ( v )
) ;
2020-08-20 17:41:20 +08:00
this . _contextTimestampsOptimization = new SnapshotOptimization (
2020-08-22 18:01:28 +08:00
s => s . hasContextTimestamps ( ) ,
s => s . contextTimestamps ,
( s , v ) => s . setContextTimestamps ( v )
) ;
this . _contextHashesOptimization = new SnapshotOptimization (
s => s . hasContextHashes ( ) ,
s => s . contextHashes ,
( s , v ) => s . setContextHashes ( v )
2020-08-20 17:41:20 +08:00
) ;
2020-08-26 06:36:16 +08:00
this . _contextTshsOptimization = new SnapshotOptimization (
s => s . hasContextTshs ( ) ,
s => s . contextTshs ,
( s , v ) => s . setContextTshs ( v )
) ;
2020-08-20 17:41:20 +08:00
this . _missingExistenceOptimization = new SnapshotOptimization (
2020-08-22 18:01:28 +08:00
s => s . hasMissingExistence ( ) ,
s => s . missingExistence ,
( s , v ) => s . setMissingExistence ( v )
2020-08-20 17:41:20 +08:00
) ;
this . _managedItemInfoOptimization = new SnapshotOptimization (
2020-08-22 18:01:28 +08:00
s => s . hasManagedItemInfo ( ) ,
s => s . managedItemInfo ,
( s , v ) => s . setManagedItemInfo ( v )
2020-08-20 17:41:20 +08:00
) ;
2020-08-23 03:54:34 +08:00
this . _managedFilesOptimization = new SnapshotOptimization (
s => s . hasManagedFiles ( ) ,
s => s . managedFiles ,
( s , v ) => s . setManagedFiles ( v ) ,
true
) ;
this . _managedContextsOptimization = new SnapshotOptimization (
s => s . hasManagedContexts ( ) ,
s => s . managedContexts ,
( s , v ) => s . setManagedContexts ( v ) ,
true
) ;
this . _managedMissingOptimization = new SnapshotOptimization (
s => s . hasManagedMissing ( ) ,
s => s . managedMissing ,
( s , v ) => s . setManagedMissing ( v ) ,
true
) ;
2019-11-06 06:46:34 +08:00
/** @type {Map<string, FileSystemInfoEntry | "ignore" | null>} */
2018-09-27 13:22:19 +08:00
this . _fileTimestamps = new Map ( ) ;
2019-08-13 04:59:09 +08:00
/** @type {Map<string, string>} */
this . _fileHashes = new Map ( ) ;
2020-08-26 06:36:16 +08:00
/** @type {Map<string, TimestampAndHash | string>} */
this . _fileTshs = new Map ( ) ;
2019-11-06 06:46:34 +08:00
/** @type {Map<string, FileSystemInfoEntry | "ignore" | null>} */
2018-09-27 13:22:19 +08:00
this . _contextTimestamps = new Map ( ) ;
2019-08-13 04:59:09 +08:00
/** @type {Map<string, string>} */
this . _contextHashes = new Map ( ) ;
2020-08-26 06:36:16 +08:00
/** @type {Map<string, TimestampAndHash | string>} */
this . _contextTshs = new Map ( ) ;
2019-08-13 04:59:09 +08:00
/** @type {Map<string, string>} */
this . _managedItems = new Map ( ) ;
2020-01-28 21:01:19 +08:00
/** @type {AsyncQueue<string, string, FileSystemInfoEntry | null>} */
2018-09-27 13:22:19 +08:00
this . fileTimestampQueue = new AsyncQueue ( {
name : "file timestamp" ,
parallelism : 30 ,
processor : this . _readFileTimestamp . bind ( this )
} ) ;
2020-01-28 21:01:19 +08:00
/** @type {AsyncQueue<string, string, string | null>} */
2019-08-13 04:59:09 +08:00
this . fileHashQueue = new AsyncQueue ( {
name : "file hash" ,
parallelism : 10 ,
processor : this . _readFileHash . bind ( this )
} ) ;
2020-01-28 21:01:19 +08:00
/** @type {AsyncQueue<string, string, FileSystemInfoEntry | null>} */
2018-09-27 13:22:19 +08:00
this . contextTimestampQueue = new AsyncQueue ( {
name : "context timestamp" ,
parallelism : 2 ,
processor : this . _readContextTimestamp . bind ( this )
} ) ;
2020-01-28 21:01:19 +08:00
/** @type {AsyncQueue<string, string, string | null>} */
2019-08-13 04:59:09 +08:00
this . contextHashQueue = new AsyncQueue ( {
name : "context hash" ,
parallelism : 2 ,
processor : this . _readContextHash . bind ( this )
} ) ;
2020-01-28 21:01:19 +08:00
/** @type {AsyncQueue<string, string, string | null>} */
2019-08-13 04:59:09 +08:00
this . managedItemQueue = new AsyncQueue ( {
name : "managed item info" ,
parallelism : 10 ,
processor : this . _getManagedItemInfo . bind ( this )
} ) ;
2020-01-28 21:01:19 +08:00
/** @type {AsyncQueue<string, string, Set<string>>} */
2019-11-05 23:47:45 +08:00
this . managedItemDirectoryQueue = new AsyncQueue ( {
name : "managed item directory info" ,
parallelism : 10 ,
processor : this . _getManagedItemDirectoryInfo . bind ( this )
} ) ;
2019-08-13 04:59:09 +08:00
this . managedPaths = Array . from ( managedPaths ) ;
this . managedPathsWithSlash = this . managedPaths . map ( p =>
join ( fs , p , "_" ) . slice ( 0 , - 1 )
) ;
2019-08-16 17:55:10 +08:00
this . immutablePaths = Array . from ( immutablePaths ) ;
this . immutablePathsWithSlash = this . immutablePaths . map ( p =>
join ( fs , p , "_" ) . slice ( 0 , - 1 )
) ;
2020-08-21 03:57:44 +08:00
2020-09-15 20:39:09 +08:00
this . _cachedDeprecatedFileTimestamps = undefined ;
this . _cachedDeprecatedContextTimestamps = undefined ;
2021-01-14 04:16:55 +08:00
this . _warnAboutExperimentalEsmTracking = false ;
2020-08-21 03:57:44 +08:00
this . _statCreatedSnapshots = 0 ;
this . _statTestedSnapshotsCached = 0 ;
this . _statTestedSnapshotsNotCached = 0 ;
this . _statTestedChildrenCached = 0 ;
this . _statTestedChildrenNotCached = 0 ;
this . _statTestedEntries = 0 ;
}
logStatistics ( ) {
2020-08-23 03:54:34 +08:00
const logWhenMessage = ( header , message ) => {
if ( message ) {
this . logger . log ( ` ${ header } : ${ message } ` ) ;
}
} ;
2020-08-21 03:57:44 +08:00
this . logger . log ( ` ${ this . _statCreatedSnapshots } new snapshots created ` ) ;
this . logger . log (
` ${
2020-08-22 17:15:07 +08:00
this . _statTestedSnapshotsNotCached &&
Math . round (
( this . _statTestedSnapshotsNotCached * 100 ) /
( this . _statTestedSnapshotsCached +
this . _statTestedSnapshotsNotCached )
)
} % root snapshot uncached ( $ { this . _statTestedSnapshotsNotCached } / $ {
2020-08-21 03:57:44 +08:00
this . _statTestedSnapshotsCached + this . _statTestedSnapshotsNotCached
2020-08-22 17:15:07 +08:00
} ) `
) ;
this . logger . log (
` ${
this . _statTestedChildrenNotCached &&
Math . round (
( this . _statTestedChildrenNotCached * 100 ) /
( this . _statTestedChildrenCached + this . _statTestedChildrenNotCached )
)
} % children snapshot uncached ( $ { this . _statTestedChildrenNotCached } / $ {
2020-08-21 03:57:44 +08:00
this . _statTestedChildrenCached + this . _statTestedChildrenNotCached
2020-08-22 17:15:07 +08:00
} ) `
2020-08-21 03:57:44 +08:00
) ;
2020-08-22 17:15:07 +08:00
this . logger . log ( ` ${ this . _statTestedEntries } entries tested ` ) ;
2020-08-21 03:57:44 +08:00
this . logger . log (
2020-08-26 06:36:16 +08:00
` File info in cache: ${ this . _fileTimestamps . size } timestamps ${ this . _fileHashes . size } hashes ${ this . _fileTshs . size } timestamp hash combinations `
2020-08-21 03:57:44 +08:00
) ;
2020-08-23 03:54:34 +08:00
logWhenMessage (
` File timestamp snapshot optimization ` ,
this . _fileTimestampsOptimization . getStatisticMessage ( )
2020-08-21 03:57:44 +08:00
) ;
2020-08-23 03:54:34 +08:00
logWhenMessage (
` File hash snapshot optimization ` ,
this . _fileHashesOptimization . getStatisticMessage ( )
2020-08-21 03:57:44 +08:00
) ;
2020-08-26 06:36:16 +08:00
logWhenMessage (
` File timestamp hash combination snapshot optimization ` ,
this . _fileTshsOptimization . getStatisticMessage ( )
) ;
2020-08-21 03:57:44 +08:00
this . logger . log (
2020-08-26 06:36:16 +08:00
` Directory info in cache: ${ this . _contextTimestamps . size } timestamps ${ this . _contextHashes . size } hashes ${ this . _contextTshs . size } timestamp hash combinations `
2020-08-21 03:57:44 +08:00
) ;
2020-08-23 03:54:34 +08:00
logWhenMessage (
` Directory timestamp snapshot optimization ` ,
this . _contextTimestampsOptimization . getStatisticMessage ( )
2020-08-21 03:57:44 +08:00
) ;
2020-08-23 03:54:34 +08:00
logWhenMessage (
` Directory hash snapshot optimization ` ,
this . _contextHashesOptimization . getStatisticMessage ( )
2020-08-21 03:57:44 +08:00
) ;
2020-08-26 06:36:16 +08:00
logWhenMessage (
` Directory timestamp hash combination snapshot optimization ` ,
this . _contextTshsOptimization . getStatisticMessage ( )
) ;
2020-08-23 03:54:34 +08:00
logWhenMessage (
` Missing items snapshot optimization ` ,
this . _missingExistenceOptimization . getStatisticMessage ( )
2020-08-21 03:57:44 +08:00
) ;
this . logger . log (
` Managed items info in cache: ${ this . _managedItems . size } items `
) ;
2020-08-23 03:54:34 +08:00
logWhenMessage (
` Managed items snapshot optimization ` ,
this . _managedItemInfoOptimization . getStatisticMessage ( )
) ;
logWhenMessage (
` Managed files snapshot optimization ` ,
this . _managedFilesOptimization . getStatisticMessage ( )
) ;
logWhenMessage (
` Managed contexts snapshot optimization ` ,
this . _managedContextsOptimization . getStatisticMessage ( )
) ;
logWhenMessage (
` Managed missing snapshot optimization ` ,
this . _managedMissingOptimization . getStatisticMessage ( )
2020-08-21 03:57:44 +08:00
) ;
2018-09-27 13:22:19 +08:00
}
2019-11-15 00:28:10 +08:00
_log ( path , reason , ... args ) {
const key = path + reason ;
if ( this . _loggedPaths . has ( key ) ) return ;
this . _loggedPaths . add ( key ) ;
this . logger . debug ( ` ${ path } invalidated because ${ reason } ` , ... args ) ;
2019-11-04 17:06:53 +08:00
if ( -- this . _remainingLogs === 0 ) {
this . logger . debug (
2020-03-10 09:59:46 +08:00
"Logging limit has been reached and no further logging will be emitted by FileSystemInfo"
2019-11-04 17:06:53 +08:00
) ;
}
}
2021-03-25 23:35:19 +08:00
clear ( ) {
this . _remainingLogs = this . logger ? 40 : 0 ;
if ( this . _loggedPaths !== undefined ) this . _loggedPaths . clear ( ) ;
this . _snapshotCache = new WeakMap ( ) ;
this . _fileTimestampsOptimization . clear ( ) ;
this . _fileHashesOptimization . clear ( ) ;
this . _fileTshsOptimization . clear ( ) ;
this . _contextTimestampsOptimization . clear ( ) ;
this . _contextHashesOptimization . clear ( ) ;
this . _contextTshsOptimization . clear ( ) ;
this . _missingExistenceOptimization . clear ( ) ;
this . _managedItemInfoOptimization . clear ( ) ;
this . _managedFilesOptimization . clear ( ) ;
this . _managedContextsOptimization . clear ( ) ;
this . _managedMissingOptimization . clear ( ) ;
this . _fileTimestamps . clear ( ) ;
this . _fileHashes . clear ( ) ;
this . _fileTshs . clear ( ) ;
this . _contextTimestamps . clear ( ) ;
this . _contextHashes . clear ( ) ;
this . _contextTshs . clear ( ) ;
this . _managedItems . clear ( ) ;
this . _managedItems . clear ( ) ;
this . _cachedDeprecatedFileTimestamps = undefined ;
this . _cachedDeprecatedContextTimestamps = undefined ;
this . _statCreatedSnapshots = 0 ;
this . _statTestedSnapshotsCached = 0 ;
this . _statTestedSnapshotsNotCached = 0 ;
this . _statTestedChildrenCached = 0 ;
this . _statTestedChildrenNotCached = 0 ;
this . _statTestedEntries = 0 ;
}
2018-09-27 13:22:19 +08:00
/ * *
2019-11-06 06:46:34 +08:00
* @ param { Map < string , FileSystemInfoEntry | "ignore" | null > } map timestamps
2018-09-27 13:22:19 +08:00
* @ returns { void }
* /
addFileTimestamps ( map ) {
for ( const [ path , ts ] of map ) {
this . _fileTimestamps . set ( path , ts ) ;
}
2020-09-15 20:39:09 +08:00
this . _cachedDeprecatedFileTimestamps = undefined ;
2018-09-27 13:22:19 +08:00
}
/ * *
2019-11-06 06:46:34 +08:00
* @ param { Map < string , FileSystemInfoEntry | "ignore" | null > } map timestamps
2018-09-27 13:22:19 +08:00
* @ returns { void }
* /
addContextTimestamps ( map ) {
for ( const [ path , ts ] of map ) {
this . _contextTimestamps . set ( path , ts ) ;
}
2020-09-15 20:39:09 +08:00
this . _cachedDeprecatedContextTimestamps = undefined ;
2018-09-27 13:22:19 +08:00
}
/ * *
* @ param { string } path file path
2020-01-28 21:01:19 +08:00
* @ param { function ( WebpackError = , ( FileSystemInfoEntry | "ignore" | null ) = ) : void } callback callback function
2018-09-27 13:22:19 +08:00
* @ returns { void }
* /
getFileTimestamp ( path , callback ) {
const cache = this . _fileTimestamps . get ( path ) ;
2018-09-28 03:28:07 +08:00
if ( cache !== undefined ) return callback ( null , cache ) ;
2018-09-27 13:22:19 +08:00
this . fileTimestampQueue . add ( path , callback ) ;
}
/ * *
* @ param { string } path context path
2020-01-28 21:01:19 +08:00
* @ param { function ( WebpackError = , ( FileSystemInfoEntry | "ignore" | null ) = ) : void } callback callback function
2018-09-27 13:22:19 +08:00
* @ returns { void }
* /
getContextTimestamp ( path , callback ) {
const cache = this . _contextTimestamps . get ( path ) ;
2018-09-28 03:28:07 +08:00
if ( cache !== undefined ) return callback ( null , cache ) ;
2018-09-27 13:22:19 +08:00
this . contextTimestampQueue . add ( path , callback ) ;
}
2019-08-13 04:59:09 +08:00
/ * *
* @ param { string } path file path
* @ param { function ( WebpackError = , string = ) : void } callback callback function
* @ returns { void }
* /
getFileHash ( path , callback ) {
const cache = this . _fileHashes . get ( path ) ;
if ( cache !== undefined ) return callback ( null , cache ) ;
this . fileHashQueue . add ( path , callback ) ;
}
/ * *
* @ param { string } path context path
* @ param { function ( WebpackError = , string = ) : void } callback callback function
* @ returns { void }
* /
getContextHash ( path , callback ) {
const cache = this . _contextHashes . get ( path ) ;
if ( cache !== undefined ) return callback ( null , cache ) ;
this . contextHashQueue . add ( path , callback ) ;
}
2021-01-15 17:50:02 +08:00
_createBuildDependenciesResolvers ( ) {
const resolveContext = createResolver ( {
resolveToContext : true ,
exportsFields : [ ] ,
fileSystem : this . fs
} ) ;
const resolveCjs = createResolver ( {
extensions : [ ".js" , ".json" , ".node" ] ,
conditionNames : [ "require" , "node" ] ,
fileSystem : this . fs
} ) ;
const resolveEsm = createResolver ( {
extensions : [ ".js" , ".json" , ".node" ] ,
fullySpecified : true ,
conditionNames : [ "import" , "node" ] ,
fileSystem : this . fs
} ) ;
return { resolveContext , resolveEsm , resolveCjs } ;
}
2019-09-04 19:34:34 +08:00
/ * *
* @ param { string } context context directory
* @ param { Iterable < string > } deps dependencies
* @ param { function ( Error = , ResolveBuildDependenciesResult = ) : void } callback callback function
* @ returns { void }
* /
2019-08-13 04:59:09 +08:00
resolveBuildDependencies ( context , deps , callback ) {
2021-01-15 17:50:02 +08:00
const {
resolveContext ,
resolveEsm ,
resolveCjs
} = this . _createBuildDependenciesResolvers ( ) ;
2019-10-20 17:35:36 +08:00
/** @type {Set<string>} */
2019-08-13 04:59:09 +08:00
const files = new Set ( ) ;
2019-10-20 17:35:36 +08:00
/** @type {Set<string>} */
2021-01-15 17:50:02 +08:00
const fileSymlinks = new Set ( ) ;
/** @type {Set<string>} */
2019-08-13 04:59:09 +08:00
const directories = new Set ( ) ;
2019-10-20 17:35:36 +08:00
/** @type {Set<string>} */
2021-01-15 17:50:02 +08:00
const directorySymlinks = new Set ( ) ;
/** @type {Set<string>} */
2019-08-13 04:59:09 +08:00
const missing = new Set ( ) ;
2019-10-20 17:35:36 +08:00
/** @type {Set<string>} */
2019-08-20 03:57:49 +08:00
const resolveFiles = new Set ( ) ;
2019-10-20 17:35:36 +08:00
/** @type {Set<string>} */
2019-08-20 03:57:49 +08:00
const resolveDirectories = new Set ( ) ;
2019-10-20 17:35:36 +08:00
/** @type {Set<string>} */
2019-08-20 03:57:49 +08:00
const resolveMissing = new Set ( ) ;
2019-10-20 17:35:36 +08:00
/** @type {Map<string, string>} */
2019-08-20 03:57:49 +08:00
const resolveResults = new Map ( ) ;
2021-01-15 17:50:02 +08:00
const invalidResolveResults = new Set ( ) ;
const resolverContext = {
fileDependencies : resolveFiles ,
contextDependencies : resolveDirectories ,
missingDependencies : resolveMissing
} ;
2021-03-29 02:35:42 +08:00
const expectedToString = expected => {
return expected ? ` (expected ${ expected } ) ` : "" ;
} ;
const jobToString = job => {
switch ( job . type ) {
case RBDT _RESOLVE _CJS :
return ` resolve commonjs ${ job . path } ${ expectedToString (
job . expected
) } ` ;
case RBDT _RESOLVE _ESM :
return ` resolve esm ${ job . path } ${ expectedToString ( job . expected ) } ` ;
case RBDT _RESOLVE _DIRECTORY :
return ` resolve directory ${ job . path } ` ;
case RBDT _RESOLVE _CJS _FILE :
return ` resolve commonjs file ${ job . path } ${ expectedToString (
job . expected
) } ` ;
case RBDT _RESOLVE _ESM _FILE :
return ` resolve esm file ${ job . path } ${ expectedToString (
job . expected
) } ` ;
case RBDT _DIRECTORY :
return ` directory ${ job . path } ` ;
case RBDT _FILE :
return ` file ${ job . path } ` ;
case RBDT _DIRECTORY _DEPENDENCIES :
return ` directory dependencies ${ job . path } ` ;
case RBDT _FILE _DEPENDENCIES :
return ` file dependencies ${ job . path } ` ;
}
return ` unknown ${ job . type } ${ job . path } ` ;
} ;
const pathToString = job => {
let result = ` at ${ jobToString ( job ) } ` ;
job = job . issuer ;
while ( job !== undefined ) {
result += ` \n at ${ jobToString ( job ) } ` ;
job = job . issuer ;
}
return result ;
} ;
2021-01-15 17:50:02 +08:00
processAsyncTree (
Array . from ( deps , dep => ( {
type : RBDT _RESOLVE _CJS ,
context ,
path : dep ,
2021-03-29 02:35:42 +08:00
expected : undefined ,
issuer : undefined
2021-01-15 17:50:02 +08:00
} ) ) ,
20 ,
2021-03-29 02:35:42 +08:00
( job , push , callback ) => {
const { type , context , path , expected } = job ;
2019-08-20 19:50:14 +08:00
const resolveDirectory = path => {
const key = ` d \n ${ context } \n ${ path } ` ;
if ( resolveResults . has ( key ) ) {
return callback ( ) ;
}
2021-01-15 17:50:02 +08:00
resolveResults . set ( key , undefined ) ;
resolveContext ( context , path , resolverContext , ( err , result ) => {
if ( err ) {
invalidResolveResults . add ( key ) ;
if (
err . code === "ENOENT" ||
err . code === "UNDECLARED_DEPENDENCY"
) {
return callback ( ) ;
}
err . message += ` \n while resolving ' ${ path } ' in ${ context } to a directory ` ;
return callback ( err ) ;
}
resolveResults . set ( key , result ) ;
push ( {
type : RBDT _DIRECTORY ,
context : undefined ,
path : result ,
2021-03-29 02:35:42 +08:00
expected : undefined ,
issuer : job
2021-01-15 17:50:02 +08:00
} ) ;
callback ( ) ;
} ) ;
} ;
const resolveFile = ( path , symbol , resolve ) => {
const key = ` ${ symbol } \n ${ context } \n ${ path } ` ;
if ( resolveResults . has ( key ) ) {
return callback ( ) ;
}
resolveResults . set ( key , undefined ) ;
resolve ( context , path , resolverContext , ( err , result ) => {
if ( expected ) {
if ( result === expected ) {
resolveResults . set ( key , result ) ;
} else {
invalidResolveResults . add ( key ) ;
this . logger . debug (
2021-03-29 02:35:42 +08:00
` Resolving ' ${ path } ' in ${ context } for build dependencies doesn't lead to expected result ' ${ expected } ', but to ' ${ result } ' instead. Resolving dependencies are ignored for this path. \n ${ pathToString (
job
) } `
2021-01-15 17:50:02 +08:00
) ;
}
} else {
2019-08-20 19:50:14 +08:00
if ( err ) {
2021-01-15 17:50:02 +08:00
invalidResolveResults . add ( key ) ;
2019-08-20 19:50:14 +08:00
if (
err . code === "ENOENT" ||
err . code === "UNDECLARED_DEPENDENCY"
2020-08-11 01:24:11 +08:00
) {
2019-08-20 19:50:14 +08:00
return callback ( ) ;
2020-08-11 01:24:11 +08:00
}
2021-03-29 02:35:42 +08:00
err . message += ` \n while resolving ' ${ path } ' in ${ context } as file \n ${ pathToString (
job
) } ` ;
2019-08-20 19:50:14 +08:00
return callback ( err ) ;
}
resolveResults . set ( key , result ) ;
2021-01-15 17:50:02 +08:00
push ( {
type : RBDT _FILE ,
context : undefined ,
path : result ,
2021-03-29 02:35:42 +08:00
expected : undefined ,
issuer : job
2019-08-20 19:50:14 +08:00
} ) ;
2019-08-20 03:57:49 +08:00
}
2021-01-15 17:50:02 +08:00
callback ( ) ;
} ) ;
2019-08-20 19:50:14 +08:00
} ;
switch ( type ) {
2021-01-15 17:50:02 +08:00
case RBDT _RESOLVE _CJS : {
const isDirectory = /[\\/]$/ . test ( path ) ;
if ( isDirectory ) {
resolveDirectory ( path . slice ( 0 , path . length - 1 ) ) ;
} else {
resolveFile ( path , "f" , resolveCjs ) ;
}
break ;
}
case RBDT _RESOLVE _ESM : {
2019-08-20 19:50:14 +08:00
const isDirectory = /[\\/]$/ . test ( path ) ;
if ( isDirectory ) {
resolveDirectory ( path . slice ( 0 , path . length - 1 ) ) ;
} else {
resolveFile ( path ) ;
}
break ;
2019-08-20 03:57:49 +08:00
}
2019-08-20 19:50:14 +08:00
case RBDT _RESOLVE _DIRECTORY : {
resolveDirectory ( path ) ;
break ;
2019-08-13 04:59:09 +08:00
}
2021-01-15 17:50:02 +08:00
case RBDT _RESOLVE _CJS _FILE : {
resolveFile ( path , "f" , resolveCjs ) ;
break ;
}
case RBDT _RESOLVE _ESM _FILE : {
resolveFile ( path , "e" , resolveEsm ) ;
2019-08-13 04:59:09 +08:00
break ;
}
2019-08-20 19:50:14 +08:00
case RBDT _FILE : {
if ( files . has ( path ) ) {
callback ( ) ;
break ;
2019-08-20 03:57:49 +08:00
}
2021-01-15 17:50:02 +08:00
files . add ( path ) ;
2021-01-13 07:09:19 +08:00
this . fs . realpath ( path , ( err , _realPath ) => {
2019-08-20 19:50:14 +08:00
if ( err ) return callback ( err ) ;
2021-01-13 07:09:19 +08:00
const realPath = /** @type {string} */ ( _realPath ) ;
2019-08-20 19:50:14 +08:00
if ( realPath !== path ) {
2021-01-15 17:50:02 +08:00
fileSymlinks . add ( path ) ;
2019-08-20 19:50:14 +08:00
resolveFiles . add ( path ) ;
2021-01-15 17:50:02 +08:00
if ( files . has ( realPath ) ) return callback ( ) ;
2019-08-20 19:50:14 +08:00
files . add ( realPath ) ;
}
2021-01-15 17:50:02 +08:00
push ( {
type : RBDT _FILE _DEPENDENCIES ,
context : undefined ,
path : realPath ,
2021-03-29 02:35:42 +08:00
expected : undefined ,
issuer : job
2021-01-15 17:50:02 +08:00
} ) ;
2019-08-20 19:50:14 +08:00
callback ( ) ;
} ) ;
2019-08-13 04:59:09 +08:00
break ;
}
2019-08-20 19:50:14 +08:00
case RBDT _DIRECTORY : {
if ( directories . has ( path ) ) {
callback ( ) ;
break ;
2019-08-13 04:59:09 +08:00
}
2021-01-15 17:50:02 +08:00
directories . add ( path ) ;
2021-01-13 07:09:19 +08:00
this . fs . realpath ( path , ( err , _realPath ) => {
2019-08-20 19:50:14 +08:00
if ( err ) return callback ( err ) ;
2021-01-13 07:09:19 +08:00
const realPath = /** @type {string} */ ( _realPath ) ;
2019-08-20 19:50:14 +08:00
if ( realPath !== path ) {
2021-01-15 17:50:02 +08:00
directorySymlinks . add ( path ) ;
2019-08-20 19:50:14 +08:00
resolveFiles . add ( path ) ;
2021-01-15 17:50:02 +08:00
if ( directories . has ( realPath ) ) return callback ( ) ;
2019-08-20 19:50:14 +08:00
directories . add ( realPath ) ;
2019-08-13 04:59:09 +08:00
}
2021-01-15 17:50:02 +08:00
push ( {
type : RBDT _DIRECTORY _DEPENDENCIES ,
context : undefined ,
path : realPath ,
2021-03-29 02:35:42 +08:00
expected : undefined ,
issuer : job
2021-01-15 17:50:02 +08:00
} ) ;
2019-08-20 19:50:14 +08:00
callback ( ) ;
2019-08-13 04:59:09 +08:00
} ) ;
2019-08-20 19:50:14 +08:00
break ;
2019-08-13 04:59:09 +08:00
}
2019-08-20 19:50:14 +08:00
case RBDT _FILE _DEPENDENCIES : {
2021-01-14 04:16:55 +08:00
// Check for known files without dependencies
if ( /\.json5?$|\.yarn-integrity$|yarn\.lock$|\.ya?ml/ . test ( path ) ) {
process . nextTick ( callback ) ;
break ;
}
// Check commonjs cache for the module
2019-08-20 19:50:14 +08:00
/** @type {NodeModule} */
const module = require . cache [ path ] ;
if ( module && Array . isArray ( module . children ) ) {
children : for ( const child of module . children ) {
let childPath = child . filename ;
if ( childPath ) {
2021-01-15 17:50:02 +08:00
push ( {
2019-08-20 19:50:14 +08:00
type : RBDT _FILE ,
2021-01-15 17:50:02 +08:00
context : undefined ,
path : childPath ,
2021-03-29 02:35:42 +08:00
expected : undefined ,
issuer : job
2019-08-20 19:50:14 +08:00
} ) ;
const context = dirname ( this . fs , path ) ;
for ( const modulePath of module . paths ) {
if ( childPath . startsWith ( modulePath ) ) {
2021-01-15 17:50:02 +08:00
let request = childPath . slice ( modulePath . length + 1 ) ;
if ( request . endsWith ( ".js" ) )
request = request . slice ( 0 , - 3 ) ;
push ( {
type : RBDT _RESOLVE _CJS _FILE ,
2019-08-20 19:50:14 +08:00
context ,
path : request ,
2021-03-29 02:35:42 +08:00
expected : child . filename ,
issuer : job
2019-08-20 19:50:14 +08:00
} ) ;
continue children ;
}
}
let request = relative ( this . fs , context , childPath ) ;
2021-01-15 17:50:02 +08:00
if ( request . endsWith ( ".js" ) ) request = request . slice ( 0 , - 3 ) ;
2019-08-20 19:50:14 +08:00
request = request . replace ( /\\/g , "/" ) ;
if ( ! request . startsWith ( "../" ) ) request = ` ./ ${ request } ` ;
2021-01-15 17:50:02 +08:00
push ( {
type : RBDT _RESOLVE _CJS _FILE ,
2019-08-20 19:50:14 +08:00
context ,
path : request ,
2021-03-29 02:35:42 +08:00
expected : child . filename ,
issuer : job
2019-08-13 04:59:09 +08:00
} ) ;
}
}
2021-01-14 04:16:55 +08:00
} else if ( supportsEsm && /\.m?js$/ . test ( path ) ) {
if ( ! this . _warnAboutExperimentalEsmTracking ) {
this . logger . info (
"Node.js doesn't offer a (nice) way to introspect the ESM dependency graph yet.\n" +
"Until a full solution is available webpack uses an experimental ESM tracking based on parsing.\n" +
"As best effort webpack parses the ESM files to guess dependencies. But this can lead to expensive and incorrect tracking."
) ;
this . _warnAboutExperimentalEsmTracking = true ;
}
const lexer = require ( "es-module-lexer" ) ;
lexer . init . then ( ( ) => {
this . fs . readFile ( path , ( err , content ) => {
if ( err ) return callback ( err ) ;
try {
const context = dirname ( this . fs , path ) ;
const source = content . toString ( ) ;
const [ imports ] = lexer . parse ( source ) ;
for ( const imp of imports ) {
try {
let dependency ;
if ( imp . d === - 1 ) {
// import ... from "..."
dependency = JSON . parse (
source . substring ( imp . s - 1 , imp . e + 1 )
) ;
} else if ( imp . d > - 1 ) {
// import()
let expr = source . substring ( imp . s , imp . e ) . trim ( ) ;
if ( expr [ 0 ] === "'" )
expr = ` " ${ expr
. slice ( 1 , - 1 )
. replace ( /"/g , '\\"' ) } " ` ;
dependency = JSON . parse ( expr ) ;
} else {
// e.g. import.meta
continue ;
}
2021-01-15 17:50:02 +08:00
push ( {
type : RBDT _RESOLVE _ESM _FILE ,
2021-01-14 04:16:55 +08:00
context ,
2021-01-15 17:50:02 +08:00
path : dependency ,
2021-03-29 02:35:42 +08:00
expected : undefined ,
issuer : job
2021-01-14 04:16:55 +08:00
} ) ;
} catch ( e ) {
this . logger . warn (
` Parsing of ${ path } for build dependencies failed at 'import( ${ source . substring (
imp . s ,
imp . e
) } ) ' . \ n ` +
"Build dependencies behind this expression are ignored and might cause incorrect cache invalidation."
) ;
2021-03-29 02:35:42 +08:00
this . logger . debug ( pathToString ( job ) ) ;
2021-01-14 04:16:55 +08:00
this . logger . debug ( e . stack ) ;
}
}
} catch ( e ) {
this . logger . warn (
` Parsing of ${ path } for build dependencies failed and all dependencies of this file are ignored, which might cause incorrect cache invalidation.. `
) ;
2021-03-29 02:35:42 +08:00
this . logger . debug ( pathToString ( job ) ) ;
2021-01-14 04:16:55 +08:00
this . logger . debug ( e . stack ) ;
}
process . nextTick ( callback ) ;
} ) ;
} , callback ) ;
break ;
2019-08-20 19:50:14 +08:00
} else {
2021-01-14 04:16:55 +08:00
this . logger . log (
` Assuming ${ path } has no dependencies as we were unable to assign it to any module system. `
) ;
2021-03-29 02:35:42 +08:00
this . logger . debug ( pathToString ( job ) ) ;
2019-08-13 04:59:09 +08:00
}
2020-09-17 16:57:47 +08:00
process . nextTick ( callback ) ;
2019-08-20 19:50:14 +08:00
break ;
}
case RBDT _DIRECTORY _DEPENDENCIES : {
const match = /(^.+[\\/]node_modules[\\/](?:@[^\\/]+[\\/])?[^\\/]+)/ . exec (
path
) ;
const packagePath = match ? match [ 1 ] : path ;
const packageJson = join ( this . fs , packagePath , "package.json" ) ;
this . fs . readFile ( packageJson , ( err , content ) => {
if ( err ) {
if ( err . code === "ENOENT" ) {
resolveMissing . add ( packageJson ) ;
const parent = dirname ( this . fs , packagePath ) ;
if ( parent !== packagePath ) {
2021-01-15 17:50:02 +08:00
push ( {
2019-08-20 19:50:14 +08:00
type : RBDT _DIRECTORY _DEPENDENCIES ,
2021-01-15 17:50:02 +08:00
context : undefined ,
path : parent ,
2021-03-29 02:35:42 +08:00
expected : undefined ,
issuer : job
2019-08-20 19:50:14 +08:00
} ) ;
}
callback ( ) ;
return ;
}
return callback ( err ) ;
}
resolveFiles . add ( packageJson ) ;
let packageData ;
try {
packageData = JSON . parse ( content . toString ( "utf-8" ) ) ;
} catch ( e ) {
return callback ( e ) ;
}
const depsObject = packageData . dependencies ;
if ( typeof depsObject === "object" && depsObject ) {
for ( const dep of Object . keys ( depsObject ) ) {
2021-01-15 17:50:02 +08:00
push ( {
2019-08-20 19:50:14 +08:00
type : RBDT _RESOLVE _DIRECTORY ,
context : packagePath ,
2021-01-15 17:50:02 +08:00
path : dep ,
2021-03-29 02:35:42 +08:00
expected : undefined ,
issuer : job
2019-08-20 19:50:14 +08:00
} ) ;
}
}
callback ( ) ;
} ) ;
break ;
}
2019-08-13 04:59:09 +08:00
}
2019-08-20 19:50:14 +08:00
} ,
2021-01-15 17:50:02 +08:00
err => {
if ( err ) return callback ( err ) ;
for ( const l of fileSymlinks ) files . delete ( l ) ;
for ( const l of directorySymlinks ) directories . delete ( l ) ;
for ( const k of invalidResolveResults ) resolveResults . delete ( k ) ;
callback ( null , {
files ,
directories ,
missing ,
resolveResults ,
resolveDependencies : {
files : resolveFiles ,
directories : resolveDirectories ,
missing : resolveMissing
}
} ) ;
}
2019-08-20 19:50:14 +08:00
) ;
2019-08-13 04:59:09 +08:00
}
2019-08-20 03:57:49 +08:00
/ * *
* @ param { Map < string , string > } resolveResults results from resolving
* @ param { function ( Error = , boolean = ) : void } callback callback with true when resolveResults resolve the same way
* @ returns { void }
* /
checkResolveResultsValid ( resolveResults , callback ) {
2021-01-15 17:50:02 +08:00
const {
resolveCjs ,
resolveEsm ,
resolveContext
} = this . _createBuildDependenciesResolvers ( ) ;
2019-08-20 03:57:49 +08:00
asyncLib . eachLimit (
resolveResults ,
20 ,
( [ key , expectedResult ] , callback ) => {
const [ type , context , path ] = key . split ( "\n" ) ;
switch ( type ) {
case "d" :
2019-11-11 22:25:03 +08:00
resolveContext ( context , path , { } , ( err , result ) => {
2019-08-20 03:57:49 +08:00
if ( err ) return callback ( err ) ;
if ( result !== expectedResult ) return callback ( INVALID ) ;
callback ( ) ;
} ) ;
break ;
case "f" :
2021-01-15 17:50:02 +08:00
resolveCjs ( context , path , { } , ( err , result ) => {
if ( err ) return callback ( err ) ;
if ( result !== expectedResult ) return callback ( INVALID ) ;
callback ( ) ;
} ) ;
break ;
case "e" :
resolveEsm ( context , path , { } , ( err , result ) => {
2019-08-20 03:57:49 +08:00
if ( err ) return callback ( err ) ;
if ( result !== expectedResult ) return callback ( INVALID ) ;
callback ( ) ;
} ) ;
break ;
default :
callback ( new Error ( "Unexpected type in resolve result key" ) ) ;
break ;
}
} ,
/ * *
* @ param { Error | typeof INVALID = } err error or invalid flag
* @ returns { void }
* /
err => {
if ( err === INVALID ) {
return callback ( null , false ) ;
}
if ( err ) {
return callback ( err ) ;
}
return callback ( null , true ) ;
}
) ;
}
2019-07-18 05:35:05 +08:00
/ * *
*
* @ param { number } startTime when processing the files has started
* @ param { Iterable < string > } files all files
* @ param { Iterable < string > } directories all directories
* @ param { Iterable < string > } missing all missing files or directories
* @ param { Object } options options object ( for future extensions )
2019-11-14 23:49:45 +08:00
* @ param { boolean = } options . hash should use hash to snapshot
2020-08-21 03:57:44 +08:00
* @ param { boolean = } options . timestamp should use timestamp to snapshot
2019-07-18 05:35:05 +08:00
* @ param { function ( WebpackError = , Snapshot = ) : void } callback callback function
* @ returns { void }
* /
2019-01-05 21:58:06 +08:00
createSnapshot ( startTime , files , directories , missing , options , callback ) {
2020-01-28 21:01:19 +08:00
/** @type {Map<string, FileSystemInfoEntry>} */
2019-01-05 21:58:06 +08:00
const fileTimestamps = new Map ( ) ;
2020-01-28 21:01:19 +08:00
/** @type {Map<string, string>} */
2019-08-13 04:59:09 +08:00
const fileHashes = new Map ( ) ;
2020-08-26 06:36:16 +08:00
/** @type {Map<string, TimestampAndHash | string>} */
const fileTshs = new Map ( ) ;
2020-01-28 21:01:19 +08:00
/** @type {Map<string, FileSystemInfoEntry>} */
2019-01-05 21:58:06 +08:00
const contextTimestamps = new Map ( ) ;
2020-01-28 21:01:19 +08:00
/** @type {Map<string, string>} */
2019-08-13 04:59:09 +08:00
const contextHashes = new Map ( ) ;
2020-08-26 06:36:16 +08:00
/** @type {Map<string, TimestampAndHash | string>} */
const contextTshs = new Map ( ) ;
2020-01-28 21:01:19 +08:00
/** @type {Map<string, boolean>} */
2020-03-13 19:09:14 +08:00
const missingExistence = new Map ( ) ;
2020-01-28 21:01:19 +08:00
/** @type {Map<string, string>} */
2019-08-13 04:59:09 +08:00
const managedItemInfo = new Map ( ) ;
2020-08-23 03:54:34 +08:00
/** @type {Set<string>} */
const managedFiles = new Set ( ) ;
/** @type {Set<string>} */
const managedContexts = new Set ( ) ;
/** @type {Set<string>} */
const managedMissing = new Set ( ) ;
2020-01-31 17:59:28 +08:00
/** @type {Set<Snapshot>} */
const children = new Set ( ) ;
/** @type {Set<string>} */
2020-08-20 17:17:55 +08:00
let unsharedFileTimestamps ;
2020-08-20 17:41:20 +08:00
/** @type {Set<string>} */
let unsharedFileHashes ;
/** @type {Set<string>} */
2020-08-26 06:36:16 +08:00
let unsharedFileTshs ;
/** @type {Set<string>} */
2020-08-20 17:41:20 +08:00
let unsharedContextTimestamps ;
/** @type {Set<string>} */
let unsharedContextHashes ;
/** @type {Set<string>} */
2020-08-26 06:36:16 +08:00
let unsharedContextTshs ;
/** @type {Set<string>} */
2020-08-20 17:41:20 +08:00
let unsharedMissingExistence ;
/** @type {Set<string>} */
let unsharedManagedItemInfo ;
2019-08-13 04:59:09 +08:00
2020-01-28 21:01:19 +08:00
/** @type {Set<string>} */
2019-08-13 04:59:09 +08:00
const managedItems = new Set ( ) ;
2020-08-26 06:36:16 +08:00
/** 1 = timestamp, 2 = hash, 3 = timestamp + hash */
const mode = options && options . hash ? ( options . timestamp ? 3 : 2 ) : 1 ;
2019-01-05 21:58:06 +08:00
let jobs = 1 ;
const jobDone = ( ) => {
if ( -- jobs === 0 ) {
2020-08-22 18:01:28 +08:00
const snapshot = new Snapshot ( ) ;
if ( startTime ) snapshot . setStartTime ( startTime ) ;
2020-08-20 17:41:20 +08:00
if ( fileTimestamps . size !== 0 ) {
2020-08-22 18:01:28 +08:00
snapshot . setFileTimestamps ( fileTimestamps ) ;
2020-08-20 17:41:20 +08:00
this . _fileTimestampsOptimization . storeUnsharedSnapshot (
snapshot ,
unsharedFileTimestamps
) ;
}
if ( fileHashes . size !== 0 ) {
2020-08-22 18:01:28 +08:00
snapshot . setFileHashes ( fileHashes ) ;
2020-08-20 17:41:20 +08:00
this . _fileHashesOptimization . storeUnsharedSnapshot (
snapshot ,
unsharedFileHashes
) ;
}
2020-08-26 06:36:16 +08:00
if ( fileTshs . size !== 0 ) {
snapshot . setFileTshs ( fileTshs ) ;
this . _fileTshsOptimization . storeUnsharedSnapshot (
snapshot ,
unsharedFileTshs
) ;
}
2020-08-20 17:41:20 +08:00
if ( contextTimestamps . size !== 0 ) {
2020-08-22 18:01:28 +08:00
snapshot . setContextTimestamps ( contextTimestamps ) ;
2020-08-20 17:41:20 +08:00
this . _contextTimestampsOptimization . storeUnsharedSnapshot (
snapshot ,
unsharedContextTimestamps
) ;
}
if ( contextHashes . size !== 0 ) {
2020-08-22 18:01:28 +08:00
snapshot . setContextHashes ( contextHashes ) ;
2020-08-20 17:41:20 +08:00
this . _contextHashesOptimization . storeUnsharedSnapshot (
snapshot ,
unsharedContextHashes
) ;
}
2020-08-26 06:36:16 +08:00
if ( contextTshs . size !== 0 ) {
snapshot . setContextTshs ( contextTshs ) ;
this . _contextTshsOptimization . storeUnsharedSnapshot (
snapshot ,
unsharedContextTshs
) ;
}
2020-08-20 17:41:20 +08:00
if ( missingExistence . size !== 0 ) {
2020-08-22 18:01:28 +08:00
snapshot . setMissingExistence ( missingExistence ) ;
2020-08-20 17:41:20 +08:00
this . _missingExistenceOptimization . storeUnsharedSnapshot (
snapshot ,
unsharedMissingExistence
) ;
}
if ( managedItemInfo . size !== 0 ) {
2020-08-22 18:01:28 +08:00
snapshot . setManagedItemInfo ( managedItemInfo ) ;
2020-08-20 17:41:20 +08:00
this . _managedItemInfoOptimization . storeUnsharedSnapshot (
snapshot ,
unsharedManagedItemInfo
) ;
}
2020-08-23 03:54:34 +08:00
const unsharedManagedFiles = this . _managedFilesOptimization . optimize (
managedFiles ,
undefined ,
children
) ;
if ( managedFiles . size !== 0 ) {
snapshot . setManagedFiles ( managedFiles ) ;
this . _managedFilesOptimization . storeUnsharedSnapshot (
snapshot ,
unsharedManagedFiles
) ;
}
const unsharedManagedContexts = this . _managedContextsOptimization . optimize (
managedContexts ,
undefined ,
children
) ;
if ( managedContexts . size !== 0 ) {
snapshot . setManagedContexts ( managedContexts ) ;
this . _managedContextsOptimization . storeUnsharedSnapshot (
snapshot ,
unsharedManagedContexts
) ;
}
const unsharedManagedMissing = this . _managedMissingOptimization . optimize (
managedMissing ,
undefined ,
children
) ;
if ( managedMissing . size !== 0 ) {
snapshot . setManagedMissing ( managedMissing ) ;
this . _managedMissingOptimization . storeUnsharedSnapshot (
snapshot ,
unsharedManagedMissing
) ;
}
2020-08-20 17:41:20 +08:00
if ( children . size !== 0 ) {
2020-08-22 18:01:28 +08:00
snapshot . setChildren ( children ) ;
2020-08-20 17:41:20 +08:00
}
2019-11-08 00:31:18 +08:00
this . _snapshotCache . set ( snapshot , true ) ;
2020-08-21 03:57:44 +08:00
this . _statCreatedSnapshots ++ ;
2020-08-20 17:41:20 +08:00
2019-08-13 04:59:09 +08:00
callback ( null , snapshot ) ;
2019-01-05 21:58:06 +08:00
}
} ;
2020-01-28 21:01:19 +08:00
const jobError = ( ) => {
if ( jobs > 0 ) {
// large negative number instead of NaN or something else to keep jobs to stay a SMI (v8)
jobs = - 100000000 ;
callback ( null , null ) ;
}
} ;
2020-08-23 03:54:34 +08:00
const checkManaged = ( path , managedSet ) => {
2020-08-20 17:17:55 +08:00
for ( const immutablePath of this . immutablePathsWithSlash ) {
if ( path . startsWith ( immutablePath ) ) {
2020-08-23 03:54:34 +08:00
managedSet . add ( path ) ;
2020-08-20 17:17:55 +08:00
return true ;
}
}
for ( const managedPath of this . managedPathsWithSlash ) {
if ( path . startsWith ( managedPath ) ) {
const managedItem = getManagedItem ( managedPath , path ) ;
if ( managedItem ) {
managedItems . add ( managedItem ) ;
2020-08-23 03:54:34 +08:00
managedSet . add ( path ) ;
2020-08-20 17:17:55 +08:00
return true ;
}
}
}
return false ;
} ;
2020-08-23 03:54:34 +08:00
const captureNonManaged = ( items , managedSet ) => {
2020-08-20 17:17:55 +08:00
const capturedItems = new Set ( ) ;
for ( const path of items ) {
2020-08-23 03:54:34 +08:00
if ( ! checkManaged ( path , managedSet ) ) capturedItems . add ( path ) ;
2020-08-20 17:17:55 +08:00
}
return capturedItems ;
} ;
2019-01-09 20:23:26 +08:00
if ( files ) {
2020-08-23 03:54:34 +08:00
const capturedFiles = captureNonManaged ( files , managedFiles ) ;
2020-08-26 06:36:16 +08:00
switch ( mode ) {
case 3 :
unsharedFileTshs = this . _fileTshsOptimization . optimize (
capturedFiles ,
undefined ,
children
) ;
for ( const path of capturedFiles ) {
const cache = this . _fileTshs . get ( path ) ;
if ( cache !== undefined ) {
fileTshs . set ( path , cache ) ;
} else {
jobs ++ ;
this . _getFileTimestampAndHash ( path , ( err , entry ) => {
if ( err ) {
if ( this . logger ) {
this . logger . debug (
2021-01-14 03:37:45 +08:00
` Error snapshotting file timestamp hash combination of ${ path } : ${ err . stack } `
2020-08-26 06:36:16 +08:00
) ;
}
jobError ( ) ;
} else {
fileTshs . set ( path , entry ) ;
jobDone ( ) ;
2019-11-05 23:47:45 +08:00
}
2020-08-26 06:36:16 +08:00
} ) ;
2020-01-28 21:01:19 +08:00
}
2020-08-26 06:36:16 +08:00
}
break ;
case 2 :
unsharedFileHashes = this . _fileHashesOptimization . optimize (
capturedFiles ,
undefined ,
children
) ;
for ( const path of capturedFiles ) {
const cache = this . _fileHashes . get ( path ) ;
if ( cache !== undefined ) {
fileHashes . set ( path , cache ) ;
} else {
jobs ++ ;
this . fileHashQueue . add ( path , ( err , entry ) => {
if ( err ) {
if ( this . logger ) {
this . logger . debug (
2021-01-14 03:37:45 +08:00
` Error snapshotting file hash of ${ path } : ${ err . stack } `
2020-08-26 06:36:16 +08:00
) ;
}
jobError ( ) ;
} else {
fileHashes . set ( path , entry ) ;
jobDone ( ) ;
2019-11-05 23:47:45 +08:00
}
2020-08-26 06:36:16 +08:00
} ) ;
}
}
break ;
case 1 :
unsharedFileTimestamps = this . _fileTimestampsOptimization . optimize (
capturedFiles ,
startTime ,
children
) ;
for ( const path of capturedFiles ) {
const cache = this . _fileTimestamps . get ( path ) ;
if ( cache !== undefined ) {
if ( cache !== "ignore" ) {
fileTimestamps . set ( path , cache ) ;
2019-08-13 04:59:09 +08:00
}
2020-08-26 06:36:16 +08:00
} else {
jobs ++ ;
this . fileTimestampQueue . add ( path , ( err , entry ) => {
if ( err ) {
if ( this . logger ) {
this . logger . debug (
2021-01-14 03:37:45 +08:00
` Error snapshotting file timestamp of ${ path } : ${ err . stack } `
2020-08-26 06:36:16 +08:00
) ;
}
jobError ( ) ;
} else {
fileTimestamps . set ( path , entry ) ;
jobDone ( ) ;
}
} ) ;
}
2019-08-13 04:59:09 +08:00
}
2020-08-26 06:36:16 +08:00
break ;
2019-01-05 21:58:06 +08:00
}
}
2019-01-09 20:23:26 +08:00
if ( directories ) {
2020-08-23 03:54:34 +08:00
const capturedDirectories = captureNonManaged (
directories ,
managedContexts
) ;
2020-08-26 06:36:16 +08:00
switch ( mode ) {
case 3 :
unsharedContextTshs = this . _contextTshsOptimization . optimize (
capturedDirectories ,
undefined ,
children
) ;
for ( const path of capturedDirectories ) {
const cache = this . _contextTshs . get ( path ) ;
if ( cache !== undefined ) {
contextTshs . set ( path , cache ) ;
} else {
jobs ++ ;
this . _getContextTimestampAndHash ( path , ( err , entry ) => {
if ( err ) {
if ( this . logger ) {
this . logger . debug (
2021-01-14 03:37:45 +08:00
` Error snapshotting context timestamp hash combination of ${ path } : ${ err . stack } `
2020-08-26 06:36:16 +08:00
) ;
}
jobError ( ) ;
} else {
contextTshs . set ( path , entry ) ;
jobDone ( ) ;
2019-11-05 23:47:45 +08:00
}
2020-08-26 06:36:16 +08:00
} ) ;
2020-01-28 21:01:19 +08:00
}
2020-08-26 06:36:16 +08:00
}
break ;
case 2 :
unsharedContextHashes = this . _contextHashesOptimization . optimize (
capturedDirectories ,
undefined ,
children
) ;
for ( const path of capturedDirectories ) {
const cache = this . _contextHashes . get ( path ) ;
if ( cache !== undefined ) {
contextHashes . set ( path , cache ) ;
} else {
jobs ++ ;
this . contextHashQueue . add ( path , ( err , entry ) => {
if ( err ) {
if ( this . logger ) {
this . logger . debug (
2021-01-14 03:37:45 +08:00
` Error snapshotting context hash of ${ path } : ${ err . stack } `
2020-08-26 06:36:16 +08:00
) ;
}
jobError ( ) ;
} else {
contextHashes . set ( path , entry ) ;
jobDone ( ) ;
2019-11-05 23:47:45 +08:00
}
2020-08-26 06:36:16 +08:00
} ) ;
}
}
break ;
case 1 :
unsharedContextTimestamps = this . _contextTimestampsOptimization . optimize (
capturedDirectories ,
startTime ,
children
) ;
for ( const path of capturedDirectories ) {
const cache = this . _contextTimestamps . get ( path ) ;
if ( cache !== undefined ) {
if ( cache !== "ignore" ) {
contextTimestamps . set ( path , cache ) ;
2019-09-04 19:34:34 +08:00
}
2020-08-26 06:36:16 +08:00
} else {
jobs ++ ;
this . contextTimestampQueue . add ( path , ( err , entry ) => {
if ( err ) {
if ( this . logger ) {
this . logger . debug (
2021-01-14 03:37:45 +08:00
` Error snapshotting context timestamp of ${ path } : ${ err . stack } `
2020-08-26 06:36:16 +08:00
) ;
}
jobError ( ) ;
} else {
contextTimestamps . set ( path , entry ) ;
jobDone ( ) ;
}
} ) ;
}
2019-09-04 19:34:34 +08:00
}
2020-08-26 06:36:16 +08:00
break ;
2019-01-09 20:23:26 +08:00
}
2019-01-05 21:58:06 +08:00
}
2019-01-09 20:23:26 +08:00
if ( missing ) {
2020-08-23 03:54:34 +08:00
const capturedMissing = captureNonManaged ( missing , managedMissing ) ;
2020-08-20 17:41:20 +08:00
unsharedMissingExistence = this . _missingExistenceOptimization . optimize (
capturedMissing ,
startTime ,
children
) ;
for ( const path of capturedMissing ) {
2019-01-09 20:23:26 +08:00
const cache = this . _fileTimestamps . get ( path ) ;
if ( cache !== undefined ) {
2020-01-28 21:01:19 +08:00
if ( cache !== "ignore" ) {
2020-03-13 19:09:14 +08:00
missingExistence . set ( path , toExistence ( cache ) ) ;
2020-01-28 21:01:19 +08:00
}
2019-01-09 20:23:26 +08:00
} else {
jobs ++ ;
this . fileTimestampQueue . add ( path , ( err , entry ) => {
if ( err ) {
2019-11-05 23:47:45 +08:00
if ( this . logger ) {
this . logger . debug (
2021-01-14 03:37:45 +08:00
` Error snapshotting missing timestamp of ${ path } : ${ err . stack } `
2019-11-05 23:47:45 +08:00
) ;
}
2020-01-28 21:01:19 +08:00
jobError ( ) ;
2019-01-09 20:23:26 +08:00
} else {
2020-03-13 19:09:14 +08:00
missingExistence . set ( path , toExistence ( entry ) ) ;
2020-01-28 21:01:19 +08:00
jobDone ( ) ;
2019-01-09 20:23:26 +08:00
}
} ) ;
}
2019-01-05 21:58:06 +08:00
}
}
2020-08-20 17:41:20 +08:00
unsharedManagedItemInfo = this . _managedItemInfoOptimization . optimize (
managedItems ,
undefined ,
children
) ;
2019-08-13 04:59:09 +08:00
for ( const path of managedItems ) {
const cache = this . _managedItems . get ( path ) ;
if ( cache !== undefined ) {
2019-11-05 23:47:45 +08:00
managedItemInfo . set ( path , cache ) ;
2019-08-13 04:59:09 +08:00
} else {
jobs ++ ;
this . managedItemQueue . add ( path , ( err , entry ) => {
2019-11-05 23:47:45 +08:00
if ( err ) {
if ( this . logger ) {
this . logger . debug (
2021-01-14 03:37:45 +08:00
` Error snapshotting managed item ${ path } : ${ err . stack } `
2019-11-05 23:47:45 +08:00
) ;
}
2020-01-28 21:01:19 +08:00
jobError ( ) ;
2019-08-13 04:59:09 +08:00
} else {
managedItemInfo . set ( path , entry ) ;
2020-01-28 21:01:19 +08:00
jobDone ( ) ;
2019-08-13 04:59:09 +08:00
}
} ) ;
}
}
2019-01-05 21:58:06 +08:00
jobDone ( ) ;
}
2019-08-13 04:59:09 +08:00
/ * *
* @ param { Snapshot } snapshot1 a snapshot
* @ param { Snapshot } snapshot2 a snapshot
* @ returns { Snapshot } merged snapshot
* /
mergeSnapshots ( snapshot1 , snapshot2 ) {
2020-08-26 06:36:16 +08:00
const snapshot = new Snapshot ( ) ;
if ( snapshot1 . hasStartTime ( ) && snapshot2 . hasStartTime ( ) )
snapshot . setStartTime ( Math . min ( snapshot1 . startTime , snapshot2 . startTime ) ) ;
else if ( snapshot2 . hasStartTime ( ) ) snapshot . startTime = snapshot2 . startTime ;
else if ( snapshot1 . hasStartTime ( ) ) snapshot . startTime = snapshot1 . startTime ;
if ( snapshot1 . hasFileTimestamps ( ) || snapshot2 . hasFileTimestamps ( ) ) {
snapshot . setFileTimestamps (
mergeMaps ( snapshot1 . fileTimestamps , snapshot2 . fileTimestamps )
) ;
}
if ( snapshot1 . hasFileHashes ( ) || snapshot2 . hasFileHashes ( ) ) {
snapshot . setFileHashes (
mergeMaps ( snapshot1 . fileHashes , snapshot2 . fileHashes )
) ;
}
if ( snapshot1 . hasFileTshs ( ) || snapshot2 . hasFileTshs ( ) ) {
snapshot . setFileTshs ( mergeMaps ( snapshot1 . fileTshs , snapshot2 . fileTshs ) ) ;
}
if ( snapshot1 . hasContextTimestamps ( ) || snapshot2 . hasContextTimestamps ( ) ) {
snapshot . setContextTimestamps (
mergeMaps ( snapshot1 . contextTimestamps , snapshot2 . contextTimestamps )
) ;
}
if ( snapshot1 . hasContextHashes ( ) || snapshot2 . hasContextHashes ( ) ) {
snapshot . setContextHashes (
mergeMaps ( snapshot1 . contextHashes , snapshot2 . contextHashes )
2019-08-13 04:59:09 +08:00
) ;
}
2020-08-26 06:36:16 +08:00
if ( snapshot1 . hasContextTshs ( ) || snapshot2 . hasContextTshs ( ) ) {
snapshot . setContextTshs (
mergeMaps ( snapshot1 . contextTshs , snapshot2 . contextTshs )
2019-08-13 04:59:09 +08:00
) ;
}
2020-08-26 06:36:16 +08:00
if ( snapshot1 . hasMissingExistence ( ) || snapshot2 . hasMissingExistence ( ) ) {
snapshot . setMissingExistence (
mergeMaps ( snapshot1 . missingExistence , snapshot2 . missingExistence )
2019-08-13 04:59:09 +08:00
) ;
}
2020-08-26 06:36:16 +08:00
if ( snapshot1 . hasManagedItemInfo ( ) || snapshot2 . hasManagedItemInfo ( ) ) {
snapshot . setManagedItemInfo (
mergeMaps ( snapshot1 . managedItemInfo , snapshot2 . managedItemInfo )
2019-08-13 04:59:09 +08:00
) ;
}
2020-08-26 06:36:16 +08:00
if ( snapshot1 . hasManagedFiles ( ) || snapshot2 . hasManagedFiles ( ) ) {
snapshot . setManagedFiles (
mergeSets ( snapshot1 . managedFiles , snapshot2 . managedFiles )
2019-08-13 04:59:09 +08:00
) ;
}
2020-08-26 06:36:16 +08:00
if ( snapshot1 . hasManagedContexts ( ) || snapshot2 . hasManagedContexts ( ) ) {
snapshot . setManagedContexts (
mergeSets ( snapshot1 . managedContexts , snapshot2 . managedContexts )
2019-08-13 04:59:09 +08:00
) ;
}
2020-08-26 06:36:16 +08:00
if ( snapshot1 . hasManagedMissing ( ) || snapshot2 . hasManagedMissing ( ) ) {
snapshot . setManagedMissing (
mergeSets ( snapshot1 . managedMissing , snapshot2 . managedMissing )
) ;
}
if ( snapshot1 . hasChildren ( ) || snapshot2 . hasChildren ( ) ) {
snapshot . setChildren ( mergeSets ( snapshot1 . children , snapshot2 . children ) ) ;
}
2019-11-08 00:31:18 +08:00
if (
this . _snapshotCache . get ( snapshot1 ) === true &&
this . _snapshotCache . get ( snapshot2 ) === true
) {
this . _snapshotCache . set ( snapshot , true ) ;
}
2019-08-13 04:59:09 +08:00
return snapshot ;
}
2019-07-18 05:35:05 +08:00
/ * *
* @ param { Snapshot } snapshot the snapshot made
* @ param { function ( WebpackError = , boolean = ) : void } callback callback function
* @ returns { void }
* /
2019-01-05 21:58:06 +08:00
checkSnapshotValid ( snapshot , callback ) {
2019-10-27 14:38:06 +08:00
const cachedResult = this . _snapshotCache . get ( snapshot ) ;
if ( cachedResult !== undefined ) {
2020-08-21 03:57:44 +08:00
this . _statTestedSnapshotsCached ++ ;
2019-10-27 14:38:06 +08:00
if ( typeof cachedResult === "boolean" ) {
callback ( null , cachedResult ) ;
} else {
cachedResult . push ( callback ) ;
}
return ;
}
2020-08-21 03:57:44 +08:00
this . _statTestedSnapshotsNotCached ++ ;
2020-01-31 17:59:28 +08:00
this . _checkSnapshotValidNoCache ( snapshot , callback ) ;
}
2020-08-22 18:01:28 +08:00
/ * *
* @ param { Snapshot } snapshot the snapshot made
* @ param { function ( WebpackError = , boolean = ) : void } callback callback function
* @ returns { void }
* /
2020-01-31 17:59:28 +08:00
_checkSnapshotValidNoCache ( snapshot , callback ) {
2020-08-22 18:01:28 +08:00
/** @type {number | undefined} */
let startTime = undefined ;
if ( snapshot . hasStartTime ( ) ) {
startTime = snapshot . startTime ;
}
2019-01-05 21:58:06 +08:00
let jobs = 1 ;
const jobDone = ( ) => {
if ( -- jobs === 0 ) {
2019-10-27 14:38:06 +08:00
this . _snapshotCache . set ( snapshot , true ) ;
2019-11-01 19:16:30 +08:00
callback ( null , true ) ;
2019-01-05 21:58:06 +08:00
}
} ;
const invalid = ( ) => {
if ( jobs > 0 ) {
2019-11-01 19:16:30 +08:00
// large negative number instead of NaN or something else to keep jobs to stay a SMI (v8)
jobs = - 100000000 ;
2019-10-27 14:38:06 +08:00
this . _snapshotCache . set ( snapshot , false ) ;
2019-11-01 19:16:30 +08:00
callback ( null , false ) ;
2019-01-05 21:58:06 +08:00
}
} ;
2019-11-04 17:06:53 +08:00
const invalidWithError = ( path , err ) => {
if ( this . _remainingLogs > 0 ) {
2020-03-10 09:59:46 +08:00
this . _log ( path , ` error occurred: %s ` , err ) ;
2019-11-04 17:06:53 +08:00
}
invalid ( ) ;
} ;
/ * *
* @ param { string } path file path
* @ param { string } current current hash
* @ param { string } snap snapshot hash
* @ returns { boolean } true , if ok
* /
const checkHash = ( path , current , snap ) => {
if ( current !== snap ) {
// If hash differ it's invalid
if ( this . _remainingLogs > 0 ) {
2019-11-15 00:28:10 +08:00
this . _log ( path , ` hashes differ (%s != %s) ` , current , snap ) ;
2019-11-04 17:06:53 +08:00
}
return false ;
}
return true ;
2019-08-13 04:59:09 +08:00
} ;
2019-07-18 05:35:05 +08:00
/ * *
2019-11-04 17:06:53 +08:00
* @ param { string } path file path
2020-01-28 21:01:19 +08:00
* @ param { boolean } current current entry
* @ param { boolean } snap entry from snapshot
2019-07-18 05:35:05 +08:00
* @ returns { boolean } true , if ok
* /
2020-03-13 19:09:14 +08:00
const checkExistence = ( path , current , snap ) => {
2019-11-04 17:06:53 +08:00
if ( ! current !== ! snap ) {
2020-03-10 09:59:46 +08:00
// If existence of item differs
2019-11-04 17:06:53 +08:00
// it's invalid
if ( this . _remainingLogs > 0 ) {
this . _log (
path ,
current ? "it didn't exist before" : "it does no longer exist"
) ;
}
return false ;
}
return true ;
2019-01-05 21:58:06 +08:00
} ;
2019-07-18 05:35:05 +08:00
/ * *
2019-11-04 17:06:53 +08:00
* @ param { string } path file path
2020-01-28 21:01:19 +08:00
* @ param { FileSystemInfoEntry } current current entry
* @ param { FileSystemInfoEntry } snap entry from snapshot
2020-08-26 06:36:16 +08:00
* @ param { boolean } log log reason
2019-07-18 05:35:05 +08:00
* @ returns { boolean } true , if ok
* /
2020-08-26 06:36:16 +08:00
const checkFile = ( path , current , snap , log = true ) => {
2020-01-28 21:01:19 +08:00
if ( current === snap ) return true ;
2019-01-09 20:23:26 +08:00
if ( ! current !== ! snap ) {
2020-03-10 09:59:46 +08:00
// If existence of item differs
2019-01-09 20:23:26 +08:00
// it's invalid
2020-08-26 06:36:16 +08:00
if ( log && this . _remainingLogs > 0 ) {
2019-11-04 17:06:53 +08:00
this . _log (
path ,
current ? "it didn't exist before" : "it does no longer exist"
) ;
}
2019-01-09 20:23:26 +08:00
return false ;
2019-01-05 21:58:06 +08:00
}
2019-01-09 20:23:26 +08:00
if ( current ) {
// For existing items only
2020-05-31 03:09:53 +08:00
if ( typeof startTime === "number" && current . safeTime > startTime ) {
2020-01-28 21:01:19 +08:00
// If a change happened after starting reading the item
// this may no longer be valid
2020-08-26 06:36:16 +08:00
if ( log && this . _remainingLogs > 0 ) {
2020-01-28 21:01:19 +08:00
this . _log (
path ,
` it may have changed (%d) after the start time of the snapshot (%d) ` ,
current . safeTime ,
startTime
) ;
}
return false ;
}
2019-01-09 20:23:26 +08:00
if (
snap . timestamp !== undefined &&
current . timestamp !== snap . timestamp
) {
// If we have a timestamp (it was a file or symlink) and it differs from current timestamp
// it's invalid
2020-08-26 06:36:16 +08:00
if ( log && this . _remainingLogs > 0 ) {
2019-11-04 17:06:53 +08:00
this . _log (
path ,
2019-11-15 00:28:10 +08:00
` timestamps differ (%d != %d) ` ,
current . timestamp ,
snap . timestamp
2019-11-04 17:06:53 +08:00
) ;
}
2019-01-09 20:23:26 +08:00
return false ;
}
2019-09-04 19:34:34 +08:00
if (
snap . timestampHash !== undefined &&
current . timestampHash !== snap . timestampHash
) {
// If we have a timestampHash (it was a directory) and it differs from current timestampHash
// it's invalid
2020-08-26 06:36:16 +08:00
if ( log && this . _remainingLogs > 0 ) {
2019-11-04 17:06:53 +08:00
this . _log (
path ,
2019-11-15 00:28:10 +08:00
` timestamps hashes differ (%s != %s) ` ,
current . timestampHash ,
snap . timestampHash
2019-11-04 17:06:53 +08:00
) ;
}
2019-09-04 19:34:34 +08:00
return false ;
}
2019-01-09 20:23:26 +08:00
}
return true ;
2019-01-05 21:58:06 +08:00
} ;
2020-08-22 18:01:28 +08:00
if ( snapshot . hasChildren ( ) ) {
2020-01-31 17:59:28 +08:00
const childCallback = ( err , result ) => {
if ( err || ! result ) return invalid ( ) ;
else jobDone ( ) ;
} ;
2020-08-22 18:01:28 +08:00
for ( const child of snapshot . children ) {
2020-01-31 17:59:28 +08:00
const cache = this . _snapshotCache . get ( child ) ;
if ( cache !== undefined ) {
2020-08-21 03:57:44 +08:00
this . _statTestedChildrenCached ++ ;
/* istanbul ignore else */
if ( typeof cache === "boolean" ) {
if ( cache === false ) {
invalid ( ) ;
return ;
2020-01-31 17:59:28 +08:00
}
2020-08-21 03:57:44 +08:00
} else {
jobs ++ ;
cache . push ( childCallback ) ;
2020-01-31 17:59:28 +08:00
}
} else {
2020-08-21 03:57:44 +08:00
this . _statTestedChildrenNotCached ++ ;
2020-01-31 17:59:28 +08:00
jobs ++ ;
this . _checkSnapshotValidNoCache ( child , childCallback ) ;
}
}
}
2020-08-22 18:01:28 +08:00
if ( snapshot . hasFileTimestamps ( ) ) {
const { fileTimestamps } = snapshot ;
2020-08-21 03:57:44 +08:00
this . _statTestedEntries += fileTimestamps . size ;
2019-08-13 04:59:09 +08:00
for ( const [ path , ts ] of fileTimestamps ) {
const cache = this . _fileTimestamps . get ( path ) ;
if ( cache !== undefined ) {
2020-01-28 21:01:19 +08:00
if ( cache !== "ignore" && ! checkFile ( path , cache , ts ) ) {
2019-08-13 04:59:09 +08:00
invalid ( ) ;
2019-11-04 17:06:53 +08:00
return ;
2019-08-13 04:59:09 +08:00
}
} else {
jobs ++ ;
this . fileTimestampQueue . add ( path , ( err , entry ) => {
2019-11-04 17:06:53 +08:00
if ( err ) return invalidWithError ( path , err ) ;
if ( ! checkFile ( path , entry , ts ) ) {
2019-08-13 04:59:09 +08:00
invalid ( ) ;
} else {
jobDone ( ) ;
}
} ) ;
2019-01-05 21:58:06 +08:00
}
2019-08-13 04:59:09 +08:00
}
}
2020-08-26 06:36:16 +08:00
const processFileHashSnapshot = ( path , hash ) => {
const cache = this . _fileHashes . get ( path ) ;
if ( cache !== undefined ) {
if ( cache !== "ignore" && ! checkHash ( path , cache , hash ) ) {
invalid ( ) ;
return ;
}
} else {
jobs ++ ;
this . fileHashQueue . add ( path , ( err , entry ) => {
if ( err ) return invalidWithError ( path , err ) ;
if ( ! checkHash ( path , entry , hash ) ) {
invalid ( ) ;
} else {
jobDone ( ) ;
}
} ) ;
}
} ;
2020-08-22 18:01:28 +08:00
if ( snapshot . hasFileHashes ( ) ) {
const { fileHashes } = snapshot ;
2020-08-21 03:57:44 +08:00
this . _statTestedEntries += fileHashes . size ;
2019-08-13 04:59:09 +08:00
for ( const [ path , hash ] of fileHashes ) {
2020-08-26 06:36:16 +08:00
processFileHashSnapshot ( path , hash ) ;
}
}
if ( snapshot . hasFileTshs ( ) ) {
const { fileTshs } = snapshot ;
this . _statTestedEntries += fileTshs . size ;
for ( const [ path , tsh ] of fileTshs ) {
if ( typeof tsh === "string" ) {
processFileHashSnapshot ( path , tsh ) ;
2019-08-13 04:59:09 +08:00
} else {
2020-08-26 06:36:16 +08:00
const cache = this . _fileTimestamps . get ( path ) ;
if ( cache !== undefined ) {
if ( cache === "ignore" || ! checkFile ( path , cache , tsh , false ) ) {
processFileHashSnapshot ( path , tsh . hash ) ;
2019-08-13 04:59:09 +08:00
}
2020-08-26 06:36:16 +08:00
} else {
jobs ++ ;
this . fileTimestampQueue . add ( path , ( err , entry ) => {
if ( err ) return invalidWithError ( path , err ) ;
if ( ! checkFile ( path , entry , tsh , false ) ) {
processFileHashSnapshot ( path , tsh . hash ) ;
}
jobDone ( ) ;
} ) ;
}
2019-08-13 04:59:09 +08:00
}
2019-01-05 21:58:06 +08:00
}
}
2020-08-22 18:01:28 +08:00
if ( snapshot . hasContextTimestamps ( ) ) {
const { contextTimestamps } = snapshot ;
2020-08-21 03:57:44 +08:00
this . _statTestedEntries += contextTimestamps . size ;
2019-09-04 19:34:34 +08:00
for ( const [ path , ts ] of contextTimestamps ) {
const cache = this . _contextTimestamps . get ( path ) ;
if ( cache !== undefined ) {
2020-01-28 21:01:19 +08:00
if ( cache !== "ignore" && ! checkFile ( path , cache , ts ) ) {
2019-09-04 19:34:34 +08:00
invalid ( ) ;
2019-11-04 17:06:53 +08:00
return ;
2019-09-04 19:34:34 +08:00
}
} else {
jobs ++ ;
this . contextTimestampQueue . add ( path , ( err , entry ) => {
2019-11-04 17:06:53 +08:00
if ( err ) return invalidWithError ( path , err ) ;
if ( ! checkFile ( path , entry , ts ) ) {
2019-09-04 19:34:34 +08:00
invalid ( ) ;
} else {
jobDone ( ) ;
}
} ) ;
}
}
2019-01-05 21:58:06 +08:00
}
2020-08-26 06:36:16 +08:00
const processContextHashSnapshot = ( path , hash ) => {
const cache = this . _contextHashes . get ( path ) ;
if ( cache !== undefined ) {
if ( cache !== "ignore" && ! checkHash ( path , cache , hash ) ) {
invalid ( ) ;
return ;
}
} else {
jobs ++ ;
this . contextHashQueue . add ( path , ( err , entry ) => {
if ( err ) return invalidWithError ( path , err ) ;
if ( ! checkHash ( path , entry , hash ) ) {
invalid ( ) ;
} else {
jobDone ( ) ;
}
} ) ;
}
} ;
2020-08-22 18:01:28 +08:00
if ( snapshot . hasContextHashes ( ) ) {
const { contextHashes } = snapshot ;
2020-08-21 03:57:44 +08:00
this . _statTestedEntries += contextHashes . size ;
2019-08-13 04:59:09 +08:00
for ( const [ path , hash ] of contextHashes ) {
2020-08-26 06:36:16 +08:00
processContextHashSnapshot ( path , hash ) ;
}
}
if ( snapshot . hasContextTshs ( ) ) {
const { contextTshs } = snapshot ;
this . _statTestedEntries += contextTshs . size ;
for ( const [ path , tsh ] of contextTshs ) {
if ( typeof tsh === "string" ) {
processContextHashSnapshot ( path , tsh ) ;
2019-08-13 04:59:09 +08:00
} else {
2020-08-26 06:36:16 +08:00
const cache = this . _contextTimestamps . get ( path ) ;
if ( cache !== undefined ) {
if ( cache === "ignore" || ! checkFile ( path , cache , tsh , false ) ) {
processContextHashSnapshot ( path , tsh . hash ) ;
2019-08-13 04:59:09 +08:00
}
2020-08-26 06:36:16 +08:00
} else {
jobs ++ ;
this . contextTimestampQueue . add ( path , ( err , entry ) => {
if ( err ) return invalidWithError ( path , err ) ;
if ( ! checkFile ( path , entry , tsh , false ) ) {
processContextHashSnapshot ( path , tsh . hash ) ;
}
jobDone ( ) ;
} ) ;
}
2019-01-05 21:58:06 +08:00
}
2019-08-13 04:59:09 +08:00
}
}
2020-08-22 18:01:28 +08:00
if ( snapshot . hasMissingExistence ( ) ) {
const { missingExistence } = snapshot ;
2020-08-21 03:57:44 +08:00
this . _statTestedEntries += missingExistence . size ;
2020-03-13 19:09:14 +08:00
for ( const [ path , existence ] of missingExistence ) {
2019-08-13 04:59:09 +08:00
const cache = this . _fileTimestamps . get ( path ) ;
if ( cache !== undefined ) {
2020-01-28 21:01:19 +08:00
if (
cache !== "ignore" &&
2020-03-13 19:09:14 +08:00
! checkExistence ( path , toExistence ( cache ) , existence )
2020-01-28 21:01:19 +08:00
) {
2019-01-05 21:58:06 +08:00
invalid ( ) ;
2019-11-04 17:06:53 +08:00
return ;
2019-01-05 21:58:06 +08:00
}
2019-08-13 04:59:09 +08:00
} else {
jobs ++ ;
this . fileTimestampQueue . add ( path , ( err , entry ) => {
2019-11-04 17:06:53 +08:00
if ( err ) return invalidWithError ( path , err ) ;
2020-03-13 19:09:14 +08:00
if ( ! checkExistence ( path , toExistence ( entry ) , existence ) ) {
2019-08-13 04:59:09 +08:00
invalid ( ) ;
} else {
jobDone ( ) ;
}
} ) ;
}
2019-01-05 21:58:06 +08:00
}
}
2020-08-22 18:01:28 +08:00
if ( snapshot . hasManagedItemInfo ( ) ) {
const { managedItemInfo } = snapshot ;
2020-08-21 03:57:44 +08:00
this . _statTestedEntries += managedItemInfo . size ;
2019-08-13 23:26:54 +08:00
for ( const [ path , info ] of managedItemInfo ) {
const cache = this . _managedItems . get ( path ) ;
if ( cache !== undefined ) {
2019-11-04 17:06:53 +08:00
if ( ! checkHash ( path , cache , info ) ) {
2019-08-13 23:26:54 +08:00
invalid ( ) ;
2019-11-04 17:06:53 +08:00
return ;
2019-08-13 23:26:54 +08:00
}
} else {
jobs ++ ;
this . managedItemQueue . add ( path , ( err , entry ) => {
2019-11-04 17:06:53 +08:00
if ( err ) return invalidWithError ( path , err ) ;
if ( ! checkHash ( path , entry , info ) ) {
2019-08-13 23:26:54 +08:00
invalid ( ) ;
} else {
jobDone ( ) ;
}
} ) ;
}
}
}
2019-01-05 21:58:06 +08:00
jobDone ( ) ;
2019-11-01 19:16:30 +08:00
// if there was an async action
// try to join multiple concurrent request for this snapshot
if ( jobs > 0 ) {
2020-08-22 18:01:28 +08:00
const callbacks = [ callback ] ;
2019-11-01 19:16:30 +08:00
callback = ( err , result ) => {
for ( const callback of callbacks ) callback ( err , result ) ;
} ;
this . _snapshotCache . set ( snapshot , callbacks ) ;
}
2019-01-05 21:58:06 +08:00
}
2018-09-27 13:22:19 +08:00
_readFileTimestamp ( path , callback ) {
this . fs . stat ( path , ( err , stat ) => {
if ( err ) {
if ( err . code === "ENOENT" ) {
this . _fileTimestamps . set ( path , null ) ;
2020-09-15 20:39:09 +08:00
this . _cachedDeprecatedFileTimestamps = undefined ;
2018-09-27 13:22:19 +08:00
return callback ( null , null ) ;
}
return callback ( err ) ;
}
2019-11-14 23:49:14 +08:00
let ts ;
if ( stat . isDirectory ( ) ) {
ts = {
safeTime : 0 ,
timestamp : undefined
} ;
} else {
const mtime = + stat . mtime ;
2019-01-09 20:23:26 +08:00
2019-11-14 23:49:14 +08:00
if ( mtime ) applyMtime ( mtime ) ;
2018-09-27 13:22:19 +08:00
2019-11-14 23:49:14 +08:00
ts = {
safeTime : mtime ? mtime + FS _ACCURACY : Infinity ,
timestamp : mtime
} ;
}
2018-09-27 13:22:19 +08:00
this . _fileTimestamps . set ( path , ts ) ;
2020-09-15 20:39:09 +08:00
this . _cachedDeprecatedFileTimestamps = undefined ;
2018-09-27 13:22:19 +08:00
callback ( null , ts ) ;
} ) ;
}
2019-08-13 04:59:09 +08:00
_readFileHash ( path , callback ) {
this . fs . readFile ( path , ( err , content ) => {
if ( err ) {
2020-08-21 03:57:44 +08:00
if ( err . code === "EISDIR" ) {
this . _fileHashes . set ( path , "directory" ) ;
return callback ( null , "directory" ) ;
}
2019-08-13 04:59:09 +08:00
if ( err . code === "ENOENT" ) {
this . _fileHashes . set ( path , null ) ;
return callback ( null , null ) ;
}
2021-01-14 03:37:45 +08:00
if ( err . code === "ERR_FS_FILE_TOO_LARGE" ) {
this . logger . warn ( ` Ignoring ${ path } for hashing as it's very large ` ) ;
this . _fileHashes . set ( path , "too large" ) ;
return callback ( null , "too large" ) ;
}
2019-08-13 04:59:09 +08:00
return callback ( err ) ;
}
const hash = createHash ( "md4" ) ;
hash . update ( content ) ;
const digest = /** @type {string} */ ( hash . digest ( "hex" ) ) ;
this . _fileHashes . set ( path , digest ) ;
callback ( null , digest ) ;
} ) ;
}
2020-08-26 06:36:16 +08:00
_getFileTimestampAndHash ( path , callback ) {
const continueWithHash = hash => {
const cache = this . _fileTimestamps . get ( path ) ;
if ( cache !== undefined ) {
if ( cache !== "ignore" ) {
const result = {
... cache ,
hash
} ;
this . _fileTshs . set ( path , result ) ;
return callback ( null , result ) ;
} else {
this . _fileTshs . set ( path , hash ) ;
return callback ( null , hash ) ;
}
} else {
this . fileTimestampQueue . add ( path , ( err , entry ) => {
if ( err ) {
return callback ( err ) ;
}
const result = {
... entry ,
hash
} ;
this . _fileTshs . set ( path , result ) ;
return callback ( null , result ) ;
} ) ;
}
} ;
const cache = this . _fileHashes . get ( path ) ;
if ( cache !== undefined ) {
continueWithHash ( cache ) ;
} else {
this . fileHashQueue . add ( path , ( err , entry ) => {
if ( err ) {
return callback ( err ) ;
}
continueWithHash ( entry ) ;
} ) ;
}
}
2018-09-27 13:22:19 +08:00
_readContextTimestamp ( path , callback ) {
2021-01-13 07:09:19 +08:00
this . fs . readdir ( path , ( err , _files ) => {
2019-09-04 19:34:34 +08:00
if ( err ) {
if ( err . code === "ENOENT" ) {
this . _contextTimestamps . set ( path , null ) ;
2020-09-15 20:39:09 +08:00
this . _cachedDeprecatedContextTimestamps = undefined ;
2019-09-04 19:34:34 +08:00
return callback ( null , null ) ;
}
return callback ( err ) ;
}
2021-01-13 07:09:19 +08:00
const files = /** @type {string[]} */ ( _files )
2019-09-04 19:34:34 +08:00
. map ( file => file . normalize ( "NFC" ) )
. filter ( file => ! /^\./ . test ( file ) )
. sort ( ) ;
asyncLib . map (
files ,
( file , callback ) => {
const child = join ( this . fs , path , file ) ;
this . fs . stat ( child , ( err , stat ) => {
if ( err ) return callback ( err ) ;
for ( const immutablePath of this . immutablePathsWithSlash ) {
if ( path . startsWith ( immutablePath ) ) {
// ignore any immutable path for timestamping
return callback ( null , null ) ;
}
}
for ( const managedPath of this . managedPathsWithSlash ) {
if ( path . startsWith ( managedPath ) ) {
const managedItem = getManagedItem ( managedPath , child ) ;
if ( managedItem ) {
// construct timestampHash from managed info
2019-11-06 07:27:47 +08:00
return this . managedItemQueue . add ( managedItem , ( err , info ) => {
2019-09-04 19:34:34 +08:00
if ( err ) return callback ( err ) ;
return callback ( null , {
safeTime : 0 ,
timestampHash : info
} ) ;
} ) ;
}
}
}
if ( stat . isFile ( ) ) {
return this . getFileTimestamp ( child , callback ) ;
}
if ( stat . isDirectory ( ) ) {
this . contextTimestampQueue . increaseParallelism ( ) ;
this . getContextTimestamp ( child , ( err , tsEntry ) => {
this . contextTimestampQueue . decreaseParallelism ( ) ;
callback ( err , tsEntry ) ;
} ) ;
return ;
}
callback ( null , null ) ;
} ) ;
} ,
( err , tsEntries ) => {
2019-09-08 21:53:12 +08:00
if ( err ) return callback ( err ) ;
2019-09-04 19:34:34 +08:00
const hash = createHash ( "md4" ) ;
for ( const file of files ) hash . update ( file ) ;
let safeTime = 0 ;
for ( const entry of tsEntries ) {
if ( ! entry ) {
hash . update ( "n" ) ;
continue ;
}
if ( entry . timestamp ) {
hash . update ( "f" ) ;
hash . update ( ` ${ entry . timestamp } ` ) ;
} else if ( entry . timestampHash ) {
hash . update ( "d" ) ;
hash . update ( ` ${ entry . timestampHash } ` ) ;
}
if ( entry . safeTime ) {
safeTime = Math . max ( safeTime , entry . safeTime ) ;
}
}
const digest = /** @type {string} */ ( hash . digest ( "hex" ) ) ;
const result = {
safeTime ,
timestampHash : digest
} ;
this . _contextTimestamps . set ( path , result ) ;
2020-09-15 20:39:09 +08:00
this . _cachedDeprecatedContextTimestamps = undefined ;
2019-09-04 19:34:34 +08:00
callback ( null , result ) ;
}
) ;
} ) ;
2018-09-27 13:22:19 +08:00
}
2019-08-13 04:59:09 +08:00
_readContextHash ( path , callback ) {
2021-01-13 07:09:19 +08:00
this . fs . readdir ( path , ( err , _files ) => {
2019-08-13 04:59:09 +08:00
if ( err ) {
if ( err . code === "ENOENT" ) {
this . _contextHashes . set ( path , null ) ;
return callback ( null , null ) ;
}
return callback ( err ) ;
}
2021-01-13 07:09:19 +08:00
const files = /** @type {string[]} */ ( _files )
2019-08-13 04:59:09 +08:00
. map ( file => file . normalize ( "NFC" ) )
. filter ( file => ! /^\./ . test ( file ) )
. sort ( ) ;
asyncLib . map (
files ,
( file , callback ) => {
const child = join ( this . fs , path , file ) ;
this . fs . stat ( child , ( err , stat ) => {
if ( err ) return callback ( err ) ;
2019-09-04 19:34:34 +08:00
for ( const immutablePath of this . immutablePathsWithSlash ) {
if ( path . startsWith ( immutablePath ) ) {
// ignore any immutable path for hashing
return callback ( null , "" ) ;
}
}
for ( const managedPath of this . managedPathsWithSlash ) {
if ( path . startsWith ( managedPath ) ) {
const managedItem = getManagedItem ( managedPath , child ) ;
if ( managedItem ) {
// construct hash from managed info
2019-11-06 07:27:47 +08:00
return this . managedItemQueue . add ( managedItem , ( err , info ) => {
2019-09-04 19:34:34 +08:00
if ( err ) return callback ( err ) ;
callback ( null , info || "" ) ;
} ) ;
}
}
}
2019-08-13 04:59:09 +08:00
if ( stat . isFile ( ) ) {
2019-09-08 21:35:53 +08:00
return this . getFileHash ( child , ( err , hash ) => {
callback ( err , hash || "" ) ;
} ) ;
2019-08-13 04:59:09 +08:00
}
if ( stat . isDirectory ( ) ) {
this . contextHashQueue . increaseParallelism ( ) ;
this . getContextHash ( child , ( err , hash ) => {
this . contextHashQueue . decreaseParallelism ( ) ;
callback ( err , hash || "" ) ;
} ) ;
return ;
}
callback ( null , "" ) ;
} ) ;
} ,
( err , fileHashes ) => {
2019-09-08 21:53:12 +08:00
if ( err ) return callback ( err ) ;
2019-08-13 04:59:09 +08:00
const hash = createHash ( "md4" ) ;
for ( const file of files ) hash . update ( file ) ;
for ( const h of fileHashes ) hash . update ( h ) ;
const digest = /** @type {string} */ ( hash . digest ( "hex" ) ) ;
this . _contextHashes . set ( path , digest ) ;
callback ( null , digest ) ;
}
) ;
} ) ;
}
2020-08-26 06:36:16 +08:00
_getContextTimestampAndHash ( path , callback ) {
const continueWithHash = hash => {
const cache = this . _contextTimestamps . get ( path ) ;
if ( cache !== undefined ) {
if ( cache !== "ignore" ) {
const result = {
... cache ,
hash
} ;
this . _contextTshs . set ( path , result ) ;
return callback ( null , result ) ;
} else {
this . _contextTshs . set ( path , hash ) ;
return callback ( null , hash ) ;
}
} else {
this . contextTimestampQueue . add ( path , ( err , entry ) => {
if ( err ) {
return callback ( err ) ;
}
const result = {
... entry ,
hash
} ;
this . _contextTshs . set ( path , result ) ;
return callback ( null , result ) ;
} ) ;
}
} ;
const cache = this . _contextHashes . get ( path ) ;
if ( cache !== undefined ) {
continueWithHash ( cache ) ;
} else {
this . contextHashQueue . add ( path , ( err , entry ) => {
if ( err ) {
return callback ( err ) ;
}
continueWithHash ( entry ) ;
} ) ;
}
}
2019-11-05 23:47:45 +08:00
_getManagedItemDirectoryInfo ( path , callback ) {
this . fs . readdir ( path , ( err , elements ) => {
2019-09-04 19:34:34 +08:00
if ( err ) {
2019-09-09 03:30:27 +08:00
if ( err . code === "ENOENT" || err . code === "ENOTDIR" ) {
2019-11-05 23:47:45 +08:00
return callback ( null , EMPTY _SET ) ;
2019-09-04 19:34:34 +08:00
}
return callback ( err ) ;
}
2019-11-05 23:47:45 +08:00
const set = new Set (
2021-01-13 07:09:19 +08:00
/** @type {string[]} */ ( elements ) . map ( element =>
join ( this . fs , path , element )
)
2019-11-05 23:47:45 +08:00
) ;
callback ( null , set ) ;
} ) ;
}
_getManagedItemInfo ( path , callback ) {
const dir = dirname ( this . fs , path ) ;
this . managedItemDirectoryQueue . add ( dir , ( err , elements ) => {
if ( err ) {
return callback ( err ) ;
}
if ( ! elements . has ( path ) ) {
// file or directory doesn't exist
this . _managedItems . set ( path , "missing" ) ;
return callback ( null , "missing" ) ;
}
// something exists
// it may be a file or directory
if (
path . endsWith ( "node_modules" ) &&
( path . endsWith ( "/node_modules" ) || path . endsWith ( "\\node_modules" ) )
) {
2020-03-10 09:59:46 +08:00
// we are only interested in existence of this special directory
2019-11-05 23:47:45 +08:00
this . _managedItems . set ( path , "exists" ) ;
return callback ( null , "exists" ) ;
2019-08-13 04:59:09 +08:00
}
2019-11-05 23:47:45 +08:00
// we assume it's a directory, as files shouldn't occur in managed paths
const packageJsonPath = join ( this . fs , path , "package.json" ) ;
this . fs . readFile ( packageJsonPath , ( err , content ) => {
if ( err ) {
if ( err . code === "ENOENT" || err . code === "ENOTDIR" ) {
// no package.json or path is not a directory
2020-09-17 21:08:38 +08:00
this . fs . readdir ( path , ( err , elements ) => {
if (
! err &&
elements . length === 1 &&
elements [ 0 ] === "node_modules"
) {
// This is only a grouping folder e. g. used by yarn
// we are only interested in existence of this special directory
this . _managedItems . set ( path , "nested" ) ;
return callback ( null , "nested" ) ;
}
const problem = ` Managed item ${ path } isn't a directory or doesn't contain a package.json ` ;
this . logger . warn ( problem ) ;
return callback ( new Error ( problem ) ) ;
} ) ;
return ;
2019-11-05 23:47:45 +08:00
}
return callback ( err ) ;
}
let data ;
try {
data = JSON . parse ( content . toString ( "utf-8" ) ) ;
} catch ( e ) {
return callback ( e ) ;
}
const info = ` ${ data . name || "" } @ ${ data . version || "" } ` ;
this . _managedItems . set ( path , info ) ;
callback ( null , info ) ;
} ) ;
2019-08-13 04:59:09 +08:00
} ) ;
}
2018-09-27 13:22:19 +08:00
getDeprecatedFileTimestamps ( ) {
2020-09-15 20:39:09 +08:00
if ( this . _cachedDeprecatedFileTimestamps !== undefined )
return this . _cachedDeprecatedFileTimestamps ;
2018-09-27 13:22:19 +08:00
const map = new Map ( ) ;
for ( const [ path , info ] of this . _fileTimestamps ) {
2019-11-06 06:46:34 +08:00
if ( info ) map . set ( path , typeof info === "object" ? info . safeTime : null ) ;
2018-09-27 13:22:19 +08:00
}
2020-09-15 20:39:09 +08:00
return ( this . _cachedDeprecatedFileTimestamps = map ) ;
2018-09-27 13:22:19 +08:00
}
getDeprecatedContextTimestamps ( ) {
2020-09-15 20:39:09 +08:00
if ( this . _cachedDeprecatedContextTimestamps !== undefined )
return this . _cachedDeprecatedContextTimestamps ;
2018-09-27 13:22:19 +08:00
const map = new Map ( ) ;
for ( const [ path , info ] of this . _contextTimestamps ) {
2019-11-06 06:46:34 +08:00
if ( info ) map . set ( path , typeof info === "object" ? info . safeTime : null ) ;
2018-09-27 13:22:19 +08:00
}
2020-09-15 20:39:09 +08:00
return ( this . _cachedDeprecatedContextTimestamps = map ) ;
2018-09-27 13:22:19 +08:00
}
}
module . exports = FileSystemInfo ;
2020-08-22 18:01:28 +08:00
module . exports . Snapshot = Snapshot ;