@jsenv/core 27.3.2 → 27.4.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 (38) hide show
  1. package/README.md +16 -4
  2. package/dist/controllable_child_process.mjs +1 -0
  3. package/dist/controllable_worker_thread.mjs +1 -0
  4. package/dist/js/event_source_client.js +45 -24
  5. package/dist/js/execute_using_dynamic_import.js +5 -3
  6. package/dist/js/html_supervisor_installer.js +368 -139
  7. package/dist/main.js +668 -471
  8. package/package.json +5 -6
  9. package/src/build/build.js +6 -4
  10. package/src/build/graph_utils.js +14 -11
  11. package/src/execute/run.js +29 -28
  12. package/src/execute/runtimes/browsers/from_playwright.js +90 -92
  13. package/src/execute/runtimes/node/execute_using_dynamic_import.js +8 -2
  14. package/src/execute/runtimes/node/node_child_process.js +2 -0
  15. package/src/execute/runtimes/node/node_worker_thread.js +11 -6
  16. package/src/helpers/event_source/event_source.js +38 -17
  17. package/src/omega/errors.js +41 -9
  18. package/src/omega/kitchen.js +35 -19
  19. package/src/omega/omega_server.js +54 -1
  20. package/src/omega/server/file_service.js +30 -3
  21. package/src/omega/url_graph/url_graph_report.js +2 -4
  22. package/src/omega/url_graph.js +29 -16
  23. package/src/plugins/autoreload/dev_sse/client/event_source_client.js +8 -8
  24. package/src/plugins/autoreload/dev_sse/jsenv_plugin_dev_sse_server.js +160 -172
  25. package/src/plugins/autoreload/jsenv_plugin_autoreload.js +0 -4
  26. package/src/plugins/bundling/js_module/bundle_js_module.js +0 -1
  27. package/src/plugins/html_supervisor/client/error_in_document.js +268 -121
  28. package/src/plugins/html_supervisor/client/html_supervisor_installer.js +47 -5
  29. package/src/plugins/html_supervisor/jsenv_plugin_html_supervisor.js +37 -12
  30. package/src/plugins/node_esm_resolution/jsenv_plugin_node_esm_resolution.js +1 -2
  31. package/src/plugins/plugins.js +0 -2
  32. package/src/plugins/url_analysis/js/js_urls.js +0 -9
  33. package/src/plugins/url_analysis/jsenv_plugin_url_analysis.js +3 -1
  34. package/src/test/coverage/report_to_coverage.js +16 -11
  35. package/src/test/execute_plan.js +3 -2
  36. package/src/test/execute_test_plan.js +3 -1
  37. package/src/test/logs_file_execution.js +60 -27
  38. package/src/test/logs_file_execution.test.mjs +41 -0
@@ -1,8 +1,13 @@
1
1
  /* eslint-env browser */
2
2
 
