@jsenv/core 39.14.3 → 40.0.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 (34) hide show
  1. package/dist/js/directory_listing.js +16 -9
  2. package/dist/js/server_events_client.js +2 -2
  3. package/dist/jsenv_core.js +7220 -11089
  4. package/package.json +22 -19
  5. package/src/build/build.js +122 -93
  6. package/src/build/build_specifier_manager.js +103 -94
  7. package/src/build/build_urls_generator.js +1 -1
  8. package/src/build/{version_mappings_injection.js → mappings_injection.js} +62 -21
  9. package/src/build/start_build_server.js +46 -36
  10. package/src/dev/start_dev_server.js +246 -248
  11. package/src/helpers/watch_source_files.js +50 -36
  12. package/src/kitchen/fetched_content_compliance.js +4 -2
  13. package/src/kitchen/kitchen.js +31 -24
  14. package/src/kitchen/url_graph/references.js +10 -2
  15. package/src/kitchen/url_graph/url_graph.js +3 -0
  16. package/src/kitchen/url_graph/url_graph_visitor.js +3 -0
  17. package/src/plugins/autoreload/jsenv_plugin_autoreload_server.js +29 -16
  18. package/src/plugins/html_syntax_error_fallback/jsenv_plugin_html_syntax_error_fallback.js +1 -1
  19. package/src/plugins/plugin_controller.js +194 -200
  20. package/src/plugins/plugins.js +5 -0
  21. package/src/plugins/protocol_file/client/directory_listing.jsx +5 -0
  22. package/src/plugins/protocol_file/jsenv_plugin_directory_listing.js +92 -67
  23. package/src/plugins/protocol_file/jsenv_plugin_fs_redirection.js +17 -7
  24. package/src/plugins/protocol_file/jsenv_plugin_protocol_file.js +6 -0
  25. package/src/plugins/protocol_http/jsenv_plugin_protocol_http.js +33 -3
  26. package/src/plugins/reference_analysis/html/jsenv_plugin_html_reference_analysis.js +15 -22
  27. package/src/plugins/reference_analysis/js/jsenv_plugin_js_reference_analysis.js +53 -2
  28. package/src/plugins/resolution_node_esm/jsenv_plugin_node_esm_resolution.js +37 -30
  29. package/src/plugins/resolution_node_esm/node_esm_resolver.js +4 -8
  30. package/src/plugins/resolution_web/jsenv_plugin_web_resolution.js +8 -6
  31. package/src/plugins/server_events/client/server_events_client.js +2 -2
  32. package/src/plugins/server_events/jsenv_plugin_server_events.js +18 -16
  33. package/dist/js/ws.js +0 -6863
  34. package/src/helpers/lookup_package_directory.js +0 -9
@@ -1,12 +1,36 @@
1
- import { registerDirectoryLifecycle } from "@jsenv/filesystem";
1
+ import {
2
+ lookupPackageDirectory,
3
+ registerDirectoryLifecycle,
4
+ } from "@jsenv/filesystem";
2
5
  import { urlToRelativeUrl } from "@jsenv/urls";
3
6
  import { readFileSync } from "node:fs";
4
- import { lookupPackageDirectory } from "./lookup_package_directory.js";
7
+
8
+ export const getDirectoryWatchPatterns = (
9
+ directoryUrl,
10
+ watchedDirectoryUrl,
11
+ { sourceFilesConfig },
12
+ ) => {
13
+ const directoryUrlRelativeToWatchedDirectory = urlToRelativeUrl(
14
+ directoryUrl,
15
+ watchedDirectoryUrl,
16
+ );
17
+ const watchPatterns = {
18
+ [`${directoryUrlRelativeToWatchedDirectory}**/*`]: true, // by default watch everything inside the source directory
19
+ [`${directoryUrlRelativeToWatchedDirectory}**/.*`]: false, // file starting with a dot -> do not watch
20
+ [`${directoryUrlRelativeToWatchedDirectory}**/.*/`]: false, // directory starting with a dot -> do not watch
21
+ [`${directoryUrlRelativeToWatchedDirectory}**/node_modules/`]: false, // node_modules directory -> do not watch
22
+ };
23
+ for (const key of Object.keys(sourceFilesConfig)) {
24
+ watchPatterns[`${directoryUrlRelativeToWatchedDirectory}${key}`] =
25
+ sourceFilesConfig[key];
26
+ }
27
+ return watchPatterns;
28
+ };
5
29
 
