@jsenv/core 41.5.20 → 41.5.22
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/build/build.js +152 -0
- package/dist/client/server_events/server_events_client.js +57 -41
- package/dist/start_dev_server/start_dev_server.js +315 -35
- package/package.json +2 -2
- package/src/build/build.js +14 -0
- package/src/dev/start_dev_server.js +25 -2
- package/src/helpers/watch_dependencies.js +156 -34
- package/src/plugins/patches/jsenv_plugin_patches.js +137 -0
- package/src/plugins/plugins.js +5 -0
- package/src/plugins/server_events/client/server_events_client.js +57 -41
package/dist/build/build.js
CHANGED
|
@@ -8184,6 +8184,140 @@ const asInheritedInjections = (injections) => {
|
|
|
8184
8184
|
return inheritedInjections;
|
|
8185
8185
|
};
|
|
8186
8186
|
|
|
8187
|
+
/*
|
|
8188
|
+
* Text patches applied to files as they are served and built, keyed by file:
|
|
8189
|
+
*
|
|
8190
|
+
* patches: {
|
|
8191
|
+
* "preact/dist/preact.mjs": [{ from: "a&&b", to: "a&&b&&c" }],
|
|
8192
|
+
* }
|
|
8193
|
+
*
|
|
8194
|
+
* A key is a url pattern relative to the root directory ("./main.js",
|
|
8195
|
+
* "**\/*.css"), or a path inside a package ("preact/dist/preact.mjs") found by
|
|
8196
|
+
* walking up from the root directory into node_modules, the way node does,
|
|
8197
|
+
* so the key holds wherever the package manager hoists the package.
|
|
8198
|
+
*
|
|
8199
|
+
* Every `from` must occur exactly once in the file, otherwise the file fails
|
|
8200
|
+
* to cook and says which patch did not apply: a dependency update that moved
|
|
8201
|
+
* the patched code must be looked at, never silently unpatched.
|
|
8202
|
+
*/
|
|
8203
|
+
|
|
8204
|
+
|
|
8205
|
+
const jsenvPluginPatches = (rawPatches) => {
|
|
8206
|
+
if (!rawPatches || Object.keys(rawPatches).length === 0) {
|
|
8207
|
+
return [];
|
|
8208
|
+
}
|
|
8209
|
+
let findPatches;
|
|
8210
|
+
const patchesPlugin = {
|
|
8211
|
+
name: "jsenv:patches",
|
|
8212
|
+
appliesDuring: "*",
|
|
8213
|
+
init: (context) => {
|
|
8214
|
+
const { rootDirectoryUrl } = context;
|
|
8215
|
+
const patchesByPattern = {};
|
|
8216
|
+
for (const key of Object.keys(rawPatches)) {
|
|
8217
|
+
const patches = rawPatches[key];
|
|
8218
|
+
assertPatches(patches, key);
|
|
8219
|
+
patchesByPattern[resolvePatchKey(key, rootDirectoryUrl)] = patches;
|
|
8220
|
+
}
|
|
8221
|
+
const associations = URL_META.resolveAssociations(
|
|
8222
|
+
{ patches: patchesByPattern },
|
|
8223
|
+
rootDirectoryUrl,
|
|
8224
|
+
);
|
|
8225
|
+
findPatches = (url) => {
|
|
8226
|
+
const { patches } = URL_META.applyAssociations({
|
|
8227
|
+
url: asUrlWithoutSearch(url),
|
|
8228
|
+
associations,
|
|
8229
|
+
});
|
|
8230
|
+
return patches;
|
|
8231
|
+
};
|
|
8232
|
+
},
|
|
8233
|
+
transformUrlContent: (urlInfo) => {
|
|
8234
|
+
const patches = findPatches(urlInfo.url);
|
|
8235
|
+
if (!patches) {
|
|
8236
|
+
return null;
|
|
8237
|
+
}
|
|
8238
|
+
const { content } = urlInfo;
|
|
8239
|
+
const magicSource = createMagicSource(content);
|
|
8240
|
+
for (const { from, to } of patches) {
|
|
8241
|
+
const start = content.indexOf(from);
|
|
8242
|
+
const occurrenceCount =
|
|
8243
|
+
start === -1 ? 0 : content.indexOf(from, start + 1) === -1 ? 1 : 2;
|
|
8244
|
+
if (occurrenceCount !== 1) {
|
|
8245
|
+
const fileRelativeUrl = urlToRelativeUrl(
|
|
8246
|
+
urlInfo.url,
|
|
8247
|
+
urlInfo.context.rootDirectoryUrl,
|
|
8248
|
+
);
|
|
8249
|
+
throw new Error(
|
|
8250
|
+
`patch cannot apply on "${fileRelativeUrl}": ${JSON.stringify(from)} found ${occurrenceCount === 0 ? "nowhere" : "more than once"} in the file. The file may have changed since the patch was written.`,
|
|
8251
|
+
);
|
|
8252
|
+
}
|
|
8253
|
+
magicSource.replace({
|
|
8254
|
+
start,
|
|
8255
|
+
end: start + from.length,
|
|
8256
|
+
replacement: to,
|
|
8257
|
+
});
|
|
8258
|
+
}
|
|
8259
|
+
return magicSource.toContentAndSourcemap();
|
|
8260
|
+
},
|
|
8261
|
+
};
|
|
8262
|
+
return [patchesPlugin];
|
|
8263
|
+
};
|
|
8264
|
+
|
|
8265
|
+
const assertPatches = (patches, key) => {
|
|
8266
|
+
if (!Array.isArray(patches)) {
|
|
8267
|
+
throw new TypeError(
|
|
8268
|
+
`patches["${key}"] must be an array of { from, to }, got ${patches}`,
|
|
8269
|
+
);
|
|
8270
|
+
}
|
|
8271
|
+
for (const patch of patches) {
|
|
8272
|
+
if (
|
|
8273
|
+
!patch ||
|
|
8274
|
+
typeof patch.from !== "string" ||
|
|
8275
|
+
patch.from === "" ||
|
|
8276
|
+
typeof patch.to !== "string"
|
|
8277
|
+
) {
|
|
8278
|
+
throw new TypeError(
|
|
8279
|
+
`patches["${key}"] entries must be { from: string, to: string } with a non-empty "from"`,
|
|
8280
|
+
);
|
|
8281
|
+
}
|
|
8282
|
+
}
|
|
8283
|
+
};
|
|
8284
|
+
|
|
8285
|
+
// "./x", "../x", "/x", "file:///x" and "**/x" are url patterns; anything
|
|
8286
|
+
// else names a path inside a package, looked up in node_modules
|
|
8287
|
+
const resolvePatchKey = (key, rootDirectoryUrl) => {
|
|
8288
|
+
if (
|
|
8289
|
+
key.startsWith("./") ||
|
|
8290
|
+
key.startsWith("../") ||
|
|
8291
|
+
key.startsWith("/") ||
|
|
8292
|
+
key.startsWith("file:") ||
|
|
8293
|
+
key.startsWith("*")
|
|
8294
|
+
) {
|
|
8295
|
+
return key;
|
|
8296
|
+
}
|
|
8297
|
+
const segments = key.split("/");
|
|
8298
|
+
const packageName = key.startsWith("@")
|
|
8299
|
+
? `${segments[0]}/${segments[1]}`
|
|
8300
|
+
: segments[0];
|
|
8301
|
+
const pathInsidePackage = key.slice(packageName.length);
|
|
8302
|
+
let directoryUrl = new URL(rootDirectoryUrl);
|
|
8303
|
+
while (true) {
|
|
8304
|
+
const packageDirectoryUrl = new URL(
|
|
8305
|
+
`./node_modules/${packageName}/`,
|
|
8306
|
+
directoryUrl,
|
|
8307
|
+
);
|
|
8308
|
+
if (existsSync(packageDirectoryUrl)) {
|
|
8309
|
+
return String(new URL(`.${pathInsidePackage}`, packageDirectoryUrl));
|
|
8310
|
+
}
|
|
8311
|
+
const parentDirectoryUrl = new URL("../", directoryUrl);
|
|
8312
|
+
if (parentDirectoryUrl.href === directoryUrl.href) {
|
|
8313
|
+
throw new Error(
|
|
8314
|
+
`patches["${key}"]: package "${packageName}" not found in any node_modules above ${rootDirectoryUrl}`,
|
|
8315
|
+
);
|
|
8316
|
+
}
|
|
8317
|
+
directoryUrl = parentDirectoryUrl;
|
|
8318
|
+
}
|
|
8319
|
+
};
|
|
8320
|
+
|
|
8187
8321
|
/*
|
|
8188
8322
|
* Some code uses globals specific to Node.js in code meant to run in browsers...
|
|
8189
8323
|
* This plugin will replace some node globals to things compatible with web:
|
|
@@ -10320,6 +10454,7 @@ const getCorePlugins = ({
|
|
|
10320
10454
|
directoryListing = true,
|
|
10321
10455
|
directoryReferenceEffect,
|
|
10322
10456
|
supervisor,
|
|
10457
|
+
patches,
|
|
10323
10458
|
injections,
|
|
10324
10459
|
transpilation = true,
|
|
10325
10460
|
inlining = true,
|
|
@@ -10361,6 +10496,9 @@ const getCorePlugins = ({
|
|
|
10361
10496
|
...(packageBundle
|
|
10362
10497
|
? [jsenvPluginWorkspaceBundle({ packageDirectory })]
|
|
10363
10498
|
: []),
|
|
10499
|
+
// before everything else: what the other plugins read must be the
|
|
10500
|
+
// patched file
|
|
10501
|
+
...jsenvPluginPatches(patches),
|
|
10364
10502
|
// before reference analysis: an url written by an injection must hold its
|
|
10365
10503
|
// final value when references are analyzed
|
|
10366
10504
|
jsenvPluginInjections(injections),
|
|
@@ -12645,6 +12783,17 @@ const jsenvPluginMappings = (mappings) => {
|
|
|
12645
12783
|
* }
|
|
12646
12784
|
*
|
|
12647
12785
|
* See startDevServer "ribbon" param for the full list of options.
|
|
12786
|
+
* @param {object} [entryPoint.patches]
|
|
12787
|
+
* Text patches applied to files as they are built, as { file: [{ from, to }] }.
|
|
12788
|
+
* A key is a url pattern relative to sourceDirectoryUrl ("./main.js") or a path
|
|
12789
|
+
* inside a package ("preact/dist/preact.mjs", found in node_modules the way node does):
|
|
12790
|
+
*
|
|
12791
|
+
* patches: {
|
|
12792
|
+
* "preact/dist/preact.mjs": [{ from: "a&&b", to: "a&&b&&c" }],
|
|
12793
|
+
* }
|
|
12794
|
+
*
|
|
12795
|
+
* Each `from` must occur exactly once in the file, otherwise the build fails
|
|
12796
|
+
* and says which patch did not apply.
|
|
12648
12797
|
* @param {object} [entryPoint.injections]
|
|
12649
12798
|
* Values to inject into files, as { urlPattern: getInjections }.
|
|
12650
12799
|
* Keys are url patterns relative to sourceDirectoryUrl ("./index.html", "**\/*.js"),
|
|
@@ -13510,6 +13659,7 @@ const entryPointDefaultParams = {
|
|
|
13510
13659
|
directoryReferenceEffect: undefined,
|
|
13511
13660
|
scenarioPlaceholders: undefined,
|
|
13512
13661
|
ribbon: false,
|
|
13662
|
+
patches: undefined,
|
|
13513
13663
|
injections: undefined,
|
|
13514
13664
|
transpilation: {},
|
|
13515
13665
|
preserveComments: undefined,
|
|
@@ -13566,6 +13716,7 @@ const prepareEntryPointBuild = async (
|
|
|
13566
13716
|
directoryReferenceEffect,
|
|
13567
13717
|
scenarioPlaceholders,
|
|
13568
13718
|
ribbon,
|
|
13719
|
+
patches,
|
|
13569
13720
|
injections,
|
|
13570
13721
|
transpilation,
|
|
13571
13722
|
preserveComments,
|
|
@@ -13734,6 +13885,7 @@ const prepareEntryPointBuild = async (
|
|
|
13734
13885
|
magicExtensions,
|
|
13735
13886
|
magicDirectoryIndex,
|
|
13736
13887
|
directoryReferenceEffect,
|
|
13888
|
+
patches,
|
|
13737
13889
|
injections,
|
|
13738
13890
|
transpilation: {
|
|
13739
13891
|
babelHelpersAsImport: !explicitJsModuleConversion,
|
|
@@ -4,7 +4,7 @@ let createEventsManager;
|
|
|
4
4
|
const callbacksMap = new Map();
|
|
5
5
|
let cleanup;
|
|
6
6
|
const addCallbacks = (namedCallbacks) => {
|
|
7
|
-
|
|
7
|
+
const sizeBeforeAdd = callbacksMap.size;
|
|
8
8
|
Object.keys(namedCallbacks).forEach((eventName) => {
|
|
9
9
|
const callback = namedCallbacks[eventName];
|
|
10
10
|
const existingCallbacks = callbacksMap.get(eventName);
|
|
@@ -17,7 +17,10 @@ let createEventsManager;
|
|
|
17
17
|
}
|
|
18
18
|
callbacks.push(callback);
|
|
19
19
|
});
|
|
20
|
-
|
|
20
|
+
// the effect owns whatever the callbacks need to exist (the connection),
|
|
21
|
+
// so it runs on the edge into "someone is listening" and its cleanup runs
|
|
22
|
+
// on the edge back out of it
|
|
23
|
+
if (sizeBeforeAdd === 0 && callbacksMap.size > 0) {
|
|
21
24
|
cleanup = effect();
|
|
22
25
|
}
|
|
23
26
|
|
|
@@ -25,7 +28,7 @@ let createEventsManager;
|
|
|
25
28
|
return () => {
|
|
26
29
|
if (removed) return;
|
|
27
30
|
removed = true;
|
|
28
|
-
|
|
31
|
+
const sizeBeforeRemove = callbacksMap.size;
|
|
29
32
|
Object.keys(namedCallbacks).forEach((eventName) => {
|
|
30
33
|
const callback = namedCallbacks[eventName];
|
|
31
34
|
const callbacks = callbacksMap.get(eventName);
|
|
@@ -40,12 +43,7 @@ let createEventsManager;
|
|
|
40
43
|
}
|
|
41
44
|
});
|
|
42
45
|
namedCallbacks = null; // allow garbage collect
|
|
43
|
-
if (
|
|
44
|
-
cleanup &&
|
|
45
|
-
typeof cleanup === "function" &&
|
|
46
|
-
callbacksMapSize > 0 &&
|
|
47
|
-
callbacksMapSize.size === 0
|
|
48
|
-
) {
|
|
46
|
+
if (cleanup && sizeBeforeRemove > 0 && callbacksMap.size === 0) {
|
|
49
47
|
cleanup();
|
|
50
48
|
cleanup = null;
|
|
51
49
|
}
|
|
@@ -85,9 +83,14 @@ let createConnectionManager;
|
|
|
85
83
|
CLOSED: "closed",
|
|
86
84
|
};
|
|
87
85
|
|
|
86
|
+
// The peer is a dev server being restarted, not a flaky network: there is no
|
|
87
|
+
// attempt count or time budget past which giving up would be correct. Attempts
|
|
88
|
+
// space out to retryAfterMax and never stop, and the moments where the server
|
|
89
|
+
// is most likely to be back already (tab visible, window focused, network
|
|
90
|
+
// back) skip the wait instead of leaving a detached page on screen.
|
|
88
91
|
createConnectionManager = (
|
|
89
92
|
attemptConnection,
|
|
90
|
-
{ logs, retry, retryAfter,
|
|
93
|
+
{ logs, retry, retryAfter, retryAfterMax },
|
|
91
94
|
) => {
|
|
92
95
|
const readyState = {
|
|
93
96
|
value: READY_STATES.CLOSED,
|
|
@@ -102,6 +105,7 @@ let createConnectionManager;
|
|
|
102
105
|
};
|
|
103
106
|
|
|
104
107
|
let _disconnect = () => {};
|
|
108
|
+
let _retryNow = () => {};
|
|
105
109
|
const connect = () => {
|
|
106
110
|
if (
|
|
107
111
|
readyState.value === READY_STATES.CONNECTING ||
|
|
@@ -110,11 +114,11 @@ let createConnectionManager;
|
|
|
110
114
|
return;
|
|
111
115
|
}
|
|
112
116
|
|
|
113
|
-
let
|
|
114
|
-
let
|
|
117
|
+
let retryDelay = retryAfter;
|
|
118
|
+
let retryTimeout = null;
|
|
115
119
|
const attempt = () => {
|
|
120
|
+
retryTimeout = null;
|
|
116
121
|
readyState.goTo(READY_STATES.CONNECTING);
|
|
117
|
-
let timeout;
|
|
118
122
|
const cancelAttempt = attemptConnection({
|
|
119
123
|
onClosed: () => {
|
|
120
124
|
if (!retry) {
|
|
@@ -124,24 +128,6 @@ let createConnectionManager;
|
|
|
124
128
|
}
|
|
125
129
|
return;
|
|
126
130
|
}
|
|
127
|
-
if (retryCount > retryMaxAttempt) {
|
|
128
|
-
readyState.goTo(READY_STATES.CLOSED);
|
|
129
|
-
if (logs) {
|
|
130
|
-
console.info(
|
|
131
|
-
`[jsenv] could not connect to server after ${retryMaxAttempt} attempt`,
|
|
132
|
-
);
|
|
133
|
-
}
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
if (retryAllocatedMs && msSpent > retryAllocatedMs) {
|
|
137
|
-
readyState.goTo(READY_STATES.CLOSED);
|
|
138
|
-
if (logs) {
|
|
139
|
-
console.info(
|
|
140
|
-
`[jsenv] could not connect to server in less than ${retryAllocatedMs}ms`,
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
131
|
// if closed while open -> connection lost
|
|
146
132
|
// otherwise it's the attempt to connect for the first time
|
|
147
133
|
// or to reconnect
|
|
@@ -152,22 +138,31 @@ let createConnectionManager;
|
|
|
152
138
|
);
|
|
153
139
|
}
|
|
154
140
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}, retryAfter);
|
|
141
|
+
retryTimeout = setTimeout(attempt, retryDelay);
|
|
142
|
+
const nextRetryDelay = retryDelay * 2;
|
|
143
|
+
retryDelay =
|
|
144
|
+
nextRetryDelay > retryAfterMax ? retryAfterMax : nextRetryDelay;
|
|
160
145
|
},
|
|
161
146
|
onOpen: () => {
|
|
147
|
+
retryDelay = retryAfter;
|
|
162
148
|
readyState.goTo(READY_STATES.OPEN);
|
|
163
149
|
},
|
|
164
150
|
});
|
|
165
151
|
_disconnect = () => {
|
|
166
152
|
cancelAttempt();
|
|
167
|
-
clearTimeout(
|
|
153
|
+
clearTimeout(retryTimeout);
|
|
154
|
+
retryTimeout = null;
|
|
168
155
|
readyState.goTo(READY_STATES.CLOSED);
|
|
169
156
|
};
|
|
170
157
|
};
|
|
158
|
+
_retryNow = () => {
|
|
159
|
+
if (retryTimeout === null) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
clearTimeout(retryTimeout);
|
|
163
|
+
retryDelay = retryAfter;
|
|
164
|
+
attempt();
|
|
165
|
+
};
|
|
171
166
|
attempt();
|
|
172
167
|
};
|
|
173
168
|
|
|
@@ -194,6 +189,9 @@ let createConnectionManager;
|
|
|
194
189
|
_disconnect();
|
|
195
190
|
}
|
|
196
191
|
});
|
|
192
|
+
const removeServerMightBeBackListeners = listenServerMightBeBack(() => {
|
|
193
|
+
_retryNow();
|
|
194
|
+
});
|
|
197
195
|
|
|
198
196
|
return {
|
|
199
197
|
readyState,
|
|
@@ -201,11 +199,31 @@ let createConnectionManager;
|
|
|
201
199
|
disconnect,
|
|
202
200
|
destroy: () => {
|
|
203
201
|
removePageUnloadListener();
|
|
202
|
+
removeServerMightBeBackListeners();
|
|
204
203
|
disconnect();
|
|
205
204
|
},
|
|
206
205
|
};
|
|
207
206
|
};
|
|
208
207
|
|
|
208
|
+
const listenServerMightBeBack = (callback) => {
|
|
209
|
+
const removeOnlineListener = listenEvent(window, "online", callback);
|
|
210
|
+
const removeFocusListener = listenEvent(window, "focus", callback);
|
|
211
|
+
const removeVisibilityChangeListener = listenEvent(
|
|
212
|
+
document,
|
|
213
|
+
"visibilitychange",
|
|
214
|
+
() => {
|
|
215
|
+
if (document.visibilityState === "visible") {
|
|
216
|
+
callback();
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
);
|
|
220
|
+
return () => {
|
|
221
|
+
removeOnlineListener();
|
|
222
|
+
removeFocusListener();
|
|
223
|
+
removeVisibilityChangeListener();
|
|
224
|
+
};
|
|
225
|
+
};
|
|
226
|
+
|
|
209
227
|
// const listenPageMightFreeze = (callback) => {
|
|
210
228
|
// const removePageHideListener = listenEvent(window, "pagehide", (pageHideEvent) => {
|
|
211
229
|
// if (pageHideEvent.persisted === true) {
|
|
@@ -271,8 +289,7 @@ let createWebSocketConnection;
|
|
|
271
289
|
useEventsToManageConnection = true,
|
|
272
290
|
retry = false,
|
|
273
291
|
retryAfter = 1000,
|
|
274
|
-
|
|
275
|
-
retryAllocatedMs = Infinity,
|
|
292
|
+
retryAfterMax = 5000,
|
|
276
293
|
} = {},
|
|
277
294
|
) => {
|
|
278
295
|
const connectionManager = createConnectionManager(
|
|
@@ -315,7 +332,7 @@ let createWebSocketConnection;
|
|
|
315
332
|
}
|
|
316
333
|
};
|
|
317
334
|
},
|
|
318
|
-
{ logs, retry, retryAfter,
|
|
335
|
+
{ logs, retry, retryAfter, retryAfterMax },
|
|
319
336
|
);
|
|
320
337
|
const eventsManager = createEventsManager({
|
|
321
338
|
effect: () => {
|
|
@@ -439,7 +456,6 @@ const serverEventsInterface = {
|
|
|
439
456
|
const websocketConnection = createWebSocketConnection(websocketUrl, {
|
|
440
457
|
logs,
|
|
441
458
|
retry: true,
|
|
442
|
-
retryAllocatedMs: 10_000,
|
|
443
459
|
});
|
|
444
460
|
|
|
445
461
|
const { readyState, connect, disconnect, listenEvents } =
|