@jsenv/core 39.11.2 → 39.13.0

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.
Files changed (37) hide show
  1. package/dist/css/directory_listing.css +211 -0
  2. package/dist/html/directory_listing.html +18 -0
  3. package/dist/js/directory_listing.js +240 -0
  4. package/dist/jsenv_core.js +1057 -757
  5. package/dist/other/dir.png +0 -0
  6. package/dist/other/file.png +0 -0
  7. package/dist/other/home.svg +6 -0
  8. package/package.json +6 -6
  9. package/src/build/build.js +7 -7
  10. package/src/build/build_specifier_manager.js +0 -1
  11. package/src/dev/start_dev_server.js +39 -49
  12. package/src/kitchen/kitchen.js +20 -4
  13. package/src/kitchen/out_directory_url.js +2 -1
  14. package/src/kitchen/url_graph/references.js +3 -1
  15. package/src/kitchen/url_graph/url_graph.js +1 -0
  16. package/src/kitchen/url_graph/url_info_transformations.js +37 -4
  17. package/src/plugins/inlining/jsenv_plugin_inlining_into_html.js +10 -8
  18. package/src/plugins/plugin_controller.js +170 -114
  19. package/src/plugins/plugins.js +5 -4
  20. package/src/plugins/protocol_file/client/assets/home.svg +6 -0
  21. package/src/plugins/protocol_file/client/directory_listing.css +190 -0
  22. package/src/plugins/protocol_file/client/directory_listing.html +18 -0
  23. package/src/plugins/protocol_file/client/directory_listing.jsx +250 -0
  24. package/src/plugins/protocol_file/file_and_server_urls_converter.js +32 -0
  25. package/src/plugins/protocol_file/jsenv_plugin_directory_listing.js +398 -0
  26. package/src/plugins/protocol_file/jsenv_plugin_protocol_file.js +40 -333
  27. package/src/plugins/protocol_http/jsenv_plugin_protocol_http.js +3 -2
  28. package/src/plugins/reference_analysis/html/jsenv_plugin_html_reference_analysis.js +7 -6
  29. package/src/plugins/reference_analysis/js/jsenv_plugin_js_reference_analysis.js +1 -3
  30. package/src/plugins/reference_analysis/jsenv_plugin_reference_analysis.js +2 -18
  31. package/src/plugins/server_events/jsenv_plugin_server_events.js +100 -0
  32. package/dist/html/directory.html +0 -165
  33. package/dist/html/html_404_and_ancestor_dir.html +0 -203
  34. package/src/plugins/protocol_file/client/assets/directory.css +0 -133
  35. package/src/plugins/protocol_file/client/directory.html +0 -17
  36. package/src/plugins/protocol_file/client/html_404_and_ancestor_dir.html +0 -54
  37. package/src/plugins/server_events/jsenv_plugin_server_events_client_injection.js +0 -37
Binary file
Binary file
@@ -0,0 +1,6 @@
1
+ <svg id="root" fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="30.4 23.47 40.05 45.63">
2
+ <path d="M32.5,69.1c-0.6,0-1.1-0.2-1.4-0.5c-0.7-0.6-0.7-1.5-0.7-1.8V41.1h4v24h9v4H32.9C32.8,69.1,32.6,69.1,32.5,69.1z" />
3
+ <path d="M67.8,69.1c-0.2,0-0.3,0-0.5,0c-0.1,0-0.1,0-0.2,0H56.4v-4h10v-23h4v24.7c0,0.6-0.3,1.2-0.7,1.7 C69.1,69,68.3,69.1,67.8,69.1z" />
4
+ <path d="M68.4,44.1c-0.5,0-1-0.2-1.3-0.5L50,28.2L33.7,42.6c-0.8,0.7-2.1,0.7-2.8-0.2c-0.7-0.8-0.7-2.1,0.2-2.8L48.7,24 c0.8-0.7,1.9-0.7,2.7,0l18.4,16.6c0.8,0.7,0.9,2,0.1,2.8C69.5,43.8,68.9,44.1,68.4,44.1z" />
5
+ <path d="M58.4,69.1h-4v-13h-8v13h-4v-15c0-1.1,0.9-2,2-2h12c1.1,0,2,0.9,2,2V69.1z" />
6
+ </svg>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/core",
3
- "version": "39.11.2",
3
+ "version": "39.13.0",
4
4
  "description": "Tool to develop, test and build js projects",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -69,19 +69,19 @@
