@jsenv/core 27.6.1 → 27.7.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/core",
3
- "version": "27.6.1",
3
+ "version": "27.7.0",
4
4
  "description": "Tool to develop, test and build js projects",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -73,7 +73,7 @@
73
73
  "@jsenv/integrity": "0.0.1",
74
74
  "@jsenv/log": "3.1.0",
75
75
  "@jsenv/node-esm-resolution": "0.1.0",
76
- "@jsenv/server": "13.0.0",
76
+ "@jsenv/server": "13.1.0",
77
77
  "@jsenv/sourcemap": "1.0.4",
78
78
  "@jsenv/uneval": "1.6.0",
79
79
  "@jsenv/url-meta": "7.0.0",
@@ -109,7 +109,7 @@
109
109
  "eslint-plugin-html": "7.0.0",
110
110
  "eslint-plugin-import": "2.26.0",
111
111
  "eslint-plugin-react": "7.30.1",
112
- "playwright": "1.23.4",
112
+ "playwright": "1.24.1",
113
113
  "prettier": "2.7.1"
114
114
  }
115
115
  }
@@ -47,6 +47,20 @@ import { createVersionGenerator } from "./version_generator.js"
47
47
  import { injectServiceWorkerUrls } from "./inject_service_worker_urls.js"
48
48
  import { resyncRessourceHints } from "./resync_ressource_hints.js"
49
49
 
