@jsenv/core 27.6.1 → 27.8.1

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 (47) hide show
  1. package/dist/js/autoreload.js +3 -6
  2. package/dist/js/html_supervisor_installer.js +36 -32
  3. package/dist/js/html_supervisor_setup.js +10 -2
  4. package/dist/js/server_events_client.js +249 -216
  5. package/dist/js/wrapper.mjs +4233 -0
  6. package/dist/main.js +21266 -21628
  7. package/package.json +3 -3
  8. package/src/build/build.js +19 -18
  9. package/src/dev/start_dev_server.js +7 -11
  10. package/src/execute/execute.js +2 -2
  11. package/src/execute/runtimes/browsers/chromium.js +1 -1
  12. package/src/execute/runtimes/browsers/firefox.js +1 -1
  13. package/src/execute/runtimes/browsers/webkit.js +1 -1
  14. package/src/omega/kitchen.js +9 -14
  15. package/src/omega/omega_server.js +13 -2
  16. package/src/omega/server/file_service.js +13 -29
  17. package/src/plugins/autoreload/client/autoreload.js +3 -4
  18. package/src/plugins/autoreload/jsenv_plugin_autoreload.js +0 -4
  19. package/src/plugins/autoreload/jsenv_plugin_autoreload_client.js +1 -1
  20. package/src/plugins/autoreload/jsenv_plugin_autoreload_server.js +1 -1
  21. package/src/plugins/autoreload/jsenv_plugin_hmr.js +1 -1
  22. package/src/plugins/bundling/jsenv_plugin_bundling.js +1 -3
  23. package/src/plugins/cache_control/jsenv_plugin_cache_control.js +2 -5
  24. package/src/plugins/commonjs_globals/jsenv_plugin_commonjs_globals.js +2 -2
  25. package/src/plugins/file_urls/jsenv_plugin_file_urls.js +4 -8
  26. package/src/plugins/html_supervisor/client/error_formatter.js +43 -37
  27. package/src/plugins/html_supervisor/client/html_supervisor_installer.js +1 -4
  28. package/src/plugins/html_supervisor/client/html_supervisor_setup.js +10 -2
  29. package/src/plugins/html_supervisor/jsenv_plugin_html_supervisor.js +4 -5
  30. package/src/plugins/import_meta_hot/jsenv_plugin_import_meta_hot.js +2 -2
  31. package/src/plugins/import_meta_scenarios/jsenv_plugin_import_meta_scenarios.js +18 -24
  32. package/src/plugins/importmap/jsenv_plugin_importmap.js +1 -1
  33. package/src/plugins/minification/jsenv_plugin_minification.js +1 -3
  34. package/src/plugins/node_esm_resolution/jsenv_plugin_node_esm_resolution.js +9 -7
  35. package/src/plugins/plugin_controller.js +17 -6
  36. package/src/plugins/plugins.js +0 -2
  37. package/src/plugins/server_events/client/connection_manager.js +165 -0
  38. package/src/plugins/server_events/client/event_source_connection.js +50 -256
  39. package/src/plugins/server_events/client/events_manager.js +75 -0
  40. package/src/plugins/server_events/client/server_events_client.js +12 -11
  41. package/src/plugins/server_events/client/web_socket_connection.js +81 -0
  42. package/src/plugins/server_events/server_events_dispatcher.js +70 -54
  43. package/src/plugins/toolbar/jsenv_plugin_toolbar.js +1 -3
  44. package/src/plugins/transpilation/import_assertions/jsenv_plugin_import_assertions.js +1 -1
  45. package/src/plugins/url_analysis/html/html_urls.js +2 -2
  46. package/src/test/execute_plan.js +2 -2
  47. package/src/test/execute_test_plan.js +1 -1
@@ -9,11 +9,12 @@ window.__html_supervisor__ = {
9
9
  window.__html_supervisor__.scriptsToExecute.push(scriptToExecute)
10
10
  },
11
11
  superviseScript: ({ src, isInline, crossorigin, integrity }) => {
12
+ const { currentScript } = document
12
13
  window.__html_supervisor__.addScriptToExecute({
13
14
  src,
14
15
  type: "js_classic",
15
16
  isInline,
16
- currentScript: document.currentScript,
17
+ currentScript,
17
18
  execute: (url) => {
18
19
  return new Promise((resolve, reject) => {
19
20
  const script = document.createElement("script")
@@ -50,7 +51,14 @@ window.__html_supervisor__ = {
50
51
  resolve()
51
52
  }
52
53
  })
53
- document.body.appendChild(script)
54
+ if (currentScript) {
55
+ currentScript.parentNode.insertBefore(
56
+ script,
57
+ currentScript.nextSibling,
58
+ )
59
+ } else {
60
+ document.body.appendChild(script)
61
+ }
54
62
  })
55
63
  },
56
64
  })
