@jsenv/core 41.4.6 → 41.4.8
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 +61 -43
- package/dist/start_dev_server/start_dev_server.js +40 -10
- package/package.json +1 -1
- package/src/build/build.js +1 -0
- package/src/build/build_specifier_manager.js +10 -20
- package/src/build/build_urls_generator.js +43 -22
- package/src/dev/dev_server_plugins/dev_server_plugin_serve_source_files.js +10 -1
- package/src/plugins/client_monitoring/jsenv_plugin_client_monitoring.js +23 -8
- package/src/plugins/resolution_web/jsenv_plugin_web_resolution.js +7 -1
package/dist/build/build.js
CHANGED
|
@@ -6551,7 +6551,13 @@ const jsenvPluginWebResolution = () => {
|
|
|
6551
6551
|
if (ownerUrlInfo.originalUrl?.startsWith("http")) {
|
|
6552
6552
|
return new URL(resource, ownerUrlInfo.originalUrl);
|
|
6553
6553
|
}
|
|
6554
|
-
|
|
6554
|
+
// "/x" is the web meaning of a root-relative url: the root of what is
|
|
6555
|
+
// served, which is the source directory — not the directory the entry
|
|
6556
|
+
// point happens to sit in.
|
|
6557
|
+
const url = new URL(
|
|
6558
|
+
resource.slice(1),
|
|
6559
|
+
ownerUrlInfo.context.rootDirectoryUrl,
|
|
6560
|
+
);
|
|
6555
6561
|
return url;
|
|
6556
6562
|
}
|
|
6557
6563
|
// baseUrl happens second argument to new URL() is different from
|
|
@@ -10364,6 +10370,7 @@ const createBuildSpecifierManager = ({
|
|
|
10364
10370
|
buildDirectoryUrl,
|
|
10365
10371
|
assetsDirectory,
|
|
10366
10372
|
buildUrlsGenerator,
|
|
10373
|
+
entryKey,
|
|
10367
10374
|
base,
|
|
10368
10375
|
length = 8,
|
|
10369
10376
|
|
|
@@ -10402,6 +10409,7 @@ const createBuildSpecifierManager = ({
|
|
|
10402
10409
|
urlInfo,
|
|
10403
10410
|
ownerUrlInfo: reference.ownerUrlInfo,
|
|
10404
10411
|
assetsDirectory,
|
|
10412
|
+
entryKey,
|
|
10405
10413
|
});
|
|
10406
10414
|
}
|
|
10407
10415
|
|
|
@@ -11459,9 +11467,14 @@ const createBuildSpecifierManager = ({
|
|
|
11459
11467
|
let contentKey;
|
|
11460
11468
|
// if to guard for html where versioned build specifier is not generated
|
|
11461
11469
|
if (buildSpecifierVersioned) {
|
|
11462
|
-
|
|
11463
|
-
|
|
11464
|
-
|
|
11470
|
+
// The version goes on the build url, not on the specifier: a
|
|
11471
|
+
// specifier is written for one importer to read (base: "./" makes
|
|
11472
|
+
// it relative to that importer, base: "https://cdn…" makes it
|
|
11473
|
+
// absolute elsewhere) and says nothing about where the file lands.
|
|
11474
|
+
const buildUrlVersioned = injectVersionIntoBuildSpecifier({
|
|
11475
|
+
buildSpecifier: buildUrl,
|
|
11476
|
+
version: versionMap.get(urlInfo),
|
|
11477
|
+
versioningMethod,
|
|
11465
11478
|
});
|
|
11466
11479
|
const buildRelativeUrlVersioned = urlToRelativeUrl(
|
|
11467
11480
|
buildUrlVersioned,
|
|
@@ -11705,23 +11718,6 @@ const injectVersionIntoBuildSpecifier = ({
|
|
|
11705
11718
|
);
|
|
11706
11719
|
};
|
|
11707
11720
|
|
|
11708
|
-
const asBuildUrlVersioned = ({
|
|
11709
|
-
buildSpecifierVersioned,
|
|
11710
|
-
buildDirectoryUrl,
|
|
11711
|
-
}) => {
|
|
11712
|
-
if (buildSpecifierVersioned[0] === "/") {
|
|
11713
|
-
return new URL(buildSpecifierVersioned.slice(1), buildDirectoryUrl).href;
|
|
11714
|
-
}
|
|
11715
|
-
const buildUrl = new URL(buildSpecifierVersioned, buildDirectoryUrl).href;
|
|
11716
|
-
if (buildUrl.startsWith(buildDirectoryUrl)) {
|
|
11717
|
-
return buildUrl;
|
|
11718
|
-
}
|
|
11719
|
-
// it's likely "base" parameter was set to an url origin like "https://cdn.example.com"
|
|
11720
|
-
// let's move url to build directory
|
|
11721
|
-
const { pathname, search, hash } = new URL(buildSpecifierVersioned);
|
|
11722
|
-
return `${buildDirectoryUrl}${pathname}${search}${hash}`;
|
|
11723
|
-
};
|
|
11724
|
-
|
|
11725
11721
|
const targetsAFileThatDoesNotExist = (url) => {
|
|
11726
11722
|
if (!url.startsWith("file:")) {
|
|
11727
11723
|
return false;
|
|
@@ -11759,12 +11755,41 @@ const createBuildUrlsGenerator = ({
|
|
|
11759
11755
|
};
|
|
11760
11756
|
|
|
11761
11757
|
const nameSetPerDirectoryMap = new Map();
|
|
11762
|
-
const
|
|
11763
|
-
|
|
11758
|
+
const reserveName = (directoryPath, urlName) => {
|
|
11759
|
+
let nameSet = nameSetPerDirectoryMap.get(directoryPath);
|
|
11760
|
+
if (!nameSet) {
|
|
11761
|
+
nameSet = new Set();
|
|
11762
|
+
nameSetPerDirectoryMap.set(directoryPath, nameSet);
|
|
11763
|
+
}
|
|
11764
|
+
let [basename, extension] = splitFileExtension(urlName);
|
|
11765
|
+
extension = extensionMappings[extension] || extension;
|
|
11766
|
+
let nameCandidate = `${basename}${extension}`; // reconstruct name in case extension was normalized
|
|
11767
|
+
let integer = 1;
|
|
11768
|
+
while (nameSet.has(nameCandidate)) {
|
|
11769
|
+
integer++;
|
|
11770
|
+
nameCandidate = `${basename}${integer}${extension}`;
|
|
11771
|
+
}
|
|
11772
|
+
nameSet.add(nameCandidate);
|
|
11773
|
+
return nameCandidate;
|
|
11774
|
+
};
|
|
11775
|
+
const generate = (
|
|
11776
|
+
url,
|
|
11777
|
+
{ urlInfo, ownerUrlInfo, assetsDirectory, entryKey },
|
|
11778
|
+
) => {
|
|
11779
|
+
// A url already inside the build directory names a chunk a bundler just
|
|
11780
|
+
// produced, and each entry point is bundled on its own: two of them
|
|
11781
|
+
// routinely produce a chunk of the same name holding different code (the
|
|
11782
|
+
// shared dependencies of THAT entry point, exported under names decided by
|
|
11783
|
+
// THAT bundle). They are told apart by the entry point they come from —
|
|
11784
|
+
// sharing the file would leave one entry importing exports the file on disk
|
|
11785
|
+
// does not have.
|
|
11786
|
+
const insideBuildDirectory = urlIsOrIsInsideOf(url, buildDirectoryUrl);
|
|
11787
|
+
const key = insideBuildDirectory ? `${entryKey} ${url}` : url;
|
|
11788
|
+
const buildUrlFromMap = buildUrlMap.get(key);
|
|
11764
11789
|
if (buildUrlFromMap) {
|
|
11765
11790
|
return buildUrlFromMap;
|
|
11766
11791
|
}
|
|
11767
|
-
if (
|
|
11792
|
+
if (insideBuildDirectory) {
|
|
11768
11793
|
if (ownerUrlInfo.searchParams.has("dynamic_import_id")) {
|
|
11769
11794
|
const ownerDirectoryPath = determineDirectoryPath({
|
|
11770
11795
|
sourceDirectoryUrl,
|
|
@@ -11776,11 +11801,18 @@ const createBuildUrlsGenerator = ({
|
|
|
11776
11801
|
buildUrl = injectQueryParams(buildUrl, {
|
|
11777
11802
|
dynamic_import_id: undefined,
|
|
11778
11803
|
});
|
|
11779
|
-
associateBuildUrl(
|
|
11804
|
+
associateBuildUrl(key, buildUrl);
|
|
11780
11805
|
return buildUrl;
|
|
11781
11806
|
}
|
|
11782
|
-
|
|
11783
|
-
|
|
11807
|
+
const urlObject = new URL(url);
|
|
11808
|
+
const chunkDirectoryPath = urlToRelativeUrl(
|
|
11809
|
+
new URL("./", urlObject),
|
|
11810
|
+
buildDirectoryUrl,
|
|
11811
|
+
);
|
|
11812
|
+
const chunkName = reserveName(chunkDirectoryPath, urlToFilename(url));
|
|
11813
|
+
const buildUrl = `${buildDirectoryUrl}${chunkDirectoryPath}${chunkName}${urlObject.search}${urlObject.hash}`;
|
|
11814
|
+
associateBuildUrl(key, buildUrl);
|
|
11815
|
+
return buildUrl;
|
|
11784
11816
|
}
|
|
11785
11817
|
if (urlInfo.type === "entry_build") {
|
|
11786
11818
|
const buildUrl = new URL(urlInfo.filenameHint, buildDirectoryUrl).href;
|
|
@@ -11812,25 +11844,10 @@ const createBuildUrlsGenerator = ({
|
|
|
11812
11844
|
urlInfo,
|
|
11813
11845
|
ownerUrlInfo,
|
|
11814
11846
|
});
|
|
11815
|
-
let nameSet = nameSetPerDirectoryMap.get(directoryPath);
|
|
11816
|
-
if (!nameSet) {
|
|
11817
|
-
nameSet = new Set();
|
|
11818
|
-
nameSetPerDirectoryMap.set(directoryPath, nameSet);
|
|
11819
|
-
}
|
|
11820
11847
|
const urlObject = new URL(url);
|
|
11821
11848
|
injectQueryParams(urlObject, { dynamic_import_id: undefined });
|
|
11822
11849
|
let { search, hash } = urlObject;
|
|
11823
|
-
|
|
11824
|
-
let [basename, extension] = splitFileExtension(urlName);
|
|
11825
|
-
extension = extensionMappings[extension] || extension;
|
|
11826
|
-
let nameCandidate = `${basename}${extension}`; // reconstruct name in case extension was normalized
|
|
11827
|
-
let integer = 1;
|
|
11828
|
-
while (nameSet.has(nameCandidate)) {
|
|
11829
|
-
integer++;
|
|
11830
|
-
nameCandidate = `${basename}${integer}${extension}`;
|
|
11831
|
-
}
|
|
11832
|
-
const name = nameCandidate;
|
|
11833
|
-
nameSet.add(name);
|
|
11850
|
+
const name = reserveName(directoryPath, getUrlName(url, urlInfo));
|
|
11834
11851
|
const buildUrl = `${buildDirectoryUrl}${directoryPath}${name}${search}${hash}`;
|
|
11835
11852
|
associateBuildUrl(url, buildUrl);
|
|
11836
11853
|
return buildUrl;
|
|
@@ -13217,6 +13234,7 @@ const prepareEntryPointBuild = async (
|
|
|
13217
13234
|
base,
|
|
13218
13235
|
assetsDirectory,
|
|
13219
13236
|
buildUrlsGenerator,
|
|
13237
|
+
entryKey: sourceRelativeUrl,
|
|
13220
13238
|
|
|
13221
13239
|
versioning,
|
|
13222
13240
|
versioningMethod,
|
|
@@ -768,7 +768,9 @@ const parseUserAgentHeader = (userAgent) => {
|
|
|
768
768
|
* - "clients_list" the whole registry (the dashboard renders it)
|
|
769
769
|
* - "client_log" a single log line (a monitor appends it)
|
|
770
770
|
* - "client_activity" a single qualified activity (a monitor appends it)
|
|
771
|
-
* - "client_here" a client just appeared/resumed (every page
|
|
771
|
+
* - "client_here" a networked client just appeared/resumed (every page
|
|
772
|
+
* can toast it); never sent for the main client, which
|
|
773
|
+
* is the person reading the toast
|
|
772
774
|
* - "client_command" pilot one client (navigate/reload its tab) from the
|
|
773
775
|
* dashboard; the matching reporter runs it
|
|
774
776
|
* Server events are broadcast, so consumers filter what they care about.
|
|
@@ -783,7 +785,7 @@ const parseUserAgentHeader = (userAgent) => {
|
|
|
783
785
|
*
|
|
784
786
|
* The monitoring script is injected into EVERY cooked page, including our own
|
|
785
787
|
* dashboard and monitor pages — so opening one of those counts as a connected
|
|
786
|
-
* client, and any open page gets toasted when another client appears.
|
|
788
|
+
* client, and any open page gets toasted when another networked client appears.
|
|
787
789
|
*
|
|
788
790
|
* Pages:
|
|
789
791
|
* - /.internal/clients → dashboard listing every client seen
|
|
@@ -1114,12 +1116,25 @@ const jsenvPluginClientMonitoring = () => {
|
|
|
1114
1116
|
pruneTabs(client);
|
|
1115
1117
|
pruneClients();
|
|
1116
1118
|
|
|
1117
|
-
if (firstEver) {
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
//
|
|
1122
|
-
|
|
1119
|
+
if (firstEver || !wasOnline) {
|
|
1120
|
+
// The toast invites the reader to monitor the client that just appeared,
|
|
1121
|
+
// so it is only worth showing for a client there is something to watch:
|
|
1122
|
+
// one reaching the server over the network. The machine running the dev
|
|
1123
|
+
// server is the person reading the toast — it browses over localhost, its
|
|
1124
|
+
// devtools are one keystroke away and nothing is captured for it, so
|
|
1125
|
+
// announcing it means interrupting someone about themselves. It happens
|
|
1126
|
+
// more than one would think: the client id lives in localStorage, so the
|
|
1127
|
+
// same browser gets a fresh id per origin (localhost vs 127.0.0.1 vs the
|
|
1128
|
+
// LAN ip, another port) and in private windows, and each of those looks
|
|
1129
|
+
// like a brand new client to the server.
|
|
1130
|
+
if (!isLocalIp(client.ip)) {
|
|
1131
|
+
sendClientHere({
|
|
1132
|
+
// A report after a long quiet spell means the client was picked back
|
|
1133
|
+
// up rather than newly seen.
|
|
1134
|
+
reason: firstEver ? "new" : "resumed",
|
|
1135
|
+
client: serializeClient(client),
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1123
1138
|
sendClientsList();
|
|
1124
1139
|
}
|
|
1125
1140
|
|
|
@@ -3228,7 +3243,13 @@ const jsenvPluginWebResolution = () => {
|
|
|
3228
3243
|
if (ownerUrlInfo.originalUrl?.startsWith("http")) {
|
|
3229
3244
|
return new URL(resource, ownerUrlInfo.originalUrl);
|
|
3230
3245
|
}
|
|
3231
|
-
|
|
3246
|
+
// "/x" is the web meaning of a root-relative url: the root of what is
|
|
3247
|
+
// served, which is the source directory — not the directory the entry
|
|
3248
|
+
// point happens to sit in.
|
|
3249
|
+
const url = new URL(
|
|
3250
|
+
resource.slice(1),
|
|
3251
|
+
ownerUrlInfo.context.rootDirectoryUrl,
|
|
3252
|
+
);
|
|
3232
3253
|
return url;
|
|
3233
3254
|
}
|
|
3234
3255
|
// baseUrl happens second argument to new URL() is different from
|
|
@@ -11154,7 +11175,16 @@ const devServerPluginServeSourceFiles = ({
|
|
|
11154
11175
|
}
|
|
11155
11176
|
}
|
|
11156
11177
|
});
|
|
11157
|
-
|
|
11178
|
+
// A client we cannot identify (curl, fetch, a healthcheck, a proxy dropping
|
|
11179
|
+
// the user agent) is assumed to be one of the runtimes the project targets.
|
|
11180
|
+
// Treating it as a runtime supporting nothing would serve it the js module
|
|
11181
|
+
// fallback, breaking tooling perfectly capable of ES modules; an actual
|
|
11182
|
+
// ancient browser hiding its identity fails loudly instead, which is the
|
|
11183
|
+
// cheaper of the two mistakes.
|
|
11184
|
+
const clientRuntimeCompat =
|
|
11185
|
+
runtimeName === "unknown"
|
|
11186
|
+
? runtimeCompat
|
|
11187
|
+
: { [runtimeName]: runtimeVersion };
|
|
11158
11188
|
|
|
11159
11189
|
kitchen = createKitchen({
|
|
11160
11190
|
name: runtimeId,
|
package/package.json
CHANGED
package/src/build/build.js
CHANGED
|
@@ -39,6 +39,7 @@ export const createBuildSpecifierManager = ({
|
|
|
39
39
|
buildDirectoryUrl,
|
|
40
40
|
assetsDirectory,
|
|
41
41
|
buildUrlsGenerator,
|
|
42
|
+
entryKey,
|
|
42
43
|
base,
|
|
43
44
|
length = 8,
|
|
44
45
|
|
|
@@ -77,6 +78,7 @@ export const createBuildSpecifierManager = ({
|
|
|
77
78
|
urlInfo,
|
|
78
79
|
ownerUrlInfo: reference.ownerUrlInfo,
|
|
79
80
|
assetsDirectory,
|
|
81
|
+
entryKey,
|
|
80
82
|
});
|
|
81
83
|
}
|
|
82
84
|
|
|
@@ -1134,9 +1136,14 @@ export const createBuildSpecifierManager = ({
|
|
|
1134
1136
|
let contentKey;
|
|
1135
1137
|
// if to guard for html where versioned build specifier is not generated
|
|
1136
1138
|
if (buildSpecifierVersioned) {
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1139
|
+
// The version goes on the build url, not on the specifier: a
|
|
1140
|
+
// specifier is written for one importer to read (base: "./" makes
|
|
1141
|
+
// it relative to that importer, base: "https://cdn…" makes it
|
|
1142
|
+
// absolute elsewhere) and says nothing about where the file lands.
|
|
1143
|
+
const buildUrlVersioned = injectVersionIntoBuildSpecifier({
|
|
1144
|
+
buildSpecifier: buildUrl,
|
|
1145
|
+
version: versionMap.get(urlInfo),
|
|
1146
|
+
versioningMethod,
|
|
1140
1147
|
});
|
|
1141
1148
|
const buildRelativeUrlVersioned = urlToRelativeUrl(
|
|
1142
1149
|
buildUrlVersioned,
|
|
@@ -1380,23 +1387,6 @@ const injectVersionIntoBuildSpecifier = ({
|
|
|
1380
1387
|
);
|
|
1381
1388
|
};
|
|
1382
1389
|
|
|
1383
|
-
const asBuildUrlVersioned = ({
|
|
1384
|
-
buildSpecifierVersioned,
|
|
1385
|
-
buildDirectoryUrl,
|
|
1386
|
-
}) => {
|
|
1387
|
-
if (buildSpecifierVersioned[0] === "/") {
|
|
1388
|
-
return new URL(buildSpecifierVersioned.slice(1), buildDirectoryUrl).href;
|
|
1389
|
-
}
|
|
1390
|
-
const buildUrl = new URL(buildSpecifierVersioned, buildDirectoryUrl).href;
|
|
1391
|
-
if (buildUrl.startsWith(buildDirectoryUrl)) {
|
|
1392
|
-
return buildUrl;
|
|
1393
|
-
}
|
|
1394
|
-
// it's likely "base" parameter was set to an url origin like "https://cdn.example.com"
|
|
1395
|
-
// let's move url to build directory
|
|
1396
|
-
const { pathname, search, hash } = new URL(buildSpecifierVersioned);
|
|
1397
|
-
return `${buildDirectoryUrl}${pathname}${search}${hash}`;
|
|
1398
|
-
};
|
|
1399
|
-
|
|
1400
1390
|
const targetsAFileThatDoesNotExist = (url) => {
|
|
1401
1391
|
if (!url.startsWith("file:")) {
|
|
1402
1392
|
return false;
|
|
@@ -31,12 +31,41 @@ export const createBuildUrlsGenerator = ({
|
|
|
31
31
|
};
|
|
32
32
|
|
|
33
33
|
const nameSetPerDirectoryMap = new Map();
|
|
34
|
-
const
|
|
35
|
-
|
|
34
|
+
const reserveName = (directoryPath, urlName) => {
|
|
35
|
+
let nameSet = nameSetPerDirectoryMap.get(directoryPath);
|
|
36
|
+
if (!nameSet) {
|
|
37
|
+
nameSet = new Set();
|
|
38
|
+
nameSetPerDirectoryMap.set(directoryPath, nameSet);
|
|
39
|
+
}
|
|
40
|
+
let [basename, extension] = splitFileExtension(urlName);
|
|
41
|
+
extension = extensionMappings[extension] || extension;
|
|
42
|
+
let nameCandidate = `${basename}${extension}`; // reconstruct name in case extension was normalized
|
|
43
|
+
let integer = 1;
|
|
44
|
+
while (nameSet.has(nameCandidate)) {
|
|
45
|
+
integer++;
|
|
46
|
+
nameCandidate = `${basename}${integer}${extension}`;
|
|
47
|
+
}
|
|
48
|
+
nameSet.add(nameCandidate);
|
|
49
|
+
return nameCandidate;
|
|
50
|
+
};
|
|
51
|
+
const generate = (
|
|
52
|
+
url,
|
|
53
|
+
{ urlInfo, ownerUrlInfo, assetsDirectory, entryKey },
|
|
54
|
+
) => {
|
|
55
|
+
// A url already inside the build directory names a chunk a bundler just
|
|
56
|
+
// produced, and each entry point is bundled on its own: two of them
|
|
57
|
+
// routinely produce a chunk of the same name holding different code (the
|
|
58
|
+
// shared dependencies of THAT entry point, exported under names decided by
|
|
59
|
+
// THAT bundle). They are told apart by the entry point they come from —
|
|
60
|
+
// sharing the file would leave one entry importing exports the file on disk
|
|
61
|
+
// does not have.
|
|
62
|
+
const insideBuildDirectory = urlIsOrIsInsideOf(url, buildDirectoryUrl);
|
|
63
|
+
const key = insideBuildDirectory ? `${entryKey} ${url}` : url;
|
|
64
|
+
const buildUrlFromMap = buildUrlMap.get(key);
|
|
36
65
|
if (buildUrlFromMap) {
|
|
37
66
|
return buildUrlFromMap;
|
|
38
67
|
}
|
|
39
|
-
if (
|
|
68
|
+
if (insideBuildDirectory) {
|
|
40
69
|
if (ownerUrlInfo.searchParams.has("dynamic_import_id")) {
|
|
41
70
|
const ownerDirectoryPath = determineDirectoryPath({
|
|
42
71
|
sourceDirectoryUrl,
|
|
@@ -48,11 +77,18 @@ export const createBuildUrlsGenerator = ({
|
|
|
48
77
|
buildUrl = injectQueryParams(buildUrl, {
|
|
49
78
|
dynamic_import_id: undefined,
|
|
50
79
|
});
|
|
51
|
-
associateBuildUrl(
|
|
80
|
+
associateBuildUrl(key, buildUrl);
|
|
52
81
|
return buildUrl;
|
|
53
82
|
}
|
|
54
|
-
|
|
55
|
-
|
|
83
|
+
const urlObject = new URL(url);
|
|
84
|
+
const chunkDirectoryPath = urlToRelativeUrl(
|
|
85
|
+
new URL("./", urlObject),
|
|
86
|
+
buildDirectoryUrl,
|
|
87
|
+
);
|
|
88
|
+
const chunkName = reserveName(chunkDirectoryPath, urlToFilename(url));
|
|
89
|
+
const buildUrl = `${buildDirectoryUrl}${chunkDirectoryPath}${chunkName}${urlObject.search}${urlObject.hash}`;
|
|
90
|
+
associateBuildUrl(key, buildUrl);
|
|
91
|
+
return buildUrl;
|
|
56
92
|
}
|
|
57
93
|
if (urlInfo.type === "entry_build") {
|
|
58
94
|
const buildUrl = new URL(urlInfo.filenameHint, buildDirectoryUrl).href;
|
|
@@ -84,25 +120,10 @@ export const createBuildUrlsGenerator = ({
|
|
|
84
120
|
urlInfo,
|
|
85
121
|
ownerUrlInfo,
|
|
86
122
|
});
|
|
87
|
-
let nameSet = nameSetPerDirectoryMap.get(directoryPath);
|
|
88
|
-
if (!nameSet) {
|
|
89
|
-
nameSet = new Set();
|
|
90
|
-
nameSetPerDirectoryMap.set(directoryPath, nameSet);
|
|
91
|
-
}
|
|
92
123
|
const urlObject = new URL(url);
|
|
93
124
|
injectQueryParams(urlObject, { dynamic_import_id: undefined });
|
|
94
125
|
let { search, hash } = urlObject;
|
|
95
|
-
|
|
96
|
-
let [basename, extension] = splitFileExtension(urlName);
|
|
97
|
-
extension = extensionMappings[extension] || extension;
|
|
98
|
-
let nameCandidate = `${basename}${extension}`; // reconstruct name in case extension was normalized
|
|
99
|
-
let integer = 1;
|
|
100
|
-
while (nameSet.has(nameCandidate)) {
|
|
101
|
-
integer++;
|
|
102
|
-
nameCandidate = `${basename}${integer}${extension}`;
|
|
103
|
-
}
|
|
104
|
-
const name = nameCandidate;
|
|
105
|
-
nameSet.add(name);
|
|
126
|
+
const name = reserveName(directoryPath, getUrlName(url, urlInfo));
|
|
106
127
|
const buildUrl = `${buildDirectoryUrl}${directoryPath}${name}${search}${hash}`;
|
|
107
128
|
associateBuildUrl(url, buildUrl);
|
|
108
129
|
return buildUrl;
|
|
@@ -71,7 +71,16 @@ export const devServerPluginServeSourceFiles = ({
|
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
});
|
|
74
|
-
|
|
74
|
+
// A client we cannot identify (curl, fetch, a healthcheck, a proxy dropping
|
|
75
|
+
// the user agent) is assumed to be one of the runtimes the project targets.
|
|
76
|
+
// Treating it as a runtime supporting nothing would serve it the js module
|
|
77
|
+
// fallback, breaking tooling perfectly capable of ES modules; an actual
|
|
78
|
+
// ancient browser hiding its identity fails loudly instead, which is the
|
|
79
|
+
// cheaper of the two mistakes.
|
|
80
|
+
const clientRuntimeCompat =
|
|
81
|
+
runtimeName === "unknown"
|
|
82
|
+
? runtimeCompat
|
|
83
|
+
: { [runtimeName]: runtimeVersion };
|
|
75
84
|
|
|
76
85
|
kitchen = createKitchen({
|
|
77
86
|
name: runtimeId,
|
|
@@ -22,7 +22,9 @@
|
|
|
22
22
|
* - "clients_list" the whole registry (the dashboard renders it)
|
|
23
23
|
* - "client_log" a single log line (a monitor appends it)
|
|
24
24
|
* - "client_activity" a single qualified activity (a monitor appends it)
|
|
25
|
-
* - "client_here" a client just appeared/resumed (every page
|
|
25
|
+
* - "client_here" a networked client just appeared/resumed (every page
|
|
26
|
+
* can toast it); never sent for the main client, which
|
|
27
|
+
* is the person reading the toast
|
|
26
28
|
* - "client_command" pilot one client (navigate/reload its tab) from the
|
|
27
29
|
* dashboard; the matching reporter runs it
|
|
28
30
|
* Server events are broadcast, so consumers filter what they care about.
|
|
@@ -37,7 +39,7 @@
|
|
|
37
39
|
*
|
|
38
40
|
* The monitoring script is injected into EVERY cooked page, including our own
|
|
39
41
|
* dashboard and monitor pages — so opening one of those counts as a connected
|
|
40
|
-
* client, and any open page gets toasted when another client appears.
|
|
42
|
+
* client, and any open page gets toasted when another networked client appears.
|
|
41
43
|
*
|
|
42
44
|
* Pages:
|
|
43
45
|
* - /.internal/clients → dashboard listing every client seen
|
|
@@ -370,12 +372,25 @@ export const jsenvPluginClientMonitoring = () => {
|
|
|
370
372
|
pruneTabs(client);
|
|
371
373
|
pruneClients();
|
|
372
374
|
|
|
373
|
-
if (firstEver) {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
//
|
|
378
|
-
|
|
375
|
+
if (firstEver || !wasOnline) {
|
|
376
|
+
// The toast invites the reader to monitor the client that just appeared,
|
|
377
|
+
// so it is only worth showing for a client there is something to watch:
|
|
378
|
+
// one reaching the server over the network. The machine running the dev
|
|
379
|
+
// server is the person reading the toast — it browses over localhost, its
|
|
380
|
+
// devtools are one keystroke away and nothing is captured for it, so
|
|
381
|
+
// announcing it means interrupting someone about themselves. It happens
|
|
382
|
+
// more than one would think: the client id lives in localStorage, so the
|
|
383
|
+
// same browser gets a fresh id per origin (localhost vs 127.0.0.1 vs the
|
|
384
|
+
// LAN ip, another port) and in private windows, and each of those looks
|
|
385
|
+
// like a brand new client to the server.
|
|
386
|
+
if (!isLocalIp(client.ip)) {
|
|
387
|
+
sendClientHere({
|
|
388
|
+
// A report after a long quiet spell means the client was picked back
|
|
389
|
+
// up rather than newly seen.
|
|
390
|
+
reason: firstEver ? "new" : "resumed",
|
|
391
|
+
client: serializeClient(client),
|
|
392
|
+
});
|
|
393
|
+
}
|
|
379
394
|
sendClientsList();
|
|
380
395
|
}
|
|
381
396
|
|
|
@@ -9,7 +9,13 @@ export const jsenvPluginWebResolution = () => {
|
|
|
9
9
|
if (ownerUrlInfo.originalUrl?.startsWith("http")) {
|
|
10
10
|
return new URL(resource, ownerUrlInfo.originalUrl);
|
|
11
11
|
}
|
|
12
|
-
|
|
12
|
+
// "/x" is the web meaning of a root-relative url: the root of what is
|
|
13
|
+
// served, which is the source directory — not the directory the entry
|
|
14
|
+
// point happens to sit in.
|
|
15
|
+
const url = new URL(
|
|
16
|
+
resource.slice(1),
|
|
17
|
+
ownerUrlInfo.context.rootDirectoryUrl,
|
|
18
|
+
);
|
|
13
19
|
return url;
|
|
14
20
|
}
|
|
15
21
|
// baseUrl happens second argument to new URL() is different from
|