@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
|
@@ -236,17 +236,29 @@ const isExactVersion = (declaredVersion) => {
|
|
|
236
236
|
};
|
|
237
237
|
|
|
238
238
|
/*
|
|
239
|
-
* Detects
|
|
240
|
-
*
|
|
241
|
-
*
|
|
239
|
+
* Detects the moment "npm install" changes what the running pages get from
|
|
240
|
+
* node_modules, so the browser can be reloaded at that moment:
|
|
241
|
+
*
|
|
242
|
+
* - a declared dependency stops being missing or outdated: what package.json
|
|
243
|
+
* asks for is there;
|
|
244
|
+
* - a package the pages were served changes version: the "?v=" baked into the
|
|
245
|
+
* urls they evaluated names a version node_modules no longer holds. Left
|
|
246
|
+
* alone, the next hot reload of a file importing that package resolves it to
|
|
247
|
+
* the new version and the page evaluates a second copy of the package next
|
|
248
|
+
* to the first, two module scopes for something meant to exist once.
|
|
242
249
|
*
|
|
243
250
|
* node_modules is deliberately not watched: it is far too big, and an install
|
|
244
251
|
* rewrites, dedupes and moves package directories around, so a watcher placed
|
|
245
|
-
* on one of them is unreliable. Instead
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
*
|
|
252
|
+
* on one of them is unreliable. Instead a few package.json files are polled,
|
|
253
|
+
* at the cost of a stat each: the packages known to be missing or outdated,
|
|
254
|
+
* and the packages the pages were served, whose package.json the url graph
|
|
255
|
+
* keeps (that is where "?v=" comes from). The project package.json is watched
|
|
256
|
+
* though: it is a single file, and editing it is what puts a dependency out of
|
|
257
|
+
* date in the first place.
|
|
258
|
+
*
|
|
259
|
+
* Both detections run in the same tick so that an install fixing an outdated
|
|
260
|
+
* package the page runs is reported once: two reports would be two reloads,
|
|
261
|
+
* the second one landing on the page that just came back.
|
|
250
262
|
*/
|
|
251
263
|
|
|
252
264
|
|
|
@@ -254,7 +266,13 @@ const POLL_INTERVAL = 500;
|
|
|
254
266
|
|
|
255
267
|
const watchDependencies = (
|
|
256
268
|
packageDirectory,
|
|
257
|
-
{
|
|
269
|
+
{
|
|
270
|
+
getKitchens = () => [],
|
|
271
|
+
onProblem,
|
|
272
|
+
onInstalled,
|
|
273
|
+
onChange,
|
|
274
|
+
pollInterval = POLL_INTERVAL,
|
|
275
|
+
},
|
|
258
276
|
) => {
|
|
259
277
|
let problemMap = new Map();
|
|
260
278
|
// every path given to the browser is relative to the package directory, the
|
|
@@ -269,11 +287,49 @@ const watchDependencies = (
|
|
|
269
287
|
if (!packageDirectory.url) {
|
|
270
288
|
return watcher;
|
|
271
289
|
}
|
|
272
|
-
let timer = null;
|
|
273
290
|
|
|
274
|
-
const
|
|
291
|
+
const checkServedVersions = (installMap) => {
|
|
292
|
+
for (const kitchen of getKitchens()) {
|
|
293
|
+
for (const urlInfo of kitchen.graph.urlInfoMap.values()) {
|
|
294
|
+
const served = readServedVersion(urlInfo);
|
|
295
|
+
if (!served) {
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
const installedPackageJson = readInstalledPackageJson(
|
|
299
|
+
packageDirectory,
|
|
300
|
+
served.directoryUrl,
|
|
301
|
+
);
|
|
302
|
+
if (!installedPackageJson) {
|
|
303
|
+
// the package is being rewritten, what it becomes is known once it is back
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
const installedVersion = installedPackageJson.version;
|
|
307
|
+
if (installedVersion === served.version) {
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
// the graph learns the file moved, as it would from a watcher: the
|
|
311
|
+
// files importing this package are cooked again with the new "?v="
|
|
312
|
+
// when the page comes back
|
|
313
|
+
urlInfo.onModified();
|
|
314
|
+
if (installMap.has(served.packageName)) {
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
installMap.set(served.packageName, {
|
|
318
|
+
packageName: served.packageName,
|
|
319
|
+
installedVersion,
|
|
320
|
+
servedVersion: served.version,
|
|
321
|
+
declaredVersion: null,
|
|
322
|
+
severity: "warning",
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
const checkDeclared = (installMap) => {
|
|
329
|
+
const statusMap = new Map();
|
|
275
330
|
const nextProblemMap = new Map();
|
|
276
331
|
for (const status of readDependencyStatuses(packageDirectory)) {
|
|
332
|
+
statusMap.set(status.packageName, status);
|
|
277
333
|
if (status.state === "missing" || status.state === "outdated") {
|
|
278
334
|
nextProblemMap.set(status.packageName, status);
|
|
279
335
|
}
|
|
@@ -289,9 +345,21 @@ const watchDependencies = (
|
|
|
289
345
|
}
|
|
290
346
|
}
|
|
291
347
|
for (const [packageName, previousStatus] of problemMap) {
|
|
292
|
-
if (
|
|
293
|
-
|
|
348
|
+
if (nextProblemMap.has(packageName)) {
|
|
349
|
+
continue;
|
|
294
350
|
}
|
|
351
|
+
const install = installMap.get(packageName);
|
|
352
|
+
if (install) {
|
|
353
|
+
install.declaredVersion = previousStatus.declaredVersion;
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
installMap.set(packageName, {
|
|
357
|
+
packageName,
|
|
358
|
+
installedVersion: statusMap.get(packageName).installedVersion,
|
|
359
|
+
servedVersion: null,
|
|
360
|
+
declaredVersion: previousStatus.declaredVersion,
|
|
361
|
+
severity: previousStatus.severity,
|
|
362
|
+
});
|
|
295
363
|
}
|
|
296
364
|
const changed =
|
|
297
365
|
nextProblemMap.size !== problemMap.size ||
|
|
@@ -309,45 +377,96 @@ const watchDependencies = (
|
|
|
309
377
|
if (changed) {
|
|
310
378
|
onChange(watcher.getProblems());
|
|
311
379
|
}
|
|
312
|
-
if (problemMap.size === 0) {
|
|
313
|
-
stopPolling();
|
|
314
|
-
} else {
|
|
315
|
-
startPolling();
|
|
316
|
-
}
|
|
317
380
|
};
|
|
318
381
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
382
|
+
// The declared dependencies are compared with node_modules only when
|
|
383
|
+
// something can have changed for them: the project package.json was edited,
|
|
384
|
+
// or a problem is known and an install may be fixing it. Comparing them at
|
|
385
|
+
// every tick would catch installs halfway and report a package as missing
|
|
386
|
+
// while it is being rewritten.
|
|
387
|
+
const check = ({ declared }) => {
|
|
388
|
+
const installMap = new Map();
|
|
389
|
+
checkServedVersions(installMap);
|
|
390
|
+
if (declared || problemMap.size > 0) {
|
|
391
|
+
checkDeclared(installMap);
|
|
322
392
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
};
|
|
326
|
-
const stopPolling = () => {
|
|
327
|
-
if (!timer) {
|
|
328
|
-
return;
|
|
393
|
+
for (const install of installMap.values()) {
|
|
394
|
+
onInstalled(install);
|
|
329
395
|
}
|
|
330
|
-
clearInterval(timer);
|
|
331
|
-
timer = null;
|
|
332
396
|
};
|
|
333
397
|
|
|
334
398
|
const unwatchPackageJson = registerFileLifecycle(
|
|
335
399
|
new URL("package.json", packageDirectory.url),
|
|
336
400
|
{
|
|
337
|
-
added: check,
|
|
338
|
-
updated: check,
|
|
401
|
+
added: () => check({ declared: true }),
|
|
402
|
+
updated: () => check({ declared: true }),
|
|
339
403
|
keepProcessAlive: false,
|
|
340
404
|
},
|
|
341
405
|
);
|
|
342
|
-
check();
|
|
406
|
+
check({ declared: true });
|
|
407
|
+
const timer = setInterval(() => {
|
|
408
|
+
check({ declared: false });
|
|
409
|
+
}, pollInterval);
|
|
410
|
+
timer.unref();
|
|
343
411
|
|
|
344
412
|
watcher.stop = () => {
|
|
345
|
-
|
|
413
|
+
clearInterval(timer);
|
|
346
414
|
unwatchPackageJson();
|
|
347
415
|
};
|
|
348
416
|
return watcher;
|
|
349
417
|
};
|
|
350
418
|
|
|
419
|
+
// The url graph keeps the content of every package.json it resolved a bare
|
|
420
|
+
// specifier against, and that content holds the version the page received.
|
|
421
|
+
// It is parsed once per content: the same string is seen at every tick.
|
|
422
|
+
const servedVersionCache = new WeakMap();
|
|
423
|
+
const readServedVersion = (urlInfo) => {
|
|
424
|
+
const { url, content } = urlInfo;
|
|
425
|
+
if (content === undefined) {
|
|
426
|
+
return null;
|
|
427
|
+
}
|
|
428
|
+
if (!url.startsWith("file:") || !url.endsWith("/package.json")) {
|
|
429
|
+
return null;
|
|
430
|
+
}
|
|
431
|
+
const nodeModulesIndex = url.lastIndexOf("/node_modules/");
|
|
432
|
+
if (nodeModulesIndex === -1) {
|
|
433
|
+
return null;
|
|
434
|
+
}
|
|
435
|
+
// dereferenced url infos stay in the graph; nothing imports this package anymore
|
|
436
|
+
if (urlInfo.referenceFromOthersSet.size === 0) {
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
439
|
+
const fromCache = servedVersionCache.get(urlInfo);
|
|
440
|
+
if (fromCache && fromCache.content === content) {
|
|
441
|
+
return fromCache;
|
|
442
|
+
}
|
|
443
|
+
let packageJson;
|
|
444
|
+
try {
|
|
445
|
+
packageJson = JSON.parse(content);
|
|
446
|
+
} catch {
|
|
447
|
+
return null;
|
|
448
|
+
}
|
|
449
|
+
const served = {
|
|
450
|
+
content,
|
|
451
|
+
directoryUrl: new URL("./", url).href,
|
|
452
|
+
packageName: packageNameFromSpecifier(
|
|
453
|
+
url.slice(nodeModulesIndex + "/node_modules/".length),
|
|
454
|
+
),
|
|
455
|
+
version: packageJson.version,
|
|
456
|
+
};
|
|
457
|
+
servedVersionCache.set(urlInfo, served);
|
|
458
|
+
return served;
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
// an install in progress can be caught halfway, with a package.json not written yet
|
|
462
|
+
const readInstalledPackageJson = (packageDirectory, directoryUrl) => {
|
|
463
|
+
try {
|
|
464
|
+
return packageDirectory.read(directoryUrl);
|
|
465
|
+
} catch {
|
|
466
|
+
return null;
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
|
|
351
470
|
const jsenvCoreDirectoryUrl = new URL("../", import.meta.url);
|
|
352
471
|
|
|
353
472
|
const createPackageDirectory = ({
|
|
@@ -5652,6 +5771,140 @@ const asInheritedInjections = (injections) => {
|
|
|
5652
5771
|
return inheritedInjections;
|
|
5653
5772
|
};
|
|
5654
5773
|
|
|
5774
|
+
/*
|
|
5775
|
+
* Text patches applied to files as they are served and built, keyed by file:
|
|
5776
|
+
*
|
|
5777
|
+
* patches: {
|
|
5778
|
+
* "preact/dist/preact.mjs": [{ from: "a&&b", to: "a&&b&&c" }],
|
|
5779
|
+
* }
|
|
5780
|
+
*
|
|
5781
|
+
* A key is a url pattern relative to the root directory ("./main.js",
|
|
5782
|
+
* "**\/*.css"), or a path inside a package ("preact/dist/preact.mjs") found by
|
|
5783
|
+
* walking up from the root directory into node_modules, the way node does,
|
|
5784
|
+
* so the key holds wherever the package manager hoists the package.
|
|
5785
|
+
*
|
|
5786
|
+
* Every `from` must occur exactly once in the file, otherwise the file fails
|
|
5787
|
+
* to cook and says which patch did not apply: a dependency update that moved
|
|
5788
|
+
* the patched code must be looked at, never silently unpatched.
|
|
5789
|
+
*/
|
|
5790
|
+
|
|
5791
|
+
|
|
5792
|
+
const jsenvPluginPatches = (rawPatches) => {
|
|
5793
|
+
if (!rawPatches || Object.keys(rawPatches).length === 0) {
|
|
5794
|
+
return [];
|
|
5795
|
+
}
|
|
5796
|
+
let findPatches;
|
|
5797
|
+
const patchesPlugin = {
|
|
5798
|
+
name: "jsenv:patches",
|
|
5799
|
+
appliesDuring: "*",
|
|
5800
|
+
init: (context) => {
|
|
5801
|
+
const { rootDirectoryUrl } = context;
|
|
5802
|
+
const patchesByPattern = {};
|
|
5803
|
+
for (const key of Object.keys(rawPatches)) {
|
|
5804
|
+
const patches = rawPatches[key];
|
|
5805
|
+
assertPatches(patches, key);
|
|
5806
|
+
patchesByPattern[resolvePatchKey(key, rootDirectoryUrl)] = patches;
|
|
5807
|
+
}
|
|
5808
|
+
const associations = URL_META.resolveAssociations(
|
|
5809
|
+
{ patches: patchesByPattern },
|
|
5810
|
+
rootDirectoryUrl,
|
|
5811
|
+
);
|
|
5812
|
+
findPatches = (url) => {
|
|
5813
|
+
const { patches } = URL_META.applyAssociations({
|
|
5814
|
+
url: asUrlWithoutSearch(url),
|
|
5815
|
+
associations,
|
|
5816
|
+
});
|
|
5817
|
+
return patches;
|
|
5818
|
+
};
|
|
5819
|
+
},
|
|
5820
|
+
transformUrlContent: (urlInfo) => {
|
|
5821
|
+
const patches = findPatches(urlInfo.url);
|
|
5822
|
+
if (!patches) {
|
|
5823
|
+
return null;
|
|
5824
|
+
}
|
|
5825
|
+
const { content } = urlInfo;
|
|
5826
|
+
const magicSource = createMagicSource(content);
|
|
5827
|
+
for (const { from, to } of patches) {
|
|
5828
|
+
const start = content.indexOf(from);
|
|
5829
|
+
const occurrenceCount =
|
|
5830
|
+
start === -1 ? 0 : content.indexOf(from, start + 1) === -1 ? 1 : 2;
|
|
5831
|
+
if (occurrenceCount !== 1) {
|
|
5832
|
+
const fileRelativeUrl = urlToRelativeUrl(
|
|
5833
|
+
urlInfo.url,
|
|
5834
|
+
urlInfo.context.rootDirectoryUrl,
|
|
5835
|
+
);
|
|
5836
|
+
throw new Error(
|
|
5837
|
+
`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.`,
|
|
5838
|
+
);
|
|
5839
|
+
}
|
|
5840
|
+
magicSource.replace({
|
|
5841
|
+
start,
|
|
5842
|
+
end: start + from.length,
|
|
5843
|
+
replacement: to,
|
|
5844
|
+
});
|
|
5845
|
+
}
|
|
5846
|
+
return magicSource.toContentAndSourcemap();
|
|
5847
|
+
},
|
|
5848
|
+
};
|
|
5849
|
+
return [patchesPlugin];
|
|
5850
|
+
};
|
|
5851
|
+
|
|
5852
|
+
const assertPatches = (patches, key) => {
|
|
5853
|
+
if (!Array.isArray(patches)) {
|
|
5854
|
+
throw new TypeError(
|
|
5855
|
+
`patches["${key}"] must be an array of { from, to }, got ${patches}`,
|
|
5856
|
+
);
|
|
5857
|
+
}
|
|
5858
|
+
for (const patch of patches) {
|
|
5859
|
+
if (
|
|
5860
|
+
!patch ||
|
|
5861
|
+
typeof patch.from !== "string" ||
|
|
5862
|
+
patch.from === "" ||
|
|
5863
|
+
typeof patch.to !== "string"
|
|
5864
|
+
) {
|
|
5865
|
+
throw new TypeError(
|
|
5866
|
+
`patches["${key}"] entries must be { from: string, to: string } with a non-empty "from"`,
|
|
5867
|
+
);
|
|
5868
|
+
}
|
|
5869
|
+
}
|
|
5870
|
+
};
|
|
5871
|
+
|
|
5872
|
+
// "./x", "../x", "/x", "file:///x" and "**/x" are url patterns; anything
|
|
5873
|
+
// else names a path inside a package, looked up in node_modules
|
|
5874
|
+
const resolvePatchKey = (key, rootDirectoryUrl) => {
|
|
5875
|
+
if (
|
|
5876
|
+
key.startsWith("./") ||
|
|
5877
|
+
key.startsWith("../") ||
|
|
5878
|
+
key.startsWith("/") ||
|
|
5879
|
+
key.startsWith("file:") ||
|
|
5880
|
+
key.startsWith("*")
|
|
5881
|
+
) {
|
|
5882
|
+
return key;
|
|
5883
|
+
}
|
|
5884
|
+
const segments = key.split("/");
|
|
5885
|
+
const packageName = key.startsWith("@")
|
|
5886
|
+
? `${segments[0]}/${segments[1]}`
|
|
5887
|
+
: segments[0];
|
|
5888
|
+
const pathInsidePackage = key.slice(packageName.length);
|
|
5889
|
+
let directoryUrl = new URL(rootDirectoryUrl);
|
|
5890
|
+
while (true) {
|
|
5891
|
+
const packageDirectoryUrl = new URL(
|
|
5892
|
+
`./node_modules/${packageName}/`,
|
|
5893
|
+
directoryUrl,
|
|
5894
|
+
);
|
|
5895
|
+
if (existsSync(packageDirectoryUrl)) {
|
|
5896
|
+
return String(new URL(`.${pathInsidePackage}`, packageDirectoryUrl));
|
|
5897
|
+
}
|
|
5898
|
+
const parentDirectoryUrl = new URL("../", directoryUrl);
|
|
5899
|
+
if (parentDirectoryUrl.href === directoryUrl.href) {
|
|
5900
|
+
throw new Error(
|
|
5901
|
+
`patches["${key}"]: package "${packageName}" not found in any node_modules above ${rootDirectoryUrl}`,
|
|
5902
|
+
);
|
|
5903
|
+
}
|
|
5904
|
+
directoryUrl = parentDirectoryUrl;
|
|
5905
|
+
}
|
|
5906
|
+
};
|
|
5907
|
+
|
|
5655
5908
|
const jsenvPluginInliningAsDataUrl = () => {
|
|
5656
5909
|
return {
|
|
5657
5910
|
name: "jsenv:inlining_as_data_url",
|
|
@@ -8101,6 +8354,7 @@ const getCorePlugins = ({
|
|
|
8101
8354
|
directoryListing = true,
|
|
8102
8355
|
directoryReferenceEffect,
|
|
8103
8356
|
supervisor,
|
|
8357
|
+
patches,
|
|
8104
8358
|
injections,
|
|
8105
8359
|
transpilation = true,
|
|
8106
8360
|
inlining = true,
|
|
@@ -8142,6 +8396,9 @@ const getCorePlugins = ({
|
|
|
8142
8396
|
...(packageBundle
|
|
8143
8397
|
? [jsenvPluginWorkspaceBundle({ packageDirectory })]
|
|
8144
8398
|
: []),
|
|
8399
|
+
// before everything else: what the other plugins read must be the
|
|
8400
|
+
// patched file
|
|
8401
|
+
...jsenvPluginPatches(patches),
|
|
8145
8402
|
// before reference analysis: an url written by an injection must hold its
|
|
8146
8403
|
// final value when references are analyzed
|
|
8147
8404
|
jsenvPluginInjections(injections),
|
|
@@ -12278,6 +12535,7 @@ const EXECUTED_BY_TEST_PLAN = process.argv.includes("--jsenv-test");
|
|
|
12278
12535
|
* @param {boolean} [params.supervisor=true] - Script supervisor (better error reporting).
|
|
12279
12536
|
* @param {boolean} [params.modulepreload=false] - Send `Link: <url>; rel=modulepreload` response headers listing the static import graph of a page (as far as the graph knows it). Off by default, to enable once the server runs on http/2 or http/3: over http/1.1 the preloads take the 6 connections per origin ahead of the render-blocking scripts, and pages get slower (the why, with measures, in jsenv_plugin_modulepreload.js).
|
|
12280
12537
|
* @param {boolean|object} [params.directoryListing=true] - Directory listing pages.
|
|
12538
|
+
* @param {object} [params.patches] - Text patches applied to files as they are served, as `{ file: [{ from, to }] }`. A key is a url pattern relative to sourceDirectoryUrl (`"./main.js"`) or a path inside a package (`"preact/dist/preact.mjs"`, found in node_modules the way node does). Each `from` must occur exactly once in the file, otherwise the file fails to load and says which patch did not apply.
|
|
12281
12539
|
* @param {object} [params.injections] - Values to inject into files, as `{ urlPattern: getInjections }`. Keys are url patterns relative to sourceDirectoryUrl (`"./index.html"`, `"**\/*.js"`), values are functions receiving `urlInfo` and returning (or resolving to) an object of placeholders to replace, named `__LIKE_THIS__` by convention. In JS the value is injected as a JS literal (a string brings its own quotes), everywhere else as-is so it can be concatenated: `href="__BACKEND_URL__/users/me"`. An html url pattern also covers what is inlined in that html, so `<script>window.backendUrl = __BACKEND_URL__;</script>` shares the value with every js file of the page. See `INJECTIONS.optional` and `INJECTIONS.global`.
|
|
12282
12540
|
* @param {object} [params.runtimeCompat] - Target runtimes; warns when dev code wouldn't survive the build.
|
|
12283
12541
|
* @param {string} [params.sourcemaps="inline"] - Sourcemap mode.
|
|
@@ -12339,6 +12597,7 @@ const startDevServer = async ({
|
|
|
12339
12597
|
magicExtensions,
|
|
12340
12598
|
magicDirectoryIndex,
|
|
12341
12599
|
directoryListing,
|
|
12600
|
+
patches,
|
|
12342
12601
|
injections,
|
|
12343
12602
|
transpilation,
|
|
12344
12603
|
cacheControl = true,
|
|
@@ -12444,6 +12703,7 @@ const startDevServer = async ({
|
|
|
12444
12703
|
};
|
|
12445
12704
|
const dependencyProblemEventEmitter = createEventEmitter();
|
|
12446
12705
|
const dependencyWatcher = watchDependencies(packageDirectory, {
|
|
12706
|
+
getKitchens: () => kitchenCache.values(),
|
|
12447
12707
|
onChange: (problems) => {
|
|
12448
12708
|
dependencyProblemEventEmitter.emit(problems);
|
|
12449
12709
|
},
|
|
@@ -12465,11 +12725,30 @@ const startDevServer = async ({
|
|
|
12465
12725
|
logger.info(message);
|
|
12466
12726
|
}
|
|
12467
12727
|
},
|
|
12468
|
-
onInstalled: ({
|
|
12469
|
-
|
|
12728
|
+
onInstalled: ({
|
|
12729
|
+
packageName,
|
|
12730
|
+
installedVersion,
|
|
12731
|
+
servedVersion,
|
|
12732
|
+
declaredVersion,
|
|
12733
|
+
severity,
|
|
12734
|
+
}) => {
|
|
12735
|
+
if (servedVersion) {
|
|
12736
|
+
logger.info(
|
|
12737
|
+
`"${packageName}@${installedVersion}" is now installed, the page runs ${servedVersion}`,
|
|
12738
|
+
);
|
|
12739
|
+
} else {
|
|
12740
|
+
logger.info(`"${packageName}@${declaredVersion}" is now installed`);
|
|
12741
|
+
}
|
|
12470
12742
|
if (severity !== "warning") {
|
|
12471
12743
|
return;
|
|
12472
12744
|
}
|
|
12745
|
+
if (servedVersion) {
|
|
12746
|
+
reloadRequestEventEmitter.emit({
|
|
12747
|
+
cause: `${packageName} ${servedVersion} -> ${installedVersion} installed`,
|
|
12748
|
+
reason: `a dependency the page runs changed version in node_modules`,
|
|
12749
|
+
});
|
|
12750
|
+
return;
|
|
12751
|
+
}
|
|
12473
12752
|
reloadRequestEventEmitter.emit({
|
|
12474
12753
|
cause: `${packageName}@${declaredVersion} installed`,
|
|
12475
12754
|
reason: `a dependency became available in node_modules`,
|
|
@@ -12506,6 +12785,7 @@ const startDevServer = async ({
|
|
|
12506
12785
|
magicDirectoryIndex,
|
|
12507
12786
|
directoryListing,
|
|
12508
12787
|
supervisor,
|
|
12788
|
+
patches,
|
|
12509
12789
|
injections,
|
|
12510
12790
|
transpilation,
|
|
12511
12791
|
spa,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/core",
|
|
3
|
-
"version": "41.5.
|
|
3
|
+
"version": "41.5.22",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Tool to develop, test and build js projects",
|
|
6
6
|
"repository": {
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
"@jsenv/plugin-minification": "1.7.22",
|
|
80
80
|
"@jsenv/plugin-supervisor": "1.8.24",
|
|
81
81
|
"@jsenv/plugin-transpilation": "1.7.9",
|
|
82
|
-
"@jsenv/server": "17.6.
|
|
82
|
+
"@jsenv/server": "17.6.5",
|
|
83
83
|
"@jsenv/sourcemap": "1.4.9",
|
|
84
84
|
"react-table": "7.8.0"
|
|
85
85
|
},
|
package/src/build/build.js
CHANGED
|
@@ -149,6 +149,17 @@ import { jsenvPluginMappings } from "./jsenv_plugin_mappings.js";
|
|
|
149
149
|
* }
|
|
150
150
|
*
|
|
151
151
|
* See startDevServer "ribbon" param for the full list of options.
|
|
152
|
+
* @param {object} [entryPoint.patches]
|
|
153
|
+
* Text patches applied to files as they are built, as { file: [{ from, to }] }.
|
|
154
|
+
* A key is a url pattern relative to sourceDirectoryUrl ("./main.js") or a path
|
|
155
|
+
* inside a package ("preact/dist/preact.mjs", found in node_modules the way node does):
|
|
156
|
+
*
|
|
157
|
+
* patches: {
|
|
158
|
+
* "preact/dist/preact.mjs": [{ from: "a&&b", to: "a&&b&&c" }],
|
|
159
|
+
* }
|
|
160
|
+
*
|
|
161
|
+
* Each `from` must occur exactly once in the file, otherwise the build fails
|
|
162
|
+
* and says which patch did not apply.
|
|
152
163
|
* @param {object} [entryPoint.injections]
|
|
153
164
|
* Values to inject into files, as { urlPattern: getInjections }.
|
|
154
165
|
* Keys are url patterns relative to sourceDirectoryUrl ("./index.html", "**\/*.js"),
|
|
@@ -1016,6 +1027,7 @@ const entryPointDefaultParams = {
|
|
|
1016
1027
|
directoryReferenceEffect: undefined,
|
|
1017
1028
|
scenarioPlaceholders: undefined,
|
|
1018
1029
|
ribbon: false,
|
|
1030
|
+
patches: undefined,
|
|
1019
1031
|
injections: undefined,
|
|
1020
1032
|
transpilation: {},
|
|
1021
1033
|
preserveComments: undefined,
|
|
@@ -1072,6 +1084,7 @@ const prepareEntryPointBuild = async (
|
|
|
1072
1084
|
directoryReferenceEffect,
|
|
1073
1085
|
scenarioPlaceholders,
|
|
1074
1086
|
ribbon,
|
|
1087
|
+
patches,
|
|
1075
1088
|
injections,
|
|
1076
1089
|
transpilation,
|
|
1077
1090
|
preserveComments,
|
|
@@ -1240,6 +1253,7 @@ const prepareEntryPointBuild = async (
|
|
|
1240
1253
|
magicExtensions,
|
|
1241
1254
|
magicDirectoryIndex,
|
|
1242
1255
|
directoryReferenceEffect,
|
|
1256
|
+
patches,
|
|
1243
1257
|
injections,
|
|
1244
1258
|
transpilation: {
|
|
1245
1259
|
babelHelpersAsImport: !explicitJsModuleConversion,
|
|
@@ -60,6 +60,7 @@ const EXECUTED_BY_TEST_PLAN = process.argv.includes("--jsenv-test");
|
|
|
60
60
|
* @param {boolean} [params.supervisor=true] - Script supervisor (better error reporting).
|
|
61
61
|
* @param {boolean} [params.modulepreload=false] - Send `Link: <url>; rel=modulepreload` response headers listing the static import graph of a page (as far as the graph knows it). Off by default, to enable once the server runs on http/2 or http/3: over http/1.1 the preloads take the 6 connections per origin ahead of the render-blocking scripts, and pages get slower (the why, with measures, in jsenv_plugin_modulepreload.js).
|
|
62
62
|
* @param {boolean|object} [params.directoryListing=true] - Directory listing pages.
|
|
63
|
+
* @param {object} [params.patches] - Text patches applied to files as they are served, as `{ file: [{ from, to }] }`. A key is a url pattern relative to sourceDirectoryUrl (`"./main.js"`) or a path inside a package (`"preact/dist/preact.mjs"`, found in node_modules the way node does). Each `from` must occur exactly once in the file, otherwise the file fails to load and says which patch did not apply.
|
|
63
64
|
* @param {object} [params.injections] - Values to inject into files, as `{ urlPattern: getInjections }`. Keys are url patterns relative to sourceDirectoryUrl (`"./index.html"`, `"**\/*.js"`), values are functions receiving `urlInfo` and returning (or resolving to) an object of placeholders to replace, named `__LIKE_THIS__` by convention. In JS the value is injected as a JS literal (a string brings its own quotes), everywhere else as-is so it can be concatenated: `href="__BACKEND_URL__/users/me"`. An html url pattern also covers what is inlined in that html, so `<script>window.backendUrl = __BACKEND_URL__;</script>` shares the value with every js file of the page. See `INJECTIONS.optional` and `INJECTIONS.global`.
|
|
64
65
|
* @param {object} [params.runtimeCompat] - Target runtimes; warns when dev code wouldn't survive the build.
|
|
65
66
|
* @param {string} [params.sourcemaps="inline"] - Sourcemap mode.
|
|
@@ -121,6 +122,7 @@ export const startDevServer = async ({
|
|
|
121
122
|
magicExtensions,
|
|
122
123
|
magicDirectoryIndex,
|
|
123
124
|
directoryListing,
|
|
125
|
+
patches,
|
|
124
126
|
injections,
|
|
125
127
|
transpilation,
|
|
126
128
|
cacheControl = true,
|
|
@@ -227,6 +229,7 @@ export const startDevServer = async ({
|
|
|
227
229
|
};
|
|
228
230
|
const dependencyProblemEventEmitter = createEventEmitter();
|
|
229
231
|
const dependencyWatcher = watchDependencies(packageDirectory, {
|
|
232
|
+
getKitchens: () => kitchenCache.values(),
|
|
230
233
|
onChange: (problems) => {
|
|
231
234
|
dependencyProblemEventEmitter.emit(problems);
|
|
232
235
|
},
|
|
@@ -248,11 +251,30 @@ export const startDevServer = async ({
|
|
|
248
251
|
logger.info(message);
|
|
249
252
|
}
|
|
250
253
|
},
|
|
251
|
-
onInstalled: ({
|
|
252
|
-
|
|
254
|
+
onInstalled: ({
|
|
255
|
+
packageName,
|
|
256
|
+
installedVersion,
|
|
257
|
+
servedVersion,
|
|
258
|
+
declaredVersion,
|
|
259
|
+
severity,
|
|
260
|
+
}) => {
|
|
261
|
+
if (servedVersion) {
|
|
262
|
+
logger.info(
|
|
263
|
+
`"${packageName}@${installedVersion}" is now installed, the page runs ${servedVersion}`,
|
|
264
|
+
);
|
|
265
|
+
} else {
|
|
266
|
+
logger.info(`"${packageName}@${declaredVersion}" is now installed`);
|
|
267
|
+
}
|
|
253
268
|
if (severity !== "warning") {
|
|
254
269
|
return;
|
|
255
270
|
}
|
|
271
|
+
if (servedVersion) {
|
|
272
|
+
reloadRequestEventEmitter.emit({
|
|
273
|
+
cause: `${packageName} ${servedVersion} -> ${installedVersion} installed`,
|
|
274
|
+
reason: `a dependency the page runs changed version in node_modules`,
|
|
275
|
+
});
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
256
278
|
reloadRequestEventEmitter.emit({
|
|
257
279
|
cause: `${packageName}@${declaredVersion} installed`,
|
|
258
280
|
reason: `a dependency became available in node_modules`,
|
|
@@ -289,6 +311,7 @@ export const startDevServer = async ({
|
|
|
289
311
|
magicDirectoryIndex,
|
|
290
312
|
directoryListing,
|
|
291
313
|
supervisor,
|
|
314
|
+
patches,
|
|
292
315
|
injections,
|
|
293
316
|
transpilation,
|
|
294
317
|
spa,
|