FileSystemInfo.js 137 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const nodeModule = require("module");
  7. const { isAbsolute } = require("path");
  8. const { create: createResolver } = require("enhanced-resolve");
  9. const asyncLib = require("neo-async");
  10. const { DEFAULTS } = require("./config/defaults");
  11. const { collectCjsRequireSpecifiers } = require("./javascript/syntax");
  12. const AsyncQueue = require("./util/AsyncQueue");
  13. const StackedCacheMap = require("./util/StackedCacheMap");
  14. const createHash = require("./util/createHash");
  15. const { dirname, join, lstatReadlinkAbsolute, relative } = require("./util/fs");
  16. const makeSerializable = require("./util/makeSerializable");
  17. const memoize = require("./util/memoize");
  18. const processAsyncTree = require("./util/processAsyncTree");
  19. // only the mjs build-dependency walk needs it
  20. const getEsModuleLexer = memoize(() => require("es-module-lexer"));
  21. /** @import { ResolveRequest, ResolveFunctionAsync } from "enhanced-resolve" */
  22. /** @import { HashFunction } from "../declarations/WebpackOptions" */
  23. /** @import { Logger } from "./logging/Logger" */
  24. /** @import WebpackError from "./errors/WebpackError" */
  25. /** @import { JsonObject, IStats, InputFileSystem } from "./util/fs" */
  26. /**
  27. * @import {
  28. * ObjectDeserializerContext,
  29. * ObjectSerializerContext
  30. * } from "./serialization/ObjectMiddleware"
  31. */
  32. /**
  33. * Defines the processor callback type used by this module.
  34. * @template T
  35. * @typedef {import("./util/AsyncQueue").Callback<T>} ProcessorCallback
  36. */
  37. /**
  38. * Defines the processor type used by this module.
  39. * @template T, R
  40. * @typedef {import("./util/AsyncQueue").Processor<T, R>} Processor
  41. */
  42. const supportsEsm = Number(process.versions.modules) >= 83;
  43. /** @type {Set<string>} */
  44. const builtinModules = new Set(nodeModule.builtinModules);
  45. let FS_ACCURACY = 2000;
  46. /** @type {Set<string>} */
  47. const EMPTY_SET = new Set();
  48. const RBDT_RESOLVE_INITIAL = 0;
  49. const RBDT_RESOLVE_FILE = 1;
  50. const RBDT_RESOLVE_DIRECTORY = 2;
  51. const RBDT_RESOLVE_CJS_FILE = 3;
  52. const RBDT_RESOLVE_CJS_FILE_AS_CHILD = 4;
  53. const RBDT_RESOLVE_ESM_FILE = 5;
  54. const RBDT_DIRECTORY = 6;
  55. const RBDT_FILE = 7;
  56. const RBDT_DIRECTORY_DEPENDENCIES = 8;
  57. const RBDT_FILE_DEPENDENCIES = 9;
  58. /** @typedef {RBDT_RESOLVE_INITIAL | RBDT_RESOLVE_FILE | RBDT_RESOLVE_DIRECTORY | RBDT_RESOLVE_CJS_FILE | RBDT_RESOLVE_CJS_FILE_AS_CHILD | RBDT_RESOLVE_ESM_FILE | RBDT_DIRECTORY | RBDT_FILE | RBDT_DIRECTORY_DEPENDENCIES | RBDT_FILE_DEPENDENCIES} JobType */
  59. const INVALID = Symbol("invalid");
  60. // eslint-disable-next-line jsdoc/ts-no-empty-object-type
  61. /** @typedef {{ }} ExistenceOnlyTimeEntry */
  62. /**
  63. * Defines the file system info entry type used by this module.
  64. * @typedef {object} FileSystemInfoEntry
  65. * @property {number} safeTime
  66. * @property {number=} timestamp
  67. */
  68. /**
  69. * Defines the resolved context file system info entry type used by this module.
  70. * @typedef {object} ResolvedContextFileSystemInfoEntry
  71. * @property {number} safeTime
  72. * @property {string=} timestampHash
  73. */
  74. /** @typedef {Set<string>} Symlinks */
  75. /**
  76. * Defines the context file system info entry type used by this module.
  77. * @typedef {object} ContextFileSystemInfoEntry
  78. * @property {number} safeTime
  79. * @property {string=} timestampHash
  80. * @property {ResolvedContextFileSystemInfoEntry=} resolved
  81. * @property {Symlinks=} symlinks
  82. */
  83. /**
  84. * Defines the timestamp and hash type used by this module.
  85. * @typedef {object} TimestampAndHash
  86. * @property {number} safeTime
  87. * @property {number=} timestamp
  88. * @property {string} hash
  89. */
  90. /**
  91. * Defines the resolved context timestamp and hash type used by this module.
  92. * @typedef {object} ResolvedContextTimestampAndHash
  93. * @property {number} safeTime
  94. * @property {string=} timestampHash
  95. * @property {string} hash
  96. */
  97. /**
  98. * Defines the context timestamp and hash type used by this module.
  99. * @typedef {object} ContextTimestampAndHash
  100. * @property {number} safeTime
  101. * @property {string=} timestampHash
  102. * @property {string} hash
  103. * @property {ResolvedContextTimestampAndHash=} resolved
  104. * @property {Symlinks=} symlinks
  105. */
  106. /**
  107. * Defines the context hash type used by this module.
  108. * @typedef {object} ContextHash
  109. * @property {string} hash
  110. * @property {string=} resolved
  111. * @property {Symlinks=} symlinks
  112. */
  113. /** @typedef {Set<string>} SnapshotContent */
  114. /**
  115. * Defines the snapshot optimization entry type used by this module.
  116. * @typedef {object} SnapshotOptimizationEntry
  117. * @property {Snapshot} snapshot
  118. * @property {number} shared
  119. * @property {SnapshotContent | undefined} snapshotContent
  120. * @property {Set<SnapshotOptimizationEntry> | undefined} children
  121. */
  122. /** @typedef {Map<string, string | false | undefined>} ResolveResults */
  123. /** @typedef {Set<string>} Files */
  124. /** @typedef {Set<string>} Directories */
  125. /** @typedef {Set<string>} Missing */
  126. /**
  127. * Defines the resolve dependencies type used by this module.
  128. * @typedef {object} ResolveDependencies
  129. * @property {Files} files list of files
  130. * @property {Directories} directories list of directories
  131. * @property {Missing} missing list of missing entries
  132. */
  133. /**
  134. * Defines the resolve build dependencies result type used by this module.
  135. * @typedef {object} ResolveBuildDependenciesResult
  136. * @property {Files} files list of files
  137. * @property {Directories} directories list of directories
  138. * @property {Missing} missing list of missing entries
  139. * @property {ResolveResults} resolveResults stored resolve results
  140. * @property {ResolveDependencies} resolveDependencies dependencies of the resolving
  141. */
  142. /**
  143. * Defines the snapshot options type used by this module.
  144. * @typedef {object} SnapshotOptions
  145. * @property {boolean=} hash should use hash to snapshot
  146. * @property {boolean=} timestamp should use timestamp to snapshot
  147. */
  148. const DONE_ITERATOR_RESULT = new Set().keys().next();
  149. // cspell:word tshs
  150. // Tsh = Timestamp + Hash
  151. // Tshs = Timestamp + Hash combinations
  152. class SnapshotIterator {
  153. /**
  154. * Creates an instance of SnapshotIterator.
  155. * @param {() => IteratorResult<string>} next next
  156. */
  157. constructor(next) {
  158. this.next = next;
  159. }
  160. }
  161. /**
  162. * Defines the get maps function type used by this module.
  163. * @template T
  164. * @typedef {(snapshot: Snapshot) => T[]} GetMapsFunction
  165. */
  166. /**
  167. * Represents SnapshotIterable.
  168. * @template T
  169. */
  170. class SnapshotIterable {
  171. /**
  172. * Creates an instance of SnapshotIterable.
  173. * @param {Snapshot} snapshot snapshot
  174. * @param {GetMapsFunction<T>} getMaps get maps function
  175. */
  176. constructor(snapshot, getMaps) {
  177. /** @type {Snapshot} */
  178. this.snapshot = snapshot;
  179. /** @type {GetMapsFunction<T>} */
  180. this.getMaps = getMaps;
  181. }
  182. [Symbol.iterator]() {
  183. let state = 0;
  184. /** @type {IterableIterator<string>} */
  185. let it;
  186. /** @type {GetMapsFunction<T>} */
  187. let getMaps;
  188. /** @type {T[]} */
  189. let maps;
  190. /** @type {Snapshot} */
  191. let snapshot;
  192. /** @type {Snapshot[] | undefined} */
  193. let queue;
  194. return new SnapshotIterator(() => {
  195. for (;;) {
  196. switch (state) {
  197. case 0:
  198. snapshot = this.snapshot;
  199. getMaps = this.getMaps;
  200. maps = getMaps(snapshot);
  201. state = 1;
  202. /* falls through */
  203. case 1:
  204. if (maps.length > 0) {
  205. const map = maps.pop();
  206. if (map !== undefined) {
  207. it =
  208. /** @type {Set<string> | Map<string, EXPECTED_ANY>} */
  209. (map).keys();
  210. state = 2;
  211. } else {
  212. break;
  213. }
  214. } else {
  215. state = 3;
  216. break;
  217. }
  218. /* falls through */
  219. case 2: {
  220. const result = it.next();
  221. if (!result.done) return result;
  222. state = 1;
  223. break;
  224. }
  225. case 3: {
  226. const children = snapshot.children;
  227. if (children !== undefined) {
  228. if (children.size === 1) {
  229. // shortcut for a single child
  230. // avoids allocation of queue
  231. for (const child of children) snapshot = child;
  232. maps = getMaps(snapshot);
  233. state = 1;
  234. break;
  235. }
  236. if (queue === undefined) queue = [];
  237. for (const child of children) {
  238. queue.push(child);
  239. }
  240. }
  241. if (queue !== undefined && queue.length > 0) {
  242. snapshot = /** @type {Snapshot} */ (queue.pop());
  243. maps = getMaps(snapshot);
  244. state = 1;
  245. break;
  246. } else {
  247. state = 4;
  248. }
  249. }
  250. /* falls through */
  251. case 4:
  252. return DONE_ITERATOR_RESULT;
  253. }
  254. }
  255. });
  256. }
  257. }
  258. /** @typedef {Map<string, FileSystemInfoEntry | null>} FileTimestamps */
  259. /** @typedef {Map<string, string | null>} FileHashes */
  260. /** @typedef {Map<string, TimestampAndHash | string | null>} FileTshs */
  261. /** @typedef {Map<string, ResolvedContextFileSystemInfoEntry | null>} ContextTimestamps */
  262. /** @typedef {Map<string, string | null>} ContextHashes */
  263. /** @typedef {Map<string, ResolvedContextTimestampAndHash | null>} ContextTshs */
  264. /** @typedef {Map<string, boolean>} MissingExistence */
  265. /** @typedef {Map<string, string>} ManagedItemInfo */
  266. /** @typedef {Set<string>} ManagedFiles */
  267. /** @typedef {Set<string>} ManagedContexts */
  268. /** @typedef {Set<string>} ManagedMissing */
  269. /** @typedef {Set<Snapshot>} Children */
  270. // `Snapshot._flags` bit for each optional field. Serialized as a whole integer,
  271. // so these values are a persisted-cache format contract — never renumber them.
  272. const SNAPSHOT_FLAG_START_TIME = 1;
  273. const SNAPSHOT_FLAG_FILE_TIMESTAMPS = 2;
  274. const SNAPSHOT_FLAG_FILE_HASHES = 4;
  275. const SNAPSHOT_FLAG_FILE_TSHS = 8;
  276. const SNAPSHOT_FLAG_CONTEXT_TIMESTAMPS = 0x10;
  277. const SNAPSHOT_FLAG_CONTEXT_HASHES = 0x20;
  278. const SNAPSHOT_FLAG_CONTEXT_TSHS = 0x40;
  279. const SNAPSHOT_FLAG_MISSING_EXISTENCE = 0x80;
  280. const SNAPSHOT_FLAG_MANAGED_ITEM_INFO = 0x100;
  281. const SNAPSHOT_FLAG_MANAGED_FILES = 0x200;
  282. const SNAPSHOT_FLAG_MANAGED_CONTEXTS = 0x400;
  283. const SNAPSHOT_FLAG_MANAGED_MISSING = 0x800;
  284. const SNAPSHOT_FLAG_CHILDREN = 0x1000;
  285. class Snapshot {
  286. constructor() {
  287. /** @type {number} */
  288. this._flags = 0;
  289. /** @type {Iterable<string> | undefined} */
  290. this._cachedFileIterable = undefined;
  291. /** @type {Iterable<string> | undefined} */
  292. this._cachedContextIterable = undefined;
  293. /** @type {Iterable<string> | undefined} */
  294. this._cachedMissingIterable = undefined;
  295. /** @type {number | undefined} */
  296. this.startTime = undefined;
  297. /** @type {FileTimestamps | undefined} */
  298. this.fileTimestamps = undefined;
  299. /** @type {FileHashes | undefined} */
  300. this.fileHashes = undefined;
  301. /** @type {FileTshs | undefined} */
  302. this.fileTshs = undefined;
  303. /** @type {ContextTimestamps | undefined} */
  304. this.contextTimestamps = undefined;
  305. /** @type {ContextHashes | undefined} */
  306. this.contextHashes = undefined;
  307. /** @type {ContextTshs | undefined} */
  308. this.contextTshs = undefined;
  309. /** @type {MissingExistence | undefined} */
  310. this.missingExistence = undefined;
  311. /** @type {ManagedItemInfo | undefined} */
  312. this.managedItemInfo = undefined;
  313. /** @type {ManagedFiles | undefined} */
  314. this.managedFiles = undefined;
  315. /** @type {ManagedContexts | undefined} */
  316. this.managedContexts = undefined;
  317. /** @type {ManagedMissing | undefined} */
  318. this.managedMissing = undefined;
  319. /** @type {Children | undefined} */
  320. this.children = undefined;
  321. }
  322. hasStartTime() {
  323. return (this._flags & SNAPSHOT_FLAG_START_TIME) !== 0;
  324. }
  325. /**
  326. * Updates start time using the provided value.
  327. * @param {number} value start value
  328. */
  329. setStartTime(value) {
  330. this._flags |= SNAPSHOT_FLAG_START_TIME;
  331. this.startTime = value;
  332. }
  333. /**
  334. * Sets merged start time.
  335. * @param {number | undefined} value value
  336. * @param {Snapshot} snapshot snapshot
  337. */
  338. setMergedStartTime(value, snapshot) {
  339. if (value) {
  340. if (snapshot.hasStartTime()) {
  341. this.setStartTime(
  342. Math.min(
  343. value,
  344. /** @type {NonNullable<Snapshot["startTime"]>} */
  345. (snapshot.startTime)
  346. )
  347. );
  348. } else {
  349. this.setStartTime(value);
  350. }
  351. } else if (snapshot.hasStartTime()) {
  352. this.setStartTime(
  353. /** @type {NonNullable<Snapshot["startTime"]>} */
  354. (snapshot.startTime)
  355. );
  356. }
  357. }
  358. hasFileTimestamps() {
  359. return (this._flags & SNAPSHOT_FLAG_FILE_TIMESTAMPS) !== 0;
  360. }
  361. /**
  362. * Sets file timestamps.
  363. * @param {FileTimestamps} value file timestamps
  364. */
  365. setFileTimestamps(value) {
  366. this._flags |= SNAPSHOT_FLAG_FILE_TIMESTAMPS;
  367. this.fileTimestamps = value;
  368. }
  369. hasFileHashes() {
  370. return (this._flags & SNAPSHOT_FLAG_FILE_HASHES) !== 0;
  371. }
  372. /**
  373. * Updates file hashes using the provided value.
  374. * @param {FileHashes} value file hashes
  375. */
  376. setFileHashes(value) {
  377. this._flags |= SNAPSHOT_FLAG_FILE_HASHES;
  378. this.fileHashes = value;
  379. }
  380. hasFileTshs() {
  381. return (this._flags & SNAPSHOT_FLAG_FILE_TSHS) !== 0;
  382. }
  383. /**
  384. * Updates file tshs using the provided value.
  385. * @param {FileTshs} value file tshs
  386. */
  387. setFileTshs(value) {
  388. this._flags |= SNAPSHOT_FLAG_FILE_TSHS;
  389. this.fileTshs = value;
  390. }
  391. hasContextTimestamps() {
  392. return (this._flags & SNAPSHOT_FLAG_CONTEXT_TIMESTAMPS) !== 0;
  393. }
  394. /**
  395. * Sets context timestamps.
  396. * @param {ContextTimestamps} value context timestamps
  397. */
  398. setContextTimestamps(value) {
  399. this._flags |= SNAPSHOT_FLAG_CONTEXT_TIMESTAMPS;
  400. this.contextTimestamps = value;
  401. }
  402. hasContextHashes() {
  403. return (this._flags & SNAPSHOT_FLAG_CONTEXT_HASHES) !== 0;
  404. }
  405. /**
  406. * Sets context hashes.
  407. * @param {ContextHashes} value context hashes
  408. */
  409. setContextHashes(value) {
  410. this._flags |= SNAPSHOT_FLAG_CONTEXT_HASHES;
  411. this.contextHashes = value;
  412. }
  413. hasContextTshs() {
  414. return (this._flags & SNAPSHOT_FLAG_CONTEXT_TSHS) !== 0;
  415. }
  416. /**
  417. * Updates context tshs using the provided value.
  418. * @param {ContextTshs} value context tshs
  419. */
  420. setContextTshs(value) {
  421. this._flags |= SNAPSHOT_FLAG_CONTEXT_TSHS;
  422. this.contextTshs = value;
  423. }
  424. hasMissingExistence() {
  425. return (this._flags & SNAPSHOT_FLAG_MISSING_EXISTENCE) !== 0;
  426. }
  427. /**
  428. * Sets missing existence.
  429. * @param {MissingExistence} value context tshs
  430. */
  431. setMissingExistence(value) {
  432. this._flags |= SNAPSHOT_FLAG_MISSING_EXISTENCE;
  433. this.missingExistence = value;
  434. }
  435. hasManagedItemInfo() {
  436. return (this._flags & SNAPSHOT_FLAG_MANAGED_ITEM_INFO) !== 0;
  437. }
  438. /**
  439. * Sets managed item info.
  440. * @param {ManagedItemInfo} value managed item info
  441. */
  442. setManagedItemInfo(value) {
  443. this._flags |= SNAPSHOT_FLAG_MANAGED_ITEM_INFO;
  444. this.managedItemInfo = value;
  445. }
  446. hasManagedFiles() {
  447. return (this._flags & SNAPSHOT_FLAG_MANAGED_FILES) !== 0;
  448. }
  449. /**
  450. * Sets managed files.
  451. * @param {ManagedFiles} value managed files
  452. */
  453. setManagedFiles(value) {
  454. this._flags |= SNAPSHOT_FLAG_MANAGED_FILES;
  455. this.managedFiles = value;
  456. }
  457. hasManagedContexts() {
  458. return (this._flags & SNAPSHOT_FLAG_MANAGED_CONTEXTS) !== 0;
  459. }
  460. /**
  461. * Sets managed contexts.
  462. * @param {ManagedContexts} value managed contexts
  463. */
  464. setManagedContexts(value) {
  465. this._flags |= SNAPSHOT_FLAG_MANAGED_CONTEXTS;
  466. this.managedContexts = value;
  467. }
  468. hasManagedMissing() {
  469. return (this._flags & SNAPSHOT_FLAG_MANAGED_MISSING) !== 0;
  470. }
  471. /**
  472. * Sets managed missing.
  473. * @param {ManagedMissing} value managed missing
  474. */
  475. setManagedMissing(value) {
  476. this._flags |= SNAPSHOT_FLAG_MANAGED_MISSING;
  477. this.managedMissing = value;
  478. }
  479. hasChildren() {
  480. return (this._flags & SNAPSHOT_FLAG_CHILDREN) !== 0;
  481. }
  482. /**
  483. * Updates children using the provided value.
  484. * @param {Children} value children
  485. */
  486. setChildren(value) {
  487. this._flags |= SNAPSHOT_FLAG_CHILDREN;
  488. this.children = value;
  489. }
  490. /**
  491. * Adds the provided child to the snapshot.
  492. * @param {Snapshot} child children
  493. */
  494. addChild(child) {
  495. if (!this.hasChildren()) {
  496. this.setChildren(new Set());
  497. }
  498. /** @type {Children} */
  499. (this.children).add(child);
  500. }
  501. /**
  502. * Serializes this instance into the provided serializer context.
  503. * @param {ObjectSerializerContext} context context
  504. */
  505. serialize({ write }) {
  506. write(this._flags);
  507. if (this.hasStartTime()) write(this.startTime);
  508. if (this.hasFileTimestamps()) write(this.fileTimestamps);
  509. if (this.hasFileHashes()) write(this.fileHashes);
  510. if (this.hasFileTshs()) write(this.fileTshs);
  511. if (this.hasContextTimestamps()) write(this.contextTimestamps);
  512. if (this.hasContextHashes()) write(this.contextHashes);
  513. if (this.hasContextTshs()) write(this.contextTshs);
  514. if (this.hasMissingExistence()) write(this.missingExistence);
  515. if (this.hasManagedItemInfo()) write(this.managedItemInfo);
  516. if (this.hasManagedFiles()) write(this.managedFiles);
  517. if (this.hasManagedContexts()) write(this.managedContexts);
  518. if (this.hasManagedMissing()) write(this.managedMissing);
  519. if (this.hasChildren()) write(this.children);
  520. }
  521. /**
  522. * Restores this instance from the provided deserializer context.
  523. * @param {ObjectDeserializerContext} context context
  524. */
  525. deserialize({ read }) {
  526. this._flags = read();
  527. if (this.hasStartTime()) this.startTime = read();
  528. if (this.hasFileTimestamps()) this.fileTimestamps = read();
  529. if (this.hasFileHashes()) this.fileHashes = read();
  530. if (this.hasFileTshs()) this.fileTshs = read();
  531. if (this.hasContextTimestamps()) this.contextTimestamps = read();
  532. if (this.hasContextHashes()) this.contextHashes = read();
  533. if (this.hasContextTshs()) this.contextTshs = read();
  534. if (this.hasMissingExistence()) this.missingExistence = read();
  535. if (this.hasManagedItemInfo()) this.managedItemInfo = read();
  536. if (this.hasManagedFiles()) this.managedFiles = read();
  537. if (this.hasManagedContexts()) this.managedContexts = read();
  538. if (this.hasManagedMissing()) this.managedMissing = read();
  539. if (this.hasChildren()) this.children = read();
  540. }
  541. /**
  542. * Creates an iterable from the provided get map.
  543. * @template T
  544. * @param {GetMapsFunction<T>} getMaps first
  545. * @returns {SnapshotIterable<T>} iterable
  546. */
  547. _createIterable(getMaps) {
  548. return new SnapshotIterable(this, getMaps);
  549. }
  550. /**
  551. * Gets file iterable.
  552. * @returns {Iterable<string>} iterable
  553. */
  554. getFileIterable() {
  555. if (this._cachedFileIterable === undefined) {
  556. this._cachedFileIterable = this._createIterable((s) => [
  557. s.fileTimestamps,
  558. s.fileHashes,
  559. s.fileTshs,
  560. s.managedFiles
  561. ]);
  562. }
  563. return this._cachedFileIterable;
  564. }
  565. /**
  566. * Gets context iterable.
  567. * @returns {Iterable<string>} iterable
  568. */
  569. getContextIterable() {
  570. if (this._cachedContextIterable === undefined) {
  571. this._cachedContextIterable = this._createIterable((s) => [
  572. s.contextTimestamps,
  573. s.contextHashes,
  574. s.contextTshs,
  575. s.managedContexts
  576. ]);
  577. }
  578. return this._cachedContextIterable;
  579. }
  580. /**
  581. * Gets missing iterable.
  582. * @returns {Iterable<string>} iterable
  583. */
  584. getMissingIterable() {
  585. if (this._cachedMissingIterable === undefined) {
  586. this._cachedMissingIterable = this._createIterable((s) => [
  587. s.missingExistence,
  588. s.managedMissing
  589. ]);
  590. }
  591. return this._cachedMissingIterable;
  592. }
  593. }
  594. makeSerializable(Snapshot, "webpack/lib/FileSystemInfo", "Snapshot");
  595. const MIN_COMMON_SNAPSHOT_SIZE = 3;
  596. /**
  597. * Defines the snapshot optimization value type used by this module.
  598. * @template U, T
  599. * @typedef {U extends true ? Set<string> : Map<string, T>} SnapshotOptimizationValue
  600. */
  601. /**
  602. * Represents SnapshotOptimization.
  603. * @template T
  604. * @template {boolean} [U=false]
  605. */
  606. class SnapshotOptimization {
  607. /**
  608. * Creates an instance of SnapshotOptimization.
  609. * @param {(snapshot: Snapshot) => boolean} has has value
  610. * @param {(snapshot: Snapshot) => SnapshotOptimizationValue<U, T> | undefined} get get value
  611. * @param {(snapshot: Snapshot, value: SnapshotOptimizationValue<U, T>) => void} set set value
  612. * @param {boolean=} useStartTime use the start time of snapshots
  613. * @param {U=} isSet value is an Set instead of a Map
  614. */
  615. constructor(
  616. has,
  617. get,
  618. set,
  619. useStartTime = true,
  620. isSet = /** @type {U} */ (false)
  621. ) {
  622. this._has = has;
  623. this._get = get;
  624. this._set = set;
  625. /** @type {boolean} */
  626. this._useStartTime = useStartTime;
  627. /** @type {U} */
  628. this._isSet = isSet;
  629. /** @type {Map<string, SnapshotOptimizationEntry>} */
  630. this._map = new Map();
  631. /** @type {number} */
  632. this._statItemsShared = 0;
  633. /** @type {number} */
  634. this._statItemsUnshared = 0;
  635. /** @type {number} */
  636. this._statSharedSnapshots = 0;
  637. /** @type {number} */
  638. this._statReusedSharedSnapshots = 0;
  639. }
  640. getStatisticMessage() {
  641. const total = this._statItemsShared + this._statItemsUnshared;
  642. if (total === 0) return;
  643. return `${
  644. this._statItemsShared && Math.round((this._statItemsShared * 100) / total)
  645. }% (${this._statItemsShared}/${total}) entries shared via ${
  646. this._statSharedSnapshots
  647. } shared snapshots (${
  648. this._statReusedSharedSnapshots + this._statSharedSnapshots
  649. } times referenced)`;
  650. }
  651. clear() {
  652. this._map.clear();
  653. this._statItemsShared = 0;
  654. this._statItemsUnshared = 0;
  655. this._statSharedSnapshots = 0;
  656. this._statReusedSharedSnapshots = 0;
  657. }
  658. /**
  659. * Processes the provided new snapshot.
  660. * @param {Snapshot} newSnapshot snapshot
  661. * @param {Set<string>} capturedFiles files to snapshot/share
  662. * @returns {void}
  663. */
  664. optimize(newSnapshot, capturedFiles) {
  665. if (capturedFiles.size === 0) {
  666. return;
  667. }
  668. /**
  669. * Increase shared and store optimization entry.
  670. * @param {SnapshotOptimizationEntry} entry optimization entry
  671. * @returns {void}
  672. */
  673. const increaseSharedAndStoreOptimizationEntry = (entry) => {
  674. if (entry.children !== undefined) {
  675. for (const child of entry.children) {
  676. increaseSharedAndStoreOptimizationEntry(child);
  677. }
  678. }
  679. entry.shared++;
  680. storeOptimizationEntry(entry);
  681. };
  682. /**
  683. * Stores optimization entry.
  684. * @param {SnapshotOptimizationEntry} entry optimization entry
  685. * @returns {void}
  686. */
  687. const storeOptimizationEntry = (entry) => {
  688. for (const path of /** @type {SnapshotContent} */ (
  689. entry.snapshotContent
  690. )) {
  691. const old =
  692. /** @type {SnapshotOptimizationEntry} */
  693. (this._map.get(path));
  694. if (old.shared < entry.shared) {
  695. this._map.set(path, entry);
  696. }
  697. capturedFiles.delete(path);
  698. }
  699. };
  700. /** @type {SnapshotOptimizationEntry | undefined} */
  701. let newOptimizationEntry;
  702. const capturedFilesSize = capturedFiles.size;
  703. /** @type {Set<SnapshotOptimizationEntry> | undefined} */
  704. const optimizationEntries = new Set();
  705. for (const path of capturedFiles) {
  706. const optimizationEntry = this._map.get(path);
  707. if (optimizationEntry === undefined) {
  708. if (newOptimizationEntry === undefined) {
  709. newOptimizationEntry = {
  710. snapshot: newSnapshot,
  711. shared: 0,
  712. snapshotContent: undefined,
  713. children: undefined
  714. };
  715. }
  716. this._map.set(path, newOptimizationEntry);
  717. } else {
  718. optimizationEntries.add(optimizationEntry);
  719. }
  720. }
  721. optimizationEntriesLabel: for (const optimizationEntry of optimizationEntries) {
  722. const snapshot = optimizationEntry.snapshot;
  723. if (optimizationEntry.shared > 0) {
  724. // It's a shared snapshot
  725. // We can't change it, so we can only use it when all files match
  726. // and startTime is compatible
  727. if (
  728. this._useStartTime &&
  729. newSnapshot.startTime &&
  730. (!snapshot.startTime || snapshot.startTime > newSnapshot.startTime)
  731. ) {
  732. continue;
  733. }
  734. /** @type {Set<string>} */
  735. const nonSharedFiles = new Set();
  736. const snapshotContent =
  737. /** @type {NonNullable<SnapshotOptimizationEntry["snapshotContent"]>} */
  738. (optimizationEntry.snapshotContent);
  739. const snapshotEntries =
  740. /** @type {SnapshotOptimizationValue<U, T>} */
  741. (this._get(snapshot));
  742. for (const path of snapshotContent) {
  743. if (!capturedFiles.has(path)) {
  744. if (!snapshotEntries.has(path)) {
  745. // File is not shared and can't be removed from the snapshot
  746. // because it's in a child of the snapshot
  747. continue optimizationEntriesLabel;
  748. }
  749. nonSharedFiles.add(path);
  750. }
  751. }
  752. if (nonSharedFiles.size === 0) {
  753. // The complete snapshot is shared
  754. // add it as child
  755. newSnapshot.addChild(snapshot);
  756. increaseSharedAndStoreOptimizationEntry(optimizationEntry);
  757. this._statReusedSharedSnapshots++;
  758. } else {
  759. // Only a part of the snapshot is shared
  760. const sharedCount = snapshotContent.size - nonSharedFiles.size;
  761. if (sharedCount < MIN_COMMON_SNAPSHOT_SIZE) {
  762. // Common part it too small
  763. continue;
  764. }
  765. // Extract common timestamps from both snapshots
  766. /** @type {Set<string> | Map<string, T>} */
  767. let commonMap;
  768. if (this._isSet) {
  769. commonMap = new Set();
  770. for (const path of /** @type {Set<string>} */ (snapshotEntries)) {
  771. if (nonSharedFiles.has(path)) continue;
  772. commonMap.add(path);
  773. snapshotEntries.delete(path);
  774. }
  775. } else {
  776. commonMap = new Map();
  777. const map = /** @type {Map<string, T>} */ (snapshotEntries);
  778. for (const [path, value] of map) {
  779. if (nonSharedFiles.has(path)) continue;
  780. commonMap.set(path, value);
  781. snapshotEntries.delete(path);
  782. }
  783. }
  784. // Create and attach snapshot
  785. const commonSnapshot = new Snapshot();
  786. if (this._useStartTime) {
  787. commonSnapshot.setMergedStartTime(newSnapshot.startTime, snapshot);
  788. }
  789. this._set(
  790. commonSnapshot,
  791. /** @type {SnapshotOptimizationValue<U, T>} */ (commonMap)
  792. );
  793. newSnapshot.addChild(commonSnapshot);
  794. snapshot.addChild(commonSnapshot);
  795. // Create optimization entry
  796. const newEntry = {
  797. snapshot: commonSnapshot,
  798. shared: optimizationEntry.shared + 1,
  799. snapshotContent: new Set(commonMap.keys()),
  800. children: undefined
  801. };
  802. if (optimizationEntry.children === undefined) {
  803. optimizationEntry.children = new Set();
  804. }
  805. optimizationEntry.children.add(newEntry);
  806. storeOptimizationEntry(newEntry);
  807. this._statSharedSnapshots++;
  808. }
  809. } else {
  810. // It's a unshared snapshot
  811. // We can extract a common shared snapshot
  812. // with all common files
  813. const snapshotEntries = this._get(snapshot);
  814. if (snapshotEntries === undefined) {
  815. // Incomplete snapshot, that can't be used
  816. continue;
  817. }
  818. /** @type {Set<string> | Map<string, T>} */
  819. let commonMap;
  820. if (this._isSet) {
  821. commonMap = new Set();
  822. const set = /** @type {Set<string>} */ (snapshotEntries);
  823. if (capturedFiles.size < set.size) {
  824. for (const path of capturedFiles) {
  825. if (set.has(path)) commonMap.add(path);
  826. }
  827. } else {
  828. for (const path of set) {
  829. if (capturedFiles.has(path)) commonMap.add(path);
  830. }
  831. }
  832. } else {
  833. commonMap = new Map();
  834. const map = /** @type {Map<string, T>} */ (snapshotEntries);
  835. for (const path of capturedFiles) {
  836. const ts = map.get(path);
  837. if (ts === undefined) continue;
  838. commonMap.set(path, ts);
  839. }
  840. }
  841. if (commonMap.size < MIN_COMMON_SNAPSHOT_SIZE) {
  842. // Common part it too small
  843. continue;
  844. }
  845. // Create and attach snapshot
  846. const commonSnapshot = new Snapshot();
  847. if (this._useStartTime) {
  848. commonSnapshot.setMergedStartTime(newSnapshot.startTime, snapshot);
  849. }
  850. this._set(
  851. commonSnapshot,
  852. /** @type {SnapshotOptimizationValue<U, T>} */
  853. (commonMap)
  854. );
  855. newSnapshot.addChild(commonSnapshot);
  856. snapshot.addChild(commonSnapshot);
  857. // Remove files from snapshot
  858. for (const path of commonMap.keys()) snapshotEntries.delete(path);
  859. const sharedCount = commonMap.size;
  860. this._statItemsUnshared -= sharedCount;
  861. this._statItemsShared += sharedCount;
  862. // Create optimization entry
  863. storeOptimizationEntry({
  864. snapshot: commonSnapshot,
  865. shared: 2,
  866. snapshotContent: new Set(commonMap.keys()),
  867. children: undefined
  868. });
  869. this._statSharedSnapshots++;
  870. }
  871. }
  872. const unshared = capturedFiles.size;
  873. this._statItemsUnshared += unshared;
  874. this._statItemsShared += capturedFilesSize - unshared;
  875. }
  876. }
  877. /** @type {Record<string, string>} */
  878. const ESCAPES = {
  879. n: "\n",
  880. r: "\r",
  881. t: "\t",
  882. b: "\b",
  883. f: "\f",
  884. v: "\v"
  885. };
  886. /**
  887. * Cooks a JS string/template literal specifier into its evaluated value,
  888. * matching ECMAScript escape semantics (sloppy-mode for string literals).
  889. * @param {string} str input including the surrounding quotes
  890. * @returns {string | null} result, or null when not a quoted literal
  891. * @throws {Error} on a malformed escape sequence, like the JS parser would
  892. */
  893. const parseString = (str) => {
  894. const q = str[0];
  895. if (q !== '"' && q !== "'" && q !== "`") {
  896. return null;
  897. }
  898. const template = q === "`";
  899. const inner = str.slice(1, -1);
  900. const len = inner.length;
  901. let result = "";
  902. let i = 0;
  903. while (i < len) {
  904. const ch = inner[i];
  905. if (ch !== "\\") {
  906. // Template value normalizes raw <CR> and <CRLF> to <LF>
  907. if (template && ch === "\r") {
  908. result += "\n";
  909. i += inner[i + 1] === "\n" ? 2 : 1;
  910. } else {
  911. result += ch;
  912. i++;
  913. }
  914. continue;
  915. }
  916. i++;
  917. if (i >= len) throw new Error("Unterminated escape sequence");
  918. const esc = inner[i];
  919. if (esc === "x") {
  920. const hex = inner.slice(i + 1, i + 3);
  921. if (!/^[0-9a-fA-F]{2}$/.test(hex)) {
  922. throw new Error("Invalid hexadecimal escape sequence");
  923. }
  924. result += String.fromCharCode(Number.parseInt(hex, 16));
  925. i += 3;
  926. } else if (esc === "u") {
  927. if (inner[i + 1] === "{") {
  928. // \u{N...}
  929. const closeIdx = inner.indexOf("}", i + 2);
  930. const codeStr = closeIdx === -1 ? "" : inner.slice(i + 2, closeIdx);
  931. const code = Number.parseInt(codeStr, 16);
  932. if (!/^[0-9a-fA-F]+$/.test(codeStr) || code > 0x10ffff) {
  933. throw new Error("Invalid Unicode escape sequence");
  934. }
  935. result += String.fromCodePoint(code);
  936. i = closeIdx + 1;
  937. } else {
  938. // \uNNNN
  939. const hex = inner.slice(i + 1, i + 5);
  940. if (!/^[0-9a-fA-F]{4}$/.test(hex)) {
  941. throw new Error("Invalid Unicode escape sequence");
  942. }
  943. result += String.fromCharCode(Number.parseInt(hex, 16));
  944. i += 5;
  945. }
  946. } else if (esc === "\r") {
  947. // Line continuation: \<CR> or \<CRLF>
  948. i += inner[i + 1] === "\n" ? 2 : 1;
  949. } else if (esc === "\n" || esc === "\u2028" || esc === "\u2029") {
  950. // Line continuation: \<LF>, \<LS>, \<PS>
  951. i++;
  952. } else if (ESCAPES[esc] !== undefined) {
  953. result += ESCAPES[esc];
  954. i++;
  955. } else if (esc >= "0" && esc <= "7") {
  956. // \0 not followed by a digit is NUL, otherwise a legacy octal escape
  957. const next = inner[i + 1];
  958. if (esc === "0" && !(next >= "0" && next <= "9")) {
  959. result += "\0";
  960. i++;
  961. continue;
  962. }
  963. if (template) throw new Error("Octal escape not allowed in template");
  964. // Up to 3 octal digits, value capped at \377
  965. let oct = esc;
  966. i++;
  967. const max = esc <= "3" ? 3 : 2;
  968. while (oct.length < max && inner[i] >= "0" && inner[i] <= "7") {
  969. oct += inner[i];
  970. i++;
  971. }
  972. result += String.fromCharCode(Number.parseInt(oct, 8));
  973. } else if (esc === "8" || esc === "9") {
  974. if (template) throw new Error("\\8 and \\9 not allowed in template");
  975. result += esc;
  976. i++;
  977. } else {
  978. // \', \", \\, \`, and any other NonEscapeCharacter — the char itself
  979. result += esc;
  980. i++;
  981. }
  982. }
  983. return result;
  984. };
  985. /* istanbul ignore next */
  986. /**
  987. * Processes the provided mtime.
  988. * @param {number} mtime mtime
  989. */
  990. const applyMtime = (mtime) => {
  991. if (FS_ACCURACY > 1 && mtime % 2 !== 0) FS_ACCURACY = 1;
  992. else if (FS_ACCURACY > 10 && mtime % 20 !== 0) FS_ACCURACY = 10;
  993. else if (FS_ACCURACY > 100 && mtime % 200 !== 0) FS_ACCURACY = 100;
  994. else if (FS_ACCURACY > 1000 && mtime % 2000 !== 0) FS_ACCURACY = 1000;
  995. };
  996. /**
  997. * Merges the provided values into a single result.
  998. * @template T
  999. * @template K
  1000. * @param {Map<T, K> | undefined} a source map
  1001. * @param {Map<T, K> | undefined} b joining map
  1002. * @returns {Map<T, K>} joined map
  1003. */
  1004. const mergeMaps = (a, b) => {
  1005. if (!b || b.size === 0) return /** @type {Map<T, K>} */ (a);
  1006. if (!a || a.size === 0) return /** @type {Map<T, K>} */ (b);
  1007. /** @type {Map<T, K>} */
  1008. const map = new Map(a);
  1009. for (const [key, value] of b) {
  1010. map.set(key, value);
  1011. }
  1012. return map;
  1013. };
  1014. /**
  1015. * Merges the provided values into a single result.
  1016. * @template T
  1017. * @param {Set<T> | undefined} a source map
  1018. * @param {Set<T> | undefined} b joining map
  1019. * @returns {Set<T>} joined map
  1020. */
  1021. const mergeSets = (a, b) => {
  1022. if (!b || b.size === 0) return /** @type {Set<T>} */ (a);
  1023. if (!a || a.size === 0) return /** @type {Set<T>} */ (b);
  1024. /** @type {Set<T>} */
  1025. const map = new Set(a);
  1026. for (const item of b) {
  1027. map.add(item);
  1028. }
  1029. return map;
  1030. };
  1031. /**
  1032. * Finding file or directory to manage
  1033. * @param {string} managedPath path that is managing by {@link FileSystemInfo}
  1034. * @param {string} path path to file or directory
  1035. * @returns {string | null} managed item
  1036. * @example
  1037. * getManagedItem(
  1038. * '/Users/user/my-project/node_modules/',
  1039. * '/Users/user/my-project/node_modules/package/index.js'
  1040. * ) === '/Users/user/my-project/node_modules/package'
  1041. * getManagedItem(
  1042. * '/Users/user/my-project/node_modules/',
  1043. * '/Users/user/my-project/node_modules/package1/node_modules/package2'
  1044. * ) === '/Users/user/my-project/node_modules/package1/node_modules/package2'
  1045. * getManagedItem(
  1046. * '/Users/user/my-project/node_modules/',
  1047. * '/Users/user/my-project/node_modules/.bin/script.js'
  1048. * ) === null // hidden files are disallowed as managed items
  1049. * getManagedItem(
  1050. * '/Users/user/my-project/node_modules/',
  1051. * '/Users/user/my-project/node_modules/package'
  1052. * ) === '/Users/user/my-project/node_modules/package'
  1053. */
  1054. const getManagedItem = (managedPath, path) => {
  1055. let i = managedPath.length;
  1056. let slashes = 1;
  1057. let startingPosition = true;
  1058. loop: while (i < path.length) {
  1059. switch (path.charCodeAt(i)) {
  1060. case 47: // slash
  1061. case 92: // backslash
  1062. if (--slashes === 0) break loop;
  1063. startingPosition = true;
  1064. break;
  1065. case 46: // .
  1066. // hidden files are disallowed as managed items
  1067. // it's probably .yarn-integrity or .cache
  1068. if (startingPosition) return null;
  1069. break;
  1070. case 64: // @
  1071. if (!startingPosition) return null;
  1072. slashes++;
  1073. break;
  1074. default:
  1075. startingPosition = false;
  1076. break;
  1077. }
  1078. i++;
  1079. }
  1080. if (i === path.length) slashes--;
  1081. // return null when path is incomplete
  1082. if (slashes !== 0) return null;
  1083. // if (path.slice(i + 1, i + 13) === "node_modules")
  1084. if (
  1085. path.length >= i + 13 &&
  1086. path.charCodeAt(i + 1) === 110 &&
  1087. path.charCodeAt(i + 2) === 111 &&
  1088. path.charCodeAt(i + 3) === 100 &&
  1089. path.charCodeAt(i + 4) === 101 &&
  1090. path.charCodeAt(i + 5) === 95 &&
  1091. path.charCodeAt(i + 6) === 109 &&
  1092. path.charCodeAt(i + 7) === 111 &&
  1093. path.charCodeAt(i + 8) === 100 &&
  1094. path.charCodeAt(i + 9) === 117 &&
  1095. path.charCodeAt(i + 10) === 108 &&
  1096. path.charCodeAt(i + 11) === 101 &&
  1097. path.charCodeAt(i + 12) === 115
  1098. ) {
  1099. // if this is the end of the path
  1100. if (path.length === i + 13) {
  1101. // return the node_modules directory
  1102. // it's special
  1103. return path;
  1104. }
  1105. const c = path.charCodeAt(i + 13);
  1106. // if next symbol is slash or backslash
  1107. if (c === 47 || c === 92) {
  1108. // Managed subpath
  1109. return getManagedItem(path.slice(0, i + 14), path);
  1110. }
  1111. }
  1112. return path.slice(0, i);
  1113. };
  1114. /**
  1115. * The managed directory a `snapshot.managedPaths` RegExp matched. The schema
  1116. * documents it as matching that directory, so a capture-less pattern is it.
  1117. * @param {RegExpExecArray} match a match of a managed path RegExp
  1118. * @returns {string} the managed directory
  1119. */
  1120. const getManagedPathFromMatch = (match) =>
  1121. match[1] === undefined ? match[0] : match[1];
  1122. /**
  1123. * Gets resolved timestamp.
  1124. * @template {ContextFileSystemInfoEntry | ContextTimestampAndHash} T
  1125. * @param {T | null} entry entry
  1126. * @returns {T["resolved"] | null | undefined} the resolved entry
  1127. */
  1128. const getResolvedTimestamp = (entry) => {
  1129. if (entry === null) return null;
  1130. if (entry.resolved !== undefined) return entry.resolved;
  1131. return entry.symlinks === undefined ? entry : undefined;
  1132. };
  1133. /**
  1134. * Gets resolved hash.
  1135. * @param {ContextHash | null} entry entry
  1136. * @returns {string | null | undefined} the resolved entry
  1137. */
  1138. const getResolvedHash = (entry) => {
  1139. if (entry === null) return null;
  1140. if (entry.resolved !== undefined) return entry.resolved;
  1141. return entry.symlinks === undefined ? entry.hash : undefined;
  1142. };
  1143. /**
  1144. * Adds the provided source to the snapshot optimization.
  1145. * @template T
  1146. * @param {Set<T>} source source
  1147. * @param {Set<T>} target target
  1148. */
  1149. const addAll = (source, target) => {
  1150. for (const key of source) target.add(key);
  1151. };
  1152. // Bun doesn't populate `require.cache` `module.children`, so the CJS build-dependency
  1153. // walk needs a source-parsing fallback there. Node/Deno populate it — never pay for it.
  1154. const IS_BUN = Boolean(process.versions.bun);
  1155. /** @typedef {Set<string>} LoggedPaths */
  1156. /** @typedef {FileSystemInfoEntry | ExistenceOnlyTimeEntry | "ignore" | null} FileTimestamp */
  1157. /** @typedef {ContextFileSystemInfoEntry | ExistenceOnlyTimeEntry | "ignore" | null} ContextTimestamp */
  1158. /** @typedef {ResolvedContextFileSystemInfoEntry | "ignore" | null} ResolvedContextTimestamp */
  1159. /**
  1160. * `watchpack` may report `{}` (existence-only) for files and directories it
  1161. * is watching but has no time information for. Such entries cannot be used
  1162. * for snapshot comparison, so cache lookups treat them as "no cached value"
  1163. * and fall back to a fresh on-disk read.
  1164. * @param {FileTimestamp | ContextTimestamp | undefined} entry cache entry
  1165. * @returns {entry is ExistenceOnlyTimeEntry} true if the entry exists but carries no time info
  1166. */
  1167. const isExistenceOnly = (entry) => {
  1168. if (entry === undefined || entry === null || entry === "ignore") return false;
  1169. return (
  1170. /** @type {Partial<FileSystemInfoEntry> & Partial<ContextFileSystemInfoEntry>} */
  1171. (entry).safeTime === undefined
  1172. );
  1173. };
  1174. /**
  1175. * Watchpack directory entries may carry `safeTime` / `timestamp` but never
  1176. * `timestampHash`; such object caches cannot back snapshot checks or symlink walks.
  1177. * @param {ContextFileSystemInfoEntry | null | "ignore" | undefined} entry cache entry
  1178. * @returns {boolean} true when a non-null object entry lacks `timestampHash`
  1179. */
  1180. const contextCacheEntryLacksTimestampHash = (entry) =>
  1181. entry !== null &&
  1182. typeof entry === "object" &&
  1183. /** @type {ContextFileSystemInfoEntry} */ (entry).timestampHash === undefined;
  1184. /** @typedef {(err?: WebpackError | null, result?: boolean) => void} CheckSnapshotValidCallback */
  1185. /**
  1186. * Used to access information about the filesystem in a cached way
  1187. */
  1188. class FileSystemInfo {
  1189. /**
  1190. * Creates an instance of FileSystemInfo.
  1191. * @param {InputFileSystem} fs file system
  1192. * @param {object} options options
  1193. * @param {Iterable<string | RegExp>=} options.unmanagedPaths paths that are not managed by a package manager and the contents are subject to change
  1194. * @param {Iterable<string | RegExp>=} options.managedPaths paths that are only managed by a package manager
  1195. * @param {Iterable<string | RegExp>=} options.immutablePaths paths that are immutable
  1196. * @param {Logger=} options.logger logger used to log invalid snapshots
  1197. * @param {HashFunction=} options.hashFunction the hash function to use
  1198. */
  1199. constructor(
  1200. fs,
  1201. {
  1202. unmanagedPaths = [],
  1203. managedPaths = [],
  1204. immutablePaths = [],
  1205. logger,
  1206. hashFunction = DEFAULTS.HASH_FUNCTION
  1207. } = {}
  1208. ) {
  1209. /** @type {InputFileSystem} */
  1210. this.fs = fs;
  1211. this.logger = logger;
  1212. /** @type {number} */
  1213. this._remainingLogs = logger ? 40 : 0;
  1214. /** @type {LoggedPaths | undefined} */
  1215. this._loggedPaths = logger ? new Set() : undefined;
  1216. /** @type {HashFunction} */
  1217. this._hashFunction = hashFunction;
  1218. /** @type {WeakMap<Snapshot, boolean | CheckSnapshotValidCallback[]>} */
  1219. this._snapshotCache = new WeakMap();
  1220. /** @type {SnapshotOptimization<FileSystemInfoEntry | null, false>} */
  1221. this._fileTimestampsOptimization = new SnapshotOptimization(
  1222. (s) => s.hasFileTimestamps(),
  1223. (s) => s.fileTimestamps,
  1224. (s, v) => s.setFileTimestamps(v)
  1225. );
  1226. /** @type {SnapshotOptimization<string | null, false>} */
  1227. this._fileHashesOptimization = new SnapshotOptimization(
  1228. (s) => s.hasFileHashes(),
  1229. (s) => s.fileHashes,
  1230. (s, v) => s.setFileHashes(v),
  1231. false
  1232. );
  1233. /** @type {SnapshotOptimization<string | TimestampAndHash | null, false>} */
  1234. this._fileTshsOptimization = new SnapshotOptimization(
  1235. (s) => s.hasFileTshs(),
  1236. (s) => s.fileTshs,
  1237. (s, v) => s.setFileTshs(v)
  1238. );
  1239. /** @type {SnapshotOptimization<ResolvedContextFileSystemInfoEntry | null, false>} */
  1240. this._contextTimestampsOptimization = new SnapshotOptimization(
  1241. (s) => s.hasContextTimestamps(),
  1242. (s) => s.contextTimestamps,
  1243. (s, v) => s.setContextTimestamps(v)
  1244. );
  1245. /** @type {SnapshotOptimization<string | null, false>} */
  1246. this._contextHashesOptimization = new SnapshotOptimization(
  1247. (s) => s.hasContextHashes(),
  1248. (s) => s.contextHashes,
  1249. (s, v) => s.setContextHashes(v),
  1250. false
  1251. );
  1252. /** @type {SnapshotOptimization<ResolvedContextTimestampAndHash | null, false>} */
  1253. this._contextTshsOptimization = new SnapshotOptimization(
  1254. (s) => s.hasContextTshs(),
  1255. (s) => s.contextTshs,
  1256. (s, v) => s.setContextTshs(v)
  1257. );
  1258. /** @type {SnapshotOptimization<boolean, false>} */
  1259. this._missingExistenceOptimization = new SnapshotOptimization(
  1260. (s) => s.hasMissingExistence(),
  1261. (s) => s.missingExistence,
  1262. (s, v) => s.setMissingExistence(v),
  1263. false
  1264. );
  1265. /** @type {SnapshotOptimization<string, false>} */
  1266. this._managedItemInfoOptimization = new SnapshotOptimization(
  1267. (s) => s.hasManagedItemInfo(),
  1268. (s) => s.managedItemInfo,
  1269. (s, v) => s.setManagedItemInfo(v),
  1270. false
  1271. );
  1272. this._managedFilesOptimization = new SnapshotOptimization(
  1273. (s) => s.hasManagedFiles(),
  1274. (s) => s.managedFiles,
  1275. (s, v) => s.setManagedFiles(v),
  1276. false,
  1277. true
  1278. );
  1279. this._managedContextsOptimization = new SnapshotOptimization(
  1280. (s) => s.hasManagedContexts(),
  1281. (s) => s.managedContexts,
  1282. (s, v) => s.setManagedContexts(v),
  1283. false,
  1284. true
  1285. );
  1286. this._managedMissingOptimization = new SnapshotOptimization(
  1287. (s) => s.hasManagedMissing(),
  1288. (s) => s.managedMissing,
  1289. (s, v) => s.setManagedMissing(v),
  1290. false,
  1291. true
  1292. );
  1293. /** @type {StackedCacheMap<string, FileTimestamp>} */
  1294. this._fileTimestamps = new StackedCacheMap();
  1295. /** @type {Map<string, string | null>} */
  1296. this._fileHashes = new Map();
  1297. /** @type {Map<string, TimestampAndHash | string>} */
  1298. this._fileTshs = new Map();
  1299. /** @type {StackedCacheMap<string, ContextTimestamp>} */
  1300. this._contextTimestamps = new StackedCacheMap();
  1301. /** @type {Map<string, ContextHash>} */
  1302. this._contextHashes = new Map();
  1303. /** @type {Map<string, ContextTimestampAndHash>} */
  1304. this._contextTshs = new Map();
  1305. /** @type {Map<string, string>} */
  1306. this._managedItems = new Map();
  1307. // memoized checkManaged results: 0 = not managed, 1 = immutable, string = managed item
  1308. /** @type {Map<string, 0 | 1 | string>} */
  1309. this._pathClassificationCache = new Map();
  1310. /** @type {AsyncQueue<string, string, FileSystemInfoEntry>} */
  1311. this.fileTimestampQueue = new AsyncQueue({
  1312. name: "file timestamp",
  1313. parallelism: 30,
  1314. processor: this._readFileTimestamp.bind(this)
  1315. });
  1316. /** @type {AsyncQueue<string, string, string>} */
  1317. this.fileHashQueue = new AsyncQueue({
  1318. name: "file hash",
  1319. parallelism: 10,
  1320. processor: this._readFileHash.bind(this)
  1321. });
  1322. /** @type {AsyncQueue<string, string, ContextFileSystemInfoEntry>} */
  1323. this.contextTimestampQueue = new AsyncQueue({
  1324. name: "context timestamp",
  1325. parallelism: 2,
  1326. processor: this._readContextTimestamp.bind(this)
  1327. });
  1328. /** @type {AsyncQueue<string, string, ContextHash>} */
  1329. this.contextHashQueue = new AsyncQueue({
  1330. name: "context hash",
  1331. parallelism: 2,
  1332. processor: this._readContextHash.bind(this)
  1333. });
  1334. /** @type {AsyncQueue<string, string, ContextTimestampAndHash>} */
  1335. this.contextTshQueue = new AsyncQueue({
  1336. name: "context hash and timestamp",
  1337. parallelism: 2,
  1338. processor: this._readContextTimestampAndHash.bind(this)
  1339. });
  1340. /** @type {AsyncQueue<string, string, string>} */
  1341. this.managedItemQueue = new AsyncQueue({
  1342. name: "managed item info",
  1343. parallelism: 10,
  1344. processor: this._getManagedItemInfo.bind(this)
  1345. });
  1346. /** @type {AsyncQueue<string, string, Set<string>>} */
  1347. this.managedItemDirectoryQueue = new AsyncQueue({
  1348. name: "managed item directory info",
  1349. parallelism: 10,
  1350. processor: this._getManagedItemDirectoryInfo.bind(this)
  1351. });
  1352. const _unmanagedPaths = [...unmanagedPaths];
  1353. /** @type {string[]} */
  1354. this.unmanagedPathsWithSlash = _unmanagedPaths
  1355. .filter((p) => typeof p === "string")
  1356. .map((p) => join(fs, p, "_").slice(0, -1));
  1357. /** @type {RegExp[]} */
  1358. this.unmanagedPathsRegExps = _unmanagedPaths.filter(
  1359. (p) => typeof p !== "string"
  1360. );
  1361. this.managedPaths = [...managedPaths];
  1362. /** @type {string[]} */
  1363. this.managedPathsWithSlash = this.managedPaths
  1364. .filter((p) => typeof p === "string")
  1365. .map((p) => join(fs, p, "_").slice(0, -1));
  1366. /** @type {RegExp[]} */
  1367. this.managedPathsRegExps = this.managedPaths.filter(
  1368. (p) => typeof p !== "string"
  1369. );
  1370. this.immutablePaths = [...immutablePaths];
  1371. /** @type {string[]} */
  1372. this.immutablePathsWithSlash = this.immutablePaths
  1373. .filter((p) => typeof p === "string")
  1374. .map((p) => join(fs, p, "_").slice(0, -1));
  1375. /** @type {RegExp[]} */
  1376. this.immutablePathsRegExps = this.immutablePaths.filter(
  1377. (p) => typeof p !== "string"
  1378. );
  1379. /** @type {Map<string, number | null> | undefined} */
  1380. this._cachedDeprecatedFileTimestamps = undefined;
  1381. /** @type {Map<string, number | null> | undefined} */
  1382. this._cachedDeprecatedContextTimestamps = undefined;
  1383. /** @type {boolean} */
  1384. this._warnAboutExperimentalEsmTracking = false;
  1385. /** @type {number} */
  1386. this._statCreatedSnapshots = 0;
  1387. /** @type {number} */
  1388. this._statTestedSnapshotsCached = 0;
  1389. /** @type {number} */
  1390. this._statTestedSnapshotsNotCached = 0;
  1391. /** @type {number} */
  1392. this._statTestedChildrenCached = 0;
  1393. /** @type {number} */
  1394. this._statTestedChildrenNotCached = 0;
  1395. /** @type {number} */
  1396. this._statTestedEntries = 0;
  1397. }
  1398. logStatistics() {
  1399. const logger = /** @type {Logger} */ (this.logger);
  1400. /**
  1401. * Processes the provided header.
  1402. * @param {string} header header
  1403. * @param {string | undefined} message message
  1404. */
  1405. const logWhenMessage = (header, message) => {
  1406. if (message) {
  1407. logger.log(`${header}: ${message}`);
  1408. }
  1409. };
  1410. logger.log(`${this._statCreatedSnapshots} new snapshots created`);
  1411. logger.log(
  1412. `${
  1413. this._statTestedSnapshotsNotCached &&
  1414. Math.round(
  1415. (this._statTestedSnapshotsNotCached * 100) /
  1416. (this._statTestedSnapshotsCached +
  1417. this._statTestedSnapshotsNotCached)
  1418. )
  1419. }% root snapshot uncached (${this._statTestedSnapshotsNotCached} / ${
  1420. this._statTestedSnapshotsCached + this._statTestedSnapshotsNotCached
  1421. })`
  1422. );
  1423. logger.log(
  1424. `${
  1425. this._statTestedChildrenNotCached &&
  1426. Math.round(
  1427. (this._statTestedChildrenNotCached * 100) /
  1428. (this._statTestedChildrenCached + this._statTestedChildrenNotCached)
  1429. )
  1430. }% children snapshot uncached (${this._statTestedChildrenNotCached} / ${
  1431. this._statTestedChildrenCached + this._statTestedChildrenNotCached
  1432. })`
  1433. );
  1434. logger.log(`${this._statTestedEntries} entries tested`);
  1435. logger.log(
  1436. `File info in cache: ${this._fileTimestamps.size} timestamps ${this._fileHashes.size} hashes ${this._fileTshs.size} timestamp hash combinations`
  1437. );
  1438. logWhenMessage(
  1439. "File timestamp snapshot optimization",
  1440. this._fileTimestampsOptimization.getStatisticMessage()
  1441. );
  1442. logWhenMessage(
  1443. "File hash snapshot optimization",
  1444. this._fileHashesOptimization.getStatisticMessage()
  1445. );
  1446. logWhenMessage(
  1447. "File timestamp hash combination snapshot optimization",
  1448. this._fileTshsOptimization.getStatisticMessage()
  1449. );
  1450. logger.log(
  1451. `Directory info in cache: ${this._contextTimestamps.size} timestamps ${this._contextHashes.size} hashes ${this._contextTshs.size} timestamp hash combinations`
  1452. );
  1453. logWhenMessage(
  1454. "Directory timestamp snapshot optimization",
  1455. this._contextTimestampsOptimization.getStatisticMessage()
  1456. );
  1457. logWhenMessage(
  1458. "Directory hash snapshot optimization",
  1459. this._contextHashesOptimization.getStatisticMessage()
  1460. );
  1461. logWhenMessage(
  1462. "Directory timestamp hash combination snapshot optimization",
  1463. this._contextTshsOptimization.getStatisticMessage()
  1464. );
  1465. logWhenMessage(
  1466. "Missing items snapshot optimization",
  1467. this._missingExistenceOptimization.getStatisticMessage()
  1468. );
  1469. logger.log(`Managed items info in cache: ${this._managedItems.size} items`);
  1470. logWhenMessage(
  1471. "Managed items snapshot optimization",
  1472. this._managedItemInfoOptimization.getStatisticMessage()
  1473. );
  1474. logWhenMessage(
  1475. "Managed files snapshot optimization",
  1476. this._managedFilesOptimization.getStatisticMessage()
  1477. );
  1478. logWhenMessage(
  1479. "Managed contexts snapshot optimization",
  1480. this._managedContextsOptimization.getStatisticMessage()
  1481. );
  1482. logWhenMessage(
  1483. "Managed missing snapshot optimization",
  1484. this._managedMissingOptimization.getStatisticMessage()
  1485. );
  1486. }
  1487. /**
  1488. * Processes the provided path.
  1489. * @private
  1490. * @param {string} path path
  1491. * @param {string} reason reason
  1492. * @param {EXPECTED_ANY[]} args arguments
  1493. */
  1494. _log(path, reason, ...args) {
  1495. const key = path + reason;
  1496. const loggedPaths = /** @type {LoggedPaths} */ (this._loggedPaths);
  1497. if (loggedPaths.has(key)) return;
  1498. loggedPaths.add(key);
  1499. /** @type {Logger} */
  1500. (this.logger).debug(`${path} invalidated because ${reason}`, ...args);
  1501. if (--this._remainingLogs === 0) {
  1502. /** @type {Logger} */
  1503. (this.logger).debug(
  1504. "Logging limit has been reached and no further logging will be emitted by FileSystemInfo"
  1505. );
  1506. }
  1507. }
  1508. clear() {
  1509. this._remainingLogs = this.logger ? 40 : 0;
  1510. if (this._loggedPaths !== undefined) this._loggedPaths.clear();
  1511. this._snapshotCache = new WeakMap();
  1512. this._fileTimestampsOptimization.clear();
  1513. this._fileHashesOptimization.clear();
  1514. this._fileTshsOptimization.clear();
  1515. this._contextTimestampsOptimization.clear();
  1516. this._contextHashesOptimization.clear();
  1517. this._contextTshsOptimization.clear();
  1518. this._missingExistenceOptimization.clear();
  1519. this._managedItemInfoOptimization.clear();
  1520. this._managedFilesOptimization.clear();
  1521. this._managedContextsOptimization.clear();
  1522. this._managedMissingOptimization.clear();
  1523. this._fileTimestamps.clear();
  1524. this._fileHashes.clear();
  1525. this._fileTshs.clear();
  1526. this._contextTimestamps.clear();
  1527. this._contextHashes.clear();
  1528. this._contextTshs.clear();
  1529. this._managedItems.clear();
  1530. this._pathClassificationCache.clear();
  1531. this._cachedDeprecatedFileTimestamps = undefined;
  1532. this._cachedDeprecatedContextTimestamps = undefined;
  1533. this._statCreatedSnapshots = 0;
  1534. this._statTestedSnapshotsCached = 0;
  1535. this._statTestedSnapshotsNotCached = 0;
  1536. this._statTestedChildrenCached = 0;
  1537. this._statTestedChildrenNotCached = 0;
  1538. this._statTestedEntries = 0;
  1539. }
  1540. /**
  1541. * Adds file timestamps.
  1542. * @param {ReadonlyMap<string, FileTimestamp>} map timestamps
  1543. * @param {boolean=} immutable if 'map' is immutable and FileSystemInfo can keep referencing it
  1544. * @returns {void}
  1545. */
  1546. addFileTimestamps(map, immutable) {
  1547. this._fileTimestamps.addAll(map, immutable);
  1548. this._cachedDeprecatedFileTimestamps = undefined;
  1549. }
  1550. /**
  1551. * Adds context timestamps.
  1552. * @param {ReadonlyMap<string, ContextTimestamp>} map timestamps
  1553. * @param {boolean=} immutable if 'map' is immutable and FileSystemInfo can keep referencing it
  1554. * @returns {void}
  1555. */
  1556. addContextTimestamps(map, immutable) {
  1557. this._contextTimestamps.addAll(map, immutable);
  1558. this._cachedDeprecatedContextTimestamps = undefined;
  1559. }
  1560. /**
  1561. * Gets file timestamp.
  1562. * @param {string} path file path
  1563. * @param {(err?: WebpackError | null, fileTimestamp?: FileSystemInfoEntry | "ignore" | null) => void} callback callback function
  1564. * @returns {void}
  1565. */
  1566. getFileTimestamp(path, callback) {
  1567. const cache = this._fileTimestamps.get(path);
  1568. if (cache !== undefined && !isExistenceOnly(cache)) {
  1569. return callback(
  1570. null,
  1571. /** @type {FileSystemInfoEntry | "ignore" | null} */ (cache)
  1572. );
  1573. }
  1574. this.fileTimestampQueue.add(path, callback);
  1575. }
  1576. /**
  1577. * Gets context timestamp.
  1578. * @param {string} path context path
  1579. * @param {(err?: WebpackError | null, resolvedContextTimestamp?: ResolvedContextTimestamp) => void} callback callback function
  1580. * @returns {void}
  1581. */
  1582. getContextTimestamp(path, callback) {
  1583. const cache = this._contextTimestamps.get(path);
  1584. if (cache !== undefined && !isExistenceOnly(cache)) {
  1585. if (cache === "ignore") return callback(null, "ignore");
  1586. const fullEntry =
  1587. /** @type {ContextFileSystemInfoEntry | null} */
  1588. (cache);
  1589. const resolved = getResolvedTimestamp(fullEntry);
  1590. if (resolved !== undefined) return callback(null, resolved);
  1591. return this._resolveContextTimestamp(
  1592. /** @type {ContextFileSystemInfoEntry} */
  1593. (fullEntry),
  1594. callback
  1595. );
  1596. }
  1597. this._readFreshContextTimestamp(path, callback);
  1598. }
  1599. /**
  1600. * Reads a context timestamp directly from disk, bypassing any cached
  1601. * entry. Used by `getContextTimestamp` and the snapshot validity
  1602. * checks when the cached entry is missing or is an `ExistenceOnlyTimeEntry`
  1603. * (`{}`) supplied by watchpack — both cases require a fresh read to
  1604. * obtain the `timestampHash`.
  1605. * @private
  1606. * @param {string} path context path
  1607. * @param {(err?: WebpackError | null, resolvedContextTimestamp?: ResolvedContextTimestamp) => void} callback callback function
  1608. * @returns {void}
  1609. */
  1610. _readFreshContextTimestamp(path, callback) {
  1611. this.contextTimestampQueue.add(path, (err, _entry) => {
  1612. if (err) return callback(err);
  1613. const entry = /** @type {ContextFileSystemInfoEntry | null} */ (_entry);
  1614. if (entry === null) return callback(null, null);
  1615. const resolved = getResolvedTimestamp(entry);
  1616. if (resolved !== undefined) return callback(null, resolved);
  1617. this._resolveContextTimestamp(entry, callback);
  1618. });
  1619. }
  1620. /**
  1621. * Get unresolved context timestamp. Existence-only cache entries (`{}`)
  1622. * are bypassed so the callback always receives a complete entry, "ignore"
  1623. * or null.
  1624. * @private
  1625. * @param {string} path context path
  1626. * @param {(err?: WebpackError | null, contextTimestamp?: ContextFileSystemInfoEntry | "ignore" | null) => void} callback callback function
  1627. * @returns {void}
  1628. */
  1629. _getUnresolvedContextTimestamp(path, callback) {
  1630. const cache = this._contextTimestamps.get(path);
  1631. if (
  1632. cache !== undefined &&
  1633. !isExistenceOnly(cache) &&
  1634. !contextCacheEntryLacksTimestampHash(
  1635. /** @type {ContextFileSystemInfoEntry | null | "ignore"} */ (cache)
  1636. )
  1637. ) {
  1638. return callback(
  1639. null,
  1640. /** @type {ContextFileSystemInfoEntry | "ignore" | null} */ (cache)
  1641. );
  1642. }
  1643. this.contextTimestampQueue.add(path, callback);
  1644. }
  1645. /**
  1646. * Returns file hash.
  1647. * @param {string} path file path
  1648. * @param {(err?: WebpackError | null, hash?: string | null) => void} callback callback function
  1649. * @returns {void}
  1650. */
  1651. getFileHash(path, callback) {
  1652. const cache = this._fileHashes.get(path);
  1653. if (cache !== undefined) return callback(null, cache);
  1654. this.fileHashQueue.add(path, callback);
  1655. }
  1656. /**
  1657. * Returns context hash.
  1658. * @param {string} path context path
  1659. * @param {(err?: WebpackError | null, contextHash?: string) => void} callback callback function
  1660. * @returns {void}
  1661. */
  1662. getContextHash(path, callback) {
  1663. const cache = this._contextHashes.get(path);
  1664. if (cache !== undefined) {
  1665. const resolved = getResolvedHash(cache);
  1666. if (resolved !== undefined) {
  1667. return callback(null, /** @type {string} */ (resolved));
  1668. }
  1669. return this._resolveContextHash(cache, callback);
  1670. }
  1671. this.contextHashQueue.add(path, (err, _entry) => {
  1672. if (err) return callback(err);
  1673. const entry = /** @type {ContextHash} */ (_entry);
  1674. const resolved = getResolvedHash(entry);
  1675. if (resolved !== undefined) {
  1676. return callback(null, /** @type {string} */ (resolved));
  1677. }
  1678. this._resolveContextHash(entry, callback);
  1679. });
  1680. }
  1681. /**
  1682. * Get unresolved context hash.
  1683. * @private
  1684. * @param {string} path context path
  1685. * @param {(err?: WebpackError | null, contextHash?: ContextHash | null) => void} callback callback function
  1686. * @returns {void}
  1687. */
  1688. _getUnresolvedContextHash(path, callback) {
  1689. const cache = this._contextHashes.get(path);
  1690. if (cache !== undefined) return callback(null, cache);
  1691. this.contextHashQueue.add(path, callback);
  1692. }
  1693. /**
  1694. * Returns context tsh.
  1695. * @param {string} path context path
  1696. * @param {(err?: WebpackError | null, resolvedContextTimestampAndHash?: ResolvedContextTimestampAndHash | null) => void} callback callback function
  1697. * @returns {void}
  1698. */
  1699. getContextTsh(path, callback) {
  1700. const cache = this._contextTshs.get(path);
  1701. if (cache !== undefined) {
  1702. const resolved = getResolvedTimestamp(cache);
  1703. if (resolved !== undefined) return callback(null, resolved);
  1704. return this._resolveContextTsh(cache, callback);
  1705. }
  1706. this.contextTshQueue.add(path, (err, _entry) => {
  1707. if (err) return callback(err);
  1708. const entry = /** @type {ContextTimestampAndHash} */ (_entry);
  1709. const resolved = getResolvedTimestamp(entry);
  1710. if (resolved !== undefined) return callback(null, resolved);
  1711. this._resolveContextTsh(entry, callback);
  1712. });
  1713. }
  1714. /**
  1715. * Get unresolved context tsh.
  1716. * @private
  1717. * @param {string} path context path
  1718. * @param {(err?: WebpackError | null, contextTimestampAndHash?: ContextTimestampAndHash | null) => void} callback callback function
  1719. * @returns {void}
  1720. */
  1721. _getUnresolvedContextTsh(path, callback) {
  1722. const cache = this._contextTshs.get(path);
  1723. if (cache !== undefined) return callback(null, cache);
  1724. this.contextTshQueue.add(path, callback);
  1725. }
  1726. _createBuildDependenciesResolvers() {
  1727. const resolveContext = createResolver({
  1728. resolveToContext: true,
  1729. exportsFields: [],
  1730. fileSystem: this.fs
  1731. });
  1732. const resolveCjs = createResolver({
  1733. extensions: [".js", ".json", ".node"],
  1734. conditionNames: ["require", "module-sync", "node"],
  1735. exportsFields: ["exports"],
  1736. fileSystem: this.fs
  1737. });
  1738. const resolveCjsAsChild = createResolver({
  1739. extensions: [".js", ".json", ".node"],
  1740. conditionNames: ["require", "module-sync", "node"],
  1741. exportsFields: [],
  1742. fileSystem: this.fs
  1743. });
  1744. const resolveEsm = createResolver({
  1745. extensions: [".js", ".json", ".node"],
  1746. fullySpecified: true,
  1747. conditionNames: ["import", "module-sync", "node"],
  1748. exportsFields: ["exports"],
  1749. fileSystem: this.fs
  1750. });
  1751. return { resolveContext, resolveEsm, resolveCjs, resolveCjsAsChild };
  1752. }
  1753. /**
  1754. * Resolves build dependencies.
  1755. * @param {string} context context directory
  1756. * @param {Iterable<string>} deps dependencies
  1757. * @param {(err?: Error | null, resolveBuildDependenciesResult?: ResolveBuildDependenciesResult) => void} callback callback function
  1758. * @returns {void}
  1759. */
  1760. resolveBuildDependencies(context, deps, callback) {
  1761. const { resolveContext, resolveEsm, resolveCjs, resolveCjsAsChild } =
  1762. this._createBuildDependenciesResolvers();
  1763. /** @type {Files} */
  1764. const files = new Set();
  1765. /** @type {Symlinks} */
  1766. const fileSymlinks = new Set();
  1767. /** @type {Directories} */
  1768. const directories = new Set();
  1769. /** @type {Symlinks} */
  1770. const directorySymlinks = new Set();
  1771. /** @type {Missing} */
  1772. const missing = new Set();
  1773. /** @type {ResolveDependencies["files"]} */
  1774. const resolveFiles = new Set();
  1775. /** @type {ResolveDependencies["directories"]} */
  1776. const resolveDirectories = new Set();
  1777. /** @type {ResolveDependencies["missing"]} */
  1778. const resolveMissing = new Set();
  1779. /** @type {ResolveResults} */
  1780. const resolveResults = new Map();
  1781. /** @type {Set<string>} */
  1782. const invalidResolveResults = new Set();
  1783. const resolverContext = {
  1784. fileDependencies: resolveFiles,
  1785. contextDependencies: resolveDirectories,
  1786. missingDependencies: resolveMissing
  1787. };
  1788. /**
  1789. * Expected to string.
  1790. * @param {undefined | boolean | string} expected expected result
  1791. * @returns {string} expected result
  1792. */
  1793. const expectedToString = (expected) =>
  1794. expected ? ` (expected ${expected})` : "";
  1795. /** @typedef {{ type: JobType, context: string | undefined, path: string, issuer: Job | undefined, expected: undefined | boolean | string }} Job */
  1796. /**
  1797. * Returns result.
  1798. * @param {Job} job job
  1799. * @returns {string} result
  1800. */
  1801. const jobToString = (job) => {
  1802. switch (job.type) {
  1803. case RBDT_RESOLVE_FILE:
  1804. return `resolve file ${job.path}${expectedToString(job.expected)}`;
  1805. case RBDT_RESOLVE_DIRECTORY:
  1806. return `resolve directory ${job.path}`;
  1807. case RBDT_RESOLVE_CJS_FILE:
  1808. return `resolve commonjs file ${job.path}${expectedToString(
  1809. job.expected
  1810. )}`;
  1811. case RBDT_RESOLVE_ESM_FILE:
  1812. return `resolve esm file ${job.path}${expectedToString(
  1813. job.expected
  1814. )}`;
  1815. case RBDT_DIRECTORY:
  1816. return `directory ${job.path}`;
  1817. case RBDT_FILE:
  1818. return `file ${job.path}`;
  1819. case RBDT_DIRECTORY_DEPENDENCIES:
  1820. return `directory dependencies ${job.path}`;
  1821. case RBDT_FILE_DEPENDENCIES:
  1822. return `file dependencies ${job.path}`;
  1823. }
  1824. return `unknown ${job.type} ${job.path}`;
  1825. };
  1826. /**
  1827. * Returns string value.
  1828. * @param {Job} job job
  1829. * @returns {string} string value
  1830. */
  1831. const pathToString = (job) => {
  1832. let result = ` at ${jobToString(job)}`;
  1833. /** @type {Job | undefined} */
  1834. (job) = job.issuer;
  1835. while (job !== undefined) {
  1836. result += `\n at ${jobToString(job)}`;
  1837. job = /** @type {Job} */ (job.issuer);
  1838. }
  1839. return result;
  1840. };
  1841. const logger = /** @type {Logger} */ (this.logger);
  1842. processAsyncTree(
  1843. Array.from(
  1844. deps,
  1845. (dep) =>
  1846. /** @type {Job} */ ({
  1847. type: RBDT_RESOLVE_INITIAL,
  1848. context,
  1849. path: dep,
  1850. expected: undefined,
  1851. issuer: undefined
  1852. })
  1853. ),
  1854. 20,
  1855. (job, push, callback) => {
  1856. const { type, context, path, expected } = job;
  1857. /**
  1858. * Resolves directory.
  1859. * @param {string} path path
  1860. * @returns {void}
  1861. */
  1862. const resolveDirectory = (path) => {
  1863. const key = `d\n${context}\n${path}`;
  1864. if (resolveResults.has(key)) {
  1865. return callback();
  1866. }
  1867. resolveResults.set(key, undefined);
  1868. resolveContext(
  1869. /** @type {string} */ (context),
  1870. path,
  1871. resolverContext,
  1872. (err, _, result) => {
  1873. if (err) {
  1874. if (expected === false) {
  1875. resolveResults.set(key, false);
  1876. return callback();
  1877. }
  1878. invalidResolveResults.add(key);
  1879. err.message += `\nwhile resolving '${path}' in ${context} to a directory`;
  1880. return callback(err);
  1881. }
  1882. const resultPath = /** @type {ResolveRequest} */ (result).path;
  1883. resolveResults.set(key, resultPath);
  1884. push({
  1885. type: RBDT_DIRECTORY,
  1886. context: undefined,
  1887. path: /** @type {string} */ (resultPath),
  1888. expected: undefined,
  1889. issuer: job
  1890. });
  1891. callback();
  1892. }
  1893. );
  1894. };
  1895. /**
  1896. * Processes the provided path.
  1897. * @param {string} path path
  1898. * @param {("f" | "c" | "e")=} symbol symbol
  1899. * @param {(ResolveFunctionAsync)=} resolve resolve fn
  1900. * @returns {void}
  1901. */
  1902. const resolveFile = (path, symbol, resolve) => {
  1903. const key = `${symbol}\n${context}\n${path}`;
  1904. if (resolveResults.has(key)) {
  1905. return callback();
  1906. }
  1907. resolveResults.set(key, undefined);
  1908. /** @type {ResolveFunctionAsync} */
  1909. (resolve)(
  1910. /** @type {string} */ (context),
  1911. path,
  1912. resolverContext,
  1913. (err, _, result) => {
  1914. if (typeof expected === "string") {
  1915. if (!err && result && result.path === expected) {
  1916. resolveResults.set(key, result.path);
  1917. } else {
  1918. invalidResolveResults.add(key);
  1919. logger.warn(
  1920. `Resolving '${path}' in ${context} for build dependencies doesn't lead to expected result '${expected}', but to '${
  1921. err || (result && result.path)
  1922. }' instead. Resolving dependencies are ignored for this path.\n${pathToString(
  1923. job
  1924. )}`
  1925. );
  1926. }
  1927. } else {
  1928. if (err) {
  1929. if (expected === false) {
  1930. resolveResults.set(key, false);
  1931. return callback();
  1932. }
  1933. invalidResolveResults.add(key);
  1934. err.message += `\nwhile resolving '${path}' in ${context} as file\n${pathToString(
  1935. job
  1936. )}`;
  1937. return callback(err);
  1938. }
  1939. const resultPath = /** @type {ResolveRequest} */ (result).path;
  1940. resolveResults.set(key, resultPath);
  1941. push({
  1942. type: RBDT_FILE,
  1943. context: undefined,
  1944. path: /** @type {string} */ (resultPath),
  1945. expected: undefined,
  1946. issuer: job
  1947. });
  1948. }
  1949. callback();
  1950. }
  1951. );
  1952. };
  1953. const resolvedType =
  1954. type === RBDT_RESOLVE_INITIAL
  1955. ? /[\\/]$/.test(path)
  1956. ? RBDT_RESOLVE_DIRECTORY
  1957. : RBDT_RESOLVE_FILE
  1958. : type;
  1959. switch (resolvedType) {
  1960. case RBDT_RESOLVE_FILE: {
  1961. resolveFile(
  1962. path,
  1963. "f",
  1964. /\.mjs$/.test(path) ? resolveEsm : resolveCjs
  1965. );
  1966. break;
  1967. }
  1968. case RBDT_RESOLVE_DIRECTORY: {
  1969. resolveDirectory(
  1970. type === RBDT_RESOLVE_INITIAL ? path.slice(0, -1) : path
  1971. );
  1972. break;
  1973. }
  1974. case RBDT_RESOLVE_CJS_FILE: {
  1975. resolveFile(path, "f", resolveCjs);
  1976. break;
  1977. }
  1978. case RBDT_RESOLVE_CJS_FILE_AS_CHILD: {
  1979. resolveFile(path, "c", resolveCjsAsChild);
  1980. break;
  1981. }
  1982. case RBDT_RESOLVE_ESM_FILE: {
  1983. resolveFile(path, "e", resolveEsm);
  1984. break;
  1985. }
  1986. case RBDT_FILE: {
  1987. if (files.has(path)) {
  1988. callback();
  1989. break;
  1990. }
  1991. files.add(path);
  1992. /** @type {NonNullable<InputFileSystem["realpath"]>} */
  1993. (this.fs.realpath)(path, (err, _realPath) => {
  1994. if (err) return callback(err);
  1995. const realPath = /** @type {string} */ (_realPath);
  1996. if (realPath !== path) {
  1997. fileSymlinks.add(path);
  1998. resolveFiles.add(path);
  1999. if (files.has(realPath)) return callback();
  2000. files.add(realPath);
  2001. }
  2002. push({
  2003. type: RBDT_FILE_DEPENDENCIES,
  2004. context: undefined,
  2005. path: realPath,
  2006. expected: undefined,
  2007. issuer: job
  2008. });
  2009. callback();
  2010. });
  2011. break;
  2012. }
  2013. case RBDT_DIRECTORY: {
  2014. if (directories.has(path)) {
  2015. callback();
  2016. break;
  2017. }
  2018. directories.add(path);
  2019. /** @type {NonNullable<InputFileSystem["realpath"]>} */
  2020. (this.fs.realpath)(path, (err, _realPath) => {
  2021. if (err) return callback(err);
  2022. const realPath = /** @type {string} */ (_realPath);
  2023. if (realPath !== path) {
  2024. directorySymlinks.add(path);
  2025. resolveFiles.add(path);
  2026. if (directories.has(realPath)) return callback();
  2027. directories.add(realPath);
  2028. }
  2029. push({
  2030. type: RBDT_DIRECTORY_DEPENDENCIES,
  2031. context: undefined,
  2032. path: realPath,
  2033. expected: undefined,
  2034. issuer: job
  2035. });
  2036. callback();
  2037. });
  2038. break;
  2039. }
  2040. case RBDT_FILE_DEPENDENCIES: {
  2041. // Check for known files without dependencies
  2042. if (/\.json5?$|\.yarn-integrity$|yarn\.lock$|\.ya?ml/.test(path)) {
  2043. process.nextTick(callback);
  2044. break;
  2045. }
  2046. // Check commonjs cache for the module
  2047. /** @type {NodeModule | undefined} */
  2048. const module = require.cache[path];
  2049. if (
  2050. module &&
  2051. Array.isArray(module.children) &&
  2052. // https://github.com/nodejs/node/issues/59868
  2053. // Force use `es-module-lexer` for mjs
  2054. !/\.mjs$/.test(path)
  2055. ) {
  2056. children: for (const child of module.children) {
  2057. const childPath = child.filename;
  2058. if (childPath) {
  2059. push({
  2060. type: RBDT_FILE,
  2061. context: undefined,
  2062. path: childPath,
  2063. expected: undefined,
  2064. issuer: job
  2065. });
  2066. const context = dirname(this.fs, path);
  2067. for (const modulePath of module.paths) {
  2068. if (childPath.startsWith(modulePath)) {
  2069. const subPath = childPath.slice(modulePath.length + 1);
  2070. const packageMatch = /^@[^\\/]+[\\/][^\\/]+/.exec(
  2071. subPath
  2072. );
  2073. if (packageMatch) {
  2074. push({
  2075. type: RBDT_FILE,
  2076. context: undefined,
  2077. path: `${
  2078. modulePath +
  2079. childPath[modulePath.length] +
  2080. packageMatch[0] +
  2081. childPath[modulePath.length]
  2082. }package.json`,
  2083. expected: false,
  2084. issuer: job
  2085. });
  2086. }
  2087. let request = subPath.replace(/\\/g, "/");
  2088. if (request.endsWith(".js")) {
  2089. request = request.slice(0, -3);
  2090. }
  2091. push({
  2092. type: RBDT_RESOLVE_CJS_FILE_AS_CHILD,
  2093. context,
  2094. path: request,
  2095. expected: child.filename,
  2096. issuer: job
  2097. });
  2098. continue children;
  2099. }
  2100. }
  2101. let request = relative(this.fs, context, childPath);
  2102. if (request.endsWith(".js")) request = request.slice(0, -3);
  2103. request = request.replace(/\\/g, "/");
  2104. if (!request.startsWith("../") && !isAbsolute(request)) {
  2105. request = `./${request}`;
  2106. }
  2107. push({
  2108. type: RBDT_RESOLVE_CJS_FILE,
  2109. context,
  2110. path: request,
  2111. expected: child.filename,
  2112. issuer: job
  2113. });
  2114. }
  2115. }
  2116. // On Bun the children array is empty (unpopulated), so recover the
  2117. // static require() specifiers by parsing the source; Node/Deno don't.
  2118. /* istanbul ignore next */
  2119. if (IS_BUN && module.children.length === 0) {
  2120. this.fs.readFile(path, (err, content) => {
  2121. if (err) return callback(err);
  2122. try {
  2123. const context = dirname(this.fs, path);
  2124. const source = /** @type {Buffer} */ (content).toString();
  2125. for (const dependency of collectCjsRequireSpecifiers(
  2126. source
  2127. )) {
  2128. if (dependency.startsWith("node:")) continue;
  2129. if (builtinModules.has(dependency)) continue;
  2130. push({
  2131. type: RBDT_RESOLVE_CJS_FILE,
  2132. context,
  2133. path: dependency,
  2134. // Best effort: tolerate specifiers that don't resolve.
  2135. expected: false,
  2136. issuer: job
  2137. });
  2138. }
  2139. } catch (_err) {
  2140. // Source we can't parse as CommonJS — ignore its dependencies.
  2141. }
  2142. process.nextTick(callback);
  2143. });
  2144. break;
  2145. }
  2146. } else if (supportsEsm && /\.m?js$/.test(path)) {
  2147. if (!this._warnAboutExperimentalEsmTracking) {
  2148. logger.log(
  2149. "Node.js doesn't offer a (nice) way to introspect the ESM dependency graph yet.\n" +
  2150. "Until a full solution is available webpack uses an experimental ESM tracking based on parsing.\n" +
  2151. "As best effort webpack parses the ESM files to guess dependencies. But this can lead to expensive and incorrect tracking."
  2152. );
  2153. this._warnAboutExperimentalEsmTracking = true;
  2154. }
  2155. const lexer = getEsModuleLexer();
  2156. lexer.init.then(() => {
  2157. this.fs.readFile(path, (err, content) => {
  2158. if (err) return callback(err);
  2159. try {
  2160. const context = dirname(this.fs, path);
  2161. const source = /** @type {Buffer} */ (content).toString();
  2162. const [imports] = lexer.parse(source);
  2163. /** @type {Set<string>} */
  2164. const added = new Set();
  2165. for (const imp of imports) {
  2166. try {
  2167. // import.meta
  2168. if (imp.d === -2) {
  2169. continue;
  2170. }
  2171. /** @type {string | null} */
  2172. const dependency =
  2173. imp.n ||
  2174. parseString(source.slice(imp.s, imp.e).trim());
  2175. if (!dependency) {
  2176. continue;
  2177. }
  2178. // We should not track Node.js build dependencies
  2179. if (dependency.startsWith("node:")) continue;
  2180. if (builtinModules.has(dependency)) continue;
  2181. // Avoid extra jobs for identical imports
  2182. if (added.has(dependency)) continue;
  2183. push({
  2184. type: RBDT_RESOLVE_ESM_FILE,
  2185. context,
  2186. path: dependency,
  2187. expected: imp.d > -1 ? false : undefined,
  2188. issuer: job
  2189. });
  2190. added.add(dependency);
  2191. } catch (err1) {
  2192. logger.warn(
  2193. `Parsing of ${path} for build dependencies failed at 'import(${source.slice(
  2194. imp.s,
  2195. imp.e
  2196. )})'.\n` +
  2197. "Build dependencies behind this expression are ignored and might cause incorrect cache invalidation."
  2198. );
  2199. logger.debug(pathToString(job));
  2200. logger.debug(/** @type {Error} */ (err1).stack);
  2201. }
  2202. }
  2203. } catch (err2) {
  2204. logger.warn(
  2205. `Parsing of ${path} for build dependencies failed and all dependencies of this file are ignored, which might cause incorrect cache invalidation..`
  2206. );
  2207. logger.debug(pathToString(job));
  2208. logger.debug(/** @type {Error} */ (err2).stack);
  2209. }
  2210. process.nextTick(callback);
  2211. });
  2212. }, callback);
  2213. break;
  2214. } else {
  2215. logger.log(
  2216. `Assuming ${path} has no dependencies as we were unable to assign it to any module system.`
  2217. );
  2218. logger.debug(pathToString(job));
  2219. }
  2220. process.nextTick(callback);
  2221. break;
  2222. }
  2223. case RBDT_DIRECTORY_DEPENDENCIES: {
  2224. const match =
  2225. /(^.+[\\/]node_modules[\\/](?:@[^\\/]+[\\/])?[^\\/]+)/.exec(path);
  2226. const packagePath = match ? match[1] : path;
  2227. const packageJson = join(this.fs, packagePath, "package.json");
  2228. this.fs.readFile(packageJson, (err, content) => {
  2229. if (err) {
  2230. if (err.code === "ENOENT") {
  2231. resolveMissing.add(packageJson);
  2232. const parent = dirname(this.fs, packagePath);
  2233. if (parent !== packagePath) {
  2234. push({
  2235. type: RBDT_DIRECTORY_DEPENDENCIES,
  2236. context: undefined,
  2237. path: parent,
  2238. expected: undefined,
  2239. issuer: job
  2240. });
  2241. }
  2242. callback();
  2243. return;
  2244. }
  2245. return callback(err);
  2246. }
  2247. resolveFiles.add(packageJson);
  2248. /** @type {JsonObject} */
  2249. let packageData;
  2250. try {
  2251. packageData = JSON.parse(
  2252. /** @type {Buffer} */
  2253. (content).toString("utf8")
  2254. );
  2255. } catch (parseErr) {
  2256. return callback(/** @type {Error} */ (parseErr));
  2257. }
  2258. const depsObject = packageData.dependencies;
  2259. const optionalDepsObject = packageData.optionalDependencies;
  2260. /** @type {Set<string>} */
  2261. const allDeps = new Set();
  2262. /** @type {Set<string>} */
  2263. const optionalDeps = new Set();
  2264. if (typeof depsObject === "object" && depsObject) {
  2265. for (const dep of Object.keys(depsObject)) {
  2266. allDeps.add(dep);
  2267. }
  2268. }
  2269. if (
  2270. typeof optionalDepsObject === "object" &&
  2271. optionalDepsObject
  2272. ) {
  2273. for (const dep of Object.keys(optionalDepsObject)) {
  2274. allDeps.add(dep);
  2275. optionalDeps.add(dep);
  2276. }
  2277. }
  2278. for (const dep of allDeps) {
  2279. push({
  2280. type: RBDT_RESOLVE_DIRECTORY,
  2281. context: packagePath,
  2282. path: dep,
  2283. expected: !optionalDeps.has(dep),
  2284. issuer: job
  2285. });
  2286. }
  2287. callback();
  2288. });
  2289. break;
  2290. }
  2291. }
  2292. },
  2293. (err) => {
  2294. if (err) return callback(err);
  2295. for (const l of fileSymlinks) files.delete(l);
  2296. for (const l of directorySymlinks) directories.delete(l);
  2297. for (const k of invalidResolveResults) resolveResults.delete(k);
  2298. callback(null, {
  2299. files,
  2300. directories,
  2301. missing,
  2302. resolveResults,
  2303. resolveDependencies: {
  2304. files: resolveFiles,
  2305. directories: resolveDirectories,
  2306. missing: resolveMissing
  2307. }
  2308. });
  2309. }
  2310. );
  2311. }
  2312. /**
  2313. * Checks resolve results valid.
  2314. * @param {ResolveResults} resolveResults results from resolving
  2315. * @param {(err?: Error | null, result?: boolean) => void} callback callback with true when resolveResults resolve the same way
  2316. * @returns {void}
  2317. */
  2318. checkResolveResultsValid(resolveResults, callback) {
  2319. const { resolveCjs, resolveCjsAsChild, resolveEsm, resolveContext } =
  2320. this._createBuildDependenciesResolvers();
  2321. asyncLib.eachLimit(
  2322. resolveResults,
  2323. 20,
  2324. ([key, expectedResult], callback) => {
  2325. const [type, context, path] = key.split("\n");
  2326. switch (type) {
  2327. case "d":
  2328. resolveContext(context, path, {}, (err, _, result) => {
  2329. if (expectedResult === false) {
  2330. return callback(err ? undefined : INVALID);
  2331. }
  2332. if (err) return callback(err);
  2333. const resultPath = /** @type {ResolveRequest} */ (result).path;
  2334. if (resultPath !== expectedResult) return callback(INVALID);
  2335. callback();
  2336. });
  2337. break;
  2338. case "f":
  2339. resolveCjs(context, path, {}, (err, _, result) => {
  2340. if (expectedResult === false) {
  2341. return callback(err ? undefined : INVALID);
  2342. }
  2343. if (err) return callback(err);
  2344. const resultPath = /** @type {ResolveRequest} */ (result).path;
  2345. if (resultPath !== expectedResult) return callback(INVALID);
  2346. callback();
  2347. });
  2348. break;
  2349. case "c":
  2350. resolveCjsAsChild(context, path, {}, (err, _, result) => {
  2351. if (expectedResult === false) {
  2352. return callback(err ? undefined : INVALID);
  2353. }
  2354. if (err) return callback(err);
  2355. const resultPath = /** @type {ResolveRequest} */ (result).path;
  2356. if (resultPath !== expectedResult) return callback(INVALID);
  2357. callback();
  2358. });
  2359. break;
  2360. case "e":
  2361. resolveEsm(context, path, {}, (err, _, result) => {
  2362. if (expectedResult === false) {
  2363. return callback(err ? undefined : INVALID);
  2364. }
  2365. if (err) return callback(err);
  2366. const resultPath = /** @type {ResolveRequest} */ (result).path;
  2367. if (resultPath !== expectedResult) return callback(INVALID);
  2368. callback();
  2369. });
  2370. break;
  2371. default:
  2372. callback(new Error("Unexpected type in resolve result key"));
  2373. break;
  2374. }
  2375. },
  2376. /**
  2377. * Processes the provided err.
  2378. * @param {Error | typeof INVALID=} err error or invalid flag
  2379. * @returns {void}
  2380. */
  2381. /** @type {import("neo-async").ErrorCallback<Error | typeof INVALID>} */ (
  2382. (err) => {
  2383. if (err === INVALID) {
  2384. return callback(null, false);
  2385. }
  2386. if (err) {
  2387. return callback(err);
  2388. }
  2389. return callback(null, true);
  2390. }
  2391. )
  2392. );
  2393. }
  2394. /**
  2395. * Creates a snapshot.
  2396. * @param {number | null | undefined} startTime when processing the files has started
  2397. * @param {Iterable<string> | null | undefined} files all files
  2398. * @param {Iterable<string> | null | undefined} directories all directories
  2399. * @param {Iterable<string> | null | undefined} missing all missing files or directories
  2400. * @param {SnapshotOptions | null | undefined} options options object (for future extensions)
  2401. * @param {(err: WebpackError | null, snapshot: Snapshot | null) => void} callback callback function
  2402. * @returns {void}
  2403. */
  2404. createSnapshot(startTime, files, directories, missing, options, callback) {
  2405. // collections are allocated lazily to avoid per-snapshot garbage for unused modes
  2406. /** @type {FileTimestamps | undefined} */
  2407. let fileTimestamps;
  2408. /** @type {FileHashes | undefined} */
  2409. let fileHashes;
  2410. /** @type {FileTshs | undefined} */
  2411. let fileTshs;
  2412. /** @type {ContextTimestamps | undefined} */
  2413. let contextTimestamps;
  2414. /** @type {ContextHashes | undefined} */
  2415. let contextHashes;
  2416. /** @type {ContextTshs | undefined} */
  2417. let contextTshs;
  2418. /** @type {MissingExistence | undefined} */
  2419. let missingExistence;
  2420. /** @type {ManagedItemInfo | undefined} */
  2421. let managedItemInfo;
  2422. /** @type {ManagedFiles} */
  2423. const managedFiles = new Set();
  2424. /** @type {ManagedContexts} */
  2425. const managedContexts = new Set();
  2426. /** @type {ManagedMissing} */
  2427. const managedMissing = new Set();
  2428. const snapshot = new Snapshot();
  2429. if (startTime) snapshot.setStartTime(startTime);
  2430. /** @type {Set<string>} */
  2431. const managedItems = new Set();
  2432. /** 1 = timestamp, 2 = hash, 3 = timestamp + hash */
  2433. const mode = options && options.hash ? (options.timestamp ? 3 : 2) : 1;
  2434. let jobs = 1;
  2435. const jobDone = () => {
  2436. if (--jobs === 0) {
  2437. if (fileTimestamps !== undefined && fileTimestamps.size !== 0) {
  2438. snapshot.setFileTimestamps(fileTimestamps);
  2439. }
  2440. if (fileHashes !== undefined && fileHashes.size !== 0) {
  2441. snapshot.setFileHashes(fileHashes);
  2442. }
  2443. if (fileTshs !== undefined && fileTshs.size !== 0) {
  2444. snapshot.setFileTshs(fileTshs);
  2445. }
  2446. if (contextTimestamps !== undefined && contextTimestamps.size !== 0) {
  2447. snapshot.setContextTimestamps(contextTimestamps);
  2448. }
  2449. if (contextHashes !== undefined && contextHashes.size !== 0) {
  2450. snapshot.setContextHashes(contextHashes);
  2451. }
  2452. if (contextTshs !== undefined && contextTshs.size !== 0) {
  2453. snapshot.setContextTshs(contextTshs);
  2454. }
  2455. if (missingExistence !== undefined && missingExistence.size !== 0) {
  2456. snapshot.setMissingExistence(missingExistence);
  2457. }
  2458. if (managedItemInfo !== undefined && managedItemInfo.size !== 0) {
  2459. snapshot.setManagedItemInfo(managedItemInfo);
  2460. }
  2461. this._managedFilesOptimization.optimize(snapshot, managedFiles);
  2462. if (managedFiles.size !== 0) {
  2463. snapshot.setManagedFiles(managedFiles);
  2464. }
  2465. this._managedContextsOptimization.optimize(snapshot, managedContexts);
  2466. if (managedContexts.size !== 0) {
  2467. snapshot.setManagedContexts(managedContexts);
  2468. }
  2469. this._managedMissingOptimization.optimize(snapshot, managedMissing);
  2470. if (managedMissing.size !== 0) {
  2471. snapshot.setManagedMissing(managedMissing);
  2472. }
  2473. this._snapshotCache.set(snapshot, true);
  2474. this._statCreatedSnapshots++;
  2475. callback(null, snapshot);
  2476. }
  2477. };
  2478. const jobError = () => {
  2479. if (jobs > 0) {
  2480. // large negative number instead of NaN or something else to keep jobs to stay a SMI (v8)
  2481. jobs = -100000000;
  2482. callback(null, null);
  2483. }
  2484. };
  2485. const classificationCache = this._pathClassificationCache;
  2486. /**
  2487. * Checks true when managed.
  2488. * @param {string} path path
  2489. * @param {ManagedFiles} managedSet managed set
  2490. * @returns {boolean} true when managed
  2491. */
  2492. const checkManaged = (path, managedSet) => {
  2493. // classification is a pure function of the path, so it's shared across snapshots
  2494. const cached = classificationCache.get(path);
  2495. if (cached !== undefined) {
  2496. if (cached === 0) return false;
  2497. if (cached !== 1) managedItems.add(cached);
  2498. managedSet.add(path);
  2499. return true;
  2500. }
  2501. for (const unmanagedPath of this.unmanagedPathsRegExps) {
  2502. if (unmanagedPath.test(path)) {
  2503. classificationCache.set(path, 0);
  2504. return false;
  2505. }
  2506. }
  2507. for (const unmanagedPath of this.unmanagedPathsWithSlash) {
  2508. if (path.startsWith(unmanagedPath)) {
  2509. classificationCache.set(path, 0);
  2510. return false;
  2511. }
  2512. }
  2513. for (const immutablePath of this.immutablePathsRegExps) {
  2514. if (immutablePath.test(path)) {
  2515. classificationCache.set(path, 1);
  2516. managedSet.add(path);
  2517. return true;
  2518. }
  2519. }
  2520. for (const immutablePath of this.immutablePathsWithSlash) {
  2521. if (path.startsWith(immutablePath)) {
  2522. classificationCache.set(path, 1);
  2523. managedSet.add(path);
  2524. return true;
  2525. }
  2526. }
  2527. for (const managedPath of this.managedPathsRegExps) {
  2528. const match = managedPath.exec(path);
  2529. if (match) {
  2530. const managedItem = getManagedItem(
  2531. getManagedPathFromMatch(match),
  2532. path
  2533. );
  2534. if (managedItem) {
  2535. classificationCache.set(path, managedItem);
  2536. managedItems.add(managedItem);
  2537. managedSet.add(path);
  2538. return true;
  2539. }
  2540. }
  2541. }
  2542. for (const managedPath of this.managedPathsWithSlash) {
  2543. if (path.startsWith(managedPath)) {
  2544. const managedItem = getManagedItem(managedPath, path);
  2545. if (managedItem) {
  2546. classificationCache.set(path, managedItem);
  2547. managedItems.add(managedItem);
  2548. managedSet.add(path);
  2549. return true;
  2550. }
  2551. }
  2552. }
  2553. classificationCache.set(path, 0);
  2554. return false;
  2555. };
  2556. /**
  2557. * Capture non managed.
  2558. * @param {Iterable<string>} items items
  2559. * @param {Set<string>} managedSet managed set
  2560. * @returns {Set<string>} result
  2561. */
  2562. const captureNonManaged = (items, managedSet) => {
  2563. /** @type {Set<string>} */
  2564. const capturedItems = new Set();
  2565. for (const path of items) {
  2566. if (!checkManaged(path, managedSet)) capturedItems.add(path);
  2567. }
  2568. return capturedItems;
  2569. };
  2570. /**
  2571. * Process captured files.
  2572. * @param {ManagedFiles} capturedFiles captured files
  2573. */
  2574. const processCapturedFiles = (capturedFiles) => {
  2575. if (capturedFiles.size === 0) {
  2576. return;
  2577. }
  2578. switch (mode) {
  2579. case 3: {
  2580. this._fileTshsOptimization.optimize(snapshot, capturedFiles);
  2581. if (fileTshs === undefined) fileTshs = new Map();
  2582. const fileTshsMap = fileTshs;
  2583. for (const path of capturedFiles) {
  2584. const cache = this._fileTshs.get(path);
  2585. if (cache !== undefined) {
  2586. fileTshsMap.set(path, cache);
  2587. } else {
  2588. jobs++;
  2589. this._getFileTimestampAndHash(path, (err, entry) => {
  2590. if (err) {
  2591. if (this.logger) {
  2592. this.logger.debug(
  2593. `Error snapshotting file timestamp hash combination of ${path}: ${err.stack}`
  2594. );
  2595. }
  2596. jobError();
  2597. } else {
  2598. fileTshsMap.set(
  2599. path,
  2600. /** @type {TimestampAndHash} */ (entry)
  2601. );
  2602. jobDone();
  2603. }
  2604. });
  2605. }
  2606. }
  2607. break;
  2608. }
  2609. case 2: {
  2610. this._fileHashesOptimization.optimize(snapshot, capturedFiles);
  2611. if (fileHashes === undefined) fileHashes = new Map();
  2612. const fileHashesMap = fileHashes;
  2613. for (const path of capturedFiles) {
  2614. const cache = this._fileHashes.get(path);
  2615. if (cache !== undefined) {
  2616. fileHashesMap.set(path, cache);
  2617. } else {
  2618. jobs++;
  2619. this.fileHashQueue.add(path, (err, entry) => {
  2620. if (err) {
  2621. if (this.logger) {
  2622. this.logger.debug(
  2623. `Error snapshotting file hash of ${path}: ${err.stack}`
  2624. );
  2625. }
  2626. jobError();
  2627. } else {
  2628. fileHashesMap.set(path, /** @type {string} */ (entry));
  2629. jobDone();
  2630. }
  2631. });
  2632. }
  2633. }
  2634. break;
  2635. }
  2636. case 1: {
  2637. this._fileTimestampsOptimization.optimize(snapshot, capturedFiles);
  2638. if (fileTimestamps === undefined) fileTimestamps = new Map();
  2639. const fileTimestampsMap = fileTimestamps;
  2640. for (const path of capturedFiles) {
  2641. const cache = this._fileTimestamps.get(path);
  2642. if (cache !== undefined && !isExistenceOnly(cache)) {
  2643. if (cache !== "ignore") {
  2644. fileTimestampsMap.set(
  2645. path,
  2646. /** @type {FileSystemInfoEntry | null} */ (cache)
  2647. );
  2648. }
  2649. } else {
  2650. jobs++;
  2651. this.fileTimestampQueue.add(path, (err, entry) => {
  2652. if (err) {
  2653. if (this.logger) {
  2654. this.logger.debug(
  2655. `Error snapshotting file timestamp of ${path}: ${err.stack}`
  2656. );
  2657. }
  2658. jobError();
  2659. } else {
  2660. fileTimestampsMap.set(
  2661. path,
  2662. /** @type {FileSystemInfoEntry} */
  2663. (entry)
  2664. );
  2665. jobDone();
  2666. }
  2667. });
  2668. }
  2669. }
  2670. break;
  2671. }
  2672. }
  2673. };
  2674. if (files) {
  2675. processCapturedFiles(captureNonManaged(files, managedFiles));
  2676. }
  2677. /**
  2678. * Process captured directories.
  2679. * @param {ManagedContexts} capturedDirectories captured directories
  2680. */
  2681. const processCapturedDirectories = (capturedDirectories) => {
  2682. if (capturedDirectories.size === 0) {
  2683. return;
  2684. }
  2685. switch (mode) {
  2686. case 3: {
  2687. this._contextTshsOptimization.optimize(snapshot, capturedDirectories);
  2688. if (contextTshs === undefined) contextTshs = new Map();
  2689. const contextTshsMap = contextTshs;
  2690. for (const path of capturedDirectories) {
  2691. const cache = this._contextTshs.get(path);
  2692. /** @type {ResolvedContextTimestampAndHash | null | undefined} */
  2693. let resolved;
  2694. if (
  2695. cache !== undefined &&
  2696. (resolved = getResolvedTimestamp(cache)) !== undefined
  2697. ) {
  2698. contextTshsMap.set(path, resolved);
  2699. } else {
  2700. jobs++;
  2701. /**
  2702. * Processes the provided err.
  2703. * @param {(WebpackError | null)=} err error
  2704. * @param {(ResolvedContextTimestampAndHash | null)=} entry entry
  2705. * @returns {void}
  2706. */
  2707. const callback = (err, entry) => {
  2708. if (err) {
  2709. if (this.logger) {
  2710. this.logger.debug(
  2711. `Error snapshotting context timestamp hash combination of ${path}: ${err.stack}`
  2712. );
  2713. }
  2714. jobError();
  2715. } else {
  2716. contextTshsMap.set(
  2717. path,
  2718. /** @type {ResolvedContextTimestampAndHash | null} */
  2719. (entry)
  2720. );
  2721. jobDone();
  2722. }
  2723. };
  2724. if (cache !== undefined) {
  2725. this._resolveContextTsh(cache, callback);
  2726. } else {
  2727. this.getContextTsh(path, callback);
  2728. }
  2729. }
  2730. }
  2731. break;
  2732. }
  2733. case 2: {
  2734. this._contextHashesOptimization.optimize(
  2735. snapshot,
  2736. capturedDirectories
  2737. );
  2738. if (contextHashes === undefined) contextHashes = new Map();
  2739. const contextHashesMap = contextHashes;
  2740. for (const path of capturedDirectories) {
  2741. const cache = this._contextHashes.get(path);
  2742. /** @type {undefined | null | string} */
  2743. let resolved;
  2744. if (
  2745. cache !== undefined &&
  2746. (resolved = getResolvedHash(cache)) !== undefined
  2747. ) {
  2748. contextHashesMap.set(path, resolved);
  2749. } else {
  2750. jobs++;
  2751. /**
  2752. * Processes the provided err.
  2753. * @param {(WebpackError | null)=} err err
  2754. * @param {string=} entry entry
  2755. */
  2756. const callback = (err, entry) => {
  2757. if (err) {
  2758. if (this.logger) {
  2759. this.logger.debug(
  2760. `Error snapshotting context hash of ${path}: ${err.stack}`
  2761. );
  2762. }
  2763. jobError();
  2764. } else {
  2765. contextHashesMap.set(path, /** @type {string} */ (entry));
  2766. jobDone();
  2767. }
  2768. };
  2769. if (cache !== undefined) {
  2770. this._resolveContextHash(cache, callback);
  2771. } else {
  2772. this.getContextHash(path, callback);
  2773. }
  2774. }
  2775. }
  2776. break;
  2777. }
  2778. case 1: {
  2779. this._contextTimestampsOptimization.optimize(
  2780. snapshot,
  2781. capturedDirectories
  2782. );
  2783. if (contextTimestamps === undefined) contextTimestamps = new Map();
  2784. const contextTimestampsMap = contextTimestamps;
  2785. for (const path of capturedDirectories) {
  2786. const cache = this._contextTimestamps.get(path);
  2787. if (cache === "ignore") continue;
  2788. /** @type {ContextFileSystemInfoEntry | null | undefined} */
  2789. const usableCache =
  2790. cache === undefined || isExistenceOnly(cache)
  2791. ? undefined
  2792. : /** @type {ContextFileSystemInfoEntry | null} */ (cache);
  2793. const cacheLacksHash =
  2794. contextCacheEntryLacksTimestampHash(usableCache);
  2795. /** @type {undefined | null | ResolvedContextFileSystemInfoEntry} */
  2796. let resolved;
  2797. if (
  2798. usableCache !== undefined &&
  2799. !cacheLacksHash &&
  2800. (resolved = getResolvedTimestamp(usableCache)) !== undefined
  2801. ) {
  2802. contextTimestampsMap.set(path, resolved);
  2803. } else {
  2804. jobs++;
  2805. /**
  2806. * Processes the provided err.
  2807. * @param {(WebpackError | null)=} err error
  2808. * @param {ResolvedContextTimestamp=} entry entry
  2809. * @returns {void}
  2810. */
  2811. const callback = (err, entry) => {
  2812. if (err) {
  2813. if (this.logger) {
  2814. this.logger.debug(
  2815. `Error snapshotting context timestamp of ${path}: ${err.stack}`
  2816. );
  2817. }
  2818. jobError();
  2819. } else {
  2820. contextTimestampsMap.set(
  2821. path,
  2822. /** @type {ResolvedContextFileSystemInfoEntry | null} */
  2823. (entry)
  2824. );
  2825. jobDone();
  2826. }
  2827. };
  2828. if (cacheLacksHash) {
  2829. this._readFreshContextTimestamp(path, callback);
  2830. } else if (usableCache !== undefined && usableCache !== null) {
  2831. this._resolveContextTimestamp(usableCache, callback);
  2832. } else {
  2833. // Force a fresh on-disk read so the snapshot stores a
  2834. // complete entry (with `timestampHash`).
  2835. this._readFreshContextTimestamp(path, callback);
  2836. }
  2837. }
  2838. }
  2839. break;
  2840. }
  2841. }
  2842. };
  2843. if (directories) {
  2844. processCapturedDirectories(
  2845. captureNonManaged(directories, managedContexts)
  2846. );
  2847. }
  2848. /**
  2849. * Process captured missing.
  2850. * @param {ManagedMissing} capturedMissing captured missing
  2851. */
  2852. const processCapturedMissing = (capturedMissing) => {
  2853. if (capturedMissing.size === 0) {
  2854. return;
  2855. }
  2856. this._missingExistenceOptimization.optimize(snapshot, capturedMissing);
  2857. if (missingExistence === undefined) missingExistence = new Map();
  2858. const missingExistenceMap = missingExistence;
  2859. for (const path of capturedMissing) {
  2860. const cache = this._fileTimestamps.get(path);
  2861. if (cache !== undefined && !isExistenceOnly(cache)) {
  2862. if (cache !== "ignore") {
  2863. missingExistenceMap.set(path, Boolean(cache));
  2864. }
  2865. } else {
  2866. jobs++;
  2867. this.fileTimestampQueue.add(path, (err, entry) => {
  2868. if (err) {
  2869. if (this.logger) {
  2870. this.logger.debug(
  2871. `Error snapshotting missing timestamp of ${path}: ${err.stack}`
  2872. );
  2873. }
  2874. jobError();
  2875. } else {
  2876. missingExistenceMap.set(path, Boolean(entry));
  2877. jobDone();
  2878. }
  2879. });
  2880. }
  2881. }
  2882. };
  2883. if (missing) {
  2884. processCapturedMissing(captureNonManaged(missing, managedMissing));
  2885. }
  2886. this._managedItemInfoOptimization.optimize(snapshot, managedItems);
  2887. if (managedItems.size !== 0 && managedItemInfo === undefined) {
  2888. managedItemInfo = new Map();
  2889. }
  2890. const managedItemInfoMap = managedItemInfo;
  2891. for (const path of managedItems) {
  2892. const cache = this._managedItems.get(path);
  2893. if (cache !== undefined) {
  2894. if (!cache.startsWith("*")) {
  2895. managedFiles.add(join(this.fs, path, "package.json"));
  2896. } else if (cache === "*nested") {
  2897. managedMissing.add(join(this.fs, path, "package.json"));
  2898. }
  2899. /** @type {ManagedItemInfo} */ (managedItemInfoMap).set(path, cache);
  2900. } else {
  2901. jobs++;
  2902. this.managedItemQueue.add(path, (err, entry) => {
  2903. if (err) {
  2904. if (this.logger) {
  2905. this.logger.debug(
  2906. `Error snapshotting managed item ${path}: ${err.stack}`
  2907. );
  2908. }
  2909. jobError();
  2910. } else if (entry) {
  2911. if (!entry.startsWith("*")) {
  2912. managedFiles.add(join(this.fs, path, "package.json"));
  2913. } else if (cache === "*nested") {
  2914. managedMissing.add(join(this.fs, path, "package.json"));
  2915. }
  2916. /** @type {ManagedItemInfo} */ (managedItemInfoMap).set(
  2917. path,
  2918. entry
  2919. );
  2920. jobDone();
  2921. } else {
  2922. // Fallback to normal snapshotting
  2923. /**
  2924. * Processes the provided set.
  2925. * @param {Set<string>} set set
  2926. * @param {(set: Set<string>) => void} fn fn
  2927. */
  2928. const process = (set, fn) => {
  2929. if (set.size === 0) return;
  2930. /** @type {Set<string>} */
  2931. const captured = new Set();
  2932. for (const file of set) {
  2933. if (file.startsWith(path)) captured.add(file);
  2934. }
  2935. if (captured.size > 0) fn(captured);
  2936. };
  2937. process(managedFiles, processCapturedFiles);
  2938. process(managedContexts, processCapturedDirectories);
  2939. process(managedMissing, processCapturedMissing);
  2940. jobDone();
  2941. }
  2942. });
  2943. }
  2944. }
  2945. jobDone();
  2946. }
  2947. /**
  2948. * Merges the provided values into a single result.
  2949. * @param {Snapshot} snapshot1 a snapshot
  2950. * @param {Snapshot} snapshot2 a snapshot
  2951. * @returns {Snapshot} merged snapshot
  2952. */
  2953. mergeSnapshots(snapshot1, snapshot2) {
  2954. const snapshot = new Snapshot();
  2955. if (snapshot1.hasStartTime() && snapshot2.hasStartTime()) {
  2956. snapshot.setStartTime(
  2957. Math.min(
  2958. /** @type {NonNullable<Snapshot["startTime"]>} */
  2959. (snapshot1.startTime),
  2960. /** @type {NonNullable<Snapshot["startTime"]>} */
  2961. (snapshot2.startTime)
  2962. )
  2963. );
  2964. } else if (snapshot2.hasStartTime()) {
  2965. snapshot.startTime = snapshot2.startTime;
  2966. } else if (snapshot1.hasStartTime()) {
  2967. snapshot.startTime = snapshot1.startTime;
  2968. }
  2969. if (snapshot1.hasFileTimestamps() || snapshot2.hasFileTimestamps()) {
  2970. snapshot.setFileTimestamps(
  2971. mergeMaps(snapshot1.fileTimestamps, snapshot2.fileTimestamps)
  2972. );
  2973. }
  2974. if (snapshot1.hasFileHashes() || snapshot2.hasFileHashes()) {
  2975. snapshot.setFileHashes(
  2976. mergeMaps(snapshot1.fileHashes, snapshot2.fileHashes)
  2977. );
  2978. }
  2979. if (snapshot1.hasFileTshs() || snapshot2.hasFileTshs()) {
  2980. snapshot.setFileTshs(mergeMaps(snapshot1.fileTshs, snapshot2.fileTshs));
  2981. }
  2982. if (snapshot1.hasContextTimestamps() || snapshot2.hasContextTimestamps()) {
  2983. snapshot.setContextTimestamps(
  2984. mergeMaps(snapshot1.contextTimestamps, snapshot2.contextTimestamps)
  2985. );
  2986. }
  2987. if (snapshot1.hasContextHashes() || snapshot2.hasContextHashes()) {
  2988. snapshot.setContextHashes(
  2989. mergeMaps(snapshot1.contextHashes, snapshot2.contextHashes)
  2990. );
  2991. }
  2992. if (snapshot1.hasContextTshs() || snapshot2.hasContextTshs()) {
  2993. snapshot.setContextTshs(
  2994. mergeMaps(snapshot1.contextTshs, snapshot2.contextTshs)
  2995. );
  2996. }
  2997. if (snapshot1.hasMissingExistence() || snapshot2.hasMissingExistence()) {
  2998. snapshot.setMissingExistence(
  2999. mergeMaps(snapshot1.missingExistence, snapshot2.missingExistence)
  3000. );
  3001. }
  3002. if (snapshot1.hasManagedItemInfo() || snapshot2.hasManagedItemInfo()) {
  3003. snapshot.setManagedItemInfo(
  3004. mergeMaps(snapshot1.managedItemInfo, snapshot2.managedItemInfo)
  3005. );
  3006. }
  3007. if (snapshot1.hasManagedFiles() || snapshot2.hasManagedFiles()) {
  3008. snapshot.setManagedFiles(
  3009. mergeSets(snapshot1.managedFiles, snapshot2.managedFiles)
  3010. );
  3011. }
  3012. if (snapshot1.hasManagedContexts() || snapshot2.hasManagedContexts()) {
  3013. snapshot.setManagedContexts(
  3014. mergeSets(snapshot1.managedContexts, snapshot2.managedContexts)
  3015. );
  3016. }
  3017. if (snapshot1.hasManagedMissing() || snapshot2.hasManagedMissing()) {
  3018. snapshot.setManagedMissing(
  3019. mergeSets(snapshot1.managedMissing, snapshot2.managedMissing)
  3020. );
  3021. }
  3022. if (snapshot1.hasChildren() || snapshot2.hasChildren()) {
  3023. snapshot.setChildren(mergeSets(snapshot1.children, snapshot2.children));
  3024. }
  3025. if (
  3026. this._snapshotCache.get(snapshot1) === true &&
  3027. this._snapshotCache.get(snapshot2) === true
  3028. ) {
  3029. this._snapshotCache.set(snapshot, true);
  3030. }
  3031. return snapshot;
  3032. }
  3033. /**
  3034. * Checks snapshot valid.
  3035. * @param {Snapshot} snapshot the snapshot made
  3036. * @param {CheckSnapshotValidCallback} callback callback function
  3037. * @returns {void}
  3038. */
  3039. checkSnapshotValid(snapshot, callback) {
  3040. const cachedResult = this._snapshotCache.get(snapshot);
  3041. if (cachedResult !== undefined) {
  3042. this._statTestedSnapshotsCached++;
  3043. if (typeof cachedResult === "boolean") {
  3044. callback(null, cachedResult);
  3045. } else {
  3046. cachedResult.push(callback);
  3047. }
  3048. return;
  3049. }
  3050. this._statTestedSnapshotsNotCached++;
  3051. this._checkSnapshotValidNoCache(snapshot, callback);
  3052. }
  3053. /**
  3054. * Check snapshot valid no cache.
  3055. * @private
  3056. * @param {Snapshot} snapshot the snapshot made
  3057. * @param {CheckSnapshotValidCallback} callback callback function
  3058. * @returns {void}
  3059. */
  3060. _checkSnapshotValidNoCache(snapshot, callback) {
  3061. /** @type {number | undefined} */
  3062. let startTime;
  3063. if (snapshot.hasStartTime()) {
  3064. startTime = snapshot.startTime;
  3065. }
  3066. let jobs = 1;
  3067. const jobDone = () => {
  3068. if (--jobs === 0) {
  3069. this._snapshotCache.set(snapshot, true);
  3070. callback(null, true);
  3071. }
  3072. };
  3073. const invalid = () => {
  3074. if (jobs > 0) {
  3075. // large negative number instead of NaN or something else to keep jobs to stay a SMI (v8)
  3076. jobs = -100000000;
  3077. this._snapshotCache.set(snapshot, false);
  3078. callback(null, false);
  3079. }
  3080. };
  3081. /**
  3082. * Invalid with error.
  3083. * @param {string} path path
  3084. * @param {WebpackError} err err
  3085. */
  3086. const invalidWithError = (path, err) => {
  3087. if (this._remainingLogs > 0) {
  3088. this._log(path, "error occurred: %s", err);
  3089. }
  3090. invalid();
  3091. };
  3092. /**
  3093. * Checks true, if ok.
  3094. * @param {string} path file path
  3095. * @param {string | null} current current hash
  3096. * @param {string | null} snap snapshot hash
  3097. * @returns {boolean} true, if ok
  3098. */
  3099. const checkHash = (path, current, snap) => {
  3100. if (current !== snap) {
  3101. // If hash differ it's invalid
  3102. if (this._remainingLogs > 0) {
  3103. this._log(path, "hashes differ (%s != %s)", current, snap);
  3104. }
  3105. return false;
  3106. }
  3107. return true;
  3108. };
  3109. /**
  3110. * Checks true, if ok.
  3111. * @param {string} path file path
  3112. * @param {boolean} current current entry
  3113. * @param {boolean} snap entry from snapshot
  3114. * @returns {boolean} true, if ok
  3115. */
  3116. const checkExistence = (path, current, snap) => {
  3117. if (!current !== !snap) {
  3118. // If existence of item differs
  3119. // it's invalid
  3120. if (this._remainingLogs > 0) {
  3121. this._log(
  3122. path,
  3123. current ? "it didn't exist before" : "it does no longer exist"
  3124. );
  3125. }
  3126. return false;
  3127. }
  3128. return true;
  3129. };
  3130. /**
  3131. * Checks true, if ok.
  3132. * @param {string} path file path
  3133. * @param {FileSystemInfoEntry | null} c current entry
  3134. * @param {FileSystemInfoEntry | null} s entry from snapshot
  3135. * @param {boolean} log log reason
  3136. * @returns {boolean} true, if ok
  3137. */
  3138. const checkFile = (path, c, s, log = true) => {
  3139. if (c === s) return true;
  3140. if (!checkExistence(path, Boolean(c), Boolean(s))) return false;
  3141. if (c) {
  3142. // For existing items only
  3143. if (typeof startTime === "number" && c.safeTime > startTime) {
  3144. // If a change happened after starting reading the item
  3145. // this may no longer be valid
  3146. if (log && this._remainingLogs > 0) {
  3147. this._log(
  3148. path,
  3149. "it may have changed (%d) after the start time of the snapshot (%d)",
  3150. c.safeTime,
  3151. startTime
  3152. );
  3153. }
  3154. return false;
  3155. }
  3156. const snap = /** @type {FileSystemInfoEntry} */ (s);
  3157. if (snap.timestamp !== undefined && c.timestamp !== snap.timestamp) {
  3158. // If we have a timestamp (it was a file or symlink) and it differs from current timestamp
  3159. // it's invalid
  3160. if (log && this._remainingLogs > 0) {
  3161. this._log(
  3162. path,
  3163. "timestamps differ (%d != %d)",
  3164. c.timestamp,
  3165. snap.timestamp
  3166. );
  3167. }
  3168. return false;
  3169. }
  3170. }
  3171. return true;
  3172. };
  3173. /**
  3174. * Checks true, if ok.
  3175. * @param {string} path file path
  3176. * @param {ResolvedContextFileSystemInfoEntry | null} c current entry
  3177. * @param {ResolvedContextFileSystemInfoEntry | null} s entry from snapshot
  3178. * @param {boolean} log log reason
  3179. * @returns {boolean} true, if ok
  3180. */
  3181. const checkContext = (path, c, s, log = true) => {
  3182. if (c === s) return true;
  3183. if (!checkExistence(path, Boolean(c), Boolean(s))) return false;
  3184. if (c) {
  3185. // For existing items only
  3186. if (typeof startTime === "number" && c.safeTime > startTime) {
  3187. // If a change happened after starting reading the item
  3188. // this may no longer be valid
  3189. if (log && this._remainingLogs > 0) {
  3190. this._log(
  3191. path,
  3192. "it may have changed (%d) after the start time of the snapshot (%d)",
  3193. c.safeTime,
  3194. startTime
  3195. );
  3196. }
  3197. return false;
  3198. }
  3199. const snap = /** @type {ResolvedContextFileSystemInfoEntry} */ (s);
  3200. if (
  3201. snap.timestampHash !== undefined &&
  3202. c.timestampHash !== snap.timestampHash
  3203. ) {
  3204. // If we have a timestampHash (it was a directory) and it differs from current timestampHash
  3205. // it's invalid
  3206. if (log && this._remainingLogs > 0) {
  3207. this._log(
  3208. path,
  3209. "timestamps hashes differ (%s != %s)",
  3210. c.timestampHash,
  3211. snap.timestampHash
  3212. );
  3213. }
  3214. return false;
  3215. }
  3216. }
  3217. return true;
  3218. };
  3219. if (snapshot.hasChildren()) {
  3220. /**
  3221. * Processes the provided err.
  3222. * @param {(WebpackError | null)=} err err
  3223. * @param {boolean=} result result
  3224. * @returns {void}
  3225. */
  3226. const childCallback = (err, result) => {
  3227. if (err || !result) return invalid();
  3228. jobDone();
  3229. };
  3230. for (const child of /** @type {Children} */ (snapshot.children)) {
  3231. const cache = this._snapshotCache.get(child);
  3232. if (cache !== undefined) {
  3233. this._statTestedChildrenCached++;
  3234. /* istanbul ignore else */
  3235. if (typeof cache === "boolean") {
  3236. if (cache === false) {
  3237. invalid();
  3238. return;
  3239. }
  3240. } else {
  3241. jobs++;
  3242. cache.push(childCallback);
  3243. }
  3244. } else {
  3245. this._statTestedChildrenNotCached++;
  3246. jobs++;
  3247. this._checkSnapshotValidNoCache(child, childCallback);
  3248. }
  3249. }
  3250. }
  3251. if (snapshot.hasFileTimestamps()) {
  3252. const fileTimestamps =
  3253. /** @type {FileTimestamps} */
  3254. (snapshot.fileTimestamps);
  3255. this._statTestedEntries += fileTimestamps.size;
  3256. for (const [path, ts] of fileTimestamps) {
  3257. const cache = this._fileTimestamps.get(path);
  3258. if (cache !== undefined && !isExistenceOnly(cache)) {
  3259. if (
  3260. cache !== "ignore" &&
  3261. !checkFile(
  3262. path,
  3263. /** @type {FileSystemInfoEntry | null} */ (cache),
  3264. ts
  3265. )
  3266. ) {
  3267. invalid();
  3268. return;
  3269. }
  3270. } else {
  3271. jobs++;
  3272. this.fileTimestampQueue.add(path, (err, entry) => {
  3273. if (err) return invalidWithError(path, err);
  3274. if (
  3275. !checkFile(
  3276. path,
  3277. /** @type {FileSystemInfoEntry | null} */ (entry),
  3278. ts
  3279. )
  3280. ) {
  3281. invalid();
  3282. } else {
  3283. jobDone();
  3284. }
  3285. });
  3286. }
  3287. }
  3288. }
  3289. /**
  3290. * Process file hash snapshot.
  3291. * @param {string} path file path
  3292. * @param {string | null} hash hash
  3293. */
  3294. const processFileHashSnapshot = (path, hash) => {
  3295. const cache = this._fileHashes.get(path);
  3296. if (cache !== undefined) {
  3297. if (cache !== "ignore" && !checkHash(path, cache, hash)) {
  3298. invalid();
  3299. }
  3300. } else {
  3301. jobs++;
  3302. this.fileHashQueue.add(path, (err, entry) => {
  3303. if (err) return invalidWithError(path, err);
  3304. if (!checkHash(path, /** @type {string} */ (entry), hash)) {
  3305. invalid();
  3306. } else {
  3307. jobDone();
  3308. }
  3309. });
  3310. }
  3311. };
  3312. if (snapshot.hasFileHashes()) {
  3313. const fileHashes = /** @type {FileHashes} */ (snapshot.fileHashes);
  3314. this._statTestedEntries += fileHashes.size;
  3315. for (const [path, hash] of fileHashes) {
  3316. processFileHashSnapshot(path, hash);
  3317. }
  3318. }
  3319. if (snapshot.hasFileTshs()) {
  3320. const fileTshs = /** @type {FileTshs} */ (snapshot.fileTshs);
  3321. this._statTestedEntries += fileTshs.size;
  3322. for (const [path, tsh] of fileTshs) {
  3323. if (typeof tsh === "string") {
  3324. processFileHashSnapshot(path, tsh);
  3325. } else {
  3326. const cache = this._fileTimestamps.get(path);
  3327. if (cache !== undefined && !isExistenceOnly(cache)) {
  3328. if (
  3329. cache === "ignore" ||
  3330. !checkFile(
  3331. path,
  3332. /** @type {FileSystemInfoEntry | null} */ (cache),
  3333. tsh,
  3334. false
  3335. )
  3336. ) {
  3337. processFileHashSnapshot(path, tsh && tsh.hash);
  3338. }
  3339. } else {
  3340. jobs++;
  3341. this.fileTimestampQueue.add(path, (err, entry) => {
  3342. if (err) return invalidWithError(path, err);
  3343. if (
  3344. !checkFile(
  3345. path,
  3346. /** @type {FileSystemInfoEntry | null} */
  3347. (entry),
  3348. tsh,
  3349. false
  3350. )
  3351. ) {
  3352. processFileHashSnapshot(path, tsh && tsh.hash);
  3353. }
  3354. jobDone();
  3355. });
  3356. }
  3357. }
  3358. }
  3359. }
  3360. if (snapshot.hasContextTimestamps()) {
  3361. const contextTimestamps =
  3362. /** @type {ContextTimestamps} */
  3363. (snapshot.contextTimestamps);
  3364. this._statTestedEntries += contextTimestamps.size;
  3365. for (const [path, ts] of contextTimestamps) {
  3366. const cache = this._contextTimestamps.get(path);
  3367. if (cache === "ignore") continue;
  3368. // Treat existence-only entries (`{}` from watchpack) as a cache
  3369. // miss — they carry no time info, so we cannot compare them to
  3370. // the snapshot.
  3371. /** @type {ContextFileSystemInfoEntry | null | undefined} */
  3372. const usableCache =
  3373. cache === undefined || isExistenceOnly(cache)
  3374. ? undefined
  3375. : /** @type {ContextFileSystemInfoEntry | null} */ (cache);
  3376. const cacheLacksHash =
  3377. contextCacheEntryLacksTimestampHash(usableCache) &&
  3378. ts !== null &&
  3379. ts.timestampHash !== undefined;
  3380. /** @type {undefined | null | ResolvedContextFileSystemInfoEntry} */
  3381. let resolved;
  3382. if (
  3383. usableCache !== undefined &&
  3384. !cacheLacksHash &&
  3385. (resolved = getResolvedTimestamp(usableCache)) !== undefined
  3386. ) {
  3387. if (!checkContext(path, resolved, ts)) {
  3388. invalid();
  3389. return;
  3390. }
  3391. } else {
  3392. jobs++;
  3393. /**
  3394. * Processes the provided err.
  3395. * @param {(WebpackError | null)=} err error
  3396. * @param {ResolvedContextTimestamp=} entry entry
  3397. * @returns {void}
  3398. */
  3399. const callback = (err, entry) => {
  3400. if (err) return invalidWithError(path, err);
  3401. if (
  3402. !checkContext(
  3403. path,
  3404. /** @type {ResolvedContextFileSystemInfoEntry | null} */
  3405. (entry),
  3406. ts
  3407. )
  3408. ) {
  3409. invalid();
  3410. } else {
  3411. jobDone();
  3412. }
  3413. };
  3414. if (cacheLacksHash) {
  3415. this._readFreshContextTimestamp(path, callback);
  3416. } else if (usableCache !== undefined && usableCache !== null) {
  3417. this._resolveContextTimestamp(usableCache, callback);
  3418. } else {
  3419. this.getContextTimestamp(path, callback);
  3420. }
  3421. }
  3422. }
  3423. }
  3424. /**
  3425. * Process context hash snapshot.
  3426. * @param {string} path path
  3427. * @param {string | null} hash hash
  3428. */
  3429. const processContextHashSnapshot = (path, hash) => {
  3430. const cache = this._contextHashes.get(path);
  3431. /** @type {undefined | null | string} */
  3432. let resolved;
  3433. if (
  3434. cache !== undefined &&
  3435. (resolved = getResolvedHash(cache)) !== undefined
  3436. ) {
  3437. if (!checkHash(path, resolved, hash)) {
  3438. invalid();
  3439. }
  3440. } else {
  3441. jobs++;
  3442. /**
  3443. * Processes the provided err.
  3444. * @param {(WebpackError | null)=} err err
  3445. * @param {string=} entry entry
  3446. * @returns {void}
  3447. */
  3448. const callback = (err, entry) => {
  3449. if (err) return invalidWithError(path, err);
  3450. if (!checkHash(path, /** @type {string} */ (entry), hash)) {
  3451. invalid();
  3452. } else {
  3453. jobDone();
  3454. }
  3455. };
  3456. if (cache !== undefined) {
  3457. this._resolveContextHash(cache, callback);
  3458. } else {
  3459. this.getContextHash(path, callback);
  3460. }
  3461. }
  3462. };
  3463. if (snapshot.hasContextHashes()) {
  3464. const contextHashes =
  3465. /** @type {ContextHashes} */
  3466. (snapshot.contextHashes);
  3467. this._statTestedEntries += contextHashes.size;
  3468. for (const [path, hash] of contextHashes) {
  3469. processContextHashSnapshot(path, hash);
  3470. }
  3471. }
  3472. if (snapshot.hasContextTshs()) {
  3473. const contextTshs = /** @type {ContextTshs} */ (snapshot.contextTshs);
  3474. this._statTestedEntries += contextTshs.size;
  3475. for (const [path, tsh] of contextTshs) {
  3476. if (typeof tsh === "string") {
  3477. processContextHashSnapshot(path, tsh);
  3478. } else {
  3479. const cache = this._contextTimestamps.get(path);
  3480. if (cache === "ignore") continue;
  3481. // See the matching block in `hasContextTimestamps` above.
  3482. /** @type {ContextFileSystemInfoEntry | null | undefined} */
  3483. const usableCache =
  3484. cache === undefined || isExistenceOnly(cache)
  3485. ? undefined
  3486. : /** @type {ContextFileSystemInfoEntry | null} */ (cache);
  3487. const cacheLacksHash =
  3488. contextCacheEntryLacksTimestampHash(usableCache) &&
  3489. tsh !== null &&
  3490. tsh.timestampHash !== undefined;
  3491. /** @type {undefined | null | ResolvedContextFileSystemInfoEntry} */
  3492. let resolved;
  3493. if (
  3494. usableCache !== undefined &&
  3495. !cacheLacksHash &&
  3496. (resolved = getResolvedTimestamp(usableCache)) !== undefined
  3497. ) {
  3498. if (!checkContext(path, resolved, tsh, false)) {
  3499. processContextHashSnapshot(path, tsh && tsh.hash);
  3500. }
  3501. } else {
  3502. jobs++;
  3503. /**
  3504. * Processes the provided err.
  3505. * @param {(WebpackError | null)=} err error
  3506. * @param {ResolvedContextTimestamp=} entry entry
  3507. * @returns {void}
  3508. */
  3509. const callback = (err, entry) => {
  3510. if (err) return invalidWithError(path, err);
  3511. if (
  3512. !checkContext(
  3513. path,
  3514. /** @type {ResolvedContextFileSystemInfoEntry | null} */
  3515. (entry),
  3516. tsh,
  3517. false
  3518. )
  3519. ) {
  3520. processContextHashSnapshot(path, tsh && tsh.hash);
  3521. }
  3522. jobDone();
  3523. };
  3524. if (cacheLacksHash) {
  3525. this._readFreshContextTimestamp(path, callback);
  3526. } else if (usableCache !== undefined && usableCache !== null) {
  3527. this._resolveContextTimestamp(usableCache, callback);
  3528. } else {
  3529. this.getContextTimestamp(path, callback);
  3530. }
  3531. }
  3532. }
  3533. }
  3534. }
  3535. if (snapshot.hasMissingExistence()) {
  3536. const missingExistence =
  3537. /** @type {MissingExistence} */
  3538. (snapshot.missingExistence);
  3539. this._statTestedEntries += missingExistence.size;
  3540. for (const [path, existence] of missingExistence) {
  3541. const cache = this._fileTimestamps.get(path);
  3542. if (cache !== undefined && !isExistenceOnly(cache)) {
  3543. if (
  3544. cache !== "ignore" &&
  3545. !checkExistence(path, Boolean(cache), Boolean(existence))
  3546. ) {
  3547. invalid();
  3548. return;
  3549. }
  3550. } else {
  3551. jobs++;
  3552. this.fileTimestampQueue.add(path, (err, entry) => {
  3553. if (err) return invalidWithError(path, err);
  3554. if (!checkExistence(path, Boolean(entry), Boolean(existence))) {
  3555. invalid();
  3556. } else {
  3557. jobDone();
  3558. }
  3559. });
  3560. }
  3561. }
  3562. }
  3563. if (snapshot.hasManagedItemInfo()) {
  3564. const managedItemInfo =
  3565. /** @type {ManagedItemInfo} */
  3566. (snapshot.managedItemInfo);
  3567. this._statTestedEntries += managedItemInfo.size;
  3568. for (const [path, info] of managedItemInfo) {
  3569. const cache = this._managedItems.get(path);
  3570. if (cache !== undefined) {
  3571. if (!checkHash(path, cache, info)) {
  3572. invalid();
  3573. return;
  3574. }
  3575. } else {
  3576. jobs++;
  3577. this.managedItemQueue.add(path, (err, entry) => {
  3578. if (err) return invalidWithError(path, err);
  3579. if (!checkHash(path, /** @type {string} */ (entry), info)) {
  3580. invalid();
  3581. } else {
  3582. jobDone();
  3583. }
  3584. });
  3585. }
  3586. }
  3587. }
  3588. jobDone();
  3589. // if there was an async action
  3590. // try to join multiple concurrent request for this snapshot
  3591. if (jobs > 0) {
  3592. const callbacks = [callback];
  3593. callback = (err, result) => {
  3594. for (const callback of callbacks) callback(err, result);
  3595. };
  3596. this._snapshotCache.set(snapshot, callbacks);
  3597. }
  3598. }
  3599. /**
  3600. * @private
  3601. * @type {Processor<string, FileSystemInfoEntry>}
  3602. */
  3603. _readFileTimestamp(path, callback) {
  3604. this.fs.stat(path, (err, _stat) => {
  3605. if (err) {
  3606. if (err.code === "ENOENT") {
  3607. this._fileTimestamps.set(path, null);
  3608. this._cachedDeprecatedFileTimestamps = undefined;
  3609. return callback(null, null);
  3610. }
  3611. return callback(/** @type {WebpackError} */ (err));
  3612. }
  3613. const stat = /** @type {IStats} */ (_stat);
  3614. /** @type {FileSystemInfoEntry} */
  3615. let ts;
  3616. if (stat.isDirectory()) {
  3617. ts = {
  3618. safeTime: 0,
  3619. timestamp: undefined
  3620. };
  3621. } else {
  3622. const mtime = Number(stat.mtime);
  3623. if (mtime) applyMtime(mtime);
  3624. ts = {
  3625. safeTime: mtime ? mtime + FS_ACCURACY : Infinity,
  3626. timestamp: mtime
  3627. };
  3628. }
  3629. this._fileTimestamps.set(path, ts);
  3630. this._cachedDeprecatedFileTimestamps = undefined;
  3631. callback(null, ts);
  3632. });
  3633. }
  3634. /**
  3635. * @private
  3636. * @type {Processor<string, string>}
  3637. */
  3638. _readFileHash(path, callback) {
  3639. this.fs.readFile(path, (err, content) => {
  3640. if (err) {
  3641. if (err.code === "EISDIR") {
  3642. this._fileHashes.set(path, "directory");
  3643. return callback(null, "directory");
  3644. }
  3645. if (err.code === "ENOENT") {
  3646. this._fileHashes.set(path, null);
  3647. return callback(null, null);
  3648. }
  3649. if (err.code === "ERR_FS_FILE_TOO_LARGE") {
  3650. /** @type {Logger} */
  3651. (this.logger).warn(`Ignoring ${path} for hashing as it's very large`);
  3652. this._fileHashes.set(path, "too large");
  3653. return callback(null, "too large");
  3654. }
  3655. return callback(/** @type {WebpackError} */ (err));
  3656. }
  3657. const hash = createHash(this._hashFunction);
  3658. hash.update(/** @type {string | Buffer} */ (content));
  3659. const digest = hash.digest("hex");
  3660. this._fileHashes.set(path, digest);
  3661. callback(null, digest);
  3662. });
  3663. }
  3664. /**
  3665. * Get file timestamp and hash.
  3666. * @private
  3667. * @param {string} path path
  3668. * @param {(err: WebpackError | null, timestampAndHash?: TimestampAndHash | string) => void} callback callback
  3669. */
  3670. _getFileTimestampAndHash(path, callback) {
  3671. /**
  3672. * Continue with hash.
  3673. * @param {string} hash hash
  3674. * @returns {void}
  3675. */
  3676. const continueWithHash = (hash) => {
  3677. const cache = this._fileTimestamps.get(path);
  3678. if (cache !== undefined) {
  3679. if (cache !== "ignore") {
  3680. /** @type {TimestampAndHash} */
  3681. const result = {
  3682. .../** @type {FileSystemInfoEntry} */ (cache),
  3683. hash
  3684. };
  3685. this._fileTshs.set(path, result);
  3686. return callback(null, result);
  3687. }
  3688. this._fileTshs.set(path, hash);
  3689. return callback(null, hash);
  3690. }
  3691. this.fileTimestampQueue.add(path, (err, entry) => {
  3692. if (err) {
  3693. return callback(err);
  3694. }
  3695. /** @type {TimestampAndHash} */
  3696. const result = {
  3697. .../** @type {FileSystemInfoEntry} */ (entry),
  3698. hash
  3699. };
  3700. this._fileTshs.set(path, result);
  3701. return callback(null, result);
  3702. });
  3703. };
  3704. const cache = this._fileHashes.get(path);
  3705. if (cache !== undefined) {
  3706. continueWithHash(/** @type {string} */ (cache));
  3707. } else {
  3708. this.fileHashQueue.add(path, (err, entry) => {
  3709. if (err) {
  3710. return callback(err);
  3711. }
  3712. continueWithHash(/** @type {string} */ (entry));
  3713. });
  3714. }
  3715. }
  3716. /**
  3717. * Processes the provided object.
  3718. * @private
  3719. * @template T
  3720. * @template ItemType
  3721. * @param {object} options options
  3722. * @param {string} options.path path
  3723. * @param {(value: string) => ItemType} options.fromImmutablePath called when context item is an immutable path
  3724. * @param {(value: string) => ItemType} options.fromManagedItem called when context item is a managed path
  3725. * @param {(value: string, result: string, callback: (err?: WebpackError | null, itemType?: ItemType) => void) => void} options.fromSymlink called when context item is a symlink
  3726. * @param {(value: string, stats: IStats, callback: (err?: WebpackError | null, itemType?: ItemType | null) => void) => void} options.fromFile called when context item is a file
  3727. * @param {(value: string, stats: IStats, callback: (err?: WebpackError | null, itemType?: ItemType) => void) => void} options.fromDirectory called when context item is a directory
  3728. * @param {(arr: string[], arr1: ItemType[]) => T} options.reduce called from all context items
  3729. * @param {(err?: Error | null, result?: T | null) => void} callback callback
  3730. */
  3731. _readContext(
  3732. {
  3733. path,
  3734. fromImmutablePath,
  3735. fromManagedItem,
  3736. fromSymlink,
  3737. fromFile,
  3738. fromDirectory,
  3739. reduce
  3740. },
  3741. callback
  3742. ) {
  3743. this.fs.readdir(path, (err, _files) => {
  3744. if (err) {
  3745. if (err.code === "ENOENT") {
  3746. return callback(null, null);
  3747. }
  3748. return callback(err);
  3749. }
  3750. const files = /** @type {string[]} */ (_files)
  3751. .map((file) => file.normalize("NFC"))
  3752. .filter((file) => !/^\./.test(file))
  3753. .sort();
  3754. asyncLib.map(
  3755. files,
  3756. (file, callback) => {
  3757. const child = join(this.fs, path, file);
  3758. for (const immutablePath of this.immutablePathsRegExps) {
  3759. if (immutablePath.test(path)) {
  3760. // ignore any immutable path for timestamping
  3761. return callback(null, fromImmutablePath(path));
  3762. }
  3763. }
  3764. for (const immutablePath of this.immutablePathsWithSlash) {
  3765. if (path.startsWith(immutablePath)) {
  3766. // ignore any immutable path for timestamping
  3767. return callback(null, fromImmutablePath(path));
  3768. }
  3769. }
  3770. for (const managedPath of this.managedPathsRegExps) {
  3771. const match = managedPath.exec(path);
  3772. if (match) {
  3773. const managedItem = getManagedItem(
  3774. getManagedPathFromMatch(match),
  3775. path
  3776. );
  3777. if (managedItem) {
  3778. // construct timestampHash from managed info
  3779. return this.managedItemQueue.add(managedItem, (err, info) => {
  3780. if (err) return callback(err);
  3781. return callback(
  3782. null,
  3783. fromManagedItem(/** @type {string} */ (info))
  3784. );
  3785. });
  3786. }
  3787. }
  3788. }
  3789. for (const managedPath of this.managedPathsWithSlash) {
  3790. if (path.startsWith(managedPath)) {
  3791. const managedItem = getManagedItem(managedPath, child);
  3792. if (managedItem) {
  3793. // construct timestampHash from managed info
  3794. return this.managedItemQueue.add(managedItem, (err, info) => {
  3795. if (err) return callback(err);
  3796. return callback(
  3797. null,
  3798. fromManagedItem(/** @type {string} */ (info))
  3799. );
  3800. });
  3801. }
  3802. }
  3803. }
  3804. lstatReadlinkAbsolute(this.fs, child, (err, _stat) => {
  3805. if (err) return callback(err);
  3806. const stat = /** @type {IStats | string} */ (_stat);
  3807. if (typeof stat === "string") {
  3808. return fromSymlink(child, stat, callback);
  3809. }
  3810. if (stat.isFile()) {
  3811. return fromFile(child, stat, callback);
  3812. }
  3813. if (stat.isDirectory()) {
  3814. return fromDirectory(child, stat, callback);
  3815. }
  3816. callback(null, null);
  3817. });
  3818. },
  3819. (err, results) => {
  3820. if (err) return callback(err);
  3821. const result = reduce(files, /** @type {ItemType[]} */ (results));
  3822. callback(null, result);
  3823. }
  3824. );
  3825. });
  3826. }
  3827. /**
  3828. * @private
  3829. * @type {Processor<string, ContextFileSystemInfoEntry>}
  3830. */
  3831. _readContextTimestamp(path, callback) {
  3832. this._readContext(
  3833. {
  3834. path,
  3835. fromImmutablePath: () =>
  3836. /** @type {ContextFileSystemInfoEntry | FileSystemInfoEntry | "ignore" | null} */
  3837. (null),
  3838. fromManagedItem: (info) => ({
  3839. safeTime: 0,
  3840. timestampHash: info
  3841. }),
  3842. fromSymlink: (file, target, callback) => {
  3843. callback(
  3844. null,
  3845. /** @type {ContextFileSystemInfoEntry} */
  3846. ({
  3847. timestampHash: target,
  3848. symlinks: new Set([target])
  3849. })
  3850. );
  3851. },
  3852. fromFile: (file, stat, callback) => {
  3853. // Prefer the cached value over our new stat to report consistent results
  3854. const cache = this._fileTimestamps.get(file);
  3855. if (cache !== undefined && !isExistenceOnly(cache)) {
  3856. return callback(
  3857. null,
  3858. cache === "ignore"
  3859. ? null
  3860. : /** @type {FileSystemInfoEntry | null} */ (cache)
  3861. );
  3862. }
  3863. const mtime = Number(stat.mtime);
  3864. if (mtime) applyMtime(mtime);
  3865. /** @type {FileSystemInfoEntry} */
  3866. const ts = {
  3867. safeTime: mtime ? mtime + FS_ACCURACY : Infinity,
  3868. timestamp: mtime
  3869. };
  3870. this._fileTimestamps.set(file, ts);
  3871. this._cachedDeprecatedFileTimestamps = undefined;
  3872. callback(null, ts);
  3873. },
  3874. fromDirectory: (directory, stat, callback) => {
  3875. this.contextTimestampQueue.increaseParallelism();
  3876. this._getUnresolvedContextTimestamp(directory, (err, tsEntry) => {
  3877. this.contextTimestampQueue.decreaseParallelism();
  3878. callback(err, tsEntry);
  3879. });
  3880. },
  3881. reduce: (files, tsEntries) => {
  3882. /** @type {undefined | Symlinks} */
  3883. let symlinks;
  3884. const hash = createHash(this._hashFunction);
  3885. for (const file of files) hash.update(file);
  3886. let safeTime = 0;
  3887. for (const _e of tsEntries) {
  3888. if (!_e) {
  3889. hash.update("n");
  3890. continue;
  3891. }
  3892. const entry =
  3893. /** @type {FileSystemInfoEntry | ContextFileSystemInfoEntry} */
  3894. (_e);
  3895. if (/** @type {FileSystemInfoEntry} */ (entry).timestamp) {
  3896. hash.update("f");
  3897. hash.update(
  3898. `${/** @type {FileSystemInfoEntry} */ (entry).timestamp}`
  3899. );
  3900. } else if (
  3901. /** @type {ContextFileSystemInfoEntry} */ (entry).timestampHash
  3902. ) {
  3903. hash.update("d");
  3904. hash.update(
  3905. `${/** @type {ContextFileSystemInfoEntry} */ (entry).timestampHash}`
  3906. );
  3907. }
  3908. if (
  3909. /** @type {ContextFileSystemInfoEntry} */
  3910. (entry).symlinks !== undefined
  3911. ) {
  3912. if (symlinks === undefined) symlinks = new Set();
  3913. addAll(
  3914. /** @type {ContextFileSystemInfoEntry} */ (entry).symlinks,
  3915. symlinks
  3916. );
  3917. }
  3918. if (entry.safeTime) {
  3919. safeTime = Math.max(safeTime, entry.safeTime);
  3920. }
  3921. }
  3922. const digest = hash.digest("hex");
  3923. /** @type {ContextFileSystemInfoEntry} */
  3924. const result = {
  3925. safeTime,
  3926. timestampHash: digest
  3927. };
  3928. if (symlinks) result.symlinks = symlinks;
  3929. return result;
  3930. }
  3931. },
  3932. (err, result) => {
  3933. if (err) return callback(/** @type {WebpackError} */ (err));
  3934. this._contextTimestamps.set(path, result);
  3935. this._cachedDeprecatedContextTimestamps = undefined;
  3936. callback(null, result);
  3937. }
  3938. );
  3939. }
  3940. /**
  3941. * Resolve context timestamp.
  3942. * @private
  3943. * @param {ContextFileSystemInfoEntry} entry entry
  3944. * @param {(err?: WebpackError | null, resolvedContextTimestamp?: ResolvedContextTimestamp) => void} callback callback
  3945. * @returns {void}
  3946. */
  3947. _resolveContextTimestamp(entry, callback) {
  3948. /** @type {string[]} */
  3949. const hashes = [];
  3950. let safeTime = 0;
  3951. // Skip already-visited symlink targets so cyclic pnpm/peer-variant graphs terminate (#21084).
  3952. const seen = new Set(entry.symlinks);
  3953. processAsyncTree(
  3954. /** @type {NonNullable<ContextHash["symlinks"]>} */ (entry.symlinks),
  3955. 10,
  3956. (target, push, callback) => {
  3957. this._getUnresolvedContextTimestamp(target, (err, entry) => {
  3958. if (err) return callback(err);
  3959. if (entry && entry !== "ignore") {
  3960. hashes.push(/** @type {string} */ (entry.timestampHash));
  3961. if (entry.safeTime) {
  3962. safeTime = Math.max(safeTime, entry.safeTime);
  3963. }
  3964. if (entry.symlinks !== undefined) {
  3965. for (const target of entry.symlinks) {
  3966. if (!seen.has(target)) {
  3967. seen.add(target);
  3968. push(target);
  3969. }
  3970. }
  3971. }
  3972. }
  3973. callback();
  3974. });
  3975. },
  3976. (err) => {
  3977. if (err) return callback(/** @type {WebpackError} */ (err));
  3978. const hash = createHash(this._hashFunction);
  3979. hash.update(/** @type {string} */ (entry.timestampHash));
  3980. if (entry.safeTime) {
  3981. safeTime = Math.max(safeTime, entry.safeTime);
  3982. }
  3983. hashes.sort();
  3984. for (const h of hashes) {
  3985. hash.update(h);
  3986. }
  3987. callback(
  3988. null,
  3989. (entry.resolved = {
  3990. safeTime,
  3991. timestampHash: hash.digest("hex")
  3992. })
  3993. );
  3994. }
  3995. );
  3996. }
  3997. /**
  3998. * @private
  3999. * @type {Processor<string, ContextHash>}
  4000. */
  4001. _readContextHash(path, callback) {
  4002. this._readContext(
  4003. {
  4004. path,
  4005. fromImmutablePath: () => /** @type {ContextHash | ""} */ (""),
  4006. fromManagedItem: (info) => info || "",
  4007. fromSymlink: (file, target, callback) => {
  4008. callback(
  4009. null,
  4010. /** @type {ContextHash} */
  4011. ({
  4012. hash: target,
  4013. symlinks: new Set([target])
  4014. })
  4015. );
  4016. },
  4017. fromFile: (file, stat, callback) =>
  4018. this.getFileHash(file, (err, hash) => {
  4019. callback(err, hash || "");
  4020. }),
  4021. fromDirectory: (directory, stat, callback) => {
  4022. this.contextHashQueue.increaseParallelism();
  4023. this._getUnresolvedContextHash(directory, (err, hash) => {
  4024. this.contextHashQueue.decreaseParallelism();
  4025. callback(err, hash || "");
  4026. });
  4027. },
  4028. /**
  4029. * Returns reduced hash.
  4030. * @param {string[]} files files
  4031. * @param {(string | ContextHash | null)[]} fileHashes hashes
  4032. * @returns {ContextHash} reduced hash
  4033. */
  4034. reduce: (files, fileHashes) => {
  4035. /** @type {undefined | Symlinks} */
  4036. let symlinks;
  4037. const hash = createHash(this._hashFunction);
  4038. for (const file of files) hash.update(file);
  4039. for (const entry of fileHashes) {
  4040. // null for unsupported entry kinds (FIFO, socket); skip like the tsh reduce
  4041. if (!entry) {
  4042. continue;
  4043. } else if (typeof entry === "string") {
  4044. hash.update(entry);
  4045. } else {
  4046. if (entry.hash) hash.update(entry.hash);
  4047. if (entry.symlinks) {
  4048. if (symlinks === undefined) symlinks = new Set();
  4049. addAll(entry.symlinks, symlinks);
  4050. }
  4051. }
  4052. }
  4053. /** @type {ContextHash} */
  4054. const result = {
  4055. hash: hash.digest("hex")
  4056. };
  4057. if (symlinks) result.symlinks = symlinks;
  4058. return result;
  4059. }
  4060. },
  4061. (err, _result) => {
  4062. if (err) return callback(/** @type {WebpackError} */ (err));
  4063. const result = /** @type {ContextHash} */ (_result);
  4064. this._contextHashes.set(path, result);
  4065. return callback(null, result);
  4066. }
  4067. );
  4068. }
  4069. /**
  4070. * Resolve context hash.
  4071. * @private
  4072. * @param {ContextHash} entry context hash
  4073. * @param {(err: WebpackError | null, contextHash?: string) => void} callback callback
  4074. * @returns {void}
  4075. */
  4076. _resolveContextHash(entry, callback) {
  4077. /** @type {string[]} */
  4078. const hashes = [];
  4079. // Skip already-visited symlink targets so cyclic pnpm/peer-variant graphs terminate (#21084).
  4080. const seen = new Set(entry.symlinks);
  4081. processAsyncTree(
  4082. /** @type {NonNullable<ContextHash["symlinks"]>} */ (entry.symlinks),
  4083. 10,
  4084. (target, push, callback) => {
  4085. this._getUnresolvedContextHash(target, (err, hash) => {
  4086. if (err) return callback(err);
  4087. if (hash) {
  4088. hashes.push(hash.hash);
  4089. if (hash.symlinks !== undefined) {
  4090. for (const target of hash.symlinks) {
  4091. if (!seen.has(target)) {
  4092. seen.add(target);
  4093. push(target);
  4094. }
  4095. }
  4096. }
  4097. }
  4098. callback();
  4099. });
  4100. },
  4101. (err) => {
  4102. if (err) return callback(/** @type {WebpackError} */ (err));
  4103. const hash = createHash(this._hashFunction);
  4104. hash.update(entry.hash);
  4105. hashes.sort();
  4106. for (const h of hashes) {
  4107. hash.update(h);
  4108. }
  4109. callback(null, (entry.resolved = hash.digest("hex")));
  4110. }
  4111. );
  4112. }
  4113. /**
  4114. * @private
  4115. * @type {Processor<string, ContextTimestampAndHash>}
  4116. */
  4117. _readContextTimestampAndHash(path, callback) {
  4118. /**
  4119. * Processes the provided timestamp.
  4120. * @param {ContextTimestamp} timestamp timestamp
  4121. * @param {ContextHash | null} hash hash
  4122. */
  4123. const finalize = (timestamp, hash) => {
  4124. // Merging two `null` reads yields `{}`, which reads as "exists, no hash" (#21636)
  4125. const result =
  4126. /** @type {ContextTimestampAndHash} */
  4127. (
  4128. hash === null || timestamp === "ignore"
  4129. ? hash
  4130. : { ...timestamp, ...hash }
  4131. );
  4132. this._contextTshs.set(path, result);
  4133. callback(null, result);
  4134. };
  4135. const cachedHash = this._contextHashes.get(path);
  4136. const cachedTimestamp = this._contextTimestamps.get(path);
  4137. if (cachedHash !== undefined) {
  4138. if (cachedTimestamp !== undefined) {
  4139. finalize(cachedTimestamp, cachedHash);
  4140. } else {
  4141. this.contextTimestampQueue.add(path, (err, entry) => {
  4142. if (err) return callback(err);
  4143. finalize(
  4144. /** @type {ContextFileSystemInfoEntry} */
  4145. (entry),
  4146. cachedHash
  4147. );
  4148. });
  4149. }
  4150. } else if (cachedTimestamp !== undefined) {
  4151. this.contextHashQueue.add(path, (err, entry) => {
  4152. if (err) return callback(err);
  4153. finalize(cachedTimestamp, /** @type {ContextHash} */ (entry));
  4154. });
  4155. } else {
  4156. this._readContext(
  4157. {
  4158. path,
  4159. fromImmutablePath: () =>
  4160. /** @type {ContextTimestampAndHash | Omit<ContextTimestampAndHash, "safeTime"> | string | null} */ (
  4161. null
  4162. ),
  4163. fromManagedItem: (info) => ({
  4164. safeTime: 0,
  4165. timestampHash: info,
  4166. hash: info || ""
  4167. }),
  4168. fromSymlink: (file, target, callback) => {
  4169. callback(null, {
  4170. timestampHash: target,
  4171. hash: target,
  4172. symlinks: new Set([target])
  4173. });
  4174. },
  4175. fromFile: (file, stat, callback) => {
  4176. this._getFileTimestampAndHash(file, callback);
  4177. },
  4178. fromDirectory: (directory, stat, callback) => {
  4179. this.contextTshQueue.increaseParallelism();
  4180. this.contextTshQueue.add(directory, (err, result) => {
  4181. this.contextTshQueue.decreaseParallelism();
  4182. callback(err, result);
  4183. });
  4184. },
  4185. /**
  4186. * Returns tsh.
  4187. * @param {string[]} files files
  4188. * @param {(Partial<TimestampAndHash> & Partial<ContextTimestampAndHash> | string | null)[]} results results
  4189. * @returns {ContextTimestampAndHash} tsh
  4190. */
  4191. reduce: (files, results) => {
  4192. /** @type {undefined | Symlinks} */
  4193. let symlinks;
  4194. const tsHash = createHash(this._hashFunction);
  4195. const hash = createHash(this._hashFunction);
  4196. for (const file of files) {
  4197. tsHash.update(file);
  4198. hash.update(file);
  4199. }
  4200. let safeTime = 0;
  4201. for (const entry of results) {
  4202. if (!entry) {
  4203. tsHash.update("n");
  4204. continue;
  4205. }
  4206. if (typeof entry === "string") {
  4207. tsHash.update("n");
  4208. hash.update(entry);
  4209. continue;
  4210. }
  4211. if (entry.timestamp) {
  4212. tsHash.update("f");
  4213. tsHash.update(`${entry.timestamp}`);
  4214. } else if (entry.timestampHash) {
  4215. tsHash.update("d");
  4216. tsHash.update(`${entry.timestampHash}`);
  4217. }
  4218. if (entry.symlinks !== undefined) {
  4219. if (symlinks === undefined) symlinks = new Set();
  4220. addAll(entry.symlinks, symlinks);
  4221. }
  4222. if (entry.safeTime) {
  4223. safeTime = Math.max(safeTime, entry.safeTime);
  4224. }
  4225. hash.update(/** @type {string} */ (entry.hash));
  4226. }
  4227. /** @type {ContextTimestampAndHash} */
  4228. const result = {
  4229. safeTime,
  4230. timestampHash: tsHash.digest("hex"),
  4231. hash: hash.digest("hex")
  4232. };
  4233. if (symlinks) result.symlinks = symlinks;
  4234. return result;
  4235. }
  4236. },
  4237. (err, _result) => {
  4238. if (err) return callback(/** @type {WebpackError} */ (err));
  4239. const result = /** @type {ContextTimestampAndHash} */ (_result);
  4240. this._contextTshs.set(path, result);
  4241. return callback(null, result);
  4242. }
  4243. );
  4244. }
  4245. }
  4246. /**
  4247. * Resolve context tsh.
  4248. * @private
  4249. * @param {ContextTimestampAndHash} entry entry
  4250. * @param {ProcessorCallback<ResolvedContextTimestampAndHash>} callback callback
  4251. * @returns {void}
  4252. */
  4253. _resolveContextTsh(entry, callback) {
  4254. /** @type {string[]} */
  4255. const hashes = [];
  4256. /** @type {string[]} */
  4257. const tsHashes = [];
  4258. let safeTime = 0;
  4259. // Skip already-visited symlink targets so cyclic pnpm/peer-variant graphs terminate (#21084).
  4260. const seen = new Set(entry.symlinks);
  4261. processAsyncTree(
  4262. /** @type {NonNullable<ContextHash["symlinks"]>} */ (entry.symlinks),
  4263. 10,
  4264. (target, push, callback) => {
  4265. this._getUnresolvedContextTsh(target, (err, entry) => {
  4266. if (err) return callback(err);
  4267. if (entry) {
  4268. hashes.push(entry.hash);
  4269. if (entry.timestampHash) tsHashes.push(entry.timestampHash);
  4270. if (entry.safeTime) {
  4271. safeTime = Math.max(safeTime, entry.safeTime);
  4272. }
  4273. if (entry.symlinks !== undefined) {
  4274. for (const target of entry.symlinks) {
  4275. if (!seen.has(target)) {
  4276. seen.add(target);
  4277. push(target);
  4278. }
  4279. }
  4280. }
  4281. }
  4282. callback();
  4283. });
  4284. },
  4285. (err) => {
  4286. if (err) return callback(/** @type {WebpackError} */ (err));
  4287. const hash = createHash(this._hashFunction);
  4288. const tsHash = createHash(this._hashFunction);
  4289. hash.update(entry.hash);
  4290. if (entry.timestampHash) tsHash.update(entry.timestampHash);
  4291. if (entry.safeTime) {
  4292. safeTime = Math.max(safeTime, entry.safeTime);
  4293. }
  4294. hashes.sort();
  4295. for (const h of hashes) {
  4296. hash.update(h);
  4297. }
  4298. tsHashes.sort();
  4299. for (const h of tsHashes) {
  4300. tsHash.update(h);
  4301. }
  4302. callback(
  4303. null,
  4304. (entry.resolved = {
  4305. safeTime,
  4306. timestampHash: tsHash.digest("hex"),
  4307. hash: hash.digest("hex")
  4308. })
  4309. );
  4310. }
  4311. );
  4312. }
  4313. /**
  4314. * @private
  4315. * @type {Processor<string, Set<string>>}
  4316. */
  4317. _getManagedItemDirectoryInfo(path, callback) {
  4318. this.fs.readdir(path, (err, elements) => {
  4319. if (err) {
  4320. if (err.code === "ENOENT" || err.code === "ENOTDIR") {
  4321. return callback(null, EMPTY_SET);
  4322. }
  4323. return callback(/** @type {WebpackError} */ (err));
  4324. }
  4325. const set = new Set(
  4326. /** @type {string[]} */
  4327. (elements).map((element) => join(this.fs, path, element))
  4328. );
  4329. callback(null, set);
  4330. });
  4331. }
  4332. /**
  4333. * @private
  4334. * @type {Processor<string, string>}
  4335. */
  4336. _getManagedItemInfo(path, callback) {
  4337. const dir = dirname(this.fs, path);
  4338. this.managedItemDirectoryQueue.add(dir, (err, elements) => {
  4339. if (err) {
  4340. return callback(err);
  4341. }
  4342. if (!(/** @type {Set<string>} */ (elements).has(path))) {
  4343. // file or directory doesn't exist
  4344. this._managedItems.set(path, "*missing");
  4345. return callback(null, "*missing");
  4346. }
  4347. // something exists
  4348. // it may be a file or directory
  4349. if (
  4350. path.endsWith("node_modules") &&
  4351. (path.endsWith("/node_modules") || path.endsWith("\\node_modules"))
  4352. ) {
  4353. // we are only interested in existence of this special directory
  4354. this._managedItems.set(path, "*node_modules");
  4355. return callback(null, "*node_modules");
  4356. }
  4357. // we assume it's a directory, as files shouldn't occur in managed paths
  4358. const packageJsonPath = join(this.fs, path, "package.json");
  4359. this.fs.readFile(packageJsonPath, (err, content) => {
  4360. if (err) {
  4361. if (err.code === "ENOENT" || err.code === "ENOTDIR") {
  4362. // no package.json or path is not a directory
  4363. this.fs.readdir(path, (err, elements) => {
  4364. if (
  4365. !err &&
  4366. /** @type {string[]} */ (elements).length === 1 &&
  4367. /** @type {string[]} */ (elements)[0] === "node_modules"
  4368. ) {
  4369. // This is only a grouping folder e.g. used by yarn
  4370. // we are only interested in existence of this special directory
  4371. this._managedItems.set(path, "*nested");
  4372. return callback(null, "*nested");
  4373. }
  4374. /** @type {Logger} */
  4375. (this.logger).warn(
  4376. `Managed item ${path} isn't a directory or doesn't contain a package.json (see snapshot.managedPaths option)`
  4377. );
  4378. return callback();
  4379. });
  4380. return;
  4381. }
  4382. return callback(/** @type {WebpackError} */ (err));
  4383. }
  4384. /** @type {JsonObject} */
  4385. let data;
  4386. try {
  4387. data = JSON.parse(/** @type {Buffer} */ (content).toString("utf8"));
  4388. } catch (parseErr) {
  4389. return callback(/** @type {WebpackError} */ (parseErr));
  4390. }
  4391. if (!data.name) {
  4392. /** @type {Logger} */
  4393. (this.logger).warn(
  4394. `${packageJsonPath} doesn't contain a "name" property (see snapshot.managedPaths option)`
  4395. );
  4396. return callback();
  4397. }
  4398. const info = `${data.name || ""}@${data.version || ""}`;
  4399. this._managedItems.set(path, info);
  4400. callback(null, info);
  4401. });
  4402. });
  4403. }
  4404. getDeprecatedFileTimestamps() {
  4405. if (this._cachedDeprecatedFileTimestamps !== undefined) {
  4406. return this._cachedDeprecatedFileTimestamps;
  4407. }
  4408. /** @type {Map<string, number | null>} */
  4409. const map = new Map();
  4410. for (const [path, info] of this._fileTimestamps) {
  4411. if (info) {
  4412. const safeTime =
  4413. typeof info === "object"
  4414. ? /** @type {Partial<FileSystemInfoEntry>} */ (info).safeTime
  4415. : undefined;
  4416. map.set(path, safeTime === undefined ? null : safeTime);
  4417. }
  4418. }
  4419. return (this._cachedDeprecatedFileTimestamps = map);
  4420. }
  4421. getDeprecatedContextTimestamps() {
  4422. if (this._cachedDeprecatedContextTimestamps !== undefined) {
  4423. return this._cachedDeprecatedContextTimestamps;
  4424. }
  4425. /** @type {Map<string, number | null>} */
  4426. const map = new Map();
  4427. for (const [path, info] of this._contextTimestamps) {
  4428. if (info) {
  4429. const safeTime =
  4430. typeof info === "object"
  4431. ? /** @type {Partial<ContextFileSystemInfoEntry>} */ (info).safeTime
  4432. : undefined;
  4433. map.set(path, safeTime === undefined ? null : safeTime);
  4434. }
  4435. }
  4436. return (this._cachedDeprecatedContextTimestamps = map);
  4437. }
  4438. }
  4439. FileSystemInfo.Snapshot = Snapshot;
  4440. module.exports = FileSystemInfo;