@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.
- package/dist/js/autoreload.js +23 -20
- package/dist/js/import_meta_hot.js +0 -1
- package/dist/jsenv_core.js +691 -591
- package/package.json +2 -2
- package/src/build/build.js +141 -154
- package/src/build/build_versions_manager.js +53 -56
- package/src/dev/file_service.js +68 -77
- package/src/dev/start_dev_server.js +2 -4
- package/src/helpers/event_emitter.js +18 -0
- package/src/kitchen/kitchen.js +4 -6
- package/src/kitchen/url_graph/references.js +29 -8
- package/src/kitchen/url_graph/url_content.js +9 -0
- package/src/kitchen/url_graph/url_graph.js +50 -57
- package/src/kitchen/url_graph/url_graph_report.js +62 -59
- package/src/kitchen/url_graph/url_graph_visitor.js +30 -0
- package/src/kitchen/url_graph/url_info_transformations.js +26 -26
- package/src/plugins/autoreload/client/autoreload.js +7 -7
- package/src/plugins/autoreload/client/reload.js +16 -13
- package/src/plugins/autoreload/jsenv_plugin_autoreload.js +4 -4
- package/src/plugins/autoreload/jsenv_plugin_autoreload_server.js +138 -96
- package/src/plugins/autoreload/jsenv_plugin_hot_search_param.js +43 -25
- package/src/plugins/import_meta_hot/client/import_meta_hot.js +0 -1
- package/src/plugins/plugins.js +3 -14
- package/src/plugins/resolution_node_esm/jsenv_plugin_node_esm_resolution.js +4 -0
|
@@ -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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
urlInfo.
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
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 (!
|
|
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 (
|
|
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 (!
|
|
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} (
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
7
|
-
|
|
6
|
+
clientFileChangeEventEmitter,
|
|
7
|
+
clientFileDereferencedEventEmitter,
|
|
8
8
|
}) => {
|
|
9
9
|
return [
|
|
10
10
|
jsenvPluginHotSearchParam(),
|
|
11
11
|
jsenvPluginAutoreloadClient(),
|
|
12
12
|
jsenvPluginAutoreloadServer({
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
5
|
-
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
clientFileDereferencedEventEmitter.on(
|
|
239
|
+
(prunedUrlInfo, lastReferenceFromOther) => {
|
|
240
|
+
delayAction({
|
|
192
241
|
type: "prune",
|
|
193
|
-
|
|
194
|
-
|
|
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
|
);
|