69
69
  "dependencies": {
70
70
  "@financial-times/polyfill-useragent-normaliser": "1.10.2",
71
71
  "@jsenv/abort": "4.3.0",
72
- "@jsenv/ast": "6.4.4",
72
+ "@jsenv/ast": "6.5.0",
73
73
  "@jsenv/filesystem": "4.13.2",
74
74
  "@jsenv/humanize": "1.2.8",
75
75
  "@jsenv/importmap": "1.2.1",
76
76
  "@jsenv/integrity": "0.0.2",
77
- "@jsenv/js-module-fallback": "1.3.56",
77
+ "@jsenv/js-module-fallback": "1.3.57",
78
78
  "@jsenv/node-esm-resolution": "1.0.6",
79
79
  "@jsenv/plugin-bundling": "2.7.24",
80
80
  "@jsenv/plugin-minification": "1.5.13",
81
- "@jsenv/plugin-supervisor": "1.6.3",
82
- "@jsenv/plugin-transpilation": "1.4.92",
81
+ "@jsenv/plugin-supervisor": "1.6.4",
82
+ "@jsenv/plugin-transpilation": "1.4.93",
83
83
  "@jsenv/runtime-compat": "1.3.1",
84
- "@jsenv/server": "15.4.1",
84
+ "@jsenv/server": "15.5.0",
85
85
  "@jsenv/sourcemap": "1.2.30",
86
86
  "@jsenv/url-meta": "8.5.2",
87
87
  "@jsenv/urls": "2.6.0",
@@ -449,33 +449,33 @@ build ${entryPointKeys.length} entry points`);
449
449
 
450
450
  const bundlers = {};
451
451
  bundle: {
452
- rawKitchen.pluginController.plugins.forEach((plugin) => {
452
+ for (const plugin of rawKitchen.pluginController.activePlugins) {
453
453
  const bundle = plugin.bundle;
454
454
  if (!bundle) {
455
- return;
455
+ continue;
456
456
  }
457
457
  if (typeof bundle !== "object") {
458
458
  throw new Error(
459
459
  `bundle must be an object, found "${bundle}" on plugin named "${plugin.name}"`,
460
460
  );
461
461
  }
462
- Object.keys(bundle).forEach((type) => {
462
+ for (const type of Object.keys(bundle)) {
463
463
  const bundleFunction = bundle[type];
464
464
  if (!bundleFunction) {
465
- return;
465
+ continue;
466
466
  }
467
467
  const bundlerForThatType = bundlers[type];
468
468
  if (bundlerForThatType) {
469
469
  // first plugin to define a bundle hook wins
470
- return;
470
+ continue;
471
471
  }
472
472
  bundlers[type] = {
473
473
  plugin,
474
474
  bundleFunction: bundle[type],
475
475
  urlInfoMap: new Map(),
476
476
  };
477
- });
478
- });
477
+ }
478
+ }
479
479
  const addToBundlerIfAny = (rawUrlInfo) => {
480
480
  const bundler = bundlers[rawUrlInfo.type];
481
481
  if (bundler) {
@@ -295,7 +295,6 @@ export const createBuildSpecifierManager = ({
295
295
  type: reference.type,
296
296
  expectedType: reference.expectedType,
297
297
  specifier: reference.specifier,
298
- specifierPathname: reference.specifierPathname,
299
298
  specifierLine: reference.specifierLine,
300
299
  specifierColumn: reference.specifierColumn,
301
300
  specifierStart: reference.specifierStart,
@@ -15,7 +15,6 @@ import { convertFileSystemErrorToResponseProperties } from "@jsenv/server/src/in
15
15
  import { URL_META } from "@jsenv/url-meta";
16
16
  import { urlIsInsideOf, urlToRelativeUrl } from "@jsenv/urls";
17
17
  import { existsSync, readFileSync } from "node:fs";
18
-
19
18
  import { defaultRuntimeCompat } from "../build/build.js";
20
19
  import { createEventEmitter } from "../helpers/event_emitter.js";
21
20
  import { lookupPackageDirectory } from "../helpers/lookup_package_directory.js";
@@ -24,8 +23,7 @@ import { WEB_URL_CONVERTER } from "../helpers/web_url_converter.js";
24
23
  import { jsenvCoreDirectoryUrl } from "../jsenv_core_directory_url.js";
25
24
  import { createKitchen } from "../kitchen/kitchen.js";
26
25
  import { getCorePlugins } from "../plugins/plugins.js";
27
- import { jsenvPluginServerEventsClientInjection } from "../plugins/server_events/jsenv_plugin_server_events_client_injection.js";
28
- import { createServerEventsDispatcher } from "../plugins/server_events/server_events_dispatcher.js";
26
+ import { jsenvPluginServerEvents } from "../plugins/server_events/jsenv_plugin_server_events.js";
29
27
  import { parseUserAgentHeader } from "./user_agent.js";
30
28
 
31
29
  const EXECUTED_BY_TEST_PLAN = process.argv.includes("--jsenv-test");
@@ -145,10 +143,11 @@ export const startDevServer = async ({
145
143
  });
146
144
 
147
145
  const serverStopCallbackSet = new Set();
148
- const serverEventsDispatcher = createServerEventsDispatcher();
146
+ const serverStopAbortController = new AbortController();
149
147
  serverStopCallbackSet.add(() => {
150
- serverEventsDispatcher.destroy();
148
+ serverStopAbortController.abort();
151
149
  });
150
+ const serverStopAbortSignal = serverStopAbortController.signal;
152
151
  const kitchenCache = new Map();
153
152
 
154
153
  const finalServices = [];
@@ -251,7 +250,7 @@ export const startDevServer = async ({
251
250
 
252
251
  kitchen = createKitchen({
253
252
  name: runtimeId,
254
- signal,
253
+ signal: serverStopAbortSignal,
255
254
  logLevel,
256
255
  rootDirectoryUrl: sourceDirectoryUrl,
257
256
  mainFilePath: sourceMainFilePath,
@@ -260,6 +259,7 @@ export const startDevServer = async ({
260
259
  runtimeCompat,
261
260
  clientRuntimeCompat,
262
261
  plugins: [
262
+ jsenvPluginServerEvents({ clientAutoreload }),
263
263
  ...plugins,
264
264
  ...getCorePlugins({
265
265
  rootDirectoryUrl: sourceDirectoryUrl,
@@ -335,7 +335,17 @@ export const startDevServer = async ({
335
335
  for (const implicitUrl of urlInfoCreated.implicitUrlSet) {
336
336
  const implicitUrlInfo =
337
337
  urlInfoCreated.graph.getUrlInfo(implicitUrl);
338
- if (implicitUrlInfo && !implicitUrlInfo.isValid()) {
338
+ if (!implicitUrlInfo) {
339
+ continue;
340
+ }
341
+ if (implicitUrlInfo.content === undefined) {
342
+ // happens when we explicitely load an url with a search param
343
+ // - it creates an implicit url info to the url without params
344
+ // - we never explicitely request the url without search param so it has no content
345
+ // in that case the underlying urlInfo cannot be invalidate by the implicit
346
+ continue;
347
+ }
348
+ if (!implicitUrlInfo.isValid()) {
339
349
  return false;
340
350
  }
341
351
  }
@@ -354,41 +364,6 @@ export const startDevServer = async ({
354
364
  serverStopCallbackSet.add(() => {
355
365
  kitchen.pluginController.callHooks("destroy", kitchen.context);
356
366
  });
357
- server_events: {
358
- const allServerEvents = {};
359
- kitchen.pluginController.plugins.forEach((plugin) => {
360
- const { serverEvents } = plugin;
361
- if (serverEvents) {
362
- Object.keys(serverEvents).forEach((serverEventName) => {
363
- // we could throw on serverEvent name conflict
364
- // we could throw if serverEvents[serverEventName] is not a function
365
- allServerEvents[serverEventName] = serverEvents[serverEventName];
366
- });
367
- }
368
- });
369
- const serverEventNames = Object.keys(allServerEvents);
370
- if (serverEventNames.length > 0) {
371
- Object.keys(allServerEvents).forEach((serverEventName) => {
372
- const serverEventInfo = {
373
- ...kitchen.context,
374
- sendServerEvent: (data) => {
375
- serverEventsDispatcher.dispatch({
376
- type: serverEventName,
377
- data,
378
- });
379
- },
380
- };
381
- const serverEventInit = allServerEvents[serverEventName];
382
- serverEventInit(serverEventInfo);
383
- });
384
- kitchen.pluginController.unshiftPlugin(
385
- jsenvPluginServerEventsClientInjection(
386
- clientAutoreload.clientServerEventsConfig,
387
- ),
388
- );
389
- }
390
- }
391
-
392
367
  kitchenCache.set(runtimeId, kitchen);
393
368
  onKitchenCreated(kitchen);
394
369
  return kitchen;
@@ -473,9 +448,10 @@ export const startDevServer = async ({
473
448
  // If they match jsenv bypass cooking and returns 304
474
449
  // This must not happen when a plugin uses "no-store" or "no-cache" as it means
475
450
  // plugin logic wants to happens for every request to this url
476
- ...(urlInfo.headers["cache-control"] === "no-store" ||
477
- urlInfo.headers["cache-control"] === "no-cache"
478
- ? {}
451
+ ...(cacheIsDisabledInResponseHeader(urlInfoTargetedByCache)
452
+ ? {
453
+ "cache-control": "no-store", // for inline file we force no-store when parent is no-store
454
+ }
479
455
  : {
480
456
  "cache-control": `private,max-age=0,must-revalidate`,
481
457
  // it's safe to use "_" separator because etag is encoded with base64 (see https://stackoverflow.com/a/13195197)
@@ -576,13 +552,20 @@ ${error.trace?.message}`);
576
552
  };
