@jsenv/core 33.0.2 → 34.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 (29) hide show
  1. package/dist/js/autoreload.js +1 -4
  2. package/dist/js/supervisor.js +498 -290
  3. package/dist/jsenv.js +870 -346
  4. package/package.json +2 -3
  5. package/src/basic_fetch.js +23 -13
  6. package/src/build/start_build_server.js +3 -2
  7. package/src/dev/file_service.js +1 -1
  8. package/src/dev/start_dev_server.js +9 -6
  9. package/src/execute/execute.js +7 -18
  10. package/src/execute/runtimes/browsers/from_playwright.js +168 -32
  11. package/src/execute/runtimes/browsers/webkit.js +1 -1
  12. package/src/execute/web_server_param.js +68 -0
  13. package/src/kitchen/compat/features_compatibility.js +3 -0
  14. package/src/plugins/autoreload/client/reload.js +1 -4
  15. package/src/plugins/inline/jsenv_plugin_html_inline_content.js +30 -18
  16. package/src/plugins/plugins.js +1 -1
  17. package/src/plugins/ribbon/jsenv_plugin_ribbon.js +3 -2
  18. package/src/plugins/supervisor/client/supervisor.js +467 -287
  19. package/src/plugins/supervisor/html_supervisor_injection.js +281 -0
  20. package/src/plugins/supervisor/js_supervisor_injection.js +281 -0
  21. package/src/plugins/supervisor/jsenv_plugin_supervisor.js +48 -233
  22. package/src/plugins/transpilation/as_js_classic/jsenv_plugin_as_js_classic_html.js +1 -1
  23. package/src/plugins/transpilation/babel/jsenv_plugin_babel.js +5 -0
  24. package/src/plugins/transpilation/jsenv_plugin_top_level_await.js +1 -1
  25. package/src/test/execute_steps.js +10 -18
  26. package/src/test/execute_test_plan.js +12 -60
  27. package/src/test/logs_file_execution.js +74 -28
  28. package/dist/js/script_type_module_supervisor.js +0 -109
  29. package/src/plugins/supervisor/client/script_type_module_supervisor.js +0 -98
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/core",
3
- "version": "33.0.2",
3
+ "version": "34.0.0",
4
4
  "description": "Tool to develop, test and build js projects",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -43,7 +43,6 @@
43
43
  "eslint": "npx eslint . --ext=.js,.mjs,.cjs,.html",
44
44
  "dev": "node --conditions=development ./scripts/dev/dev.mjs",
45
45
  "test": "node --conditions=development ./scripts/test/test.mjs",
46
- "test:resource_hints": "npm run test -- --only-resource-hints",
47
46
  "build": "node --conditions=development ./scripts/build/build.mjs",
48
47
  "workspace:test": "npm run test --workspaces --if-present -- --workspace",
49
48
  "workspace:versions": "node ./scripts/publish/workspace_versions.mjs",
@@ -67,7 +66,7 @@
67
66
  "@c88/v8-coverage": "0.1.1",
68
67
  "@financial-times/polyfill-useragent-normaliser": "1.10.2",
69
68
  "@jsenv/abort": "4.2.4",
70
- "@jsenv/ast": "3.0.3",
69
+ "@jsenv/ast": "3.0.4",
71
70
  "@jsenv/babel-plugins": "1.1.5",
72
71
  "@jsenv/filesystem": "4.2.3",
73
72
  "@jsenv/importmap": "1.2.1",
@@ -22,19 +22,29 @@ export const basicFetch = async (
22
22
  headers,
23
23
  })
