@jsenv/core 33.0.2 → 34.0.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.
- package/dist/js/autoreload.js +1 -4
- package/dist/js/supervisor.js +498 -290
- package/dist/jsenv.js +938 -370
- package/package.json +2 -3
- package/src/basic_fetch.js +23 -13
- package/src/build/start_build_server.js +3 -2
- package/src/dev/file_service.js +1 -1
- package/src/dev/start_dev_server.js +9 -6
- package/src/execute/execute.js +7 -18
- package/src/execute/runtimes/browsers/from_playwright.js +168 -32
- package/src/execute/runtimes/browsers/webkit.js +1 -1
- package/src/execute/web_server_param.js +68 -0
- package/src/kitchen/compat/features_compatibility.js +3 -0
- package/src/plugins/autoreload/client/reload.js +1 -4
- package/src/plugins/inline/jsenv_plugin_html_inline_content.js +30 -18
- package/src/plugins/plugins.js +1 -1
- package/src/plugins/ribbon/jsenv_plugin_ribbon.js +3 -2
- package/src/plugins/supervisor/client/supervisor.js +467 -287
- package/src/plugins/supervisor/html_supervisor_injection.js +281 -0
- package/src/plugins/supervisor/js_supervisor_injection.js +283 -0
- package/src/plugins/supervisor/jsenv_plugin_supervisor.js +48 -233
- package/src/plugins/transpilation/as_js_classic/convert_js_module_to_js_classic.js +67 -30
- package/src/plugins/transpilation/as_js_classic/jsenv_plugin_as_js_classic_html.js +1 -1
- package/src/plugins/transpilation/babel/jsenv_plugin_babel.js +5 -0
- package/src/plugins/transpilation/jsenv_plugin_top_level_await.js +1 -1
- package/src/test/execute_steps.js +10 -18
- package/src/test/execute_test_plan.js +12 -60
- package/src/test/logs_file_execution.js +74 -28
- package/dist/js/babel_plugin_transform_modules_systemjs.cjs +0 -392
- package/dist/js/script_type_module_supervisor.js +0 -109
- package/src/plugins/supervisor/client/script_type_module_supervisor.js +0 -98
- package/src/plugins/transpilation/as_js_classic/babel_plugin_transform_modules_systemjs.cjs +0 -608
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/core",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "34.0.1",
|
|
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.
|
|
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",
|
package/src/basic_fetch.js
CHANGED
|
@@ -22,19 +22,29 @@ export const basicFetch = async (
|
|
|
22
22
|
headers,
|
|
23
23
|
})
|
|
24
24
|
req.on("response", (response) => {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
112
|
-
|
|
111
|
+
keepProcessAlive: process.env.IMPORTED_BY_TEST_PLAN
|
|
112
|
+
? false
|
|
113
|
+
: keepProcessAlive,
|
|
113
114
|
logLevel: serverLogLevel,
|
|
114
115
|
startLog: false,
|
|
115
116
|
|
package/src/dev/file_service.js
CHANGED
|
@@ -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 === "/
|
|
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
|
},
|
package/src/execute/execute.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: `${
|
|
149
|
-
to:
|
|
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 (
|
|
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 (
|
|
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(
|
|
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(
|
|
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
|
|
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
|
-
|
|
367
|
-
|
|
368
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
},
|