577
553
  }
578
554
  },
579
- handleWebsocket: (websocket, { request }) => {
555
+ handleWebsocket: async (websocket, { request }) => {
580
556
  // if (true || logLevel === "debug") {
581
557
  // console.log("handleWebsocket", websocket, request.headers);
582
558
  // }
583
- if (request.headers["sec-websocket-protocol"] === "jsenv") {
584
- serverEventsDispatcher.addWebsocket(websocket, request);
585
- }
559
+ const kitchen = getOrCreateKitchen(request);
560
+ const serveWebsocketHookInfo = {
561
+ request,
562
+ websocket,
563
+ context: kitchen.context,
564
+ };
565
+ await kitchen.pluginController.callAsyncHooksUntil(
566
+ "serveWebsocket",
567
+ serveWebsocketHookInfo,
568
+ );
586
569
  },
587
570
  });
588
571
  }
@@ -675,3 +658,10 @@ ${error.trace?.message}`);
675
658
  kitchenCache,
676
659
  };
677
660
  };
661
+
662
+ const cacheIsDisabledInResponseHeader = (urlInfo) => {
663
+ return (
664
+ urlInfo.headers["cache-control"] === "no-store" ||
665
+ urlInfo.headers["cache-control"] === "no-cache"
666
+ );
667
+ };
@@ -92,10 +92,7 @@ export const createKitchen = ({
92
92
  initialPluginsMeta,
93
93
  );
94
94
  kitchen.pluginController = pluginController;
95
- pluginController.pushPlugin(jsenvPluginHtmlSyntaxErrorFallback());
96
- plugins.forEach((pluginEntry) => {
97
- pluginController.pushPlugin(pluginEntry);
98
- });
95
+ pluginController.pushPlugin(jsenvPluginHtmlSyntaxErrorFallback(), ...plugins);
99
96
 
100
97
  const urlInfoTransformer = createUrlInfoTransformer({
101
98
  logger,
@@ -201,6 +198,25 @@ ${ANSI.color(reference.url, ANSI.YELLOW)}
201
198
  `);
