@@ -7170,6 +7170,7 @@ const jsenvPluginHotSearchParam = () => {
71707170 modifiedTimestamp,
71717171 descendantModifiedTimestamp,
71727172 dereferencedTimestamp,
7173+ servedWithoutHotTimestamp,
71737174 } = referencedUrlInfo ;
71747175 if (
71757176 ! modifiedTimestamp &&
@@ -7193,6 +7194,18 @@ const jsenvPluginHotSearchParam = () => {
71937194 descendantModifiedTimestamp ,
71947195 dereferencedTimestamp ,
71957196 ) ;
7197+ // These timestamps say "this url changed at some point", not "the client
7198+ // is running an outdated version of it": they are never cleared, so a
7199+ // file modified once keeps them for the rest of the dev server's life.
7200+ // A client that fetched this url after that modification (a page load:
7201+ // see rememberServedWithoutHot in the dev server) already runs the
7202+ // latest content. Sending it "?hot" then makes the browser evaluate a
7203+ // second, identical copy of a module it already has — a second module
7204+ // scope for a file that never changed, which breaks everything a module
7205+ // holds once (registries, contexts, singletons).
7206+ if ( latestTimestamp <= servedWithoutHotTimestamp ) {
7207+ return null ;
7208+ }
71967209 return {
71977210 hot : latestTimestamp ,
71987211 } ;
@@ -9675,6 +9688,10 @@ const createUrlInfo = (url, context) => {
96759688 modifiedTimestamp : 0 ,
96769689 descendantModifiedTimestamp : 0 ,
96779690 dereferencedTimestamp : 0 ,
9691+ // when a client last fetched this url outside a hot request; in other
9692+ // words the last time this url entered a fresh page (see
9693+ // jsenv_plugin_hot_search_param)
9694+ servedWithoutHotTimestamp : 0 ,
96789695 originalContentEtag : null ,
96799696 contentEtag : null ,
96809697 isValid : ( ) => false ,
@@ -11922,7 +11939,20 @@ const devServerPluginServeSourceFiles = ({
1192211939 ) ;
1192311940 return response ;
1192411941 } ;
11942+ // What the client holds for this url is what we last sent it: a
11943+ // request without "?hot" is a page loading this url into an empty
11944+ // module registry, so from here on the client runs this exact
11945+ // content. Remembering when that happened is what allows
11946+ // jsenv_plugin_hot_search_param to tell a modification the client
11947+ // has already received from one it must re-execute to see.
11948+ const rememberServedWithoutHot = ( ) => {
11949+ if ( request . searchParams . has ( "hot" ) ) {
11950+ return ;
11951+ }
11952+ urlInfo . servedWithoutHotTimestamp = Date . now ( ) ;
11953+ } ;
1192511954 const respondWithNotModified = ( ) => {
11955+ rememberServedWithoutHot ( ) ;
1192611956 const headers = {
1192711957 "cache-control" : `private,max-age=0,must-revalidate` ,
1192811958 } ;
@@ -12007,6 +12037,9 @@ const devServerPluginServeSourceFiles = ({
1200712037 ) {
1200812038 return respondWithNotModified ( ) ;
1200912039 }
12040+ if ( urlInfo . status === 200 ) {
12041+ rememberServedWithoutHot ( ) ;
12042+ }
1201012043 response = {
1201112044 url : reference . url ,
1201212045 // a plugin can cook a complete response body for an url that is
0 commit comments