@jsenv/core 37.0.5 → 37.1.1

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.
@@ -1,4 +1,5 @@
1
1
  import { ANSI, byteAsFileSize, distributePercentages } from "@jsenv/log";
2
+ import { GRAPH_VISITOR } from "./url_graph_visitor.js";
2
3
 
3
4
  export const createUrlGraphSummary = (
4
5
  urlGraph,
@@ -29,67 +30,69 @@ const createUrlGraphReport = (urlGraph) => {
29
30
  other: 0,
30
31
  total: 0,
31
32
  };
32
- urlGraph.urlInfoMap.forEach((urlInfo) => {
33
- if (urlInfo.isRoot) {
34
- return;
35
- }
36
- // ignore:
37
- // - ignored files: we don't know their content
38
- // - inline files and data files: they are already taken into account in the file where they appear
39
- if (urlInfo.url.startsWith("ignore:")) {
40
- return;
41
- }
42
- if (urlInfo.isInline) {
43
- return;
44
- }
45
- if (urlInfo.url.startsWith("data:")) {
46
- return;
47
- }
48
33
 
49
- // file loaded via import assertion are already inside the graph
50
- // their js module equivalent are ignored to avoid counting it twice
51
- // in the build graph the file targeted by import assertion will likely be gone
52
- // and only the js module remain (likely bundled)
53
- if (
54
- urlInfo.searchParams.has("as_json_module") ||
55
- urlInfo.searchParams.has("as_css_module") ||
56
- urlInfo.searchParams.has("as_text_module")
57
- ) {
58
- return;
59
- }
60
- const urlContentSize = Buffer.byteLength(urlInfo.content);
61
- const category = determineCategory(urlInfo);
62
- if (category === "sourcemap") {
63
- countGroups.sourcemaps++;
64
- sizeGroups.sourcemaps += urlContentSize;
65
- return;
66
- }
67
- countGroups.total++;
68
- sizeGroups.total += urlContentSize;
69
- if (category === "html") {
70
- countGroups.html++;
71
- sizeGroups.html += urlContentSize;
72
- return;
73
- }
74
- if (category === "css") {
75
- countGroups.css++;
76
- sizeGroups.css += urlContentSize;
77
- return;
78
- }
79
- if (category === "js") {
80
- countGroups.js++;
81
- sizeGroups.js += urlContentSize;
82
- return;
83
- }
84
- if (category === "json") {
85
- countGroups.json++;
86
- sizeGroups.json += urlContentSize;
34
+ GRAPH_VISITOR.forEachUrlInfoStronglyReferenced(
35
+ urlGraph.rootUrlInfo,
36
+ (urlInfo) => {
37
+ // ignore:
38
+ // - ignored files: we don't know their content
39
+ // - inline files and data files: they are already taken into account in the file where they appear
40
+ if (urlInfo.url.startsWith("ignore:")) {
41
+ return;
42
+ }
43
+ if (urlInfo.isInline) {
44
+ return;
45
+ }
46
+ if (urlInfo.url.startsWith("data:")) {
47
+ return;
48
+ }
49
+
50
+ // file loaded via import assertion are already inside the graph
51
+ // their js module equivalent are ignored to avoid counting it twice
52
+ // in the build graph the file targeted by import assertion will likely be gone
53
+ // and only the js module remain (likely bundled)
54
+ if (
55
+ urlInfo.searchParams.has("as_json_module") ||
56
+ urlInfo.searchParams.has("as_css_module") ||
57
+ urlInfo.searchParams.has("as_text_module")
58
+ ) {
59
+ return;
60
+ }
61
+
62
+ const urlContentSize = Buffer.byteLength(urlInfo.content);
63
+ const category = determineCategory(urlInfo);
64
+ if (category === "sourcemap") {
65
+ countGroups.sourcemaps++;
66
+ sizeGroups.sourcemaps += urlContentSize;
67
+ return;
68
+ }
69
+ countGroups.total++;
70
+ sizeGroups.total += urlContentSize;
71
+ if (category === "html") {
72
+ countGroups.html++;
73
+ sizeGroups.html += urlContentSize;
74
+ return;
75
+ }
76
+ if (category === "css") {
77
+ countGroups.css++;
78
+ sizeGroups.css += urlContentSize;
79
+ return;
80
+ }
81
+ if (category === "js") {
82
+ countGroups.js++;
83
+ sizeGroups.js += urlContentSize;
84
+ return;
85
+ }
86
+ if (category === "json") {
87
+ countGroups.json++;
88
+ sizeGroups.json += urlContentSize;
89
+ return;
90
+ }
91
+ countGroups.other++;
92
+ sizeGroups.other += urlContentSize;
87
93
  return;
88
- }
89
- countGroups.other++;
90
- sizeGroups.other += urlContentSize;
91
- return;
92
- });
94
+ },
95
+ );
93
96
 
94
97
  const sizesToDistribute = {};
95
98
  Object.keys(sizeGroups).forEach((groupName) => {
@@ -99,3 +99,33 @@ GRAPH_VISITOR.findDependency = (urlInfo, visitor) => {
99
99
  iterate(urlInfo);
100
100
  return found;
101
101
  };
102
+
103
+ // This function will be used in "build.js"
104
+ // by passing rootUrlInfo as first arg
105
+ // -> this ensure we visit only urls with strong references
106
+ // because we start from root and ignore weak ref
107
+ // The alternative would be to iterate on urlInfoMap
108
+ // and call urlInfo.isUsed() but that would be more expensive
109
+ GRAPH_VISITOR.forEachUrlInfoStronglyReferenced = (initialUrlInfo, callback) => {
110
+ const seen = new Set();
111
+ seen.add(initialUrlInfo);
112
+ const iterateOnReferences = (urlInfo) => {
113
+ for (const referenceToOther of urlInfo.referenceToOthersSet) {
114
+ if (referenceToOther.isWeak) {
115
+ continue;
116
+ }
117
+ if (referenceToOther.gotInlined()) {
118
+ continue;
119
+ }
120
+ const referencedUrlInfo = referenceToOther.urlInfo;
121
+ if (seen.has(referencedUrlInfo)) {
122
+ continue;
123
+ }
124
+ seen.add(referencedUrlInfo);
125
+ callback(referencedUrlInfo);
126
+ iterateOnReferences(referencedUrlInfo);
127
+ }
128
+ };
129
+ iterateOnReferences(initialUrlInfo);
130
+ seen.clear();
131
+ };
@@ -28,11 +28,6 @@ export const createUrlInfoTransformer = ({
28
28
  sourcemapsSourcesContent = true;
29
29
  }
30
30
 
31
- const sourcemapsEnabled =
32
- sourcemaps === "inline" ||
33
- sourcemaps === "file" ||
34
- sourcemaps === "programmatic";
35
-
36
31
  const normalizeSourcemap = (urlInfo, sourcemap) => {
37
32
  let { sources } = sourcemap;
38
33
  if (sources) {
@@ -75,15 +70,10 @@ export const createUrlInfoTransformer = ({
75
70
  urlInfo.originalContentEtag = undefined;
76
71
  urlInfo.contentAst = undefined;
77
72
  urlInfo.contentEtag = undefined;
73
+ urlInfo.contentLength = undefined;
78
74
  urlInfo.content = undefined;
79
75
  urlInfo.sourcemap = null;
80
76
  urlInfo.sourcemapIsWrong = null;
81
- urlInfo.referenceToOthersSet.forEach((referenceToOther) => {
82
- const referencedUrlInfo = referenceToOther.urlInfo;
83
- if (referencedUrlInfo.isInline) {
84
- referencedUrlInfo.deleteFromGraph();
85
- }
86
- });
87
77
  };
88
78
 
89
79
  const setContent = async (
@@ -92,6 +82,7 @@ export const createUrlInfoTransformer = ({
92
82
  {
93
83
  contentAst, // most of the time will be undefined
94
84
  contentEtag, // in practice it's always undefined
85
+ contentLength,
95
86
  originalContent = content,
96
87
  originalContentAst, // most of the time will be undefined
97
88
  originalContentEtag, // in practice always undefined
@@ -107,17 +98,12 @@ export const createUrlInfoTransformer = ({
107
98
 
108
99
  urlInfo.contentAst = contentAst;
109
100
  urlInfo.contentEtag = contentEtag;
101
+ urlInfo.contentLength = contentLength;
110
102
  urlInfo.content = content;
111
103
  defineGettersOnPropertiesDerivedFromContent(urlInfo);
112
104
 
113
105
  urlInfo.sourcemap = sourcemap;
114
- if (!sourcemapsEnabled) {
115
- return;
116
- }
117
- if (!SOURCEMAP.enabledOnContentType(urlInfo.contentType)) {
118
- return;
119
- }
120
- if (urlInfo.generatedUrl.startsWith("data:")) {
106
+ if (!shouldHandleSourcemap(urlInfo)) {
121
107
  return;
122
108
  }
123
109
  // sourcemap is a special kind of reference:
@@ -180,6 +166,7 @@ export const createUrlInfoTransformer = ({
180
166
  content,
181
167
  contentAst, // undefined most of the time
182
168
  contentEtag, // in practice always undefined
169
+ contentLength,
183
170
  sourcemap,
184
171
  sourcemapIsWrong,
185
172
  } = transformations;
@@ -193,10 +180,11 @@ export const createUrlInfoTransformer = ({
193
180
  if (contentModified) {
194
181
  urlInfo.contentAst = contentAst;
195
182
  urlInfo.contentEtag = contentEtag;
183
+ urlInfo.contentLength = contentLength;
196
184
  urlInfo.content = content;
197
185
  defineGettersOnPropertiesDerivedFromContent(urlInfo);
198
186
  }
199
- if (sourcemapsEnabled && sourcemap) {
187
+ if (sourcemap && shouldHandleSourcemap(urlInfo)) {
200
188
  const sourcemapNormalized = normalizeSourcemap(urlInfo, sourcemap);
201
189
  const finalSourcemap = composeTwoSourcemaps(
202
190
  urlInfo.sourcemap,
@@ -272,13 +260,7 @@ export const createUrlInfoTransformer = ({
272
260
  };
273
261
 
274
262
  const applySourcemapOnContent = (urlInfo) => {
275
- if (!sourcemapsEnabled) {
276
- return;
277
- }
278
- if (!urlInfo.sourcemap) {
279
- return;
280
- }
281
- if (urlInfo.generatedUrl.startsWith("data:")) {
263
+ if (!urlInfo.sourcemap || !shouldHandleSourcemap(urlInfo)) {
282
264
  return;
283
265
  }
284
266
 
@@ -363,3 +345,21 @@ export const createUrlInfoTransformer = ({
363
345
  endTransformations,
364
346
  };
365
347
  };
348
+
349
+ const shouldHandleSourcemap = (urlInfo) => {
350
+ const { sourcemaps } = urlInfo.context;
351
+ if (
352
+ sourcemaps !== "inline" &&
353
+ sourcemaps !== "file" &&
354
+ sourcemaps !== "programmatic"
355
+ ) {
356
+ return false;
357
+ }
358
+ if (urlInfo.url.startsWith("data:")) {
359
+ return false;
360
+ }
361
+ if (!SOURCEMAP.enabledOnContentType(urlInfo.contentType)) {
362
+ return false;
363
+ }
364
+ return true;
365
+ };
@@ -127,7 +127,7 @@ const dequeue = async () => {
127
127
  }
128
128
  };
129
129
 
130
- const applyHotReload = async ({ hotInstructions }) => {
130
+ const applyHotReload = async ({ cause, hot, hotInstructions }) => {
131
131
  await hotInstructions.reduce(
132
132
  async (previous, { type, boundary, acceptedBy }) => {
133
133
  await previous;
@@ -143,7 +143,7 @@ const applyHotReload = async ({ hotInstructions }) => {
143
143
  delete urlHotMetas[urlToFetch];
144
144
  if (urlHotMeta.disposeCallback) {
145
145
  console.groupCollapsed(
146
- `[jsenv] cleanup ${boundary} (previously used in ${acceptedBy})`,
146
+ `[jsenv] cleanup ${boundary} (no longer referenced by ${acceptedBy})`,
147
147
  );
148
148
  console.log(`call dispose callback`);
149
149
  await urlHotMeta.disposeCallback();
@@ -154,10 +154,10 @@ const applyHotReload = async ({ hotInstructions }) => {
154
154
  }
155
155
 
156
156
  if (acceptedBy === boundary) {
157
- console.groupCollapsed(`[jsenv] hot reloading ${boundary}`);
157
+ console.groupCollapsed(`[jsenv] hot reloading ${boundary} (${cause})`);
158
158
  } else {
159
159
  console.groupCollapsed(
160
- `[jsenv] hot reloading ${acceptedBy} usage in ${boundary}`,
160
+ `[jsenv] hot reloading ${acceptedBy} usage in ${boundary} (${cause})`,
161
161
  );
162
162
  }
163
163
  if (type === "js_module") {
@@ -174,7 +174,7 @@ const applyHotReload = async ({ hotInstructions }) => {
174
174
  type: "dynamic_import",
175
175
  url: urlToFetch,
176
176
  };
177
- const namespace = await reloadJsImport(urlToFetch);
177
+ const namespace = await reloadJsImport(urlToFetch, hot);
178
178
  if (urlHotMeta.acceptCallback) {
179
179
  await urlHotMeta.acceptCallback(namespace);
180
180
  }
@@ -201,11 +201,11 @@ const applyHotReload = async ({ hotInstructions }) => {
201
201
  console.log(`no dom node using ${acceptedBy}`);
202
202
  } else if (domNodesCount === 1) {
203
203
  console.log(`reloading`, domNodesUsingUrl[0].node);
204
- domNodesUsingUrl[0].reload();
204
+ domNodesUsingUrl[0].reload(hot);
205
205
  } else {
206
206
  console.log(`reloading ${domNodesCount} nodes using ${acceptedBy}`);
207
207
  domNodesUsingUrl.forEach((domNodesUsingUrl) => {
208
- domNodesUsingUrl.reload();
208
+ domNodesUsingUrl.reload(hot);
209
209
  });
210
210
  }
211
211
  console.groupEnd();
@@ -31,20 +31,20 @@ export const getDOMNodesUsingUrl = (urlToReload) => {
31
31
  }
32
32
  nodes.push({
33
33
  node,
34
- reload: () => {
34
+ reload: (hot) => {
35
35
  if (node.nodeName === "SCRIPT") {
36
36
  const copy = document.createElement("script");
37
37
  Array.from(node.attributes).forEach((attribute) => {
38
38
  copy.setAttribute(attribute.nodeName, attribute.nodeValue);
39
39
  });
40
- copy.src = injectQuery(node.src, { hot: Date.now() });
40
+ copy.src = injectQuery(node.src, { hot });
41
41
  if (node.parentNode) {
42
42
  node.parentNode.replaceChild(copy, node);
43
43
  } else {
44
44
  document.body.appendChild(copy);
45
45
  }
46
46
  } else {
47
- node[attributeName] = injectQuery(attribute, { hot: Date.now() });
47
+ node[attributeName] = injectQuery(attribute, { hot });
48
48
  }
49
49
  },
50
50
  });
@@ -88,16 +88,19 @@ export const getDOMNodesUsingUrl = (urlToReload) => {
88
88
  visitNodeAttributeAsUrl(img, "src");
89
89
  const srcset = img.srcset;
90
90
  if (srcset) {
91
- const srcCandidates = parseSrcSet(srcset);
92
- srcCandidates.forEach((srcCandidate) => {
93
- const url = new URL(srcCandidate.specifier, `${window.location.href}`);
94
- if (shouldReloadUrl(url)) {
95
- srcCandidate.specifier = injectQuery(url, { hot: Date.now() });
96
- }
97
- });
98
91
  nodes.push({
99
92
  node: img,
100
- reload: () => {
93
+ reload: (hot) => {
94
+ const srcCandidates = parseSrcSet(srcset);
95
+ srcCandidates.forEach((srcCandidate) => {
96
+ const url = new URL(
97
+ srcCandidate.specifier,
98
+ `${window.location.href}`,
99
+ );
100
+ if (shouldReloadUrl(url)) {
101
+ srcCandidate.specifier = injectQuery(url, { hot });
102
+ }
103
+ });
101
104
  img.srcset = stringifySrcSet(srcCandidates);
102
105
  },
103
106
  });
@@ -117,8 +120,8 @@ export const getDOMNodesUsingUrl = (urlToReload) => {
117
120
  return nodes;
118
121
  };
119
122
 
120
- export const reloadJsImport = async (url) => {
121
- const urlWithHotSearchParam = injectQuery(url, { hot: Date.now() });
123
+ export const reloadJsImport = async (url, hot) => {
124
+ const urlWithHotSearchParam = injectQuery(url, { hot });
122
125
  const namespace = await import(urlWithHotSearchParam);
123
126
  return namespace;
124
127
  };
@@ -3,15 +3,15 @@ import { jsenvPluginAutoreloadClient } from "./jsenv_plugin_autoreload_client.js
3
3
  import { jsenvPluginAutoreloadServer } from "./jsenv_plugin_autoreload_server.js";
4
4
 
5
5
  export const jsenvPluginAutoreload = ({
6
- clientFileChangeCallbackList,
7
- clientFilesPruneCallbackList,
6
+ clientFileChangeEventEmitter,
7
+ clientFileDereferencedEventEmitter,
8
8
  }) => {
9
9
  return [
10
10
  jsenvPluginHotSearchParam(),
11
11
  jsenvPluginAutoreloadClient(),
12
12
  jsenvPluginAutoreloadServer({
13
- clientFileChangeCallbackList,
14
- clientFilesPruneCallbackList,
13
+ clientFileChangeEventEmitter,
14
+ clientFileDereferencedEventEmitter,
15
15
  }),
16
16
  ];
17
17
  };
@@ -1,8 +1,8 @@
1
1
  import { urlIsInsideOf, urlToRelativeUrl } from "@jsenv/urls";
2
2
 
3
3
  export const jsenvPluginAutoreloadServer = ({
4
- clientFileChangeCallbackList,
5
- clientFilesPruneCallbackList,
4
+ clientFileChangeEventEmitter,
5
+ clientFileDereferencedEventEmitter,
6
6
  }) => {
7
7
  return {
8
8
  name: "jsenv:autoreload_server",
@@ -18,29 +18,7 @@ export const jsenvPluginAutoreloadServer = ({
18
18
  }
19
19
  return url;
20
20
  };
21
- const notifyFullReload = ({ cause, reason, declinedBy }) => {
22
- serverEventInfo.sendServerEvent({
23
- cause,
24
- type: "full",
25
- typeReason: reason,
26
- declinedBy,
27
- });
28
- };
29
- const notifyPartialReload = ({ cause, reason, instructions }) => {
30
- serverEventInfo.sendServerEvent({
31
- cause,
32
- type: "hot",
33
- typeReason: reason,
34
- hotInstructions: instructions,
35
- });
36
- };
37
21
  const propagateUpdate = (firstUrlInfo) => {
38
- if (!serverEventInfo.kitchen.graph.getUrlInfo(firstUrlInfo.url)) {
39
- return {
40
- declined: true,
41
- reason: `url not in the url graph`,
42
- };
43
- }
44
22
  const iterate = (urlInfo, seen) => {
45
23
  if (urlInfo.data.hotAcceptSelf) {
46
24
  return {
@@ -119,86 +97,150 @@ export const jsenvPluginAutoreloadServer = ({
119
97
  const seen = [];
120
98
  return iterate(firstUrlInfo, seen);
121
99
  };
122
- clientFileChangeCallbackList.push(({ url, event }) => {
123
- const onUrlInfo = (urlInfo) => {
124
- if (!urlInfo.isUsed()) {
125
- return false;
126
- }
127
- const relativeUrl = formatUrlForClient(urlInfo.url);
128
- const hotUpdate = propagateUpdate(urlInfo);
129
- if (hotUpdate.declined) {
130
- notifyFullReload({
131
- cause: `${relativeUrl} ${event}`,
132
- reason: hotUpdate.reason,
133
- declinedBy: hotUpdate.declinedBy,
134
- });
135
- return true;
136
- }
137
- notifyPartialReload({
138
- cause: `${relativeUrl} ${event}`,
139
- reason: hotUpdate.reason,
140
- instructions: hotUpdate.instructions,
141
- });
142
- return true;
143
- };
144
100
 
145
- const urlInfo = serverEventInfo.kitchen.graph.getUrlInfo(url);
146
- if (urlInfo) {
147
- if (onUrlInfo(urlInfo)) {
148
- return;
101
+ // We are delaying the moment we tell client how to reload because:
102
+ //
103
+ // 1. clientFileDereferencedEventEmitter can emit multiple times in a row
104
+ // It happens when previous references are removed by stopCollecting (in "references.js")
105
+ // In that case we could regroup the calls but we prefer to rely on debouncing to also cover
106
+ // code that would remove many url in a row by other means (like reference.remove())
107
+ //
108
+ // 2. clientFileChangeEventEmitter can emit a lot of times in a short period (git checkout for instance)
109
+ // In that case it's better to cooldown thanks to debouncing
110
+ //
111
+ // And we want to gather all the actions to take in response to these events because
112
+ // we want to favor full-reload when needed and resort to partial reload afterwards
113
+ // it's also important to ensure the client will fetch the server in the same order
114
+ const delayedActionSet = new Set();
115
+ let timeout;
116
+ const delayAction = (action) => {
117
+ delayedActionSet.add(action);
118
+ clearTimeout(timeout);
119
+ timeout = setTimeout(handleDelayedActions);
120
+ };
121
+
122
+ const handleDelayedActions = () => {
123
+ const actionSet = new Set(delayedActionSet);
124
+ delayedActionSet.clear();
125
+ let reloadMessage = null;
126
+ for (const action of actionSet) {
127
+ if (action.type === "change") {
128
+ const { changedUrlInfo, event } = action;
129
+ if (!changedUrlInfo.isUsed()) {
130
+ continue;
131
+ }
132
+ const hotUpdate = propagateUpdate(changedUrlInfo);
133
+ const relativeUrl = formatUrlForClient(changedUrlInfo.url);
134
+ if (hotUpdate.declined) {
135
+ reloadMessage = {
136
+ cause: `${relativeUrl} ${event}`,
137
+ type: "full",
138
+ typeReason: hotUpdate.reason,
139
+ declinedBy: hotUpdate.declinedBy,
140
+ };
141
+ break;
142
+ }
143
+ const instructions = hotUpdate.instructions;
144
+ if (reloadMessage) {
145
+ reloadMessage.hotInstructions.push(...instructions);
146
+ } else {
147
+ reloadMessage = {
148
+ cause: `${relativeUrl} ${event}`,
149
+ type: "hot",
150
+ typeReason: hotUpdate.reason,
151
+ hot: changedUrlInfo.modifiedTimestamp,
152
+ hotInstructions: instructions,
153
+ };
154
+ }
155
+ continue;
149
156
  }
150
- for (const searchParamVariant of urlInfo.searchParamVariantSet) {
151
- if (onUrlInfo(searchParamVariant)) {
152
- return;
157
+
158
+ if (action.type === "prune") {
159
+ const { prunedUrlInfo, lastReferenceFromOther } = action;
160
+ if (lastReferenceFromOther.type === "sourcemap_comment") {
161
+ // Can happen when starting dev server with sourcemaps: "file"
162
+ // In that case, as sourcemaps are injected, the reference
163
+ // are lost and sourcemap is considered as pruned
164
+ continue;
165
+ }
166
+ const { ownerUrlInfo } = lastReferenceFromOther;
167
+ if (!ownerUrlInfo.isUsed()) {
168
+ continue;
169
+ }
170
+ const ownerHotUpdate = propagateUpdate(ownerUrlInfo);
171
+ const cause = `${formatUrlForClient(
172
+ prunedUrlInfo.url,
173
+ )} is no longer referenced`;
174
+ // now check if we can hot update the parent resource
175
+ // then if we can hot update all dependencies
176
+ if (ownerHotUpdate.declined) {
177
+ reloadMessage = {
178
+ cause,
179
+ type: "full",
180
+ typeReason: ownerHotUpdate.reason,
181
+ declinedBy: ownerHotUpdate.declinedBy,
182
+ };
183
+ break;
184
+ }
185
+ // parent can hot update
186
+ // but pruned url info declines
187
+ if (prunedUrlInfo.data.hotDecline) {
188
+ reloadMessage = {
189
+ cause,
190
+ type: "full",
191
+ typeReason: `a pruned file declines hot reload`,
192
+ declinedBy: formatUrlForClient(prunedUrlInfo.url),
193
+ };
194
+ break;
195
+ }
196
+ const pruneInstruction = {
197
+ type: "prune",
198
+ boundary: formatUrlForClient(prunedUrlInfo.url),
199
+ acceptedBy: formatUrlForClient(
200
+ lastReferenceFromOther.ownerUrlInfo.url,
201
+ ),
202
+ };
203
+ if (reloadMessage) {
204
+ reloadMessage.hotInstructions.push(pruneInstruction);
205
+ } else {
206
+ reloadMessage = {
207
+ cause,
208
+ type: "hot",
209
+ typeReason: ownerHotUpdate.reason,
210
+ hot: prunedUrlInfo.prunedTimestamp,
211
+ hotInstructions: [pruneInstruction],
212
+ };
153
213
  }
154
214
  }
155
215
  }
156
- });
157
- clientFilesPruneCallbackList.push(
158
- (prunedUrlInfo, lastReferenceFromOther) => {
159
- if (lastReferenceFromOther.type === "sourcemap_comment") {
160
- // Can happen when starting dev server with sourcemaps: "file"
161
- // In that case, as sourcemaps are injected, the reference
162
- // are lost and sourcemap is considered as pruned
163
- return;
164
- }
165
- const parentHotUpdate = propagateUpdate(
166
- lastReferenceFromOther.ownerUrlInfo,
167
- );
168
- const cause = `following file is no longer referenced: ${formatUrlForClient(
169
- prunedUrlInfo.url,
170
- )}`;
171
- // now check if we can hot update the parent resource
172
- // then if we can hot update all dependencies
173
- if (parentHotUpdate.declined) {
174
- notifyFullReload({
175
- cause,
176
- reason: parentHotUpdate.reason,
177
- declinedBy: parentHotUpdate.declinedBy,
178
- });
179
- return;
180
- }
181
- // parent can hot update
182
- const instructions = [];
183
- if (prunedUrlInfo.data.hotDecline) {
184
- notifyFullReload({
185
- cause,
186
- reason: `a pruned file declines hot reload`,
187
- declinedBy: formatUrlForClient(prunedUrlInfo.url),
216
+ if (reloadMessage) {
217
+ serverEventInfo.sendServerEvent(reloadMessage);
218
+ }
219
+ };
220
+
221
+ clientFileChangeEventEmitter.on(({ url, event }) => {
222
+ const changedUrlInfo = serverEventInfo.kitchen.graph.getUrlInfo(url);
223
+ if (changedUrlInfo) {
224
+ delayAction({
225
+ type: "change",
226
+ changedUrlInfo,
227
+ event,
228
+ });
229
+ for (const searchParamVariant of changedUrlInfo.searchParamVariantSet) {
230
+ delayAction({
231
+ type: "change",
232
+ changedUrlInfo: searchParamVariant,
233
+ event,
188
234
  });
189
- return;
190
235
  }
191
- instructions.push({
236
+ }
237
+ });
238
+ clientFileDereferencedEventEmitter.on(
239
+ (prunedUrlInfo, lastReferenceFromOther) => {
240
+ delayAction({
192
241
  type: "prune",
193
- boundary: formatUrlForClient(prunedUrlInfo.url),
194
- acceptedBy: formatUrlForClient(
195
- lastReferenceFromOther.ownerUrlInfo.url,
196
- ),
197
- });
198
- notifyPartialReload({
199
- cause,
200
- reason: parentHotUpdate.reason,
201
- instructions,
242
+ prunedUrlInfo,
243
+ lastReferenceFromOther,
202
244
  });
203
245
  },
204
246
  );