@jsenv/core 38.2.8 → 38.2.11
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/README.md +1 -0
- package/dist/jsenv_core.js +169 -76
- package/package.json +14 -14
- package/src/kitchen/kitchen.js +1 -1
- package/src/kitchen/url_graph/url_graph.js +1 -0
- package/src/plugins/autoreload/jsenv_plugin_autoreload_server.js +125 -65
- package/src/plugins/autoreload/jsenv_plugin_hot_search_param.js +15 -6
- package/src/plugins/protocol_file/jsenv_plugin_protocol_file.js +12 -4
- package/src/plugins/reference_analysis/directory/jsenv_plugin_directory_reference_analysis.js +3 -0
package/README.md
CHANGED
package/dist/jsenv_core.js
CHANGED
|
@@ -7355,7 +7355,11 @@ const serveDirectory = (
|
|
|
7355
7355
|
const directoryContentArray = readdirSync(new URL(url));
|
|
7356
7356
|
const responseProducers = {
|
|
7357
7357
|
"application/json": () => {
|
|
7358
|
-
const directoryContentJson = JSON.stringify(
|
|
7358
|
+
const directoryContentJson = JSON.stringify(
|
|
7359
|
+
directoryContentArray,
|
|
7360
|
+
null,
|
|
7361
|
+
" ",
|
|
7362
|
+
);
|
|
7359
7363
|
return {
|
|
7360
7364
|
status: 200,
|
|
7361
7365
|
headers: {
|
|
@@ -9343,6 +9347,7 @@ const babelPluginCompatMap = {
|
|
|
9343
9347
|
samsung: "9",
|
|
9344
9348
|
electron: "3",
|
|
9345
9349
|
},
|
|
9350
|
+
"proposal-decorators": {},
|
|
9346
9351
|
"transform-parameters": {
|
|
9347
9352
|
chrome: "49",
|
|
9348
9353
|
opera: "36",
|
|
@@ -9739,6 +9744,14 @@ const getBaseBabelPluginStructure = ({
|
|
|
9739
9744
|
babelPluginStructure["proposal-unicode-property-regex"] =
|
|
9740
9745
|
requireBabelPlugin("@babel/plugin-proposal-unicode-property-regex");
|
|
9741
9746
|
}
|
|
9747
|
+
// if (isBabelPluginNeeded("proposal-decorators") && content.includes("@")) {
|
|
9748
|
+
// babelPluginStructure["proposal-decorators"] = [
|
|
9749
|
+
// requireBabelPlugin("@babel/plugin-proposal-decorators"),
|
|
9750
|
+
// {
|
|
9751
|
+
// version: "2023-05",
|
|
9752
|
+
// },
|
|
9753
|
+
// ];
|
|
9754
|
+
// }
|
|
9742
9755
|
if (isBabelPluginNeeded("transform-async-to-promises")) {
|
|
9743
9756
|
babelPluginStructure["transform-async-to-promises"] = [
|
|
9744
9757
|
requireBabelPlugin("babel-plugin-transform-async-to-promises"),
|
|
@@ -12311,6 +12324,7 @@ const createUrlInfo = (url, context) => {
|
|
|
12311
12324
|
context,
|
|
12312
12325
|
error: null,
|
|
12313
12326
|
modifiedTimestamp: 0,
|
|
12327
|
+
descendantModifiedTimestamp: 0,
|
|
12314
12328
|
dereferencedTimestamp: 0,
|
|
12315
12329
|
originalContentEtag: null,
|
|
12316
12330
|
contentEtag: null,
|
|
@@ -14378,7 +14392,7 @@ ${ANSI.color(normalizedReturnValue, ANSI.YELLOW)}
|
|
|
14378
14392
|
// the HTML in itself it still valid
|
|
14379
14393
|
// keep the syntax error and continue with the HTML
|
|
14380
14394
|
const errorInfo =
|
|
14381
|
-
e.code === "PARSE_ERROR"
|
|
14395
|
+
e.code === "PARSE_ERROR" && e.cause
|
|
14382
14396
|
? `${e.cause.reasonCode}\n${e.traceMessage}`
|
|
14383
14397
|
: e.stack;
|
|
14384
14398
|
logger.error(
|
|
@@ -14885,6 +14899,9 @@ const jsenvPluginDirectoryReferenceAnalysis = () => {
|
|
|
14885
14899
|
urlInfo.url,
|
|
14886
14900
|
urlInfo.context.rootDirectoryUrl,
|
|
14887
14901
|
);
|
|
14902
|
+
if (urlInfo.contentType !== "application/json") {
|
|
14903
|
+
return null;
|
|
14904
|
+
}
|
|
14888
14905
|
const entryNames = JSON.parse(urlInfo.content);
|
|
14889
14906
|
const newEntryNames = [];
|
|
14890
14907
|
for (const entryName of entryNames) {
|
|
@@ -18050,6 +18067,7 @@ const jsenvPluginProtocolFile = ({
|
|
|
18050
18067
|
reference.leadsToADirectory = stat && stat.isDirectory();
|
|
18051
18068
|
if (reference.leadsToADirectory) {
|
|
18052
18069
|
const directoryAllowed =
|
|
18070
|
+
reference.type === "http_request" ||
|
|
18053
18071
|
reference.type === "filesystem" ||
|
|
18054
18072
|
(typeof directoryReferenceAllowed === "function" &&
|
|
18055
18073
|
directoryReferenceAllowed(reference)) ||
|
|
@@ -18115,7 +18133,6 @@ const jsenvPluginProtocolFile = ({
|
|
|
18115
18133
|
}
|
|
18116
18134
|
const urlObject = new URL(urlInfo.url);
|
|
18117
18135
|
if (urlInfo.firstReference.leadsToADirectory) {
|
|
18118
|
-
const directoryEntries = readdirSync(urlObject);
|
|
18119
18136
|
if (!urlInfo.filenameHint) {
|
|
18120
18137
|
if (urlInfo.firstReference.type === "filesystem") {
|
|
18121
18138
|
urlInfo.filenameHint = `${
|
|
@@ -18125,10 +18142,17 @@ const jsenvPluginProtocolFile = ({
|
|
|
18125
18142
|
urlInfo.filenameHint = `${urlToFilename$1(urlInfo.url)}/`;
|
|
18126
18143
|
}
|
|
18127
18144
|
}
|
|
18145
|
+
const { headers, body } = serveDirectory(urlObject.href, {
|
|
18146
|
+
headers: urlInfo.context.request
|
|
18147
|
+
? urlInfo.context.request.headers
|
|
18148
|
+
: {},
|
|
18149
|
+
rootDirectoryUrl: urlInfo.context.rootDirectoryUrl,
|
|
18150
|
+
});
|
|
18128
18151
|
return {
|
|
18129
18152
|
type: "directory",
|
|
18130
|
-
contentType: "
|
|
18131
|
-
|
|
18153
|
+
contentType: headers["content-type"],
|
|
18154
|
+
contentLength: headers["content-length"],
|
|
18155
|
+
content: body,
|
|
18132
18156
|
};
|
|
18133
18157
|
}
|
|
18134
18158
|
if (
|
|
@@ -19254,8 +19278,16 @@ const jsenvPluginHotSearchParam = () => {
|
|
|
19254
19278
|
// At this stage the parent is using ?hot and we are going to decide if
|
|
19255
19279
|
// we propagate the search param to child.
|
|
19256
19280
|
const referencedUrlInfo = reference.urlInfo;
|
|
19257
|
-
const {
|
|
19258
|
-
|
|
19281
|
+
const {
|
|
19282
|
+
modifiedTimestamp,
|
|
19283
|
+
descendantModifiedTimestamp,
|
|
19284
|
+
dereferencedTimestamp,
|
|
19285
|
+
} = referencedUrlInfo;
|
|
19286
|
+
if (
|
|
19287
|
+
!modifiedTimestamp &&
|
|
19288
|
+
!descendantModifiedTimestamp &&
|
|
19289
|
+
!dereferencedTimestamp
|
|
19290
|
+
) {
|
|
19259
19291
|
return null;
|
|
19260
19292
|
}
|
|
19261
19293
|
// The goal is to send an url that will bypass client (the browser) cache
|
|
@@ -19268,10 +19300,11 @@ const jsenvPluginHotSearchParam = () => {
|
|
|
19268
19300
|
// We use the latest timestamp to ensure it's fresh
|
|
19269
19301
|
// The dereferencedTimestamp is needed because when a js module is re-referenced
|
|
19270
19302
|
// browser must re-execute it, even if the code is not modified
|
|
19271
|
-
const latestTimestamp =
|
|
19272
|
-
|
|
19273
|
-
|
|
19274
|
-
|
|
19303
|
+
const latestTimestamp = Math.max(
|
|
19304
|
+
modifiedTimestamp,
|
|
19305
|
+
descendantModifiedTimestamp,
|
|
19306
|
+
dereferencedTimestamp,
|
|
19307
|
+
);
|
|
19275
19308
|
return {
|
|
19276
19309
|
hot: latestTimestamp,
|
|
19277
19310
|
};
|
|
@@ -19333,89 +19366,149 @@ const jsenvPluginAutoreloadServer = ({
|
|
|
19333
19366
|
}
|
|
19334
19367
|
return url;
|
|
19335
19368
|
};
|
|
19336
|
-
const
|
|
19337
|
-
const
|
|
19338
|
-
|
|
19339
|
-
|
|
19340
|
-
|
|
19341
|
-
|
|
19342
|
-
|
|
19343
|
-
|
|
19344
|
-
|
|
19345
|
-
|
|
19346
|
-
|
|
19347
|
-
|
|
19348
|
-
|
|
19369
|
+
const update = (firstUrlInfo) => {
|
|
19370
|
+
const boundaries = new Set();
|
|
19371
|
+
const instructions = [];
|
|
19372
|
+
const propagateUpdate = (firstUrlInfo) => {
|
|
19373
|
+
const iterate = (urlInfo, chain) => {
|
|
19374
|
+
if (urlInfo.data.hotAcceptSelf) {
|
|
19375
|
+
boundaries.add(urlInfo);
|
|
19376
|
+
instructions.push({
|
|
19377
|
+
type: urlInfo.type,
|
|
19378
|
+
boundary: formatUrlForClient(urlInfo.url),
|
|
19379
|
+
acceptedBy: formatUrlForClient(urlInfo.url),
|
|
19380
|
+
});
|
|
19381
|
+
return {
|
|
19382
|
+
accepted: true,
|
|
19383
|
+
reason:
|
|
19384
|
+
urlInfo === firstUrlInfo
|
|
19385
|
+
? `file accepts hot reload`
|
|
19386
|
+
: `a dependent file accepts hot reload`,
|
|
19387
|
+
};
|
|
19388
|
+
}
|
|
19389
|
+
if (urlInfo.data.hotDecline) {
|
|
19390
|
+
return {
|
|
19391
|
+
declined: true,
|
|
19392
|
+
reason: `file declines hot reload`,
|
|
19393
|
+
declinedBy: formatUrlForClient(urlInfo.url),
|
|
19394
|
+
};
|
|
19395
|
+
}
|
|
19396
|
+
let instructionCountBefore = instructions.length;
|
|
19397
|
+
for (const referenceFromOther of urlInfo.referenceFromOthersSet) {
|
|
19398
|
+
if (
|
|
19399
|
+
referenceFromOther.isImplicit &&
|
|
19400
|
+
referenceFromOther.isWeak
|
|
19401
|
+
) {
|
|
19402
|
+
if (!referenceFromOther.original) {
|
|
19403
|
+
continue;
|
|
19404
|
+
}
|
|
19405
|
+
if (referenceFromOther.original.isWeak) {
|
|
19406
|
+
continue;
|
|
19407
|
+
}
|
|
19408
|
+
}
|
|
19409
|
+
const urlInfoReferencingThisOne =
|
|
19410
|
+
referenceFromOther.ownerUrlInfo;
|
|
19411
|
+
if (urlInfoReferencingThisOne.data.hotDecline) {
|
|
19412
|
+
return {
|
|
19413
|
+
declined: true,
|
|
19414
|
+
reason: `a dependent file declines hot reload`,
|
|
19415
|
+
declinedBy: formatUrlForClient(
|
|
19416
|
+
urlInfoReferencingThisOne.url,
|
|
19417
|
+
),
|
|
19418
|
+
};
|
|
19419
|
+
}
|
|
19420
|
+
const { hotAcceptDependencies = [] } =
|
|
19421
|
+
urlInfoReferencingThisOne.data;
|
|
19422
|
+
if (hotAcceptDependencies.includes(urlInfo.url)) {
|
|
19423
|
+
boundaries.add(urlInfoReferencingThisOne);
|
|
19424
|
+
instructions.push({
|
|
19425
|
+
type: urlInfoReferencingThisOne.type,
|
|
19426
|
+
boundary: formatUrlForClient(urlInfoReferencingThisOne.url),
|
|
19349
19427
|
acceptedBy: formatUrlForClient(urlInfo.url),
|
|
19350
|
-
}
|
|
19351
|
-
],
|
|
19352
|
-
};
|
|
19353
|
-
}
|
|
19354
|
-
const instructions = [];
|
|
19355
|
-
for (const referenceFromOther of urlInfo.referenceFromOthersSet) {
|
|
19356
|
-
if (referenceFromOther.isImplicit && referenceFromOther.isWeak) {
|
|
19357
|
-
if (!referenceFromOther.original) {
|
|
19428
|
+
});
|
|
19358
19429
|
continue;
|
|
19359
19430
|
}
|
|
19360
|
-
if (
|
|
19431
|
+
if (chain.includes(urlInfoReferencingThisOne.url)) {
|
|
19432
|
+
return {
|
|
19433
|
+
declined: true,
|
|
19434
|
+
reason: "dead end",
|
|
19435
|
+
declinedBy: formatUrlForClient(
|
|
19436
|
+
urlInfoReferencingThisOne.url,
|
|
19437
|
+
),
|
|
19438
|
+
};
|
|
19439
|
+
}
|
|
19440
|
+
const dependentPropagationResult = iterateMemoized(
|
|
19441
|
+
urlInfoReferencingThisOne,
|
|
19442
|
+
[...chain, urlInfoReferencingThisOne.url],
|
|
19443
|
+
);
|
|
19444
|
+
if (dependentPropagationResult.accepted) {
|
|
19361
19445
|
continue;
|
|
19362
19446
|
}
|
|
19447
|
+
if (
|
|
19448
|
+
// declined explicitely by an other file, it must decline the whole update
|
|
19449
|
+
dependentPropagationResult.declinedBy
|
|
19450
|
+
) {
|
|
19451
|
+
return dependentPropagationResult;
|
|
19452
|
+
}
|
|
19453
|
+
// declined by absence of boundary, we can keep searching
|
|
19363
19454
|
}
|
|
19364
|
-
|
|
19365
|
-
if (urlInfoReferencingThisOne.data.hotDecline) {
|
|
19455
|
+
if (instructionCountBefore === instructions.length) {
|
|
19366
19456
|
return {
|
|
19367
19457
|
declined: true,
|
|
19368
|
-
reason: `
|
|
19369
|
-
declinedBy: urlInfoReferencingThisOne.url,
|
|
19458
|
+
reason: `there is no file accepting hot reload while propagating update`,
|
|
19370
19459
|
};
|
|
19371
19460
|
}
|
|
19461
|
+
return {
|
|
19462
|
+
accepted: true,
|
|
19463
|
+
reason: `${instructions.length} dependent file(s) accepts hot reload`,
|
|
19464
|
+
};
|
|
19465
|
+
};
|
|
19466
|
+
|
|
19467
|
+
const map = new Map();
|
|
19468
|
+
const iterateMemoized = (urlInfo, chain) => {
|
|
19469
|
+
const resultFromCache = map.get(urlInfo.url);
|
|
19470
|
+
if (resultFromCache) {
|
|
19471
|
+
return resultFromCache;
|
|
19472
|
+
}
|
|
19473
|
+
const result = iterate(urlInfo, chain);
|
|
19474
|
+
map.set(urlInfo.url, result);
|
|
19475
|
+
return result;
|
|
19476
|
+
};
|
|
19477
|
+
map.clear();
|
|
19478
|
+
return iterateMemoized(firstUrlInfo, []);
|
|
19479
|
+
};
|
|
19480
|
+
|
|
19481
|
+
const propagationResult = propagateUpdate(firstUrlInfo);
|
|
19482
|
+
const seen = new Set();
|
|
19483
|
+
const invalidateImporters = (urlInfo) => {
|
|
19484
|
+
// to indicate this urlInfo should be modified
|
|
19485
|
+
for (const referenceFromOther of urlInfo.referenceFromOthersSet) {
|
|
19486
|
+
const urlInfoReferencingThisOne = referenceFromOther.ownerUrlInfo;
|
|
19372
19487
|
const { hotAcceptDependencies = [] } =
|
|
19373
19488
|
urlInfoReferencingThisOne.data;
|
|
19374
19489
|
if (hotAcceptDependencies.includes(urlInfo.url)) {
|
|
19375
|
-
instructions.push({
|
|
19376
|
-
type: urlInfoReferencingThisOne.type,
|
|
19377
|
-
boundary: formatUrlForClient(urlInfoReferencingThisOne.url),
|
|
19378
|
-
acceptedBy: formatUrlForClient(urlInfo.url),
|
|
19379
|
-
});
|
|
19380
19490
|
continue;
|
|
19381
19491
|
}
|
|
19382
|
-
if (seen.
|
|
19383
|
-
return {
|
|
19384
|
-
declined: true,
|
|
19385
|
-
reason: "circular dependency",
|
|
19386
|
-
declinedBy: formatUrlForClient(urlInfoReferencingThisOne.url),
|
|
19387
|
-
};
|
|
19388
|
-
}
|
|
19389
|
-
const dependentPropagationResult = iterate(
|
|
19390
|
-
urlInfoReferencingThisOne,
|
|
19391
|
-
[...seen, urlInfoReferencingThisOne.url],
|
|
19392
|
-
);
|
|
19393
|
-
if (dependentPropagationResult.accepted) {
|
|
19394
|
-
instructions.push(...dependentPropagationResult.instructions);
|
|
19492
|
+
if (seen.has(urlInfoReferencingThisOne)) {
|
|
19395
19493
|
continue;
|
|
19396
19494
|
}
|
|
19397
|
-
|
|
19398
|
-
|
|
19399
|
-
|
|
19400
|
-
|
|
19401
|
-
return dependentPropagationResult;
|
|
19495
|
+
seen.add(urlInfoReferencingThisOne);
|
|
19496
|
+
// see https://github.com/vitejs/vite/blob/ab5bb40942c7023046fa6f6d0b49cabc105b6073/packages/vite/src/node/server/moduleGraph.ts#L205C5-L207C6
|
|
19497
|
+
if (boundaries.has(urlInfoReferencingThisOne)) {
|
|
19498
|
+
return;
|
|
19402
19499
|
}
|
|
19403
|
-
|
|
19404
|
-
|
|
19405
|
-
|
|
19406
|
-
return {
|
|
19407
|
-
declined: true,
|
|
19408
|
-
reason: `there is no file accepting hot reload while propagating update`,
|
|
19409
|
-
};
|
|
19500
|
+
urlInfoReferencingThisOne.descendantModifiedTimestamp =
|
|
19501
|
+
Date.now();
|
|
19502
|
+
invalidateImporters(urlInfoReferencingThisOne);
|
|
19410
19503
|
}
|
|
19411
|
-
return {
|
|
19412
|
-
accepted: true,
|
|
19413
|
-
reason: `${instructions.length} dependent file(s) accepts hot reload`,
|
|
19414
|
-
instructions,
|
|
19415
|
-
};
|
|
19416
19504
|
};
|
|
19417
|
-
|
|
19418
|
-
|
|
19505
|
+
invalidateImporters(firstUrlInfo);
|
|
19506
|
+
boundaries.clear();
|
|
19507
|
+
seen.clear();
|
|
19508
|
+
return {
|
|
19509
|
+
...propagationResult,
|
|
19510
|
+
instructions,
|
|
19511
|
+
};
|
|
19419
19512
|
};
|
|
19420
19513
|
|
|
19421
19514
|
// We are delaying the moment we tell client how to reload because:
|
|
@@ -19449,7 +19542,7 @@ const jsenvPluginAutoreloadServer = ({
|
|
|
19449
19542
|
if (!changedUrlInfo.isUsed()) {
|
|
19450
19543
|
continue;
|
|
19451
19544
|
}
|
|
19452
|
-
const hotUpdate =
|
|
19545
|
+
const hotUpdate = update(changedUrlInfo);
|
|
19453
19546
|
const relativeUrl = formatUrlForClient(changedUrlInfo.url);
|
|
19454
19547
|
if (hotUpdate.declined) {
|
|
19455
19548
|
reloadMessage = {
|
|
@@ -19486,7 +19579,7 @@ const jsenvPluginAutoreloadServer = ({
|
|
|
19486
19579
|
if (!ownerUrlInfo.isUsed()) {
|
|
19487
19580
|
continue;
|
|
19488
19581
|
}
|
|
19489
|
-
const ownerHotUpdate =
|
|
19582
|
+
const ownerHotUpdate = update(ownerUrlInfo);
|
|
19490
19583
|
const cause = `${formatUrlForClient(
|
|
19491
19584
|
prunedUrlInfo.url,
|
|
19492
19585
|
)} is no longer referenced`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/core",
|
|
3
|
-
"version": "38.2.
|
|
3
|
+
"version": "38.2.11",
|
|
4
4
|
"description": "Tool to develop, test and build js projects",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -61,20 +61,20 @@
|
|
|
61
61
|
"dependencies": {
|
|
62
62
|
"@financial-times/polyfill-useragent-normaliser": "1.10.2",
|
|
63
63
|
"@jsenv/abort": "4.2.4",
|
|
64
|
-
"@jsenv/ast": "5.
|
|
64
|
+
"@jsenv/ast": "5.2.0",
|
|
65
65
|
"@jsenv/filesystem": "4.3.2",
|
|
66
66
|
"@jsenv/importmap": "1.2.1",
|
|
67
67
|
"@jsenv/integrity": "0.0.1",
|
|
68
|
+
"@jsenv/js-module-fallback": "1.3.6",
|
|
68
69
|
"@jsenv/log": "3.4.1",
|
|
69
70
|
"@jsenv/node-esm-resolution": "1.0.1",
|
|
70
|
-
"@jsenv/
|
|
71
|
+
"@jsenv/plugin-bundling": "2.5.7",
|
|
72
|
+
"@jsenv/plugin-minification": "1.5.3",
|
|
73
|
+
"@jsenv/plugin-supervisor": "1.3.6",
|
|
74
|
+
"@jsenv/plugin-transpilation": "1.3.5",
|
|
71
75
|
"@jsenv/runtime-compat": "1.2.0",
|
|
72
|
-
"@jsenv/server": "15.1.
|
|
76
|
+
"@jsenv/server": "15.1.4",
|
|
73
77
|
"@jsenv/sourcemap": "1.2.3",
|
|
74
|
-
"@jsenv/plugin-bundling": "2.5.6",
|
|
75
|
-
"@jsenv/plugin-minification": "1.5.3",
|
|
76
|
-
"@jsenv/plugin-transpilation": "1.3.4",
|
|
77
|
-
"@jsenv/plugin-supervisor": "1.3.5",
|
|
78
78
|
"@jsenv/url-meta": "8.1.0",
|
|
79
79
|
"@jsenv/urls": "2.2.1",
|
|
80
80
|
"@jsenv/utils": "2.0.1"
|
|
@@ -82,22 +82,22 @@
|
|
|
82
82
|
"devDependencies": {
|
|
83
83
|
"@babel/eslint-parser": "7.22.15",
|
|
84
84
|
"@babel/plugin-syntax-import-assertions": "7.22.5",
|
|
85
|
-
"babel-plugin-transform-async-to-promises": "0.8.18",
|
|
86
85
|
"@jsenv/assert": "./packages/independent/assert/",
|
|
87
86
|
"@jsenv/core": "./",
|
|
88
87
|
"@jsenv/eslint-config": "./packages/independent/eslint-config/",
|
|
89
|
-
"@jsenv/file-size-impact": "14.1.
|
|
88
|
+
"@jsenv/file-size-impact": "14.1.3",
|
|
90
89
|
"@jsenv/https-local": "3.0.7",
|
|
91
90
|
"@jsenv/package-workspace": "0.5.3",
|
|
92
|
-
"@jsenv/performance-impact": "4.1.
|
|
91
|
+
"@jsenv/performance-impact": "4.1.3",
|
|
93
92
|
"@jsenv/plugin-as-js-classic": "./packages/related/plugin-as-js-classic/",
|
|
94
93
|
"@jsenv/test": "./packages/related/test/",
|
|
95
|
-
"
|
|
94
|
+
"babel-plugin-transform-async-to-promises": "0.8.18",
|
|
95
|
+
"eslint": "8.52.0",
|
|
96
96
|
"eslint-plugin-html": "7.1.0",
|
|
97
|
-
"eslint-plugin-import": "2.
|
|
97
|
+
"eslint-plugin-import": "2.29.0",
|
|
98
98
|
"eslint-plugin-react": "7.33.2",
|
|
99
99
|
"open": "9.1.0",
|
|
100
|
-
"playwright": "1.
|
|
100
|
+
"playwright": "1.39.0",
|
|
101
101
|
"prettier": "3.0.3"
|
|
102
102
|
}
|
|
103
103
|
}
|
package/src/kitchen/kitchen.js
CHANGED
|
@@ -488,7 +488,7 @@ ${ANSI.color(normalizedReturnValue, ANSI.YELLOW)}
|
|
|
488
488
|
// the HTML in itself it still valid
|
|
489
489
|
// keep the syntax error and continue with the HTML
|
|
490
490
|
const errorInfo =
|
|
491
|
-
e.code === "PARSE_ERROR"
|
|
491
|
+
e.code === "PARSE_ERROR" && e.cause
|
|
492
492
|
? `${e.cause.reasonCode}\n${e.traceMessage}`
|
|
493
493
|
: e.stack;
|
|
494
494
|
logger.error(
|
|
@@ -18,89 +18,149 @@ export const jsenvPluginAutoreloadServer = ({
|
|
|
18
18
|
}
|
|
19
19
|
return url;
|
|
20
20
|
};
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
21
|
+
const update = (firstUrlInfo) => {
|
|
22
|
+
const boundaries = new Set();
|
|
23
|
+
const instructions = [];
|
|
24
|
+
const propagateUpdate = (firstUrlInfo) => {
|
|
25
|
+
const iterate = (urlInfo, chain) => {
|
|
26
|
+
if (urlInfo.data.hotAcceptSelf) {
|
|
27
|
+
boundaries.add(urlInfo);
|
|
28
|
+
instructions.push({
|
|
29
|
+
type: urlInfo.type,
|
|
30
|
+
boundary: formatUrlForClient(urlInfo.url),
|
|
31
|
+
acceptedBy: formatUrlForClient(urlInfo.url),
|
|
32
|
+
});
|
|
33
|
+
return {
|
|
34
|
+
accepted: true,
|
|
35
|
+
reason:
|
|
36
|
+
urlInfo === firstUrlInfo
|
|
37
|
+
? `file accepts hot reload`
|
|
38
|
+
: `a dependent file accepts hot reload`,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
if (urlInfo.data.hotDecline) {
|
|
42
|
+
return {
|
|
43
|
+
declined: true,
|
|
44
|
+
reason: `file declines hot reload`,
|
|
45
|
+
declinedBy: formatUrlForClient(urlInfo.url),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
let instructionCountBefore = instructions.length;
|
|
49
|
+
for (const referenceFromOther of urlInfo.referenceFromOthersSet) {
|
|
50
|
+
if (
|
|
51
|
+
referenceFromOther.isImplicit &&
|
|
52
|
+
referenceFromOther.isWeak
|
|
53
|
+
) {
|
|
54
|
+
if (!referenceFromOther.original) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (referenceFromOther.original.isWeak) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const urlInfoReferencingThisOne =
|
|
62
|
+
referenceFromOther.ownerUrlInfo;
|
|
63
|
+
if (urlInfoReferencingThisOne.data.hotDecline) {
|
|
64
|
+
return {
|
|
65
|
+
declined: true,
|
|
66
|
+
reason: `a dependent file declines hot reload`,
|
|
67
|
+
declinedBy: formatUrlForClient(
|
|
68
|
+
urlInfoReferencingThisOne.url,
|
|
69
|
+
),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
const { hotAcceptDependencies = [] } =
|
|
73
|
+
urlInfoReferencingThisOne.data;
|
|
74
|
+
if (hotAcceptDependencies.includes(urlInfo.url)) {
|
|
75
|
+
boundaries.add(urlInfoReferencingThisOne);
|
|
76
|
+
instructions.push({
|
|
77
|
+
type: urlInfoReferencingThisOne.type,
|
|
78
|
+
boundary: formatUrlForClient(urlInfoReferencingThisOne.url),
|
|
34
79
|
acceptedBy: formatUrlForClient(urlInfo.url),
|
|
35
|
-
}
|
|
36
|
-
],
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
const instructions = [];
|
|
40
|
-
for (const referenceFromOther of urlInfo.referenceFromOthersSet) {
|
|
41
|
-
if (referenceFromOther.isImplicit && referenceFromOther.isWeak) {
|
|
42
|
-
if (!referenceFromOther.original) {
|
|
80
|
+
});
|
|
43
81
|
continue;
|
|
44
82
|
}
|
|
45
|
-
if (
|
|
83
|
+
if (chain.includes(urlInfoReferencingThisOne.url)) {
|
|
84
|
+
return {
|
|
85
|
+
declined: true,
|
|
86
|
+
reason: "dead end",
|
|
87
|
+
declinedBy: formatUrlForClient(
|
|
88
|
+
urlInfoReferencingThisOne.url,
|
|
89
|
+
),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
const dependentPropagationResult = iterateMemoized(
|
|
93
|
+
urlInfoReferencingThisOne,
|
|
94
|
+
[...chain, urlInfoReferencingThisOne.url],
|
|
95
|
+
);
|
|
96
|
+
if (dependentPropagationResult.accepted) {
|
|
46
97
|
continue;
|
|
47
98
|
}
|
|
99
|
+
if (
|
|
100
|
+
// declined explicitely by an other file, it must decline the whole update
|
|
101
|
+
dependentPropagationResult.declinedBy
|
|
102
|
+
) {
|
|
103
|
+
return dependentPropagationResult;
|
|
104
|
+
}
|
|
105
|
+
// declined by absence of boundary, we can keep searching
|
|
48
106
|
}
|
|
49
|
-
|
|
50
|
-
if (urlInfoReferencingThisOne.data.hotDecline) {
|
|
107
|
+
if (instructionCountBefore === instructions.length) {
|
|
51
108
|
return {
|
|
52
109
|
declined: true,
|
|
53
|
-
reason: `
|
|
54
|
-
declinedBy: urlInfoReferencingThisOne.url,
|
|
110
|
+
reason: `there is no file accepting hot reload while propagating update`,
|
|
55
111
|
};
|
|
56
112
|
}
|
|
113
|
+
return {
|
|
114
|
+
accepted: true,
|
|
115
|
+
reason: `${instructions.length} dependent file(s) accepts hot reload`,
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const map = new Map();
|
|
120
|
+
const iterateMemoized = (urlInfo, chain) => {
|
|
121
|
+
const resultFromCache = map.get(urlInfo.url);
|
|
122
|
+
if (resultFromCache) {
|
|
123
|
+
return resultFromCache;
|
|
124
|
+
}
|
|
125
|
+
const result = iterate(urlInfo, chain);
|
|
126
|
+
map.set(urlInfo.url, result);
|
|
127
|
+
return result;
|
|
128
|
+
};
|
|
129
|
+
map.clear();
|
|
130
|
+
return iterateMemoized(firstUrlInfo, []);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const propagationResult = propagateUpdate(firstUrlInfo);
|
|
134
|
+
const seen = new Set();
|
|
135
|
+
const invalidateImporters = (urlInfo) => {
|
|
136
|
+
// to indicate this urlInfo should be modified
|
|
137
|
+
for (const referenceFromOther of urlInfo.referenceFromOthersSet) {
|
|
138
|
+
const urlInfoReferencingThisOne = referenceFromOther.ownerUrlInfo;
|
|
57
139
|
const { hotAcceptDependencies = [] } =
|
|
58
140
|
urlInfoReferencingThisOne.data;
|
|
59
141
|
if (hotAcceptDependencies.includes(urlInfo.url)) {
|
|
60
|
-
instructions.push({
|
|
61
|
-
type: urlInfoReferencingThisOne.type,
|
|
62
|
-
boundary: formatUrlForClient(urlInfoReferencingThisOne.url),
|
|
63
|
-
acceptedBy: formatUrlForClient(urlInfo.url),
|
|
64
|
-
});
|
|
65
142
|
continue;
|
|
66
143
|
}
|
|
67
|
-
if (seen.
|
|
68
|
-
return {
|
|
69
|
-
declined: true,
|
|
70
|
-
reason: "circular dependency",
|
|
71
|
-
declinedBy: formatUrlForClient(urlInfoReferencingThisOne.url),
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
const dependentPropagationResult = iterate(
|
|
75
|
-
urlInfoReferencingThisOne,
|
|
76
|
-
[...seen, urlInfoReferencingThisOne.url],
|
|
77
|
-
);
|
|
78
|
-
if (dependentPropagationResult.accepted) {
|
|
79
|
-
instructions.push(...dependentPropagationResult.instructions);
|
|
144
|
+
if (seen.has(urlInfoReferencingThisOne)) {
|
|
80
145
|
continue;
|
|
81
146
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
return dependentPropagationResult;
|
|
147
|
+
seen.add(urlInfoReferencingThisOne);
|
|
148
|
+
// see https://github.com/vitejs/vite/blob/ab5bb40942c7023046fa6f6d0b49cabc105b6073/packages/vite/src/node/server/moduleGraph.ts#L205C5-L207C6
|
|
149
|
+
if (boundaries.has(urlInfoReferencingThisOne)) {
|
|
150
|
+
return;
|
|
87
151
|
}
|
|
88
|
-
|
|
152
|
+
urlInfoReferencingThisOne.descendantModifiedTimestamp =
|
|
153
|
+
Date.now();
|
|
154
|
+
invalidateImporters(urlInfoReferencingThisOne);
|
|
89
155
|
}
|
|
90
|
-
if (instructions.length === 0) {
|
|
91
|
-
return {
|
|
92
|
-
declined: true,
|
|
93
|
-
reason: `there is no file accepting hot reload while propagating update`,
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
return {
|
|
97
|
-
accepted: true,
|
|
98
|
-
reason: `${instructions.length} dependent file(s) accepts hot reload`,
|
|
99
|
-
instructions,
|
|
100
|
-
};
|
|
101
156
|
};
|
|
102
|
-
|
|
103
|
-
|
|
157
|
+
invalidateImporters(firstUrlInfo);
|
|
158
|
+
boundaries.clear();
|
|
159
|
+
seen.clear();
|
|
160
|
+
return {
|
|
161
|
+
...propagationResult,
|
|
162
|
+
instructions,
|
|
163
|
+
};
|
|
104
164
|
};
|
|
105
165
|
|
|
106
166
|
// We are delaying the moment we tell client how to reload because:
|
|
@@ -134,7 +194,7 @@ export const jsenvPluginAutoreloadServer = ({
|
|
|
134
194
|
if (!changedUrlInfo.isUsed()) {
|
|
135
195
|
continue;
|
|
136
196
|
}
|
|
137
|
-
const hotUpdate =
|
|
197
|
+
const hotUpdate = update(changedUrlInfo);
|
|
138
198
|
const relativeUrl = formatUrlForClient(changedUrlInfo.url);
|
|
139
199
|
if (hotUpdate.declined) {
|
|
140
200
|
reloadMessage = {
|
|
@@ -171,7 +231,7 @@ export const jsenvPluginAutoreloadServer = ({
|
|
|
171
231
|
if (!ownerUrlInfo.isUsed()) {
|
|
172
232
|
continue;
|
|
173
233
|
}
|
|
174
|
-
const ownerHotUpdate =
|
|
234
|
+
const ownerHotUpdate = update(ownerUrlInfo);
|
|
175
235
|
const cause = `${formatUrlForClient(
|
|
176
236
|
prunedUrlInfo.url,
|
|
177
237
|
)} is no longer referenced`;
|
|
@@ -44,8 +44,16 @@ export const jsenvPluginHotSearchParam = () => {
|
|
|
44
44
|
// At this stage the parent is using ?hot and we are going to decide if
|
|
45
45
|
// we propagate the search param to child.
|
|
46
46
|
const referencedUrlInfo = reference.urlInfo;
|
|
47
|
-
const {
|
|
48
|
-
|
|
47
|
+
const {
|
|
48
|
+
modifiedTimestamp,
|
|
49
|
+
descendantModifiedTimestamp,
|
|
50
|
+
dereferencedTimestamp,
|
|
51
|
+
} = referencedUrlInfo;
|
|
52
|
+
if (
|
|
53
|
+
!modifiedTimestamp &&
|
|
54
|
+
!descendantModifiedTimestamp &&
|
|
55
|
+
!dereferencedTimestamp
|
|
56
|
+
) {
|
|
49
57
|
return null;
|
|
50
58
|
}
|
|
51
59
|
// The goal is to send an url that will bypass client (the browser) cache
|
|
@@ -58,10 +66,11 @@ export const jsenvPluginHotSearchParam = () => {
|
|
|
58
66
|
// We use the latest timestamp to ensure it's fresh
|
|
59
67
|
// The dereferencedTimestamp is needed because when a js module is re-referenced
|
|
60
68
|
// browser must re-execute it, even if the code is not modified
|
|
61
|
-
const latestTimestamp =
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
69
|
+
const latestTimestamp = Math.max(
|
|
70
|
+
modifiedTimestamp,
|
|
71
|
+
descendantModifiedTimestamp,
|
|
72
|
+
dereferencedTimestamp,
|
|
73
|
+
);
|
|
65
74
|
return {
|
|
66
75
|
hot: latestTimestamp,
|
|
67
76
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { readFileSync,
|
|
1
|
+
import { readFileSync, realpathSync, statSync } from "node:fs";
|
|
2
|
+
import { serveDirectory } from "@jsenv/server";
|
|
2
3
|
import { pathToFileURL } from "node:url";
|
|
3
4
|
import {
|
|
4
5
|
urlIsInsideOf,
|
|
@@ -98,6 +99,7 @@ export const jsenvPluginProtocolFile = ({
|
|
|
98
99
|
reference.leadsToADirectory = stat && stat.isDirectory();
|
|
99
100
|
if (reference.leadsToADirectory) {
|
|
100
101
|
const directoryAllowed =
|
|
102
|
+
reference.type === "http_request" ||
|
|
101
103
|
reference.type === "filesystem" ||
|
|
102
104
|
(typeof directoryReferenceAllowed === "function" &&
|
|
103
105
|
directoryReferenceAllowed(reference)) ||
|
|
@@ -163,7 +165,6 @@ export const jsenvPluginProtocolFile = ({
|
|
|
163
165
|
}
|
|
164
166
|
const urlObject = new URL(urlInfo.url);
|
|
165
167
|
if (urlInfo.firstReference.leadsToADirectory) {
|
|
166
|
-
const directoryEntries = readdirSync(urlObject);
|
|
167
168
|
if (!urlInfo.filenameHint) {
|
|
168
169
|
if (urlInfo.firstReference.type === "filesystem") {
|
|
169
170
|
urlInfo.filenameHint = `${
|
|
@@ -173,10 +174,17 @@ export const jsenvPluginProtocolFile = ({
|
|
|
173
174
|
urlInfo.filenameHint = `${urlToFilename(urlInfo.url)}/`;
|
|
174
175
|
}
|
|
175
176
|
}
|
|
177
|
+
const { headers, body } = serveDirectory(urlObject.href, {
|
|
178
|
+
headers: urlInfo.context.request
|
|
179
|
+
? urlInfo.context.request.headers
|
|
180
|
+
: {},
|
|
181
|
+
rootDirectoryUrl: urlInfo.context.rootDirectoryUrl,
|
|
182
|
+
});
|
|
176
183
|
return {
|
|
177
184
|
type: "directory",
|
|
178
|
-
contentType: "
|
|
179
|
-
|
|
185
|
+
contentType: headers["content-type"],
|
|
186
|
+
contentLength: headers["content-length"],
|
|
187
|
+
content: body,
|
|
180
188
|
};
|
|
181
189
|
}
|
|
182
190
|
if (
|
package/src/plugins/reference_analysis/directory/jsenv_plugin_directory_reference_analysis.js
CHANGED
|
@@ -11,6 +11,9 @@ export const jsenvPluginDirectoryReferenceAnalysis = () => {
|
|
|
11
11
|
urlInfo.url,
|
|
12
12
|
urlInfo.context.rootDirectoryUrl,
|
|
13
13
|
);
|
|
14
|
+
if (urlInfo.contentType !== "application/json") {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
14
17
|
const entryNames = JSON.parse(urlInfo.content);
|
|
15
18
|
const newEntryNames = [];
|
|
16
19
|
for (const entryName of entryNames) {
|