6
30
  export const watchSourceFiles = (
7
31
  sourceDirectoryUrl,
8
32
  callback,
9
- { sourceFileConfig = {}, keepProcessAlive, cooldownBetweenFileEvents },
33
+ { sourceFilesConfig = {}, keepProcessAlive, cooldownBetweenFileEvents },
10
34
  ) => {
11
35
  // Project should use a dedicated directory (usually "src/")
12
36
  // passed to the dev server via "sourceDirectoryUrl" param
@@ -15,23 +39,18 @@ export const watchSourceFiles = (
15
39
  // In that case source directory might contain files matching "node_modules/*" or ".git/*"
16
40
  // And jsenv should not consider these as source files and watch them (to not hurt performances)
17
41
  const watchPatterns = {};
18
- const addDirectoryToWatch = (directoryUrlRelativeToRoot) => {
19
- Object.assign(watchPatterns, {
20
- [`${directoryUrlRelativeToRoot}**/*`]: true, // by default watch everything inside the source directory
21
- // line below is commented until @jsenv/url-meta fixes the fact that is matches
22
- // any file with an extension
23
- [`${directoryUrlRelativeToRoot}**/.*`]: false, // file starting with a dot -> do not watch
24
- [`${directoryUrlRelativeToRoot}**/.*/`]: false, // directory starting with a dot -> do not watch
25
- [`${directoryUrlRelativeToRoot}**/node_modules/`]: false, // node_modules directory -> do not watch
26
- });
27
- for (const key of Object.keys(sourceFileConfig)) {
28
- watchPatterns[`${directoryUrlRelativeToRoot}${key}`] =
29
- sourceFileConfig[key];
30
- }
42
+ let watchedDirectoryUrl = "";
43
+ const addDirectoryToWatch = (directoryUrl) => {
44
+ Object.assign(
45
+ watchPatterns,
46
+ getDirectoryWatchPatterns(directoryUrl, watchedDirectoryUrl, {
47
+ sourceFilesConfig,
48
+ }),
49
+ );
31
50
  };
32
- const watch = (rootDirectoryUrl) => {
51
+ const watch = () => {
33
52
  const stopWatchingSourceFiles = registerDirectoryLifecycle(
34
- rootDirectoryUrl,
53
+ watchedDirectoryUrl,
35
54
  {
36
55
  watchPatterns,
37
56
  cooldownBetweenFileEvents,
@@ -39,19 +58,19 @@ export const watchSourceFiles = (
39
58
  recursive: true,
40
59
  added: ({ relativeUrl }) => {
41
60
  callback({
42
- url: new URL(relativeUrl, rootDirectoryUrl).href,
61
+ url: new URL(relativeUrl, watchedDirectoryUrl).href,
43
62
  event: "added",
44
63
  });
45
64
  },
46
65
  updated: ({ relativeUrl }) => {
47
66
  callback({
48
- url: new URL(relativeUrl, rootDirectoryUrl).href,
67
+ url: new URL(relativeUrl, watchedDirectoryUrl).href,
49
68
  event: "modified",
50
69
  });
51
70
  },
52
71
  removed: ({ relativeUrl }) => {
53
72
  callback({
54
- url: new URL(relativeUrl, rootDirectoryUrl).href,
73
+ url: new URL(relativeUrl, watchedDirectoryUrl).href,
55
74
  event: "removed",
56
75
  });
57
76
  },
@@ -75,31 +94,26 @@ export const watchSourceFiles = (
75
94
  if (!workspaces || !Array.isArray(workspaces) || workspaces.length === 0) {
76
95
  break npm_workspaces;
77
96
  }
97
+ watchedDirectoryUrl = packageDirectoryUrl;
78
98
  for (const workspace of workspaces) {
79
99
  if (workspace.endsWith("*")) {
80
- const workspaceRelativeUrl = urlToRelativeUrl(
81
- new URL(workspace.slice(0, -1), packageDirectoryUrl),
100
+ const workspaceDirectoryUrl = new URL(
101
+ workspace.slice(0, -1),
82
102
  packageDirectoryUrl,
83
103
  );
84
- addDirectoryToWatch(workspaceRelativeUrl);
104
+ addDirectoryToWatch(workspaceDirectoryUrl);
85
105
  } else {
86
- const workspaceRelativeUrl = urlToRelativeUrl(
87
- new URL(workspace, packageDirectoryUrl),
88
- packageDirectoryUrl,
89
- );
106
+ const workspaceRelativeUrl = new URL(workspace, packageDirectoryUrl);
90
107
  addDirectoryToWatch(workspaceRelativeUrl);
91
108
  }
92
109
  }
93
110
  // we are updating the root directory
94
111
  // we must make the patterns relative to source directory relative to the new root directory
95
- const sourceRelativeToPackage = urlToRelativeUrl(
96
- sourceDirectoryUrl,
97
- packageDirectoryUrl,
98
- );
99
- addDirectoryToWatch(sourceRelativeToPackage);
100
- return watch(packageDirectoryUrl);
112
+ addDirectoryToWatch(sourceDirectoryUrl);
113
+ return watch();
101
114
  }
102
115
 
103
- addDirectoryToWatch("");
104
- return watch(sourceDirectoryUrl);
116
+ watchedDirectoryUrl = sourceDirectoryUrl;
117
+ addDirectoryToWatch(sourceDirectoryUrl);
118
+ return watch();
105
119
  };
@@ -7,12 +7,14 @@ export const assertFetchedContentCompliance = ({ urlInfo, content }) => {
7
7
  const { expectedContentType } = urlInfo.firstReference;
8
8
  if (expectedContentType && urlInfo.contentType !== expectedContentType) {
9
9
  throw new Error(
10
- `content-type must be "${expectedContentType}", got "${urlInfo.contentType}`,
10
+ `content-type must be "${expectedContentType}", got "${urlInfo.contentType} on ${urlInfo.url}`,
11
11
  );
12
12
  }
13
13
  const { expectedType } = urlInfo.firstReference;
14
14
  if (expectedType && urlInfo.type !== expectedType) {
15
- throw new Error(`type must be "${expectedType}", got "${urlInfo.type}"`);
15
+ throw new Error(
16
+ `type must be "${expectedType}", got "${urlInfo.type}" on ${urlInfo.url}`,
17
+ );
16
18
  }
17
19
  const { integrity } = urlInfo.firstReference;
18
20
  if (integrity) {
@@ -3,8 +3,6 @@ import { RUNTIME_COMPAT } from "@jsenv/runtime-compat";
3
3
  import { URL_META } from "@jsenv/url-meta";
4
4
  import { normalizeUrl } from "@jsenv/urls";
5
5
  import { CONTENT_TYPE } from "@jsenv/utils/src/content_type/content_type.js";
6
- import { jsenvPluginHtmlSyntaxErrorFallback } from "../plugins/html_syntax_error_fallback/jsenv_plugin_html_syntax_error_fallback.js";
7
- import { createPluginController } from "../plugins/plugin_controller.js";
8
6
  import {
9
7
  createFetchUrlContentError,
10
8
  createFinalizeUrlContentError,
@@ -42,7 +40,6 @@ export const createKitchen = ({
42
40
  // during dev/test clientRuntimeCompat is a single runtime
43
41
  // during build clientRuntimeCompat is runtimeCompat
44
42
  clientRuntimeCompat = runtimeCompat,
45
- plugins,
46
43
  supervisor,
47
44
  sourcemaps = dev ? "inline" : "none", // "programmatic" and "file" also allowed
48
45
  sourcemapsComment,
@@ -51,9 +48,9 @@ export const createKitchen = ({
51
48
  sourcemapsSourcesContent,
52
49
  outDirectoryUrl,
53
50
  initialContext = {},
54
- initialPluginsMeta = {},
55
51
  }) => {
56
52
  const logger = createLogger({ logLevel });
53
+
57
54
  const kitchen = {
58
55
  context: {
59
56
  ...initialContext,
@@ -74,12 +71,17 @@ export const createKitchen = ({
74
71
  outDirectoryUrl,
75
72
  },
76
73
  graph: null,
77
- pluginController: null,
78
74
  urlInfoTransformer: null,
75
+ pluginController: null,
79
76
  };
80
77
  const kitchenContext = kitchen.context;
81
78
  kitchenContext.kitchen = kitchen;
82
79
 
80
+ let pluginController;
81
+ kitchen.setPluginController = (value) => {
82
+ pluginController = kitchen.pluginController = value;
83
+ };
84
+
83
85
  const graph = createUrlGraph({
84
86
  name,
85
87
  rootDirectoryUrl,
@@ -87,13 +89,6 @@ export const createKitchen = ({
87
89
  });
88
90
  kitchen.graph = graph;
89
91
 
90
- const pluginController = createPluginController(
91
- kitchenContext,
92
- initialPluginsMeta,
93
- );
94
- kitchen.pluginController = pluginController;
95
- pluginController.pushPlugin(jsenvPluginHtmlSyntaxErrorFallback(), ...plugins);
96
-
97
92
  const urlInfoTransformer = createUrlInfoTransformer({
98
93
  logger,
99
94
  sourcemaps,
@@ -108,9 +103,7 @@ export const createKitchen = ({
108
103
 
109
104
  const isIgnoredByProtocol = (url) => {
110
105
  const { protocol } = new URL(url);
111
- const protocolIsSupported = supportedProtocols.some(
112
- (supportedProtocol) => protocol === supportedProtocol,
113
- );
106
+ const protocolIsSupported = supportedProtocols.includes(protocol);
114
107
  return !protocolIsSupported;
115
108
  };
116
109
  let isIgnoredByParam = () => false;
@@ -153,6 +146,12 @@ export const createKitchen = ({
153
146
  ? isIgnored(reference.original.url)
154
147
  : isIgnored(referenceUrl)
155
148
  ) {
149
+ if (
150
+ referenceUrl.startsWith("node:") &&
151
+ !reference.specifier.startsWith("node:")
152
+ ) {
153
+ reference.specifier = `node:${reference.specifier}`;
154
+ }
156
155
  referenceUrl = `ignore:${referenceUrl}`;
157
156
  }
158
157
 
@@ -338,6 +337,8 @@ ${ANSI.color(normalizedReturnValue, ANSI.YELLOW)}
338
337
  headers = {},
339
338
  body,
340
339
  isEntryPoint,
340
+ isDynamicEntryPoint,
341
+ filenameHint,
341
342
  } = fetchUrlContentReturnValue;
342
343
  if (content === undefined) {
343
344
  content = body;
@@ -345,6 +346,9 @@ ${ANSI.color(normalizedReturnValue, ANSI.YELLOW)}
345
346
  if (contentType === undefined) {
346
347
  contentType = headers["content-type"] || "application/octet-stream";
347
348
  }
349
+ if (filenameHint) {
350
+ urlInfo.filenameHint = filenameHint;
351
+ }
348
352
  urlInfo.status = status;
349
353
  urlInfo.contentType = contentType;
350
354
  urlInfo.headers = headers;
@@ -364,6 +368,9 @@ ${ANSI.color(normalizedReturnValue, ANSI.YELLOW)}
364
368
  if (typeof isEntryPoint === "boolean") {
365
369
  urlInfo.isEntryPoint = isEntryPoint;
366
370
  }
371
+ if (typeof isDynamicEntryPoint === "boolean") {
372
+ urlInfo.isDynamicEntryPoint = isDynamicEntryPoint;
373
+ }
367
374
  assertFetchedContentCompliance({
368
375
  urlInfo,
369
376
  content,
@@ -672,18 +679,18 @@ const memoizeIsSupported = (runtimeCompat) => {
672
679
 
673
680
  const inferUrlInfoType = (urlInfo) => {
674
681
  const { type, typeHint } = urlInfo;
675
- const { contentType } = urlInfo;
682
+ const mediaType = CONTENT_TYPE.asMediaType(urlInfo.contentType);
676
683
  const { expectedType } = urlInfo.firstReference;
677
684
  if (type === "sourcemap" || typeHint === "sourcemap") {
678
685
  return "sourcemap";
679
686
  }
680
- if (contentType === "text/html") {
687
+ if (mediaType === "text/html") {
681
688
  return "html";
682
689
  }
683
- if (contentType === "text/css") {
690
+ if (mediaType === "text/css") {
684
691
  return "css";
685
692
  }
686
- if (contentType === "text/javascript") {
693
+ if (mediaType === "text/javascript") {
687
694
  if (expectedType === "js_classic") {
688
695
  return "js_classic";
689
696
  }
@@ -692,19 +699,19 @@ const inferUrlInfoType = (urlInfo) => {
692
699
  }
693
700
  return "js_module";
694
701
  }
695
- if (contentType === "application/importmap+json") {
702
+ if (mediaType === "application/importmap+json") {
696
703
  return "importmap";
697
704
  }
698
- if (contentType === "application/manifest+json") {
705
+ if (mediaType === "application/manifest+json") {
699
706
  return "webmanifest";
700
707
  }
701
- if (contentType === "image/svg+xml") {
708
+ if (mediaType === "image/svg+xml") {
702
709
  return "svg";
703
710
  }
704
- if (CONTENT_TYPE.isJson(contentType)) {
711
+ if (CONTENT_TYPE.isJson(mediaType)) {
705
712
  return "json";
706
713
  }
707
- if (CONTENT_TYPE.isTextual(contentType)) {
714
+ if (CONTENT_TYPE.isTextual(mediaType)) {
708
715
  return "text";
709
716
  }
710
717
  return expectedType || "other";
@@ -9,7 +9,6 @@ import {
9
9
  } from "@jsenv/urls";
10
10
 
11
11
  import { prependContent } from "../prepend_content.js";
12
- import { isWebWorkerEntryPointReference } from "../web_workers.js";
13
12
 
14
13
  let referenceId = 0;
15
14
 
@@ -275,6 +274,7 @@ const createReference = ({
275
274
  baseUrl,
276
275
  isOriginalPosition,
277
276
  isEntryPoint = false,
277
+ isDynamicEntryPoint = false,
278
278
  isResourceHint = false,
279
279
  // implicit references are not real references
280
280
  // they represent an abstract relationship
@@ -350,6 +350,7 @@ const createReference = ({
350
350
  isOriginalPosition,
351
351
  baseUrl,
352
352
  isEntryPoint,
353
+ isDynamicEntryPoint,
353
354
  isResourceHint,
354
355
  isImplicit,
355
356
  implicitReferenceSet: new Set(),
@@ -731,9 +732,12 @@ const applyReferenceEffectsOnUrlInfo = (reference) => {
731
732
  referencedUrlInfo.originalUrl =
732
733
  referencedUrlInfo.originalUrl || (reference.original || reference).url;
733
734
 
734
- if (reference.isEntryPoint || isWebWorkerEntryPointReference(reference)) {
735
+ if (reference.isEntryPoint) {
735
736
  referencedUrlInfo.isEntryPoint = true;
736
737
  }
738
+ if (reference.isDynamicEntryPoint) {
739
+ referencedUrlInfo.isDynamicEntryPoint = true;
740
+ }
737
741
  Object.assign(referencedUrlInfo.data, reference.data);
738
742
  Object.assign(referencedUrlInfo.timing, reference.timing);
739
743
  if (reference.injected) {
@@ -754,4 +758,8 @@ const applyReferenceEffectsOnUrlInfo = (reference) => {
754
758
  if (reference.expectedSubtype) {
755
759
  referencedUrlInfo.subtypeHint = reference.expectedSubtype;
756
760
  }
761
+
762
+ referencedUrlInfo.entryUrlInfo = reference.isEntryPoint
763
+ ? referencedUrlInfo
764
+ : reference.ownerUrlInfo.entryUrlInfo;
757
765
  };
@@ -129,6 +129,7 @@ export const createUrlGraph = ({
129
129
 
130
130
  const rootUrlInfo = createUrlInfo(rootDirectoryUrl, kitchen.context);
131
131
  rootUrlInfo.isRoot = true;
132
+ rootUrlInfo.entryUrlInfo = rootUrlInfo;
132
133
  addUrlInfo(rootUrlInfo);
133
134
 
134
135
  Object.assign(urlGraph, {
@@ -204,6 +205,8 @@ const createUrlInfo = (url, context) => {
204
205
  url: null,
205
206
  originalUrl: undefined,
206
207
  isEntryPoint: false,
208
+ isDynamicEntryPoint: false,
209
+ entryUrlInfo: null,
207
210
  originalContent: undefined,
208
211
  originalContentAst: undefined,
209
212
  content: undefined,
@@ -118,6 +118,9 @@ GRAPH_VISITOR.forEachUrlInfoStronglyReferenced = (
118
118
  if (referenceToOther.gotInlined()) {
119
119
  continue;
120
120
  }
121
+ if (referenceToOther.url.startsWith("ignore:")) {
122
+ continue;
123
+ }
121
124
  const referencedUrlInfo = referenceToOther.urlInfo;
122
125
  if (
123
126
  directoryUrlInfoSet &&
@@ -243,6 +243,13 @@ export const jsenvPluginAutoreloadServer = ({
243
243
  // happens when reloading the current html page for instance
244
244
  continue;
245
245
  }
246
+ if (
247
+ lastReferenceFromOther.injected &&
248
+ lastReferenceFromOther.isWeak &&
249
+ lastReferenceFromOther.isImplicit
250
+ ) {
251
+ continue;
252
+ }
246
253
  const { ownerUrlInfo } = lastReferenceFromOther;
247
254
  if (!ownerUrlInfo.isUsed()) {
248
255
  continue;
@@ -326,21 +333,27 @@ export const jsenvPluginAutoreloadServer = ({
326
333
  );
327
334
  },
328
335
  },
329
- serve: (serveInfo) => {
330
- if (serveInfo.request.pathname === "/__graph__") {
331
- const graphJson = JSON.stringify(
332
- serveInfo.kitchen.graph.toJSON(serveInfo.rootDirectoryUrl),
333
- );
334
- return {
335
- status: 200,
336
- headers: {
337
- "content-type": "application/json",
338
- "content-length": Buffer.byteLength(graphJson),
339
- },
340
- body: graphJson,
341
- };
342
- }
343
- return null;
344
- },
336
+ devServerRoutes: [
337
+ {
338
+ endpoint: "GET /.internal/graph.json",
339
+ description:
340
+ "Return a url graph of the project as a JSON file. This is useful to debug the project graph.",
341
+ availableMediaTypes: ["application/json"],
342
+ declarationSource: import.meta.url,
343
+ fetch: (request, { kitchen }) => {
344
+ const graphJson = JSON.stringify(
345
+ kitchen.graph.toJSON(kitchen.context.rootDirectoryUrl),
346
+ );
347
+ return {
348
+ status: 200,
349
+ headers: {
350
+ "content-type": "application/json",
351
+ "content-length": Buffer.byteLength(graphJson),
352
+ },
353
+ body: graphJson,
354
+ };
355
+ },
356
+ },
357
+ ],
345
358
  };
346
359
  };
@@ -66,7 +66,7 @@ const generateHtmlForSyntaxError = (
66
66
  const replacers = {
67
67
  fileRelativeUrl: htmlRelativeUrl,
68
68
  reasonCode: htmlSyntaxError.reasonCode,
69
- errorLinkHref: `javascript:window.fetch('/__open_in_editor__/${encodeURIComponent(
69
+ errorLinkHref: `javascript:window.fetch('/.internal/open_file/${encodeURIComponent(
70
70
  urlWithLineAndColumn,
71
71
  )}')`,
72
72
  errorLinkText: `${htmlRelativeUrl}:${line}:${column}`,