3
+ const STATUSES = {
4
+ CONNECTING: "connecting",
5
+ CONNECTED: "connected",
6
+ DISCONNECTED: "disconnected",
7
+ }
8
+
3
9
  export const createEventSourceConnection = (
4
10
  eventSourceUrl,
5
- events = {},
6
11
  { retryMaxAttempt = Infinity, retryAllocatedMs = Infinity, lastEventId } = {},
7
12
  ) => {
8
13
  const { EventSource } = window
@@ -10,18 +15,26 @@ export const createEventSourceConnection = (
10
15
  return () => {}
11
16
  }
12
17
 
18
+ let eventSource
19
+ const events = {}
13
20
  const eventSourceOrigin = new URL(eventSourceUrl).origin
14
- Object.keys(events).forEach((eventName) => {
15
- const eventCallback = events[eventName]
16
- events[eventName] = (e) => {
17
- if (e.origin === eventSourceOrigin) {
18
- if (e.lastEventId) {
19
- lastEventId = e.lastEventId
21
+ const addEventCallbacks = (eventCallbacks) => {
22
+ Object.keys(eventCallbacks).forEach((eventName) => {
23
+ const eventCallback = eventCallbacks[eventName]
24
+ events[eventName] = (e) => {
25
+ if (e.origin === eventSourceOrigin) {
26
+ if (e.lastEventId) {
27
+ lastEventId = e.lastEventId
28
+ }
29
+ eventCallback(e)
20
30
  }
21
- eventCallback(e)
22
31
  }
23
- }
24
- })
32
+ if (eventSource) {
33
+ eventSource.addEventListener(eventName, events[eventName])
34
+ }
35
+ })
36
+ }
37
+ addEventCallbacks(events)
25
38
 
26
39
  const status = {
27
40
  value: "default",
@@ -37,11 +50,14 @@ export const createEventSourceConnection = (
37
50
  let _disconnect = () => {}
38
51
 
39
52
  const attemptConnection = (url) => {
40
- const eventSource = new EventSource(url, {
53
+ eventSource = new EventSource(url, {
41
54
  withCredentials: true,
42
55
  })
43
56
  _disconnect = () => {
44
- if (status.value !== "connecting" && status.value !== "connected") {
57
+ if (
58
+ status.value !== STATUSES.CONNECTING &&
59
+ status.value !== STATUSES.CONNECTED
60
+ ) {
45
61
  console.warn(
46
62
  `disconnect() ignored because connection is ${status.value}`,
47
63
  )
@@ -52,7 +68,8 @@ export const createEventSourceConnection = (
52
68
  Object.keys(events).forEach((eventName) => {
53
69
  eventSource.removeEventListener(eventName, events[eventName])
54
70
  })
55
- status.goTo("disconnected")
71
+ eventSource = null
72
+ status.goTo(STATUSES.DISCONNECTED)
56
73
  }
57
74
  let retryCount = 0
58
75
  let firstRetryMs = Date.now()
@@ -78,7 +95,7 @@ export const createEventSourceConnection = (
78
95
  }
79
96
 
80
97
  retryCount++
81
- status.goTo("connecting")
98
+ status.goTo(STATUSES.CONNECTING)
82
99
  return
83
100
  }
84
101
 
@@ -88,7 +105,7 @@ export const createEventSourceConnection = (
88
105
  }
89
106
  }
90
107
  eventSource.onopen = () => {
91
- status.goTo("connected")
108
+ status.goTo(STATUSES.CONNECTED)
92
109
  }
93
110
  Object.keys(events).forEach((eventName) => {
94
111
  eventSource.addEventListener(eventName, events[eventName])
@@ -100,7 +117,7 @@ export const createEventSourceConnection = (
100
117
  }
101
118
  })
102
119
  }
103
- status.goTo("connecting")
120
+ status.goTo(STATUSES.CONNECTING)
104
121
  }
105
122
 
106
123
  let connect = () => {
@@ -115,7 +132,10 @@ export const createEventSourceConnection = (
115
132
  }
116
133
 
117
134
  const removePageUnloadListener = listenPageUnload(() => {
118
- if (status.value === "connecting" || status.value === "connected") {
135
+ if (
136
+ status.value === STATUSES.CONNECTING ||
137
+ status.value === STATUSES.CONNECTED
138
+ ) {
119
139
  _disconnect()
120
140
  }
121
141
  })
@@ -128,6 +148,7 @@ export const createEventSourceConnection = (
128
148
  return {
129
149
  status,
130
150
  connect,
151
+ addEventCallbacks,
131
152
  disconnect: () => _disconnect(),
132
153
  destroy,
133
154
  }
@@ -1,4 +1,5 @@
1
1
  import { createDetailedMessage } from "@jsenv/log"
2
+ import { stringifyUrlSite } from "@jsenv/urls"
2
3
 
3
4
  export const createResolveUrlError = ({
4
5
  pluginController,
@@ -15,7 +16,7 @@ export const createResolveUrlError = ({
15
16
  reason,
16
17
  ...details,
17
18
  "specifier": `"${reference.specifier}"`,
18
- "specifier trace": reference.trace,
19
+ "specifier trace": reference.trace.message,
19
20
  ...detailsFromPluginController(pluginController),
20
21
  }),
21
22
  )
@@ -46,19 +47,23 @@ export const createFetchUrlContentError = ({
46
47
  reason,
47
48
  ...details
48
49
  }) => {
49
- const fetchContentError = new Error(
50
+ const fetchError = new Error(
50
51
  createDetailedMessage(`Failed to fetch url content`, {
51
52
  reason,
52
53
  ...details,
53
54
  "url": urlInfo.url,
54
- "url reference trace": reference.trace,
55
+ "url reference trace": reference.trace.message,
55
56
  ...detailsFromPluginController(pluginController),
56
57
  }),
57
58
  )
58
- fetchContentError.name = "FETCH_URL_CONTENT_ERROR"
59
- fetchContentError.code = code
60
- fetchContentError.reason = reason
61
- return fetchContentError
59
+ fetchError.name = "FETCH_URL_CONTENT_ERROR"
60
+ fetchError.code = code
61
+ fetchError.reason = reason
62
+ fetchError.url = reference.trace.url
63
+ fetchError.line = reference.trace.line
64
+ fetchError.column = reference.trace.column
65
+ fetchError.contentFrame = reference.trace.message
66
+ return fetchError
62
67
  }
63
68
 
64
69
  if (error.code === "EPERM") {
@@ -103,7 +108,7 @@ export const createTransformUrlContentError = ({
103
108
  reason,
104
109
  ...details,
105
110
  "url": urlInfo.url,
106
- "url reference trace": reference.trace,
111
+ "url reference trace": reference.trace.message,
107
112
  ...detailsFromPluginController(pluginController),
108
113
  },
109
114
  ),
@@ -111,6 +116,33 @@ export const createTransformUrlContentError = ({
111
116
  transformError.name = "TRANSFORM_URL_CONTENT_ERROR"
112
117
  transformError.code = code
113
118
  transformError.reason = reason
119
+ transformError.url = reference.trace.url
120
+ transformError.line = reference.trace.line
121
+ transformError.column = reference.trace.column
122
+ transformError.stack = error.stack
123
+ transformError.contentFrame = reference.trace.message
124
+ if (code === "PARSE_ERROR") {
125
+ transformError.reason = error.message
126
+ if (urlInfo.isInline) {
127
+ transformError.line = reference.trace.line + error.line - 1
128
+ transformError.column = reference.trace.column + error.column
129
+ transformError.contentFrame = stringifyUrlSite({
130
+ url: urlInfo.inlineUrlSite.url,
131
+ line: transformError.line,
132
+ column: transformError.column,
133
+ content: urlInfo.inlineUrlSite.content,
134
+ })
135
+ } else {
136
+ transformError.line = error.line
137
+ transformError.column = error.column
138
+ transformError.contentFrame = stringifyUrlSite({
139
+ url: urlInfo.url,
140
+ line: transformError.line,
141
+ column: transformError.column,
142
+ content: urlInfo.content,
143
+ })
144
+ }
145
+ }
114
146
  return transformError
115
147
  }
116
148
  return createFailedToTransformError({
@@ -130,7 +162,7 @@ export const createFinalizeUrlContentError = ({
130
162
  "reason": `An error occured during "finalizeUrlContent"`,
131
163
  ...detailsFromValueThrown(error),
132
164
  "url": urlInfo.url,
133
- "url reference trace": reference.trace,
165
+ "url reference trace": reference.trace.message,
134
166
  ...detailsFromPluginController(pluginController),
135
167
  }),
136
168
  )
@@ -221,7 +221,10 @@ export const createKitchen = ({
221
221
  sourcemapsRelativeSources,
222
222
  injectSourcemapPlaceholder: ({ urlInfo, specifier }) => {
223
223
  const sourcemapReference = createReference({
224
- trace: `sourcemap comment placeholder for ${urlInfo.url}`,
224
+ trace: {
225
+ message: `sourcemap comment placeholder`,
226
+ url: urlInfo.url,
227
+ },
225
228
  type: "sourcemap_comment",
226
229
  subtype: urlInfo.contentType === "text/javascript" ? "js" : "css",
227
230
  parentUrl: urlInfo.url,
@@ -238,15 +241,14 @@ export const createKitchen = ({
238
241
  specifierLine,
239
242
  specifierColumn,
240
243
  }) => {
244
+ const sourcemapUrlSite = adjustUrlSite(urlInfo, {
245
+ urlGraph,
246
+ url: urlInfo.url,
247
+ line: specifierLine,
248
+ column: specifierColumn,
249
+ })
241
250
  const sourcemapReference = createReference({
242
- trace: stringifyUrlSite(
243
- adjustUrlSite(urlInfo, {
244
- urlGraph,
245
- url: urlInfo.url,
246
- line: specifierLine,
247
- column: specifierColumn,
248
- }),
249
- ),
251
+ trace: traceFromUrlSite(sourcemapUrlSite),
250
252
  type,
251
253
  parentUrl: urlInfo.url,
252
254
  specifier,
@@ -273,7 +275,7 @@ export const createKitchen = ({
273
275
  `no plugin has handled url during "fetchUrlContent" hook -> url will be ignored`,
274
276
  {
275
277
  "url": urlInfo.url,
276
- "url reference trace": reference.trace,
278
+ "url reference trace": reference.trace.message,
277
279
  },
278
280
  ),
279
281
  )
@@ -408,7 +410,7 @@ export const createKitchen = ({
408
410
  },
409
411
  found: ({ trace, ...rest }) => {
410
412
  if (trace === undefined) {
411
- trace = stringifyUrlSite(
413
+ trace = traceFromUrlSite(
412
414
  adjustUrlSite(urlInfo, {
413
415
  urlGraph,
414
416
  url: urlInfo.url,
@@ -423,7 +425,12 @@ export const createKitchen = ({
423
425
  ...rest,
424
426
  })
425
427
  },
426
- foundInline: ({ isOriginalPosition, line, column, ...rest }) => {
428
+ foundInline: ({
429
+ isOriginalPosition,
430
+ specifierLine,
431
+ specifierColumn,
432
+ ...rest
433
+ }) => {
427
434
  const parentUrl = isOriginalPosition
428
435
  ? urlInfo.url
429
436
  : urlInfo.generatedUrl
@@ -431,15 +438,15 @@ export const createKitchen = ({
431
438
  ? urlInfo.originalContent
432
439
  : urlInfo.content
433
440
  return addReference({
434
- trace: stringifyUrlSite({
441
+ trace: traceFromUrlSite({
435
442
  url: parentUrl,
436
443
  content: parentContent,
437
- line,
438
- column,
444
+ line: specifierLine,
445
+ column: specifierColumn,
439
446
  }),
440
447
  isOriginalPosition,
441
- line,
442
- column,
448
+ specifierLine,
449
+ specifierColumn,
443
450
  isInline: true,
444
451
  ...rest,
445
452
  })
@@ -487,7 +494,7 @@ export const createKitchen = ({
487
494
  ? urlInfo.originalContent
488
495
  : urlInfo.content
489
496
  return referenceUtils.update(reference, {
490
- trace: stringifyUrlSite({
497
+ trace: traceFromUrlSite({
491
498
  url: parentUrl,
492
499
  content: parentContent,
493
500
  line: specifierLine,
@@ -505,7 +512,7 @@ export const createKitchen = ({
505
512
  inject: ({ trace, ...rest }) => {
506
513
  if (trace === undefined) {
507
514
  const { url, line, column } = getCallerPosition()
508
- trace = stringifyUrlSite({
515
+ trace = traceFromUrlSite({
509
516
  url,
510
517
  line,
511
518
  column,
@@ -722,6 +729,15 @@ const memoizeCook = (cook) => {
722
729
  }
723
730
  }
724
731
 
732
+ const traceFromUrlSite = (urlSite) => {
733
+ return {
734
+ message: stringifyUrlSite(urlSite),
735
+ url: urlSite.url,
736
+ line: urlSite.line,
737
+ column: urlSite.column,
738
+ }
739
+ }
740
+
725
741
  const applyReferenceEffectsOnUrlInfo = (reference, urlInfo, context) => {
726
742
  if (reference.shouldHandle) {
727
743
  urlInfo.shouldHandle = true
@@ -7,8 +7,12 @@ import {
7
7
  pluginCORS,
8
8
  } from "@jsenv/server"
9
9
  import { convertFileSystemErrorToResponseProperties } from "@jsenv/server/src/internal/convertFileSystemErrorToResponseProperties.js"
10
- import { createCallbackListNotifiedOnce } from "@jsenv/abort"
10
+ import {
11
+ createCallbackListNotifiedOnce,
12
+ createCallbackList,
13
+ } from "@jsenv/abort"
11
14
 
15
+ import { createSSEService } from "@jsenv/core/src/helpers/event_source/sse_service.js"
12
16
  import { createFileService } from "./server/file_service.js"
13
17
 
14
18
  export const startOmegaServer = async ({
@@ -33,12 +37,60 @@ export const startOmegaServer = async ({
33
37
  kitchen,
34
38
  }) => {
35
39
  const serverStopCallbackList = createCallbackListNotifiedOnce()
40
+
41
+ const serverEventCallbackList = createCallbackList()
42
+ const sseService = createSSEService({ serverEventCallbackList })
43
+ const sendServerEvent = ({ type, data }) => {
44
+ serverEventCallbackList.notify({
45
+ type,
46
+ data: JSON.stringify(data),
47
+ })
48
+ }
49
+
50
+ kitchen.pluginController.addHook("registerServerEvents")
51
+ kitchen.pluginController.callHooks(
52
+ "registerServerEvents",
53
+ { sendServerEvent },
54
+ {
55
+ rootDirectoryUrl,
56
+ urlGraph,
57
+ scenario,
58
+ },
59
+ () => {},
60
+ )
61
+
62
+ const sendServerErrorEvent = (event) => {
63
+ // setTimeout display first the error
64
+ // dispatched on window by browser
65
+ // then display the jsenv error
66
+ setTimeout(() => {
67
+ sendServerEvent(event)
68
+ }, 10)
69
+ }
70
+
36
71
  const coreServices = {
72
+ "service:server_events": (request) => {
73
+ const { accept } = request.headers
74
+ if (accept && accept.includes("text/event-stream")) {
75
+ const room = sseService.getOrCreateSSERoom(request)
76
+ return room.join(request)
77
+ }
78
+ return null
79
+ },
37
80
  "service:file": createFileService({
38
81
  rootDirectoryUrl,
39
82
  urlGraph,
40
83
  kitchen,
41
84
  scenario,
85
+ onFileNotFound: (data) => {
86
+ sendServerErrorEvent({ type: "file_not_found", data })
87
+ },
88
+ onParseError: (data) => {
89
+ sendServerErrorEvent({ type: "parse_error", data })
90
+ },
91
+ onUnexpectedError: (data) => {
92
+ sendServerErrorEvent({ type: "unexpected_error", data })
93
+ },
42
94
  }),
43
95
  }
44
96
  const server = await startServer({
@@ -119,6 +171,7 @@ export const startOmegaServer = async ({
119
171
  }),
120
172
  onStop: (reason) => {
121
173
  onStop()
174
+ sseService.destroy()
122
175
  serverStopCallbackList.notify(reason)
123
176
  },
124
177
  })
@@ -12,6 +12,9 @@ export const createFileService = ({
12
12
  urlGraph,
13
13
  kitchen,
14
14
  scenario,
15
+ onParseError,
16
+ onFileNotFound,
17
+ onUnexpectedError,
15
18
  }) => {
16
19
  kitchen.pluginController.addHook("serve")
17
20
  kitchen.pluginController.addHook("augmentResponse")
@@ -51,7 +54,7 @@ export const createFileService = ({
51
54
  }
52
55
  if (!reference) {
53
56
  const entryPoint = kitchen.injectReference({
54
- trace: parentUrl || rootDirectoryUrl,
57
+ trace: { message: parentUrl || rootDirectoryUrl },
55
58
  parentUrl: parentUrl || rootDirectoryUrl,
56
59
  type: "http_request",
57
60
  specifier: request.ressource,
@@ -135,10 +138,17 @@ export const createFileService = ({
135
138
  } catch (e) {
136
139
  const code = e.code
137
140
  if (code === "PARSE_ERROR") {
138
- // let the browser re-throw the syntax error
141
+ onParseError({
142
+ reason: e.reason,
143
+ message: e.message,
144
+ url: e.url,
145
+ line: e.line,
146
+ column: e.column,
147
+ contentFrame: e.contentFrame,
148
+ })
139
149
  return {
140
150
  url: reference.url,
141
- status: 200,
151
+ status: 200, // let the browser re-throw the syntax error
142
152
  statusText: e.reason,
143
153
  statusMessage: e.message,
144
154
  headers: {
@@ -166,6 +176,14 @@ export const createFileService = ({
166
176
  }
167
177
  }
168
178
  if (code === "NOT_FOUND") {
179
+ onFileNotFound({
180
+ reason: e.reason,
181
+ message: e.message,
182
+ url: e.url,
183
+ line: e.line,
184
+ column: e.column,
185
+ contentFrame: e.contentFrame,
186
+ })
169
187
  return {
170
188
  url: reference.url,
171
189
  status: 404,
@@ -173,6 +191,15 @@ export const createFileService = ({
173
191
  statusMessage: e.message,
174
192
  }
175
193
  }
194
+ onUnexpectedError({
195
+ reason: e.reason,
196
+ message: e.message,
197
+ stack: e.stack,
198
+ url: e.url,
199
+ line: e.line,
200
+ column: e.column,
201
+ contentFrame: e.contentFrame,
202
+ })
176
203
  return {
177
204
  url: reference.url,
178
205
  status: 500,
@@ -11,7 +11,6 @@ ${createRepartitionMessage(graphReport)}
11
11
  }
12
12
 
13
13
  const createUrlGraphReport = (urlGraph) => {
14
- const { urlInfos } = urlGraph
15
14
  const countGroups = {
16
15
  sourcemaps: 0,
17
16
  html: 0,
@@ -30,11 +29,10 @@ const createUrlGraphReport = (urlGraph) => {
30
29
  other: 0,
31
30
  total: 0,
32
31
  }
33
- Object.keys(urlInfos).forEach((url) => {
34
- if (url.startsWith("data:")) {
32
+ urlGraph.urlInfoMap.forEach((urlInfo) => {
33
+ if (urlInfo.url.startsWith("data:")) {
35
34
  return
36
35
  }
37
- const urlInfo = urlInfos[url]
38
36
  // ignore:
39
37
  // - inline files: they are already taken into account in the file where they appear
40
38
  // - ignored files: we don't know their content
@@ -5,12 +5,12 @@ export const createUrlGraph = ({
5
5
  clientFileChangeCallbackList,
6
6
  clientFilesPruneCallbackList,
7
7
  } = {}) => {
8
- const urlInfos = {}
9
- const getUrlInfo = (url) => urlInfos[url]
8
+ const urlInfoMap = new Map()
9
+ const getUrlInfo = (url) => urlInfoMap.get(url)
10
10
  const deleteUrlInfo = (url) => {
11
- const urlInfo = urlInfos[url]
11
+ const urlInfo = urlInfoMap.get(url)
12
12
  if (urlInfo) {
13
- delete urlInfos[url]
13
+ urlInfoMap.delete(url)
14
14
  if (urlInfo.sourcemapReference) {
15
15
  deleteUrlInfo(urlInfo.sourcemapReference.url)
16
16
  }
@@ -18,14 +18,14 @@ export const createUrlGraph = ({
18
18
  }
19
19
 
20
20
  const reuseOrCreateUrlInfo = (url) => {
21
- const existingUrlInfo = urlInfos[url]
21
+ const existingUrlInfo = getUrlInfo(url)
22
22
  if (existingUrlInfo) return existingUrlInfo
23
23
  const urlInfo = createUrlInfo(url)
24
- urlInfos[url] = urlInfo
24
+ urlInfoMap.set(url, urlInfo)
25
25
  return urlInfo
26
26
  }
27
27
  const inferReference = (specifier, parentUrl) => {
28
- const parentUrlInfo = urlInfos[parentUrl]
28
+ const parentUrlInfo = getUrlInfo(parentUrl)
29
29
  if (!parentUrlInfo) {
30
30
  return null
31
31
  }
@@ -37,13 +37,13 @@ export const createUrlGraph = ({
37
37
  return firstReferenceOnThatUrl
38
38
  }
39
39
  const findDependent = (url, predicate) => {
40
- const urlInfo = urlInfos[url]
40
+ const urlInfo = getUrlInfo(url)
41
41
  if (!urlInfo) {
42
42
  return null
43
43
  }
44
44
  const visitDependents = (urlInfo) => {
45
45
  for (const dependentUrl of urlInfo.dependents) {
46
- const dependent = urlInfos[dependentUrl]
46
+ const dependent = getUrlInfo(dependentUrl)
47
47
  if (predicate(dependent)) {
48
48
  return dependent
49
49
  }
@@ -90,7 +90,7 @@ export const createUrlGraph = ({
90
90
  const removeDependencies = (urlInfo, urlsToPrune) => {
91
91
  urlsToPrune.forEach((urlToPrune) => {
92
92
  urlInfo.dependencies.delete(urlToPrune)
93
- const dependency = urlInfos[urlToPrune]
93
+ const dependency = getUrlInfo(urlToPrune)
94
94
  if (!dependency) {
95
95
  return
96
96
  }
@@ -122,7 +122,7 @@ export const createUrlGraph = ({
122
122
 
123
123
  if (clientFileChangeCallbackList) {
124
124
  clientFileChangeCallbackList.push(({ url }) => {
125
- const urlInfo = urlInfos[url]
125
+ const urlInfo = getUrlInfo(url)
126
126
  if (urlInfo) {
127
127
  considerModified(urlInfo, Date.now())
128
128
  }
@@ -139,12 +139,18 @@ export const createUrlGraph = ({
139
139
  urlInfo.modifiedTimestamp = modifiedTimestamp
140
140
  urlInfo.contentEtag = undefined
141
141
  urlInfo.dependents.forEach((dependentUrl) => {
142
- const dependentUrlInfo = urlInfos[dependentUrl]
142
+ const dependentUrlInfo = getUrlInfo(dependentUrl)
143
143
  const { hotAcceptDependencies = [] } = dependentUrlInfo.data
144
144
  if (!hotAcceptDependencies.includes(urlInfo.url)) {
145
145
  iterate(dependentUrlInfo)
146
146
  }
147
147
  })
148
+ urlInfo.dependencies.forEach((dependencyUrl) => {
149
+ const dependencyUrlInfo = getUrlInfo(dependencyUrl)
150
+ if (dependencyUrlInfo.isInline) {
151
+ iterate(dependencyUrlInfo)
152
+ }
153
+ })
148
154
  }
149
155
  iterate(urlInfo)
150
156
  }
@@ -164,7 +170,7 @@ export const createUrlGraph = ({
164
170
  }
165
171
 
166
172
  return {
167
- urlInfos,
173
+ urlInfoMap,
168
174
  reuseOrCreateUrlInfo,
169
175
  getUrlInfo,
170
176
  deleteUrlInfo,
@@ -174,12 +180,19 @@ export const createUrlGraph = ({
174
180
  considerModified,
175
181
  getRelatedUrlInfos,
176
182
 
183
+ toObject: () => {
184
+ const data = {}
185
+ urlInfoMap.forEach((urlInfo) => {
186
+ data[urlInfo.url] = urlInfo
187
+ })
188
+ return data
189
+ },
177
190
  toJSON: (rootDirectoryUrl) => {
178
191
  const data = {}
179
- Object.keys(urlInfos).forEach((url) => {
180
- const dependencyUrls = Array.from(urlInfos[url].dependencies)
192
+ urlInfoMap.forEach((urlInfo) => {
193
+ const dependencyUrls = Array.from(urlInfo.dependencies)
181
194
  if (dependencyUrls.length) {
182
- const relativeUrl = urlToRelativeUrl(url, rootDirectoryUrl)
195
+ const relativeUrl = urlToRelativeUrl(urlInfo.url, rootDirectoryUrl)
183
196
  data[relativeUrl] = dependencyUrls.map((dependencyUrl) =>
184
197
  urlToRelativeUrl(dependencyUrl, rootDirectoryUrl),
185
198
  )
@@ -157,21 +157,21 @@ const addReloadMessage = (reloadMessage) => {
157
157
 
158
158
  const eventsourceConnection = createEventSourceConnection(
159
159
  document.location.href,
160
- {
161
- reload: ({ data }) => {
162
- const reloadMessage = JSON.parse(data)
163
- addReloadMessage(reloadMessage)
164
- },
165
- },
166
160
  {
167
161
  retryMaxAttempt: Infinity,
168
162
  retryAllocatedMs: 20 * 1000,
169
163
  },
170
164
  )
171
-
172
- const { status, connect, disconnect } = eventsourceConnection
165
+ const { status, connect, addEventCallbacks, disconnect } = eventsourceConnection
166
+ eventsourceConnection.addEventCallbacks({
167
+ reload: ({ data }) => {
168
+ const reloadMessage = JSON.parse(data)
169
+ addReloadMessage(reloadMessage)
170
+ },
171
+ })
173
172
  connect()
174
173
  window.__jsenv_event_source_client__ = {
174
+ addEventCallbacks,
175
175
  status,
176
176
  connect,
177
177
  disconnect,