24
24
  req.on("response", (response) => {
25
- req.setTimeout(0)
26
- let responseBody = ""
27
- response.setEncoding("utf8")
28
- response.on("data", (chunk) => {
29
- responseBody += chunk
30
- })
31
- response.on("end", () => {
32
- req.destroy()
33
- if (response.headers["content-type"] === "application/json") {
34
- resolve(JSON.parse(responseBody))
35
- } else {
36
- resolve(responseBody)
37
- }
25
+ resolve({
26
+ status: response.statusCode,
27
+ headers: response.headers,
28
+ json: () => {
29
+ req.setTimeout(0)
30
+ req.destroy()
31
+ return new Promise((resolve) => {
32
+ if (response.headers["content-type"] !== "application/json") {
33
+ console.warn("not json")
34
+ }
35
+ let responseBody = ""
36
+ response.setEncoding("utf8")
37
+ response.on("data", (chunk) => {
38
+ responseBody += chunk
39
+ })
40
+ response.on("end", () => {
41
+ resolve(JSON.parse(responseBody))
42
+ })
43
+ response.on("error", (e) => {
44
+ reject(e)
45
+ })
46
+ })
47
+ },
38
48
  })
39
49
  })
40
50
  req.on("error", reject)
@@ -108,8 +108,9 @@ export const startBuildServer = async ({
108
108
  stopOnExit: false,
109
109
  stopOnSIGINT: false,
110
110
  stopOnInternalError: false,
111
- // the worker should be kept alive by the parent otherwise
112
- keepProcessAlive,
111
+ keepProcessAlive: process.env.IMPORTED_BY_TEST_PLAN
112
+ ? false
113
+ : keepProcessAlive,
113
114
  logLevel: serverLogLevel,
114
115
  startLog: false,
115
116
 
@@ -60,7 +60,7 @@ export const createFileService = ({
60
60
 
61
61
  const getOrCreateContext = (request) => {
62
62
  const { runtimeName, runtimeVersion } = parseUserAgentHeader(
63
- request.headers["user-agent"],
63
+ request.headers["user-agent"] || "",
64
64
  )
65
65
  const runtimeId = `${runtimeName}@${runtimeVersion}`
66
66
  const existingContext = contextCache.get(runtimeId)
@@ -121,7 +121,9 @@ export const startDevServer = async ({
121
121
  stopOnExit: false,
122
122
  stopOnSIGINT: handleSIGINT,
123
123
  stopOnInternalError: false,
124
- keepProcessAlive,
124
+ keepProcessAlive: process.env.IMPORTED_BY_TEST_PLAN
125
+ ? false
126
+ : keepProcessAlive,
125
127
  logLevel: serverLogLevel,
126
128
  startLog: false,
127
129
 
@@ -132,6 +134,11 @@ export const startDevServer = async ({
132
134
  port,
133
135
  requestWaitingMs: 60_000,
134
136
  services: [
137
+ {
138
+ injectResponseHeaders: () => {
139
+ return { "x-server-name": "jsenv_dev_server" }
140
+ },
141
+ },
135
142
  jsenvServiceCORS({
136
143
  accessControlAllowRequestOrigin: true,
137
144
  accessControlAllowRequestMethod: true,
@@ -145,7 +152,7 @@ export const startDevServer = async ({
145
152
  }),
146
153
  {
147
154
  handleRequest: (request) => {
148
- if (request.pathname === "/__server_params__.json") {
155
+ if (request.pathname === "/__params__.json") {
149
156
  const json = JSON.stringify({
150
157
  sourceDirectoryUrl,
151
158
  })
@@ -158,10 +165,6 @@ export const startDevServer = async ({
158
165
  body: json,
159
166
  }
160
167
  }
161
- if (request.pathname === "/__stop__") {
162
- server.stop()
163
- return { status: 200 }
164
- }
165
168
  return null
166
169
  },
167
170
  },
@@ -14,7 +14,7 @@ import { Abort, raceProcessTeardownEvents } from "@jsenv/abort"
14
14
  import { assertAndNormalizeDirectoryUrl } from "@jsenv/filesystem"
15
15
  import { createLogger } from "@jsenv/log"
16
16
 
17
- import { pingServer } from "../ping_server.js"
17
+ import { assertAndNormalizeWebServer } from "./web_server_param.js"
18
18
  import { run } from "./run.js"
19
19
 
20
20
  export const execute = async ({
@@ -22,8 +22,7 @@ export const execute = async ({
22
22
  handleSIGINT = true,
23
23
  logLevel,
24
24
  rootDirectoryUrl,
25
- sourceDirectoryUrl = rootDirectoryUrl,
26
- devServerOrigin,
25
+ webServer,
27
26
 
28
27
  fileRelativeUrl,
29
28
  allocatedMs,
@@ -57,27 +56,17 @@ export const execute = async ({
57
56
  })
58
57
  }
59
58
 
59
+ if (runtime.type === "browser") {
60
+ await assertAndNormalizeWebServer(webServer)
61
+ }
62
+
60
63
  let resultTransformer = (result) => result
61
64
  runtimeParams = {
62
65
  rootDirectoryUrl,
63
- sourceDirectoryUrl,
64
- devServerOrigin,
66
+ webServer,
65
67
  fileRelativeUrl,
66
68
  ...runtimeParams,
67
69
  }
68
- if (runtime.type === "browser") {
69
- if (!devServerOrigin) {
70
- throw new TypeError(
71
- `devServerOrigin is required to execute file on a browser`,
72
- )
73
- }
74
- const devServerStarted = await pingServer(devServerOrigin)
75
- if (!devServerStarted) {
76
- throw new Error(
77
- `no server listening at ${devServerOrigin}. It is required to execute file`,
78
- )
79
- }
80
- }
81
70
 
82
71
  let result = await run({
83
72
  signal: executeOperation.signal,
@@ -1,4 +1,4 @@
1
- import { writeFileSync } from "node:fs"
1
+ import { readFileSync, writeFileSync } from "node:fs"
2
2
 
3
3
  import { createDetailedMessage } from "@jsenv/log"
4
4
  import {
@@ -7,17 +7,21 @@ import {
7
7
  raceProcessTeardownEvents,
8
8
  raceCallbacks,
9
9
  } from "@jsenv/abort"
10
- import { moveUrl, urlIsInsideOf } from "@jsenv/urls"
10
+ import { moveUrl, urlIsInsideOf, urlToExtension } from "@jsenv/urls"
11
11
  import { memoize } from "@jsenv/utils/src/memoize/memoize.js"
12
12
 
13
13
  import { filterV8Coverage } from "@jsenv/core/src/test/coverage/v8_coverage.js"
14
14
  import { composeTwoFileByFileIstanbulCoverages } from "@jsenv/core/src/test/coverage/istanbul_coverage_composition.js"
15
+ import {
16
+ injectSupervisorIntoHTML,
17
+ supervisorFileUrl,
18
+ } from "../../../plugins/supervisor/html_supervisor_injection.js"
15
19
 
16
20
  export const createRuntimeFromPlaywright = ({
17
21
  browserName,
18
22
  browserVersion,
19
23
  coveragePlaywrightAPIAvailable = false,
20
- ignoreErrorHook = () => false,
24
+ shouldIgnoreError = () => false,
21
25
  transformErrorHook = (error) => error,
22
26
  isolatedTab = false,
23
27
  }) => {
@@ -31,9 +35,8 @@ export const createRuntimeFromPlaywright = ({
31
35
  signal = new AbortController().signal,
32
36
  logger,
33
37
  rootDirectoryUrl,
38
+ webServer,
34
39
  fileRelativeUrl,
35
- devServerOrigin,
36
- sourceDirectoryUrl,
37
40
 
38
41
  // measurePerformance,
39
42
  collectPerformance,
@@ -51,6 +54,20 @@ export const createRuntimeFromPlaywright = ({
51
54
  playwrightLaunchOptions = {},
52
55
  ignoreHTTPSErrors = true,
53
56
  }) => {
57
+ const fileUrl = new URL(fileRelativeUrl, rootDirectoryUrl).href
58
+ if (!urlIsInsideOf(fileUrl, webServer.rootDirectoryUrl)) {
59
+ throw new Error(`Cannot execute file that is outside web server root directory
60
+ --- file ---
61
+ ${fileUrl}
62
+ --- web server root directory url ---
63
+ ${webServer.rootDirectoryUrl}`)
64
+ }
65
+ const fileServerUrl = moveUrl({
66
+ url: fileUrl,
67
+ from: webServer.rootDirectoryUrl,
68
+ to: `${webServer.origin}/`,
69
+ })
70
+
54
71
  const cleanupCallbackList = createCallbackListNotifiedOnce()
55
72
  const cleanup = memoize(async (reason) => {
56
73
  await cleanupCallbackList.notify({ reason })
@@ -112,6 +129,13 @@ export const createRuntimeFromPlaywright = ({
112
129
  : {}),
113
130
  },
114
131
  })
132
+ if (!webServer.isJsenvDevServer) {
133
+ await initJsExecutionMiddleware(page, {
134
+ webServer,
135
+ fileUrl,
136
+ fileServerUrl,
137
+ })
138
+ }
115
139
  const closePage = async () => {
116
140
  try {
117
141
  await page.close()
@@ -145,8 +169,8 @@ export const createRuntimeFromPlaywright = ({
145
169
  (v8CoveragesWithWebUrl) => {
146
170
  const fsUrl = moveUrl({
147
171
  url: v8CoveragesWithWebUrl.url,
148
- from: `${devServerOrigin}/`,
149
- to: sourceDirectoryUrl,
172
+ from: `${webServer.origin}/`,
173
+ to: webServer.rootDirectoryUrl,
150
174
  })
151
175
  return {
152
176
  ...v8CoveragesWithWebUrl,
@@ -218,19 +242,6 @@ export const createRuntimeFromPlaywright = ({
218
242
  })
219
243
  }
220
244
 
221
- const fileUrl = new URL(fileRelativeUrl, rootDirectoryUrl).href
222
- if (!urlIsInsideOf(fileUrl, sourceDirectoryUrl)) {
223
- throw new Error(`Cannot execute file that is outside source directory
224
- --- file ---
225
- ${fileUrl}
226
- --- source directory ---
227
- ${sourceDirectoryUrl}`)
228
- }
229
- const fileDevServerUrl = moveUrl({
230
- url: fileUrl,
231
- from: sourceDirectoryUrl,
232
- to: `${devServerOrigin}/`,
233
- })
234
245
  // https://github.com/GoogleChrome/puppeteer/blob/v1.4.0/docs/api.md#event-console
235
246
  const removeConsoleListener = registerEvent({
236
247
  object: page,
@@ -247,6 +258,7 @@ ${sourceDirectoryUrl}`)
247
258
  cleanupCallbackList.add(removeConsoleListener)
248
259
  const actionOperation = Abort.startOperation()
249
260
  actionOperation.addAbortSignal(signal)
261
+
250
262
  const winnerPromise = new Promise((resolve, reject) => {
251
263
  raceCallbacks(
252
264
  {
@@ -259,7 +271,7 @@ ${sourceDirectoryUrl}`)
259
271
  object: page,
260
272
  eventType: "error",
261
273
  callback: (error) => {
262
- if (ignoreErrorHook(error)) {
274
+ if (shouldIgnoreError(error, "error")) {
263
275
  return
264
276
  }
265
277
  cb(transformErrorHook(error))
@@ -272,7 +284,10 @@ ${sourceDirectoryUrl}`)
272
284
  // object: page,
273
285
  // eventType: "pageerror",
274
286
  // callback: (error) => {
275
- // if (ignoreErrorHook(error)) {
287
+ // if (
288
+ // webServer.isJsenvDevServer ||
289
+ // shouldIgnoreError(error, "pageerror")
290
+ // ) {
276
291
  // return
277
292
  // }
278
293
  // result.errors.push(transformErrorHook(error))
@@ -312,15 +327,29 @@ ${sourceDirectoryUrl}`)
312
327
  },
313
328
  response: async (cb) => {
314
329
  try {
315
- await page.goto(fileDevServerUrl, { timeout: 0 })
330
+ await page.goto(fileServerUrl, { timeout: 0 })
316
331
  const returnValue = await page.evaluate(
317
332
  /* eslint-disable no-undef */
318
333
  /* istanbul ignore next */
319
- () => {
334
+ async () => {
335
+ let startTime
336
+ try {
337
+ startTime = window.performance.timing.navigationStart
338
+ } catch (e) {
339
+ startTime = Date.now()
340
+ }
320
341
  if (!window.__supervisor__) {
321
- throw new Error(`window.__supervisor__ not found`)
342
+ throw new Error("window.__supervisor__ is undefined")
343
+ }
344
+ const executionResultFromJsenvSupervisor =
345
+ await window.__supervisor__.getDocumentExecutionResult()
346
+ return {
347
+ type: "window_supervisor",
348
+ startTime,
349
+ endTime: Date.now(),
350
+ executionResults:
351
+ executionResultFromJsenvSupervisor.executionResults,
322
352
  }
323
- return window.__supervisor__.getDocumentExecutionResult()
324
353
  },
325
354
  /* eslint-enable no-undef */
326
355
  )
@@ -346,6 +375,12 @@ ${sourceDirectoryUrl}`)
346
375
  result.errors.push(error)
347
376
  return
348
377
  }
378
+ if (winner.name === "pageerror") {
379
+ let error = winner.data
380
+ result.status = "failed"
381
+ result.errors.push(error)
382
+ return
383
+ }
349
384
  if (winner.name === "closed") {
350
385
  result.status = "failed"
351
386
  result.errors.push(
@@ -355,7 +390,7 @@ ${sourceDirectoryUrl}`)
355
390
  )
356
391
  return
357
392
  }
358
- // winner.name = 'response'
393
+ // winner.name === "response"
359
394
  const { executionResults } = winner.data
360
395
  result.status = "completed"
361
396
  result.namespace = executionResults
@@ -363,10 +398,17 @@ ${sourceDirectoryUrl}`)
363
398
  const executionResult = executionResults[key]
364
399
  if (executionResult.status === "failed") {
365
400
  result.status = "failed"
366
- result.errors.push({
367
- ...executionResult.exception,
368
- stack: executionResult.exception.text,
369
- })
401
+ if (executionResult.exception) {
402
+ result.errors.push({
403
+ ...executionResult.exception,
404
+ stack: executionResult.exception.text,
405
+ })
406
+ } else {
407
+ result.errors.push({
408
+ ...executionResult.error,
409
+ stack: executionResult.error.stack,
410
+ })
411
+ }
370
412
  }
371
413
  })
372
414
  }
@@ -397,7 +439,7 @@ ${sourceDirectoryUrl}`)
397
439
  browserName,
398
440
  browserVersion,
399
441
  coveragePlaywrightAPIAvailable,
400
- ignoreErrorHook,
442
+ shouldIgnoreError,
401
443
  transformErrorHook,
402
444
  isolatedTab: true,
403
445
  })
@@ -528,6 +570,100 @@ const extractTextFromConsoleMessage = (consoleMessage) => {
528
570
  // return text
529
571
  }
530
572
 
573
+ const initJsExecutionMiddleware = async (
574
+ page,
575
+ { webServer, fileUrl, fileServerUrl },
576
+ ) => {
577
+ const inlineScriptContents = new Map()
578
+
579
+ const interceptHtmlToExecute = async ({ route }) => {
580
+ // Fetch original response.
581
+ const response = await route.fetch()
582
+ // Add a prefix to the title.
583
+ const originalBody = await response.text()
584
+ const injectionResult = await injectSupervisorIntoHTML(
585
+ {
586
+ content: originalBody,
587
+ url: fileUrl,
588
+ },
589
+ {
590
+ supervisorScriptSrc: `/@fs/${supervisorFileUrl.slice(
591
+ "file:///".length,
592
+ )}`,
593
+ supervisorOptions: {},
594
+ inlineAsRemote: true,
595
+ webServer,
596
+ onInlineScript: ({ src, textContent }) => {
597
+ const inlineScriptWebUrl = new URL(src, `${webServer.origin}/`).href
598
+ inlineScriptContents.set(inlineScriptWebUrl, textContent)
599
+ },
600
+ },
601
+ )
602
+ route.fulfill({
603
+ response,
604
+ body: injectionResult.content,
605
+ headers: {
606
+ ...response.headers(),
607
+ "content-length": Buffer.byteLength(injectionResult.content),
608
+ },
609
+ })
610
+ }
611
+
612
+ const interceptInlineScript = ({ url, route }) => {
613
+ const inlineScriptContent = inlineScriptContents.get(url)
614
+ route.fulfill({
615
+ status: 200,
616
+ body: inlineScriptContent,
617
+ headers: {
618
+ "content-type": "text/javascript",
619
+ "content-length": Buffer.byteLength(inlineScriptContent),
620
+ },
621
+ })
622
+ }
623
+
624
+ const interceptFileSystemUrl = ({ url, route }) => {
625
+ const relativeUrl = url.slice(webServer.origin.length)
626
+ const fsPath = relativeUrl.slice("/@fs/".length)
627
+ const fsUrl = `file:///${fsPath}`
628
+ const fileContent = readFileSync(new URL(fsUrl), "utf8")
629
+ route.fulfill({
630
+ status: 200,
631
+ body: fileContent,
632
+ headers: {
633
+ "content-type": "text/javascript",
634
+ "content-length": Buffer.byteLength(fileContent),
635
+ },
636
+ })
637
+ }
638
+
639
+ await page.route("**", async (route) => {
640
+ const request = route.request()
641
+ const url = request.url()
642
+ if (url === fileServerUrl && urlToExtension(url) === ".html") {
643
+ interceptHtmlToExecute({
644
+ url,
645
+ request,
646
+ route,
647
+ })
648
+ return
649
+ }
650
+ if (inlineScriptContents.has(url)) {
651
+ interceptInlineScript({
652
+ url,
653
+ request,
654
+ route,
655
+ })
656
+ return
657
+ }
658
+ const fsServerUrl = new URL("/@fs/", webServer.origin)
659
+ if (url.startsWith(fsServerUrl)) {
660
+ interceptFileSystemUrl({ url, request, route })
661
+ return
662
+ }
663
+ route.fallback()
664
+ })
665
+ }
666
+
531
667
  const registerEvent = ({ object, eventType, callback }) => {
532
668
  object.on(eventType, callback)
533
669
  return () => {
@@ -5,7 +5,7 @@ export const webkit = createRuntimeFromPlaywright({
5
5
  // browserVersion will be set by "browser._initializer.version"
6
6
  // see also https://github.com/microsoft/playwright/releases
7
7
  browserVersion: "unset",
8
- ignoreErrorHook: (error) => {
8
+ shouldIgnoreError: (error) => {
9
9
  // we catch error during execution but safari throw unhandled rejection
10
10
  // in a non-deterministic way.
11
11
  // I suppose it's due to some race condition to decide if the promise is catched or not
@@ -0,0 +1,68 @@
1
+ import { assertAndNormalizeDirectoryUrl } from "@jsenv/filesystem"
2
+
3
+ import { pingServer } from "../ping_server.js"
4
+ import { basicFetch } from "../basic_fetch.js"
5
+
6
+ export const assertAndNormalizeWebServer = async (webServer) => {
7
+ if (!webServer) {
8
+ throw new TypeError(
9
+ `webServer is required when running tests on browser(s)`,
10
+ )
11
+ }
12
+ const unexpectedParamNames = Object.keys(webServer).filter((key) => {
13
+ return !["origin", "moduleUrl", "rootDirectoryUrl"].includes(key)
14
+ })
15
+ if (unexpectedParamNames.length > 0) {
16
+ throw new TypeError(
17
+ `${unexpectedParamNames.join(",")}: there is no such param to webServer`,
18
+ )
19
+ }
20
+
21
+ let aServerIsListening = await pingServer(webServer.origin)
22
+ if (!aServerIsListening) {
23
+ if (!webServer.moduleUrl) {
24
+ throw new TypeError(
25
+ `webServer.moduleUrl is required as there is no server listening "${webServer.origin}"`,
26
+ )
27
+ }
28
+ try {
29
+ process.env.IMPORTED_BY_TEST_PLAN = "1"
30
+ await import(webServer.moduleUrl)
31
+ delete process.env.IMPORTED_BY_TEST_PLAN
32
+ } catch (e) {
33
+ if (e.code === "ERR_MODULE_NOT_FOUND") {
34
+ throw new Error(
35
+ `webServer.moduleUrl does not lead to a file at "${webServer.moduleUrl}"`,
36
+ )
37
+ }
38
+ throw e
39
+ }
40
+ aServerIsListening = await pingServer(webServer.origin)
41
+ if (!aServerIsListening) {
42
+ throw new Error(
43
+ `webServer.moduleUrl did not start a server listening at "${webServer.origin}", check file at "${webServer.moduleUrl}"`,
44
+ )
45
+ }
46
+ }
47
+ const { headers } = await basicFetch(webServer.origin)
48
+ if (headers["x-server-name"] === "jsenv_dev_server") {
49
+ webServer.isJsenvDevServer = true
50
+ const { json } = await basicFetch(`${webServer.origin}/__params__.json`, {
51
+ rejectUnauthorized: false,
52
+ })
53
+ if (webServer.rootDirectoryUrl === undefined) {
54
+ const jsenvDevServerParams = await json()
55
+ webServer.rootDirectoryUrl = jsenvDevServerParams.sourceDirectoryUrl
56
+ } else {
57
+ webServer.rootDirectoryUrl = assertAndNormalizeDirectoryUrl(
58
+ webServer.rootDirectoryUrl,
59
+ "webServer.rootDirectoryUrl",
60
+ )
61
+ }
62
+ } else {
63
+ webServer.rootDirectoryUrl = assertAndNormalizeDirectoryUrl(
64
+ webServer.rootDirectoryUrl,
65
+ "webServer.rootDirectoryUrl",
66
+ )
67
+ }
68
+ }
@@ -31,6 +31,8 @@ export const featuresCompatMap = {
31
31
  },
32
32
  import_meta_resolve: {
33
33
  chrome: "107",
34
+ edge: "105",
35
+ firefox: "106",
34
36
  },
35
37
  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#browser_compatibility
36
38
  import_dynamic: {
@@ -61,6 +63,7 @@ export const featuresCompatMap = {
61
63
  opera: "76",
62
64
  samsung: "15",
63
65
  firefox: "108",
66
+ safari: "16.4",
64
67
  },
65
68
  import_type_json: {
66
69
  chrome: "91",
@@ -67,10 +67,7 @@ export const getDOMNodesUsingUrl = (urlToReload) => {
67
67
  nodes.push({
68
68
  node: script,
69
69
  reload: () =>
70
- window.__supervisor__.reloadSupervisedScript({
71
- type: script.type,
72
- src: inlinedFromSrc,
73
- }),
70
+ window.__supervisor__.reloadSupervisedScript(inlinedFromSrc),
74
71
  })
75
72
  }
76
73
  }
@@ -1,3 +1,9 @@
1
+ /*
2
+ * This plugin ensure content inlined inside HTML is cooked (inline <script> for instance)
3
+ * For <script hot-accept> the script content will be moved to a virtual file
4
+ * to enable hot reloading
5
+ */
6
+
1
7
  import { generateInlineContentUrl } from "@jsenv/urls"
2
8
  import {
3
9
  parseHtmlString,
@@ -8,6 +14,7 @@ import {
8
14
  analyzeScriptNode,
9
15
  setHtmlNodeAttributes,
10
16
  setHtmlNodeText,
17
+ removeHtmlNodeText,
11
18
  getHtmlNodeAttribute,
12
19
  } from "@jsenv/ast"
13
20
  import { CONTENT_TYPE } from "@jsenv/utils/src/content_type/content_type.js"
@@ -113,16 +120,9 @@ ${e.traceMessage}`)
113
120
  ) {
114
121
  return
115
122
  }
116
- if (
117
- getHtmlNodeAttribute(scriptNode, "jsenv-cooked-by") ===
118
- "jsenv:supervisor" ||
119
- getHtmlNodeAttribute(scriptNode, "jsenv-inlined-by") ===
120
- "jsenv:supervisor" ||
121
- getHtmlNodeAttribute(scriptNode, "jsenv-injected-by") ===
122
- "jsenv:supervisor"
123
- ) {
124
- return
125
- }
123
+
124
+ const hotAccept =
125
+ getHtmlNodeAttribute(scriptNode, "hot-accept") !== undefined
126
126
  const { type, contentType, extension } =
127
127
  analyzeScriptNode(scriptNode)
128
128
  const { line, column, lineEnd, columnEnd, isOriginal } =
@@ -161,14 +161,26 @@ ${e.traceMessage}`)
161
161
  inlineContentUrlInfo: inlineScriptUrlInfo,
162
162
  inlineContentReference: inlineScriptReference,
163
163
  })
164
- })
165
- mutations.push(() => {
166
- setHtmlNodeText(scriptNode, inlineScriptUrlInfo.content)
167
- setHtmlNodeAttributes(scriptNode, {
168
- "jsenv-cooked-by": "jsenv:html_inline_content",
169
- ...(extension
170
- ? { type: type === "js_module" ? "module" : undefined }
171
- : {}),
164
+ mutations.push(() => {
165
+ const attributes = {
166
+ "jsenv-cooked-by": "jsenv:html_inline_content",
167
+ // 1. <script type="jsx"> becomes <script>
168
+ // 2. <script type="module/jsx"> becomes <script type="module">
169
+ ...(extension
170
+ ? { type: type === "js_module" ? "module" : undefined }
171
+ : {}),
172
+ }
173
+ if (hotAccept) {
174
+ removeHtmlNodeText(scriptNode)
175
+ setHtmlNodeAttributes(scriptNode, {
176
+ ...attributes,
177
+ })
178
+ } else {
179
+ setHtmlNodeText(scriptNode, inlineScriptUrlInfo.content)
180
+ setHtmlNodeAttributes(scriptNode, {
181
+ ...attributes,
182
+ })
183
+ }
172
184
  })
173
185
  })
174
186
  },