50
+ // default runtimeCompat corresponds to
51
+ // "we can keep <script type="module"> intact":
52
+ // so script_type_module + dynamic_import + import_meta
53
+ export const defaultRuntimeCompat = {
54
+ // android: "8",
55
+ chrome: "64",
56
+ edge: "79",
57
+ firefox: "67",
58
+ ios: "12",
59
+ opera: "51",
60
+ safari: "11.3",
61
+ samsung: "9.2",
62
+ }
63
+
50
64
  /**
51
65
  * Generate an optimized version of source files into a directory
52
66
  * @param {Object} buildParameters
@@ -85,19 +99,7 @@ export const build = async ({
85
99
  entryPoints = {},
86
100
  baseUrl = "/",
87
101
 
88
- // default runtimeCompat corresponds to
89
- // "we can keep <script type="module"> intact":
90
- // so script_type_module + dynamic_import + import_meta
91
- runtimeCompat = {
92
- // android: "8",
93
- chrome: "64",
94
- edge: "79",
95
- firefox: "67",
96
- ios: "12",
97
- opera: "51",
98
- safari: "11.3",
99
- samsung: "9.2",
100
- },
102
+ runtimeCompat = defaultRuntimeCompat,
101
103
  plugins = [],
102
104
  sourcemaps = false,
103
105
  sourcemapsSourcesContent,
@@ -7,6 +7,7 @@ import { Abort, raceProcessTeardownEvents } from "@jsenv/abort"
7
7
  import { createLogger, loggerToLevels, createTaskLog } from "@jsenv/log"
8
8
  import { getCallerPosition } from "@jsenv/urls"
9
9
 
10
+ import { defaultRuntimeCompat } from "@jsenv/core/src/build/build.js"
10
11
  import { createReloadableWorker } from "@jsenv/core/src/helpers/worker_reload.js"
11
12
  import { startOmegaServer } from "@jsenv/core/src/omega/omega_server.js"
12
13
 
@@ -41,16 +42,10 @@ export const startDevServer = async ({
41
42
  devServerMainFile = getCallerPosition().url,
42
43
  cooldownBetweenFileEvents,
43
44
 
44
- // default runtimeCompat assume dev server will be request by recent browsers
45
- // Used by "jsenv_plugin_node_runtime.js" to deactivate itself
46
- // If dev server can be requested by Node.js to exec files
47
- // we would add "node" to the potential runtimes. For now it's out of the scope of the dev server
48
- // and "jsenv_plugin_node_runtime.js" applies only during build made for node.js
49
- runtimeCompat = {
50
- chrome: "100",
51
- firefox: "100",
52
- safari: "15.5",
53
- },
45
+ // runtimeCompat is the runtimeCompat for the build
46
+ // when specified, dev server use it to warn in case
47
+ // code would be supported during dev but not after build
48
+ runtimeCompat = defaultRuntimeCompat,
54
49
  plugins = [],
55
50
  urlAnalysis = {},
56
51
  htmlSupervisor = true,
@@ -162,6 +157,7 @@ export const startDevServer = async ({
162
157
  rootDirectoryUrl,
163
158
  scenario: "dev",
164
159
  runtimeCompat,
160
+
165
161
  plugins,
166
162
  urlAnalysis,
167
163
  htmlSupervisor,
@@ -2,7 +2,7 @@ import { createRuntimeFromPlaywright } from "./from_playwright.js"
2
2
 
3
3
  export const chromium = createRuntimeFromPlaywright({
4
4
  browserName: "chromium",
5
- browserVersion: "104.0.5112.20", // to update, check https://github.com/microsoft/playwright/releases
5
+ browserVersion: "104.0.5112.48", // to update, check https://github.com/microsoft/playwright/releases
6
6
  coveragePlaywrightAPIAvailable: true,
7
7
  })
8
8
  export const chromiumIsolatedTab = chromium.isolatedTab
@@ -2,6 +2,6 @@ import { createRuntimeFromPlaywright } from "./from_playwright.js"
2
2
 
3
3
  export const firefox = createRuntimeFromPlaywright({
4
4
  browserName: "firefox",
5
- browserVersion: "100.0.2", // to update, check https://github.com/microsoft/playwright/releases
5
+ browserVersion: "102.0", // to update, check https://github.com/microsoft/playwright/releases
6
6
  })
7
7
  export const firefoxIsolatedTab = firefox.isolatedTab
@@ -2,7 +2,7 @@ import { createRuntimeFromPlaywright } from "./from_playwright.js"
2
2
 
3
3
  export const webkit = createRuntimeFromPlaywright({
4
4
  browserName: "webkit",
5
- browserVersion: "15.4", // to update, check https://github.com/microsoft/playwright/releases
5
+ browserVersion: "16.0", // to update, check https://github.com/microsoft/playwright/releases
6
6
  ignoreErrorHook: (error) => {
7
7
  // we catch error during execution but safari throw unhandled rejection
8
8
  // in a non-deterministic way.
@@ -5,6 +5,7 @@ import {
5
5
  jsenvServiceErrorHandler,
6
6
  } from "@jsenv/server"
7
7
  import { convertFileSystemErrorToResponseProperties } from "@jsenv/server/src/internal/convertFileSystemErrorToResponseProperties.js"
8
+ import { createServerEventsDispatcher } from "@jsenv/core/src/plugins/server_events/server_events_dispatcher.js"
8
9
 
9
10
  import { createFileService } from "./server/file_service.js"
10
11
 
@@ -43,6 +44,10 @@ export const startOmegaServer = async ({
43
44
  writeGeneratedFiles,
44
45
  }) => {
45
46
  const serverStopCallbacks = []
47
+ const serverEventsDispatcher = createServerEventsDispatcher()
48
+ serverStopCallbacks.push(() => {
49
+ serverEventsDispatcher.destroy()
50
+ })
46
51
  const server = await startServer({
47
52
  signal,
48
53
  stopOnExit: false,
@@ -79,6 +84,7 @@ export const startOmegaServer = async ({
79
84
  signal,
80
85
  logLevel,
81
86
  serverStopCallbacks,
87
+ serverEventsDispatcher,
82
88
 
83
89
  rootDirectoryUrl,
84
90
  scenario,
@@ -99,6 +105,11 @@ export const startOmegaServer = async ({
99
105
  sourcemapsSourcesContent,
100
106
  writeGeneratedFiles,
101
107
  }),
108
+ handleWebsocket: (websocket, { request }) => {
109
+ if (request.headers["sec-websocket-protocol"] === "jsenv") {
110
+ serverEventsDispatcher.addWebsocket(websocket, request)
111
+ }
112
+ },
102
113
  },
103
114
  {
104
115
  name: "jsenv:omega_error_handler",
@@ -10,7 +10,6 @@ import { URL_META } from "@jsenv/url-meta"
10
10
  import { getCorePlugins } from "@jsenv/core/src/plugins/plugins.js"
11
11
  import { createUrlGraph } from "@jsenv/core/src/omega/url_graph.js"
12
12
  import { createKitchen } from "@jsenv/core/src/omega/kitchen.js"
13
- import { createServerEventsDispatcher } from "@jsenv/core/src/plugins/server_events/server_events_dispatcher.js"
14
13
  import { jsenvPluginServerEventsClientInjection } from "@jsenv/core/src/plugins/server_events/jsenv_plugin_server_events_client_injection.js"
15
14
  import { parseUserAgentHeader } from "./user_agent.js"
16
15
 
@@ -18,6 +17,7 @@ export const createFileService = ({
18
17
  signal,
19
18
  logLevel,
20
19
  serverStopCallbacks,
20
+ serverEventsDispatcher,
21
21
 
22
22
  rootDirectoryUrl,
23
23
  scenario,
@@ -152,10 +152,6 @@ export const createFileService = ({
152
152
  })
153
153
  const serverEventNames = Object.keys(allServerEvents)
154
154
  if (serverEventNames.length > 0) {
155
- const serverEventsDispatcher = createServerEventsDispatcher()
156
- serverStopCallbacks.push(() => {
157
- serverEventsDispatcher.destroy()
158
- })
159
155
  Object.keys(allServerEvents).forEach((serverEventName) => {
160
156
  allServerEvents[serverEventName]({
161
157
  rootDirectoryUrl,
@@ -169,19 +165,7 @@ export const createFileService = ({
169
165
  },
170
166
  })
171
167
  })
172
- // "unshift" because serve must come first to catch event source client request
173
- kitchen.pluginController.unshiftPlugin({
174
- name: "jsenv:provide_server_events",
175
- serve: (request) => {
176
- const { accept } = request.headers
177
- if (accept && accept.includes("text/event-stream")) {
178
- const room = serverEventsDispatcher.addRoom(request)
179
- return room.join(request)
180
- }
181
- return null
182
- },
183
- })
184
- // "push" so that event source client connection can be put as early as possible in html
168
+ // "pushPlugin" so that event source client connection can be put as early as possible in html
185
169
  kitchen.pluginController.pushPlugin(
186
170
  jsenvPluginServerEventsClientInjection(),
187
171
  )
@@ -192,10 +192,9 @@ const applyHotReload = async ({ hotInstructions }) => {
192
192
  }
193
193
 
194
194
  window.__reloader__ = reloader
195
- window.__server_events__.addEventCallbacks({
196
- reload: ({ data }) => {
197
- const reloadMessage = JSON.parse(data)
198
- reloader.addMessage(reloadMessage)
195
+ window.__server_events__.listenEvents({
196
+ reload: (reloadServerEvent) => {
197
+ reloader.addMessage(reloadServerEvent.data)
199
198
  },
200
199
  })
201
200
 
@@ -123,46 +123,52 @@ export const formatError = (
123
123
  const onErrorLocated = (urlSite) => {
124
124
  errorUrlSite = urlSite
125
125
  errorDetailsPromiseReference.current = (async () => {
126
- if (errorMeta.type === "dynamic_import_fetch_error") {
127
- const response = await window.fetch(
128
- `/__get_error_cause__/${
129
- urlSite.isInline ? urlSite.originalUrl : urlSite.url
130
- }`,
131
- )
132
- if (response.status !== 200) {
133
- return null
134
- }
135
- const causeInfo = await response.json()
136
- if (!causeInfo) {
137
- return null
138
- }
126
+ try {
127
+ if (errorMeta.type === "dynamic_import_fetch_error") {
128
+ const response = await window.fetch(
129
+ `/__get_error_cause__/${
130
+ urlSite.isInline ? urlSite.originalUrl : urlSite.url
131
+ }`,
132
+ )
139
133
 
140
- const causeText =
141
- causeInfo.code === "NOT_FOUND"
142
- ? formatErrorText({
143
- message: causeInfo.reason,
144
- stack: causeInfo.codeFrame,
145
- })
146
- : formatErrorText({
147
- message: causeInfo.stack,
148
- stack: causeInfo.codeFrame,
149
- })
150
- return {
151
- cause: causeText,
152
- }
153
- }
154
- if (urlSite.line !== undefined) {
155
- let ressourceToFetch = `/__get_code_frame__/${formatUrlWithLineAndColumn(
156
- urlSite,
157
- )}`
158
- if (!Error.captureStackTrace) {
159
- ressourceToFetch += `?remap`
134
+ if (response.status !== 200) {
135
+ return null
136
+ }
137
+ const causeInfo = await response.json()
138
+ if (!causeInfo) {
139
+ return null
140
+ }
141
+
142
+ const causeText =
143
+ causeInfo.code === "NOT_FOUND"
144
+ ? formatErrorText({
145
+ message: causeInfo.reason,
146
+ stack: causeInfo.codeFrame,
147
+ })
148
+ : formatErrorText({
149
+ message: causeInfo.stack,
150
+ stack: causeInfo.codeFrame,
151
+ })
152
+ return {
153
+ cause: causeText,
154
+ }
160
155
  }
161
- const response = await window.fetch(ressourceToFetch)
162
- const codeFrame = await response.text()
163
- return {
164
- codeFrame: formatErrorText({ message: codeFrame }),
156
+ if (urlSite.line !== undefined) {
157
+ let ressourceToFetch = `/__get_code_frame__/${formatUrlWithLineAndColumn(
158
+ urlSite,
159
+ )}`
160
+ if (!Error.captureStackTrace) {
161
+ ressourceToFetch += `?remap`
162
+ }
163
+ const response = await window.fetch(ressourceToFetch)
164
+ const codeFrame = await response.text()
165
+ return {
166
+ codeFrame: formatErrorText({ message: codeFrame }),
167
+ }
165
168
  }
169
+ } catch (e) {
170
+ // happens if server is closed for instance
171
+ return null
166
172
  }
167
173
  return null
168
174
  })()
@@ -132,9 +132,7 @@ export const installHtmlSupervisor = ({
132
132
  }
133
133
  executionResult.error = error
134
134
  onExecutionSettled(src, executionResult)
135
- onExecutionError(executionResult, {
136
- currentScript,
137
- })
135
+ onExecutionError(executionResult, { currentScript })
138
136
  if (errorExposureInConsole) {
139
137
  if (typeof window.reportError === "function") {
140
138
  window.reportError(error)
@@ -181,7 +179,6 @@ export const installHtmlSupervisor = ({
181
179
  const useDeferQueue =
182
180
  scriptToExecute.defer || scriptToExecute.type === "module"
183
181
  if (useDeferQueue) {
184
- // defer must wait for classic script to be done
185
182
  const classicExecutionPromise = classicExecutionQueue.getPromise()
186
183
  if (classicExecutionPromise) {
187
184
  deferedExecutionQueue.waitFor(classicExecutionPromise)
@@ -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
  })
@@ -132,7 +132,9 @@ export const jsenvPluginHtmlSupervisor = ({
132
132
  code: causeInfo.code,
133
133
  message: causeInfo.message,
134
134
  reason: causeInfo.reason,
135
- stack: causeInfo.stack,
135
+ stack: errorBaseUrl
136
+ ? `stack mocked for snapshot`
137
+ : causeInfo.stack,
136
138
  codeFrame: causeInfo.traceMessage,
137
139
  }
138
140
  : null,
@@ -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
+ }