202
199
  }
203
200
  }
201
+ const request = kitchen.context.request;
202
+ if (request) {
203
+ let requestResource = request.resource;
204
+ let requestedUrl;
205
+ if (requestResource.startsWith("/@fs/")) {
206
+ const fsRootRelativeUrl = requestResource.slice("/@fs/".length);
207
+ requestedUrl = `file:///${fsRootRelativeUrl}`;
208
+ } else {
209
+ const requestedUrlObject = new URL(
210
+ requestResource === "/" ? mainFilePath : requestResource.slice(1),
211
+ rootDirectoryUrl,
212
+ );
213
+ requestedUrlObject.searchParams.delete("hot");
214
+ requestedUrl = requestedUrlObject.href;
215
+ }
216
+ if (requestedUrl === reference.url) {
217
+ reference.isDirectRequest = true;
218
+ }
219
+ }
204
220
  redirect: {
205
221
  if (reference.isImplicit && reference.isWeak) {
206
222
  // not needed for implicit references that are not rendered anywhere
@@ -18,11 +18,12 @@ export const determineFileUrlForOutDirectory = (urlInfo) => {
18
18
  if (filenameHint) {
19
19
  url = setUrlFilename(url, filenameHint);
20
20
  }
21
- return moveUrl({
21
+ const outUrl = moveUrl({
22
22
  url,
23
23
  from: rootDirectoryUrl,
24
24
  to: outDirectoryUrl,
25
25
  });
26
+ return outUrl;
26
27
  };
27
28
 
28
29
  export const determineSourcemapFileUrl = (urlInfo) => {
@@ -111,7 +111,7 @@ export const createDependencies = (ownerUrlInfo) => {
111
111
  const injectAsBannerCodeBeforeFinalize = (urlInfoReceiver) => {
112
112
  const basename = urlToBasename(sideEffectFileUrl);
113
113
  const inlineUrl = generateUrlForInlineContent({
114
- url: urlInfoReceiver.url,
114
+ url: urlInfoReceiver.originalUrl || urlInfoReceiver.url,
115
115
  basename,
116
116
  extension: urlToExtension(sideEffectFileUrl),
117
117
  });
@@ -274,6 +274,7 @@ const createReference = ({
274
274
  specifierColumn,
275
275
  baseUrl,
276
276
  isOriginalPosition,
277
+ isDirectRequest = false,
277
278
  isEntryPoint = false,
278
279
  isResourceHint = false,
279
280
  // implicit references are not real references
@@ -348,6 +349,7 @@ const createReference = ({
348
349
  specifierColumn,
349
350
  isOriginalPosition,
350
351
  baseUrl,
352
+ isDirectRequest,
351
353
  isEntryPoint,
352
354
  isResourceHint,
353
355
  isImplicit,
@@ -236,6 +236,7 @@ const createUrlInfo = (url, context) => {
236
236
  writable: false,
237
237
  value: url,
238
238
  });
239
+ urlInfo.pathname = new URL(url).pathname;
239
240
  urlInfo.searchParams = new URL(url).searchParams;
240
241
 
241
242
  urlInfo.dependencies = createDependencies(urlInfo);
@@ -4,7 +4,14 @@ import {
4
4
  generateSourcemapDataUrl,
5
5
  SOURCEMAP,
6
6
  } from "@jsenv/sourcemap";
7
- import { isFileSystemPath, urlToPathname, urlToRelativeUrl } from "@jsenv/urls";
7
+ import {
8
+ isFileSystemPath,
9
+ setUrlBasename,
10
+ urlToBasename,
11
+ urlToFileSystemPath,
12
+ urlToPathname,
13
+ urlToRelativeUrl,
14
+ } from "@jsenv/urls";
8
15
  import { pathToFileURL } from "node:url";
9
16
  import {
10
17
  defineGettersOnPropertiesDerivedFromContent,
@@ -279,7 +286,15 @@ export const createUrlInfoTransformer = ({
279
286
  contentIsInlined = false;
280
287
  }
281
288
  if (!contentIsInlined) {
282
- writeFileSync(new URL(generatedUrl), urlInfo.content, { force: true });
289
+ const generatedUrlObject = new URL(generatedUrl);
290
+ let baseName = urlToBasename(generatedUrlObject);
291
+ for (const [key, value] of generatedUrlObject.searchParams) {
292
+ baseName += `7${encodeFilePathComponent(key)}=${encodeFilePathComponent(value)}`;
293
+ }
294
+ const outFileUrl = setUrlBasename(generatedUrlObject, baseName);
295
+ let outFilePath = urlToFileSystemPath(outFileUrl);
296
+ outFilePath = truncate(outFilePath, 2055); // for windows
297
+ writeFileSync(outFilePath, urlInfo.content, { force: true });
283
298
  }
284
299
  const { sourcemapGeneratedUrl, sourcemapReference } = urlInfo;
285
300
  if (sourcemapGeneratedUrl && sourcemapReference) {
@@ -398,6 +413,26 @@ export const createUrlInfoTransformer = ({
398
413
  };
399
414
  };
400
415
 
416
+ // https://gist.github.com/barbietunnie/7bc6d48a424446c44ff4
417
+ const illegalRe = /[/?<>\\:*|"]/g;
418
+ // eslint-disable-next-line no-control-regex
419
+ const controlRe = /[\x00-\x1f\x80-\x9f]/g;
420
+ const reservedRe = /^\.+$/;
421
+ const windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i;
422
+ const encodeFilePathComponent = (input, replacement = "") => {
423
+ const encoded = input
424
+ .replace(illegalRe, replacement)
425
+ .replace(controlRe, replacement)
426
+ .replace(reservedRe, replacement)
427
+ .replace(windowsReservedRe, replacement);
428
+ return encoded;
429
+ };
430
+ const truncate = (sanitized, length) => {
431
+ const uint8Array = new TextEncoder().encode(sanitized);
432
+ const truncated = uint8Array.slice(0, length);
433
+ return new TextDecoder().decode(truncated);
434
+ };
435
+
401
436
  const shouldUpdateSourcemapComment = (urlInfo, sourcemaps) => {
402
437
  if (urlInfo.context.buildStep === "shape") {
403
438
  return false;
@@ -407,7 +442,6 @@ const shouldUpdateSourcemapComment = (urlInfo, sourcemaps) => {
407
442
  }
408
443
  return false;
409
444
  };
410
-
411
445
  const mayHaveSourcemap = (urlInfo) => {
412
446
  if (urlInfo.url.startsWith("data:")) {
413
447
  return false;
@@ -417,7 +451,6 @@ const mayHaveSourcemap = (urlInfo) => {
417
451
  }
418
452
  return true;
419
453
  };
420
-
421
454
  const shouldHandleSourcemap = (urlInfo) => {
422
455
  const { sourcemaps } = urlInfo.context;
423
456
  if (
@@ -42,10 +42,11 @@ export const jsenvPluginInliningIntoHtml = () => {
42
42
  const { line, column, isOriginal } = getHtmlNodePosition(linkNode, {
43
43
  preferOriginal: true,
44
44
  });
45
- const linkInlineUrl = getUrlForContentInsideHtml(linkNode, {
46
- htmlUrl: urlInfo.url,
47
- url: linkReference.url,
48
- });
45
+ const linkInlineUrl = getUrlForContentInsideHtml(
46
+ linkNode,
47
+ urlInfo,
48
+ linkReference,
49
+ );
49
50
  const linkReferenceInlined = linkReference.inline({
50
51
  line,
51
52
  column,
@@ -94,10 +95,11 @@ export const jsenvPluginInliningIntoHtml = () => {
94
95
  const { line, column, isOriginal } = getHtmlNodePosition(scriptNode, {
95
96
  preferOriginal: true,
96
97
  });
97
- const scriptInlineUrl = getUrlForContentInsideHtml(scriptNode, {
98
- htmlUrl: urlInfo.url,
99
- url: scriptReference.url,
100
- });
98
+ const scriptInlineUrl = getUrlForContentInsideHtml(
99
+ scriptNode,
100
+ urlInfo,
101
+ scriptReference,
102
+ );
101
103
  const scriptReferenceInlined = scriptReference.inline({
102
104
  line,
103
105
  column,