@@ -43,10 +43,7 @@ export const jsenvPluginHtmlSupervisor = ({
43
43
 
44
44
  return {
45
45
  name: "jsenv:html_supervisor",
46
- appliesDuring: {
47
- dev: true,
48
- test: true,
49
- },
46
+ appliesDuring: "dev",
50
47
  serve: async (request, context) => {
51
48
  if (request.ressource.startsWith("/__get_code_frame__/")) {
52
49
  const { pathname, searchParams } = new URL(request.url)
@@ -132,7 +129,9 @@ export const jsenvPluginHtmlSupervisor = ({
132
129
  code: causeInfo.code,
133
130
  message: causeInfo.message,
134
131
  reason: causeInfo.reason,
135
- stack: causeInfo.stack,
132
+ stack: errorBaseUrl
133
+ ? `stack mocked for snapshot`
134
+ : causeInfo.stack,
136
135
  codeFrame: causeInfo.traceMessage,
137
136
  }
138
137
  : null,
@@ -16,7 +16,7 @@ export const jsenvPluginImportMetaHot = () => {
16
16
  transformUrlContent: {
17
17
  html: (htmlUrlInfo, context) => {
18
18
  // during build we don't really care to parse html hot dependencies
19
- if (context.scenario === "build") {
19
+ if (context.scenarios.build) {
20
20
  return
21
21
  }
22
22
  const htmlAst = parseHtmlString(htmlUrlInfo.content)
@@ -58,7 +58,7 @@ export const jsenvPluginImportMetaHot = () => {
58
58
  if (importMetaHotPaths.length === 0) {
59
59
  return null
60
60
  }
61
- if (context.scenario === "build") {
61
+ if (context.scenarios.build) {
62
62
  return removeImportMetaHots(urlInfo, importMetaHotPaths)
63
63
  }
64
64
  return injectImportMetaHot(urlInfo, context, importMetaHotClientFileUrl)
@@ -1,11 +1,10 @@
1
1
  /*
2
2
  * Source code can contain the following
3
3
  * - import.meta.dev
4
- * - import.meta.test
5
4
  * - import.meta.build
6
5
  * They are either:
7
6
  * - replaced by true: When scenario matches (import.meta.dev and it's the dev server)
8
- * - left as is to be evaluated to undefined (import.meta.test but it's the dev server)
7
+ * - left as is to be evaluated to undefined (import.meta.build but it's the dev server)
9
8
  * - replaced by undefined (import.meta.dev but it's build; the goal is to ensure it's tree-shaked)
10
9
  */
11
10
 
@@ -17,7 +16,7 @@ export const jsenvPluginImportMetaScenarios = () => {
17
16
  name: "jsenv:import_meta_scenario",
18
17
  appliesDuring: "*",
19
18
  transformUrlContent: {
20
- js_module: async (urlInfo, { scenario }) => {
19
+ js_module: async (urlInfo, context) => {
21
20
  if (
22
21
  !urlInfo.content.includes("import.meta.dev") &&
23
22
  !urlInfo.content.includes("import.meta.test") &&
@@ -34,34 +33,30 @@ export const jsenvPluginImportMetaScenarios = () => {
34
33
  const replace = (path, value) => {
35
34
  replacements.push({ path, value })
36
35
  }
37
- if (scenario === "dev") {
38
- dev.forEach((path) => {
39
- replace(path, "true")
40
- })
41
- } else if (scenario === "test") {
42
- // test is also considered a dev environment
43
- // just like the dev server can be used to debug test files
44
- // without this people would have to write
45
- // if (import.meta.dev || import.meta.test) or if (!import.meta.build)
46
- dev.forEach((path) => {
47
- replace(path, "true")
48
- })
49
- test.forEach((path) => {
50
- replace(path, "true")
51
- })
52
- } else if (scenario === "build") {
53
- // replacing by undefined might not be required
54
- // as I suppose rollup would consider them as undefined
55
- // but let's make it explicit to ensure code is properly tree-shaked
36
+ if (context.scenarios.build) {
37
+ // during build ensure replacement for tree-shaking
56
38
  dev.forEach((path) => {
57
39
  replace(path, "undefined")
58
40
  })
59
41
  test.forEach((path) => {
60
- replace(path, "undefined")
42
+ replace(path, context.scenarios.test ? "true" : "undefined")
61
43
  })
62
44
  build.forEach((path) => {
63
45
  replace(path, "true")
64
46
  })
47
+ } else {
48
+ // during dev we can let "import.meta.build" untouched
49
+ // it will be evaluated to undefined.
50
+ // Moreover it can be surprising to see some "undefined"
51
+ // when source file contains "import.meta.build"
52
+ dev.forEach((path) => {
53
+ replace(path, "true")
54
+ })
55
+ if (context.scenarios.test) {
56
+ test.forEach((path) => {
57
+ replace(path, "true")
58
+ })
59
+ }
65
60
  }
66
61
  const magicSource = createMagicSource(urlInfo.content)
67
62
  replacements.forEach(({ path, value }) => {
@@ -106,7 +101,6 @@ const babelPluginMetadataImportMetaScenarios = () => {
106
101
  })
107
102
  state.file.metadata.importMetaScenarios = {
108
103
  dev: importMetas.dev,
109
- test: importMetas.test,
110
104
  build: importMetas.build,
111
105
  }
112
106
  },
@@ -202,7 +202,7 @@ export const jsenvPluginImportmap = () => {
202
202
  // by "formatReferencedUrl" making the importmap presence useless.
203
203
  // In dev/test we keep importmap into the HTML to see it even if useless
204
204
  // Duing build we get rid of it
205
- if (context.scenario === "build") {
205
+ if (context.scenarios.build) {
206
206
  removeHtmlNode(importmap)
207
207
  }
208
208
  return {
@@ -40,9 +40,7 @@ export const jsenvPluginMinification = (minification) => {
40
40
 
41
41
  return {
42
42
  name: "jsenv:minification",
43
- appliesDuring: {
44
- build: true,
45
- },
43
+ appliesDuring: "build",
46
44
  optimizeUrlContent: {
47
45
  html: htmlOptimizer,
48
46
  svg: htmlOptimizer,
@@ -27,8 +27,10 @@ export const jsenvPluginNodeEsmResolution = ({
27
27
  return {
28
28
  name: "jsenv:node_esm_resolution",
29
29
  appliesDuring: "*",
30
- init: ({ rootDirectoryUrl, scenario, runtimeCompat, urlGraph }) => {
31
- const nodeRuntimeEnabled = Object.keys(runtimeCompat).includes("node")
30
+ init: (context) => {
31
+ const nodeRuntimeEnabled = Object.keys(context.runtimeCompat).includes(
32
+ "node",
33
+ )
32
34
  // https://nodejs.org/api/esm.html#resolver-algorithm-specification
33
35
  packageConditions = packageConditions || [
34
36
  ...readCustomConditionsFromProcessArgs(),
@@ -57,19 +59,19 @@ export const jsenvPluginNodeEsmResolution = ({
57
59
  return packageJson
58
60
  }
59
61
 
60
- if (scenario === "dev") {
62
+ if (context.scenarios.dev) {
61
63
  const onFileChange = () => {
62
64
  packageScopesCache.clear()
63
65
  packageJsonsCache.clear()
64
- urlGraph.urlInfoMap.forEach((urlInfo) => {
66
+ context.urlGraph.urlInfoMap.forEach((urlInfo) => {
65
67
  if (urlInfo.dependsOnPackageJson) {
66
- urlGraph.considerModified(urlInfo)
68
+ context.urlGraph.considerModified(urlInfo)
67
69
  }
68
70
  })
69
71
  }
70
72
  filesInvalidatingCache.forEach((file) => {
71
73
  const unregister = registerFileLifecycle(
72
- new URL(file, rootDirectoryUrl),
74
+ new URL(file, context.rootDirectoryUrl),
73
75
  {
74
76
  added: () => {
75
77
  onFileChange()
@@ -131,7 +133,7 @@ export const jsenvPluginNodeEsmResolution = ({
131
133
  return null
132
134
  },
133
135
  transformUrlSearchParams: (reference, context) => {
134
- if (context.scenario === "build") {
136
+ if (context.scenarios.build) {
135
137
  return null
136
138
  }
137
139
  if (!reference.url.startsWith("file:")) {
@@ -17,8 +17,8 @@ const HOOK_NAMES = [
17
17
  "destroy",
18
18
  ]
19
19
 
20
- export const createPluginController = ({ plugins, scenario }) => {
21
- const flatPlugins = flattenAndFilterPlugins(plugins, { scenario })
20
+ export const createPluginController = ({ plugins, scenarios }) => {
21
+ const flatPlugins = flattenAndFilterPlugins(plugins, { scenarios })
22
22
  // precompute a list of hooks per hookName for one major reason:
23
23
  // - When debugging, there is less iteration
24
24
  // also it should increase perf as there is less work to do
@@ -187,7 +187,7 @@ export const createPluginController = ({ plugins, scenario }) => {
187
187
  }
188
188
  }
189
189
 
190
- const flattenAndFilterPlugins = (plugins, { scenario }) => {
190
+ const flattenAndFilterPlugins = (plugins, { scenarios }) => {
191
191
  const flatPlugins = []
192
192
  const visitPluginEntry = (pluginEntry) => {
193
193
  if (Array.isArray(pluginEntry)) {
@@ -208,13 +208,14 @@ const flattenAndFilterPlugins = (plugins, { scenario }) => {
208
208
  return
209
209
  }
210
210
  if (typeof appliesDuring === "string") {
211
- if (!["dev", "build", "test"].includes(appliesDuring)) {
211
+ if (!["dev", "test", "build"].includes(appliesDuring)) {
212
212
  throw new Error(
213
213
  `"appliesDuring" must be "dev", "test" or "build", got ${appliesDuring}`,
214
214
  )
215
215
  }
216
- if (appliesDuring === scenario) {
216
+ if (scenarios[appliesDuring]) {
217
217
  flatPlugins.push(pluginEntry)
218
+ return
218
219
  }
219
220
  return
220
221
  }
@@ -223,7 +224,17 @@ const flattenAndFilterPlugins = (plugins, { scenario }) => {
223
224
  `"appliesDuring" must be an object or a string, got ${appliesDuring}`,
224
225
  )
225
226
  }
226
- if (appliesDuring[scenario]) {
227
+ let applies
228
+ for (const key of Object.keys(appliesDuring)) {
229
+ if (!appliesDuring[key] && scenarios[key]) {
230
+ applies = false
231
+ break
232
+ }
233
+ if (appliesDuring[key] && scenarios[key]) {
234
+ applies = true
235
+ }
236
+ }
237
+ if (applies) {
227
238
  flatPlugins.push(pluginEntry)
228
239
  return
229
240
  }
@@ -24,7 +24,6 @@ import { jsenvPluginExplorer } from "./explorer/jsenv_plugin_explorer.js"
24
24
 
25
25
  export const getCorePlugins = ({
26
26
  rootDirectoryUrl,
27
- scenario,
28
27
  runtimeCompat,
29
28
 
30
29
  urlAnalysis = {},
@@ -84,7 +83,6 @@ export const getCorePlugins = ({
84
83
  ? [
85
84
  jsenvPluginAutoreload({
86
85
  ...clientAutoreload,
87
- scenario,
88
86
  clientFileChangeCallbackList,
89
87
  clientFilesPruneCallbackList,
90
88
  }),
@@ -0,0 +1,165 @@
1
+ const READY_STATES = {
2
+ CONNECTING: "connecting",
3
+ OPEN: "open",
4
+ CLOSING: "closing",
5
+ CLOSED: "closed",
6
+ }
7
+
8
+ export const createConnectionManager = (
9
+ attemptConnection,
10
+ { retry, retryAfter, retryMaxAttempt, retryAllocatedMs },
11
+ ) => {
12
+ const readyState = {
13
+ value: READY_STATES.CLOSED,
14
+ goTo: (value) => {
15
+ if (value === readyState.value) {
16
+ return
17
+ }
18
+ readyState.value = value
19
+ readyState.onchange()
20
+ },
21
+ onchange: () => {},
22
+ }
23
+
24
+ let _disconnect = () => {}
25
+ const connect = () => {
26
+ if (
27
+ readyState.value === READY_STATES.CONNECTING ||
28
+ readyState.value === READY_STATES.OPEN
29
+ ) {
30
+ return
31
+ }
32
+
33
+ let retryCount = 0
34
+ let msSpent = 0
35
+ const attempt = () => {
36
+ readyState.goTo(READY_STATES.CONNECTING)
37
+ _disconnect = attemptConnection({
38
+ onClosed: () => {
39
+ if (!retry) {
40
+ readyState.goTo(READY_STATES.CLOSED)
41
+ console.info(`[jsenv] failed to connect to server`)
42
+ return
43
+ }
44
+ if (retryCount > retryMaxAttempt) {
45
+ readyState.goTo(READY_STATES.CLOSED)
46
+ console.info(
47
+ `[jsenv] could not connect to server after ${retryMaxAttempt} attempt`,
48
+ )
49
+ return
50
+ }
51
+ if (retryAllocatedMs && msSpent > retryAllocatedMs) {
52
+ readyState.goTo(READY_STATES.CLOSED)
53
+ console.info(
54
+ `[jsenv] could not connect to server in less than ${retryAllocatedMs}ms`,
55
+ )
56
+ return
57
+ }
58
+
59
+ // if closed while open -> connection lost
60
+ // otherwise it's the attempt to connect for the first time
61
+ // or to reconnect
62
+ if (readyState.value === READY_STATES.OPEN) {
63
+ console.info(`[jsenv] server connection lost; retrying to connect`)
64
+ }
65
+ retryCount++
66
+ setTimeout(() => {
67
+ msSpent += retryAfter
68
+ attempt()
69
+ }, retryAfter)
70
+ },
71
+ onOpen: () => {
72
+ readyState.goTo(READY_STATES.OPEN)
73
+ // console.info(`[jsenv] connected to server`)
74
+ },
75
+ })
76
+ }
77
+ attempt()
78
+ }
79
+
80
+ const disconnect = () => {
81
+ if (
82
+ readyState.value !== READY_STATES.CONNECTING &&
83
+ readyState.value !== READY_STATES.OPEN
84
+ ) {
85
+ console.warn(
86
+ `disconnect() ignored because connection is ${readyState.value}`,
87
+ )
88
+ return null
89
+ }
90
+ return _disconnect()
91
+ }
92
+
93
+ const removePageUnloadListener = listenPageUnload(() => {
94
+ if (
95
+ readyState.value === READY_STATES.CONNECTING ||
96
+ readyState.value === READY_STATES.OPEN
97
+ ) {
98
+ _disconnect()
99
+ }
100
+ })
101
+
102
+ return {
103
+ readyState,
104
+ connect,
105
+ disconnect,
106
+ destroy: () => {
107
+ removePageUnloadListener()
108
+ disconnect()
109
+ },
110
+ }
111
+ }
112
+
113
+ // const listenPageMightFreeze = (callback) => {
114
+ // const removePageHideListener = listenEvent(window, "pagehide", (pageHideEvent) => {
115
+ // if (pageHideEvent.persisted === true) {
116
+ // callback(pageHideEvent)
117
+ // }
118
+ // })
119
+ // return removePageHideListener
120
+ // }
121
+
122
+ // const listenPageFreeze = (callback) => {
123
+ // const removeFreezeListener = listenEvent(document, "freeze", (freezeEvent) => {
124
+ // callback(freezeEvent)
125
+ // })
126
+ // return removeFreezeListener
127
+ // }
128
+
129
+ // const listenPageIsRestored = (callback) => {
130
+ // const removeResumeListener = listenEvent(document, "resume", (resumeEvent) => {
131
+ // removePageshowListener()
132
+ // callback(resumeEvent)
133
+ // })
134
+ // const removePageshowListener = listenEvent(window, "pageshow", (pageshowEvent) => {
135
+ // if (pageshowEvent.persisted === true) {
136
+ // removePageshowListener()
137
+ // removeResumeListener()
138
+ // callback(pageshowEvent)
139
+ // }
140
+ // })
141
+ // return () => {
142
+ // removeResumeListener()
143
+ // removePageshowListener()
144
+ // }
145
+ // }
146
+
147
+ const listenPageUnload = (callback) => {
148
+ const removePageHideListener = listenEvent(
149
+ window,
150
+ "pagehide",
151
+ (pageHideEvent) => {
152
+ if (pageHideEvent.persisted !== true) {
153
+ callback(pageHideEvent)
154
+ }
155
+ },
156
+ )
157
+ return removePageHideListener
158
+ }
159
+
160
+ const listenEvent = (emitter, event, callback) => {
161
+ emitter.addEventListener(event, callback)
162
+ return () => {
163
+ emitter.removeEventListener(event, callback)
164
+ }
165
+ }