@jsenv/core 41.5.14 → 41.5.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2131,6 +2131,10 @@ const createUrlInfo = (url, context) => {
2131
2131
  modifiedTimestamp: 0,
2132
2132
  descendantModifiedTimestamp: 0,
2133
2133
  dereferencedTimestamp: 0,
2134
+ // when a client last fetched this url outside a hot request; in other
2135
+ // words the last time this url entered a fresh page (see
2136
+ // jsenv_plugin_hot_search_param)
2137
+ servedWithoutHotTimestamp: 0,
2134
2138
  originalContentEtag: null,
2135
2139
  contentEtag: null,
2136
2140
  isValid: () => false,
@@ -9445,6 +9449,7 @@ const jsenvPluginHotSearchParam = () => {
9445
9449
  modifiedTimestamp,
9446
9450
  descendantModifiedTimestamp,
9447
9451
  dereferencedTimestamp,
9452
+ servedWithoutHotTimestamp,
9448
9453
  } = referencedUrlInfo;
9449
9454
  if (
9450
9455
  !modifiedTimestamp &&
@@ -9468,6 +9473,18 @@ const jsenvPluginHotSearchParam = () => {
9468
9473
  descendantModifiedTimestamp,
9469
9474
  dereferencedTimestamp,
9470
9475
  );
9476
+ // These timestamps say "this url changed at some point", not "the client
9477
+ // is running an outdated version of it": they are never cleared, so a
9478
+ // file modified once keeps them for the rest of the dev server's life.
9479
+ // A client that fetched this url after that modification (a page load:
9480
+ // see rememberServedWithoutHot in the dev server) already runs the
9481
+ // latest content. Sending it "?hot" then makes the browser evaluate a
9482
+ // second, identical copy of a module it already has — a second module
9483
+ // scope for a file that never changed, which breaks everything a module
9484
+ // holds once (registries, contexts, singletons).
9485
+ if (latestTimestamp <= servedWithoutHotTimestamp) {
9486
+ return null;
9487
+ }
9471
9488
  return {
9472
9489
  hot: latestTimestamp,
9473
9490
  };
@@ -12193,8 +12210,16 @@ const createBuildUrlsGenerator = ({
12193
12210
  // THAT bundle). They are told apart by the entry point they come from —
12194
12211
  // sharing the file would leave one entry importing exports the file on disk
12195
12212
  // does not have.
12213
+ //
12214
+ // A source file is shared between entry points only while they write it to
12215
+ // the same assets directory. Two entry points with their own directory each
12216
+ // get their own copy: the file lands where the entry point that references
12217
+ // it lands, so removing one entry point's directory takes nothing away from
12218
+ // another.
12196
12219
  const insideBuildDirectory = urlIsOrIsInsideOf(url, buildDirectoryUrl);
12197
- const key = insideBuildDirectory ? `${entryKey} ${url}` : url;
12220
+ const key = insideBuildDirectory
12221
+ ? `${entryKey} ${url}`
12222
+ : `${assetsDirectory} ${url}`;
12198
12223
  const buildUrlFromMap = buildUrlMap.get(key);
12199
12224
  if (buildUrlFromMap) {
12200
12225
  return buildUrlFromMap;
@@ -12226,7 +12251,7 @@ const createBuildUrlsGenerator = ({
12226
12251
  }
12227
12252
  if (urlInfo.type === "entry_build") {
12228
12253
  const buildUrl = new URL(urlInfo.filenameHint, buildDirectoryUrl).href;
12229
- associateBuildUrl(url, buildUrl);
12254
+ associateBuildUrl(key, buildUrl);
12230
12255
  return buildUrl;
12231
12256
  }
12232
12257
  if (
@@ -12244,7 +12269,7 @@ const createBuildUrlsGenerator = ({
12244
12269
  const urlObject = new URL(url);
12245
12270
  const { search } = urlObject;
12246
12271
  const buildUrl = `${buildDirectoryUrl}${directoryPath}${search}`;
12247
- associateBuildUrl(url, buildUrl);
12272
+ associateBuildUrl(key, buildUrl);
12248
12273
  return buildUrl;
12249
12274
  }
12250
12275
 
@@ -12259,7 +12284,7 @@ const createBuildUrlsGenerator = ({
12259
12284
  let { search, hash } = urlObject;
12260
12285
  const name = reserveName(directoryPath, getUrlName(url, urlInfo));
12261
12286
  const buildUrl = `${buildDirectoryUrl}${directoryPath}${name}${search}${hash}`;
12262
- associateBuildUrl(url, buildUrl);
12287
+ associateBuildUrl(key, buildUrl);
12263
12288
  return buildUrl;
12264
12289
  };
12265
12290
 
@@ -7170,6 +7170,7 @@ const jsenvPluginHotSearchParam = () => {
7170
7170
  modifiedTimestamp,
7171
7171
  descendantModifiedTimestamp,
7172
7172
  dereferencedTimestamp,
7173
+ servedWithoutHotTimestamp,
7173
7174
  } = referencedUrlInfo;
7174
7175
  if (
7175
7176
  !modifiedTimestamp &&
@@ -7193,6 +7194,18 @@ const jsenvPluginHotSearchParam = () => {
7193
7194
  descendantModifiedTimestamp,
7194
7195
  dereferencedTimestamp,
7195
7196
  );
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
+ }
7196
7209
  return {
7197
7210
  hot: latestTimestamp,
7198
7211
  };
@@ -9675,6 +9688,10 @@ const createUrlInfo = (url, context) => {
9675
9688
  modifiedTimestamp: 0,
9676
9689
  descendantModifiedTimestamp: 0,
9677
9690
  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,
9678
9695
  originalContentEtag: null,
9679
9696
  contentEtag: null,
9680
9697
  isValid: () => false,
@@ -11922,7 +11939,20 @@ const devServerPluginServeSourceFiles = ({
11922
11939
  );
11923
11940
  return response;
11924
11941
  };
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
+ };
11925
11954
  const respondWithNotModified = () => {
11955
+ rememberServedWithoutHot();
11926
11956
  const headers = {
11927
11957
  "cache-control": `private,max-age=0,must-revalidate`,
11928
11958
  };
@@ -12007,6 +12037,9 @@ const devServerPluginServeSourceFiles = ({
12007
12037
  ) {
12008
12038
  return respondWithNotModified();
12009
12039
  }
12040
+ if (urlInfo.status === 200) {
12041
+ rememberServedWithoutHot();
12042
+ }
12010
12043
  response = {
12011
12044
  url: reference.url,
12012
12045
  // a plugin can cook a complete response body for an url that is
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/core",
3
- "version": "41.5.14",
3
+ "version": "41.5.16",
4
4
  "type": "module",
5
5
  "description": "Tool to develop, test and build js projects",
6
6
  "repository": {
@@ -44,7 +44,7 @@
44
44
  "scripts": {
45
45
  "build": "node --conditions=dev:jsenv ./scripts/build/build.mjs",
46
46
  "build:file_size": "node ./scripts/build/build_file_size.mjs --log",
47
- "build:packages": "npm run build --workspaces --if-present --conditions=developement",
47
+ "build:packages": "npm run build -- ./packages/",
48
48
  "database:install": "npx @jsenv/database install",
49
49
  "database:manage": "node --conditions=dev:jsenv ./packages/backend/database/src/cli/manage.js",
50
50
  "database:setup": "npm run database:setup --workspaces --if-present --conditions=dev:jsenv",
@@ -59,8 +59,16 @@ export const createBuildUrlsGenerator = ({
59
59
  // THAT bundle). They are told apart by the entry point they come from —
60
60
  // sharing the file would leave one entry importing exports the file on disk
61
61
  // does not have.
62
+ //
63
+ // A source file is shared between entry points only while they write it to
64
+ // the same assets directory. Two entry points with their own directory each
65
+ // get their own copy: the file lands where the entry point that references
66
+ // it lands, so removing one entry point's directory takes nothing away from
67
+ // another.
62
68
  const insideBuildDirectory = urlIsOrIsInsideOf(url, buildDirectoryUrl);
63
- const key = insideBuildDirectory ? `${entryKey} ${url}` : url;
69
+ const key = insideBuildDirectory
70
+ ? `${entryKey} ${url}`
71
+ : `${assetsDirectory} ${url}`;
64
72
  const buildUrlFromMap = buildUrlMap.get(key);
65
73
  if (buildUrlFromMap) {
66
74
  return buildUrlFromMap;
@@ -92,7 +100,7 @@ export const createBuildUrlsGenerator = ({
92
100
  }
93
101
  if (urlInfo.type === "entry_build") {
94
102
  const buildUrl = new URL(urlInfo.filenameHint, buildDirectoryUrl).href;
95
- associateBuildUrl(url, buildUrl);
103
+ associateBuildUrl(key, buildUrl);
96
104
  return buildUrl;
97
105
  }
98
106
  if (
@@ -110,7 +118,7 @@ export const createBuildUrlsGenerator = ({
110
118
  const urlObject = new URL(url);
111
119
  const { search } = urlObject;
112
120
  const buildUrl = `${buildDirectoryUrl}${directoryPath}${search}`;
113
- associateBuildUrl(url, buildUrl);
121
+ associateBuildUrl(key, buildUrl);
114
122
  return buildUrl;
115
123
  }
116
124
 
@@ -125,7 +133,7 @@ export const createBuildUrlsGenerator = ({
125
133
  let { search, hash } = urlObject;
126
134
  const name = reserveName(directoryPath, getUrlName(url, urlInfo));
127
135
  const buildUrl = `${buildDirectoryUrl}${directoryPath}${name}${search}${hash}`;
128
- associateBuildUrl(url, buildUrl);
136
+ associateBuildUrl(key, buildUrl);
129
137
  return buildUrl;
130
138
  };
131
139
 
@@ -387,7 +387,20 @@ export const devServerPluginServeSourceFiles = ({
387
387
  );
388
388
  return response;
389
389
  };
390
+ // What the client holds for this url is what we last sent it: a
391
+ // request without "?hot" is a page loading this url into an empty
392
+ // module registry, so from here on the client runs this exact
393
+ // content. Remembering when that happened is what allows
394
+ // jsenv_plugin_hot_search_param to tell a modification the client
395
+ // has already received from one it must re-execute to see.
396
+ const rememberServedWithoutHot = () => {
397
+ if (request.searchParams.has("hot")) {
398
+ return;
399
+ }
400
+ urlInfo.servedWithoutHotTimestamp = Date.now();
401
+ };
390
402
  const respondWithNotModified = () => {
403
+ rememberServedWithoutHot();
391
404
  const headers = {
392
405
  "cache-control": `private,max-age=0,must-revalidate`,
393
406
  };
@@ -472,6 +485,9 @@ export const devServerPluginServeSourceFiles = ({
472
485
  ) {
473
486
  return respondWithNotModified();
474
487
  }
488
+ if (urlInfo.status === 200) {
489
+ rememberServedWithoutHot();
490
+ }
475
491
  response = {
476
492
  url: reference.url,
477
493
  // a plugin can cook a complete response body for an url that is
@@ -183,6 +183,10 @@ const createUrlInfo = (url, context) => {
183
183
  modifiedTimestamp: 0,
184
184
  descendantModifiedTimestamp: 0,
185
185
  dereferencedTimestamp: 0,
186
+ // when a client last fetched this url outside a hot request; in other
187
+ // words the last time this url entered a fresh page (see
188
+ // jsenv_plugin_hot_search_param)
189
+ servedWithoutHotTimestamp: 0,
186
190
  originalContentEtag: null,
187
191
  contentEtag: null,
188
192
  isValid: () => false,
@@ -48,6 +48,7 @@ export const jsenvPluginHotSearchParam = () => {
48
48
  modifiedTimestamp,
49
49
  descendantModifiedTimestamp,
50
50
  dereferencedTimestamp,
51
+ servedWithoutHotTimestamp,
51
52
  } = referencedUrlInfo;
52
53
  if (
53
54
  !modifiedTimestamp &&
@@ -71,6 +72,18 @@ export const jsenvPluginHotSearchParam = () => {
71
72
  descendantModifiedTimestamp,
72
73
  dereferencedTimestamp,
73
74
  );
75
+ // These timestamps say "this url changed at some point", not "the client
76
+ // is running an outdated version of it": they are never cleared, so a
77
+ // file modified once keeps them for the rest of the dev server's life.
78
+ // A client that fetched this url after that modification (a page load:
79
+ // see rememberServedWithoutHot in the dev server) already runs the
80
+ // latest content. Sending it "?hot" then makes the browser evaluate a
81
+ // second, identical copy of a module it already has — a second module
82
+ // scope for a file that never changed, which breaks everything a module
83
+ // holds once (registries, contexts, singletons).
84
+ if (latestTimestamp <= servedWithoutHotTimestamp) {
85
+ return null;
86
+ }
74
87
  return {
75
88
  hot: latestTimestamp,
76
89
  };