@jsenv/core 27.0.0-alpha.82 → 27.0.0-alpha.83
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/event_source_client.js +205 -1
- package/dist/main.js +845 -60
- package/package.json +2 -2
- package/src/build/start_build_server.js +29 -26
- package/src/dev/start_dev_server.js +34 -30
- package/src/execute/runtimes/browsers/from_playwright.js +3 -2
- package/src/helpers/event_source/event_source.js +197 -0
- package/src/helpers/event_source/sse_service.js +53 -0
- package/src/helpers/worker_reload.js +56 -0
- package/src/plugins/autoreload/dev_sse/client/event_source_client.js +1 -1
- package/src/plugins/autoreload/dev_sse/jsenv_plugin_dev_sse_server.js +1 -1
- package/src/test/coverage/babel_plugin_instrument.js +82 -0
- package/src/test/coverage/coverage_reporter_html_directory.js +36 -0
- package/src/test/coverage/coverage_reporter_json_file.js +22 -0
- package/src/test/coverage/coverage_reporter_text_log.js +19 -0
- package/src/test/coverage/empty_coverage_factory.js +52 -0
- package/src/test/coverage/file_by_file_coverage.js +26 -0
- package/src/test/coverage/istanbul_coverage_composition.js +28 -0
- package/src/test/coverage/istanbul_coverage_map_from_coverage.js +16 -0
- package/src/test/coverage/list_files_not_covered.js +15 -0
- package/src/test/coverage/missing_coverage.js +41 -0
- package/src/test/coverage/report_to_coverage.js +196 -0
- package/src/test/coverage/v8_and_istanbul.js +37 -0
- package/src/test/coverage/v8_coverage_composition.js +24 -0
- package/src/test/coverage/v8_coverage_from_directory.js +87 -0
- package/src/test/coverage/v8_coverage_to_istanbul.js +99 -0
- package/src/test/execute_plan.js +2 -2
- package/src/test/execute_test_plan.js +3 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/core",
|
|
3
|
-
"version": "27.0.0-alpha.
|
|
3
|
+
"version": "27.0.0-alpha.83",
|
|
4
4
|
"description": "Tool to develop, test and build js projects",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"@jsenv/node-esm-resolution": "0.1.0",
|
|
75
75
|
"@jsenv/server": "12.7.0",
|
|
76
76
|
"@jsenv/uneval": "1.6.0",
|
|
77
|
-
"@jsenv/utils": "1.9.
|
|
77
|
+
"@jsenv/utils": "1.9.2",
|
|
78
78
|
"@jsenv/url-meta": "7.0.0",
|
|
79
79
|
"@jsenv/urls": "1.2.5",
|
|
80
80
|
"construct-style-sheets-polyfill": "3.1.0",
|
|
@@ -13,7 +13,6 @@
|
|
|
13
13
|
* we want to be in the user shoes and we should not alter build files.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import { parentPort } from "node:worker_threads"
|
|
17
16
|
import {
|
|
18
17
|
jsenvAccessControlAllowedHeaders,
|
|
19
18
|
startServer,
|
|
@@ -22,15 +21,17 @@ import {
|
|
|
22
21
|
pluginCORS,
|
|
23
22
|
fetchFileSystem,
|
|
24
23
|
composeServices,
|
|
24
|
+
findFreePort,
|
|
25
25
|
} from "@jsenv/server"
|
|
26
|
-
|
|
27
26
|
import {
|
|
28
27
|
assertAndNormalizeDirectoryUrl,
|
|
29
28
|
registerDirectoryLifecycle,
|
|
30
29
|
} from "@jsenv/filesystem"
|
|
30
|
+
import { Abort, raceProcessTeardownEvents } from "@jsenv/abort"
|
|
31
31
|
import { createLogger, loggerToLevels, createTaskLog } from "@jsenv/log"
|
|
32
32
|
import { getCallerPosition } from "@jsenv/urls"
|
|
33
|
-
|
|
33
|
+
|
|
34
|
+
import { createReloadableWorker } from "@jsenv/core/src/helpers/worker_reload.js"
|
|
34
35
|
|
|
35
36
|
export const startBuildServer = async ({
|
|
36
37
|
signal = new AbortController().signal,
|
|
@@ -56,10 +57,9 @@ export const startBuildServer = async ({
|
|
|
56
57
|
buildServerMainFile = getCallerPosition().url,
|
|
57
58
|
// force disable server autoreload when this code is executed:
|
|
58
59
|
// - inside a forked child process
|
|
59
|
-
// -
|
|
60
|
-
//
|
|
60
|
+
// - debugged by vscode
|
|
61
|
+
// otherwise we get net:ERR_CONNECTION_REFUSED
|
|
61
62
|
buildServerAutoreload = typeof process.send !== "function" &&
|
|
62
|
-
!parentPort &&
|
|
63
63
|
!process.env.VSCODE_INSPECTOR_OPTIONS,
|
|
64
64
|
cooldownBetweenFileEvents,
|
|
65
65
|
}) => {
|
|
@@ -67,25 +67,29 @@ export const startBuildServer = async ({
|
|
|
67
67
|
rootDirectoryUrl = assertAndNormalizeDirectoryUrl(rootDirectoryUrl)
|
|
68
68
|
buildDirectoryUrl = assertAndNormalizeDirectoryUrl(buildDirectoryUrl)
|
|
69
69
|
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
70
|
+
const operation = Abort.startOperation()
|
|
71
|
+
operation.addAbortSignal(signal)
|
|
72
|
+
if (handleSIGINT) {
|
|
73
|
+
operation.addAbortSource((abort) => {
|
|
74
|
+
return raceProcessTeardownEvents(
|
|
75
|
+
{
|
|
76
|
+
SIGINT: true,
|
|
77
|
+
},
|
|
78
|
+
abort,
|
|
79
|
+
)
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
if (port === 0) {
|
|
83
|
+
port = await findFreePort(port, { signal: operation.signal })
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const reloadableWorker = createReloadableWorker(buildServerMainFile)
|
|
87
|
+
if (buildServerAutoreload && reloadableWorker.isPrimary) {
|
|
84
88
|
const buildServerFileChangeCallback = ({ relativeUrl, event }) => {
|
|
85
89
|
const url = new URL(relativeUrl, rootDirectoryUrl).href
|
|
86
90
|
if (buildServerAutoreload) {
|
|
87
91
|
logger.info(`file ${event} ${url} -> restarting server...`)
|
|
88
|
-
|
|
92
|
+
reloadableWorker.reload()
|
|
89
93
|
}
|
|
90
94
|
}
|
|
91
95
|
const stopWatchingBuildServerFiles = registerDirectoryLifecycle(
|
|
@@ -109,19 +113,19 @@ export const startBuildServer = async ({
|
|
|
109
113
|
},
|
|
110
114
|
},
|
|
111
115
|
)
|
|
112
|
-
|
|
116
|
+
operation.addAbortCallback(() => {
|
|
113
117
|
stopWatchingBuildServerFiles()
|
|
118
|
+
reloadableWorker.terminate()
|
|
114
119
|
})
|
|
120
|
+
await reloadableWorker.load()
|
|
115
121
|
return {
|
|
116
122
|
origin: `${protocol}://127.0.0.1:${port}`,
|
|
117
123
|
stop: () => {
|
|
118
124
|
stopWatchingBuildServerFiles()
|
|
119
|
-
|
|
120
|
-
reloadableProcess.stop()
|
|
125
|
+
reloadableWorker.terminate()
|
|
121
126
|
},
|
|
122
127
|
}
|
|
123
128
|
}
|
|
124
|
-
signal = reloadableProcess.signal
|
|
125
129
|
|
|
126
130
|
const startBuildServerTask = createTaskLog("start build server", {
|
|
127
131
|
disabled: !loggerToLevels(logger).info,
|
|
@@ -170,7 +174,6 @@ export const startBuildServer = async ({
|
|
|
170
174
|
logger.info(`- ${server.origins[key]}`)
|
|
171
175
|
})
|
|
172
176
|
logger.info(``)
|
|
173
|
-
|
|
174
177
|
return {
|
|
175
178
|
origin: server.origin,
|
|
176
179
|
stop: () => {
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { findFreePort } from "@jsenv/server"
|
|
3
2
|
import {
|
|
4
3
|
assertAndNormalizeDirectoryUrl,
|
|
5
4
|
registerDirectoryLifecycle,
|
|
6
5
|
} from "@jsenv/filesystem"
|
|
6
|
+
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 { createReloadableWorker } from "@jsenv/core/src/helpers/worker_reload.js"
|
|
10
11
|
import { getCorePlugins } from "@jsenv/core/src/plugins/plugins.js"
|
|
11
12
|
import { createUrlGraph } from "@jsenv/core/src/omega/url_graph.js"
|
|
12
13
|
import { createKitchen } from "@jsenv/core/src/omega/kitchen.js"
|
|
@@ -17,7 +18,7 @@ import { jsenvPluginExplorer } from "./plugins/explorer/jsenv_plugin_explorer.js
|
|
|
17
18
|
|
|
18
19
|
export const startDevServer = async ({
|
|
19
20
|
signal = new AbortController().signal,
|
|
20
|
-
handleSIGINT,
|
|
21
|
+
handleSIGINT = true,
|
|
21
22
|
logLevel = "info",
|
|
22
23
|
omegaServerLogLevel = "warn",
|
|
23
24
|
port = 3456,
|
|
@@ -37,10 +38,9 @@ export const startDevServer = async ({
|
|
|
37
38
|
devServerMainFile = getCallerPosition().url,
|
|
38
39
|
// force disable server autoreload when this code is executed:
|
|
39
40
|
// - inside a forked child process
|
|
40
|
-
// -
|
|
41
|
-
//
|
|
41
|
+
// - debugged by vscode
|
|
42
|
+
// otherwise we get net:ERR_CONNECTION_REFUSED
|
|
42
43
|
devServerAutoreload = typeof process.send !== "function" &&
|
|
43
|
-
!parentPort &&
|
|
44
44
|
!process.env.VSCODE_INSPECTOR_OPTIONS,
|
|
45
45
|
clientFiles = {
|
|
46
46
|
"./src/": true,
|
|
@@ -80,28 +80,30 @@ export const startDevServer = async ({
|
|
|
80
80
|
}) => {
|
|
81
81
|
const logger = createLogger({ logLevel })
|
|
82
82
|
rootDirectoryUrl = assertAndNormalizeDirectoryUrl(rootDirectoryUrl)
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
83
|
+
const operation = Abort.startOperation()
|
|
84
|
+
operation.addAbortSignal(signal)
|
|
85
|
+
if (handleSIGINT) {
|
|
86
|
+
operation.addAbortSource((abort) => {
|
|
87
|
+
return raceProcessTeardownEvents(
|
|
88
|
+
{
|
|
89
|
+
SIGINT: true,
|
|
90
|
+
},
|
|
91
|
+
abort,
|
|
92
|
+
)
|
|
93
|
+
})
|
|
94
|
+
}
|
|
95
|
+
if (port === 0) {
|
|
96
|
+
port = await findFreePort(port, { signal: operation.signal })
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const reloadableWorker = createReloadableWorker(devServerMainFile)
|
|
100
|
+
if (devServerAutoreload && reloadableWorker.isPrimary) {
|
|
97
101
|
const devServerFileChangeCallback = ({ relativeUrl, event }) => {
|
|
98
102
|
const url = new URL(relativeUrl, rootDirectoryUrl).href
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
reloadableProcess.reload()
|
|
102
|
-
}
|
|
103
|
+
logger.info(`file ${event} ${url} -> restarting server...`)
|
|
104
|
+
reloadableWorker.reload()
|
|
103
105
|
}
|
|
104
|
-
const
|
|
106
|
+
const stopWatchingDevServerFiles = registerDirectoryLifecycle(
|
|
105
107
|
rootDirectoryUrl,
|
|
106
108
|
{
|
|
107
109
|
watchPatterns: {
|
|
@@ -122,14 +124,16 @@ export const startDevServer = async ({
|
|
|
122
124
|
},
|
|
123
125
|
},
|
|
124
126
|
)
|
|
125
|
-
|
|
126
|
-
|
|
127
|
+
operation.addAbortCallback(() => {
|
|
128
|
+
stopWatchingDevServerFiles()
|
|
129
|
+
reloadableWorker.terminate()
|
|
127
130
|
})
|
|
131
|
+
await reloadableWorker.load()
|
|
128
132
|
return {
|
|
129
133
|
origin: `${protocol}://127.0.0.1:${port}`,
|
|
130
134
|
stop: () => {
|
|
131
|
-
|
|
132
|
-
|
|
135
|
+
stopWatchingDevServerFiles()
|
|
136
|
+
reloadableWorker.terminate()
|
|
133
137
|
},
|
|
134
138
|
}
|
|
135
139
|
}
|
|
@@ -9,10 +9,11 @@ import {
|
|
|
9
9
|
} from "@jsenv/abort"
|
|
10
10
|
import { moveUrl } from "@jsenv/urls"
|
|
11
11
|
import { memoize } from "@jsenv/utils/memoize/memoize.js"
|
|
12
|
-
import { filterV8Coverage } from "@jsenv/utils/coverage/v8_coverage_from_directory.js"
|
|
13
|
-
import { composeTwoFileByFileIstanbulCoverages } from "@jsenv/utils/coverage/istanbul_coverage_composition.js"
|
|
14
12
|
import { escapeRegexpSpecialChars } from "@jsenv/utils/string/escape_regexp_special_chars.js"
|
|
15
13
|
|
|
14
|
+
import { filterV8Coverage } from "@jsenv/core/src/test/coverage/v8_coverage_from_directory.js"
|
|
15
|
+
import { composeTwoFileByFileIstanbulCoverages } from "@jsenv/core/src/test/coverage/istanbul_coverage_composition.js"
|
|
16
|
+
|
|
16
17
|
export const createRuntimeFromPlaywright = ({
|
|
17
18
|
browserName,
|
|
18
19
|
browserVersion,
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/* eslint-env browser */
|
|
2
|
+
|
|
3
|
+
export const createEventSourceConnection = (
|
|
4
|
+
eventSourceUrl,
|
|
5
|
+
events = {},
|
|
6
|
+
{ retryMaxAttempt = Infinity, retryAllocatedMs = Infinity, lastEventId } = {},
|
|
7
|
+
) => {
|
|
8
|
+
const { EventSource } = window
|
|
9
|
+
if (typeof EventSource !== "function") {
|
|
10
|
+
return () => {}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
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
|
|
20
|
+
}
|
|
21
|
+
eventCallback(e)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
const status = {
|
|
27
|
+
value: "default",
|
|
28
|
+
goTo: (value) => {
|
|
29
|
+
if (value === status.value) {
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
status.value = value
|
|
33
|
+
status.onchange()
|
|
34
|
+
},
|
|
35
|
+
onchange: () => {},
|
|
36
|
+
}
|
|
37
|
+
let _disconnect = () => {}
|
|
38
|
+
|
|
39
|
+
const attemptConnection = (url) => {
|
|
40
|
+
const eventSource = new EventSource(url, {
|
|
41
|
+
withCredentials: true,
|
|
42
|
+
})
|
|
43
|
+
_disconnect = () => {
|
|
44
|
+
if (status.value !== "connecting" && status.value !== "connected") {
|
|
45
|
+
console.warn(
|
|
46
|
+
`disconnect() ignored because connection is ${status.value}`,
|
|
47
|
+
)
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
eventSource.onerror = undefined
|
|
51
|
+
eventSource.close()
|
|
52
|
+
Object.keys(events).forEach((eventName) => {
|
|
53
|
+
eventSource.removeEventListener(eventName, events[eventName])
|
|
54
|
+
})
|
|
55
|
+
status.goTo("disconnected")
|
|
56
|
+
}
|
|
57
|
+
let retryCount = 0
|
|
58
|
+
let firstRetryMs = Date.now()
|
|
59
|
+
eventSource.onerror = (errorEvent) => {
|
|
60
|
+
if (errorEvent.target.readyState === EventSource.CONNECTING) {
|
|
61
|
+
if (retryCount > retryMaxAttempt) {
|
|
62
|
+
console.info(`could not connect after ${retryMaxAttempt} attempt`)
|
|
63
|
+
_disconnect()
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (retryCount === 0) {
|
|
68
|
+
firstRetryMs = Date.now()
|
|
69
|
+
} else {
|
|
70
|
+
const allRetryDuration = Date.now() - firstRetryMs
|
|
71
|
+
if (retryAllocatedMs && allRetryDuration > retryAllocatedMs) {
|
|
72
|
+
console.info(
|
|
73
|
+
`could not connect in less than ${retryAllocatedMs} ms`,
|
|
74
|
+
)
|
|
75
|
+
_disconnect()
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
retryCount++
|
|
81
|
+
status.goTo("connecting")
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (errorEvent.target.readyState === EventSource.CLOSED) {
|
|
86
|
+
_disconnect()
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
eventSource.onopen = () => {
|
|
91
|
+
status.goTo("connected")
|
|
92
|
+
}
|
|
93
|
+
Object.keys(events).forEach((eventName) => {
|
|
94
|
+
eventSource.addEventListener(eventName, events[eventName])
|
|
95
|
+
})
|
|
96
|
+
if (!events.hasOwnProperty("welcome")) {
|
|
97
|
+
eventSource.addEventListener("welcome", (e) => {
|
|
98
|
+
if (e.origin === eventSourceOrigin && e.lastEventId) {
|
|
99
|
+
lastEventId = e.lastEventId
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
}
|
|
103
|
+
status.goTo("connecting")
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
let connect = () => {
|
|
107
|
+
attemptConnection(eventSourceUrl)
|
|
108
|
+
connect = () => {
|
|
109
|
+
attemptConnection(
|
|
110
|
+
lastEventId
|
|
111
|
+
? addLastEventIdIntoUrlSearchParams(eventSourceUrl, lastEventId)
|
|
112
|
+
: eventSourceUrl,
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const removePageUnloadListener = listenPageUnload(() => {
|
|
118
|
+
if (status.value === "connecting" || status.value === "connected") {
|
|
119
|
+
_disconnect()
|
|
120
|
+
}
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
const destroy = () => {
|
|
124
|
+
removePageUnloadListener()
|
|
125
|
+
_disconnect()
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
status,
|
|
130
|
+
connect,
|
|
131
|
+
disconnect: () => _disconnect(),
|
|
132
|
+
destroy,
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const addLastEventIdIntoUrlSearchParams = (url, lastEventId) => {
|
|
137
|
+
if (url.indexOf("?") === -1) {
|
|
138
|
+
url += "?"
|
|
139
|
+
} else {
|
|
140
|
+
url += "&"
|
|
141
|
+
}
|
|
142
|
+
return `${url}last-event-id=${encodeURIComponent(lastEventId)}`
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// const listenPageMightFreeze = (callback) => {
|
|
146
|
+
// const removePageHideListener = listenEvent(window, "pagehide", (pageHideEvent) => {
|
|
147
|
+
// if (pageHideEvent.persisted === true) {
|
|
148
|
+
// callback(pageHideEvent)
|
|
149
|
+
// }
|
|
150
|
+
// })
|
|
151
|
+
// return removePageHideListener
|
|
152
|
+
// }
|
|
153
|
+
|
|
154
|
+
// const listenPageFreeze = (callback) => {
|
|
155
|
+
// const removeFreezeListener = listenEvent(document, "freeze", (freezeEvent) => {
|
|
156
|
+
// callback(freezeEvent)
|
|
157
|
+
// })
|
|
158
|
+
// return removeFreezeListener
|
|
159
|
+
// }
|
|
160
|
+
|
|
161
|
+
// const listenPageIsRestored = (callback) => {
|
|
162
|
+
// const removeResumeListener = listenEvent(document, "resume", (resumeEvent) => {
|
|
163
|
+
// removePageshowListener()
|
|
164
|
+
// callback(resumeEvent)
|
|
165
|
+
// })
|
|
166
|
+
// const removePageshowListener = listenEvent(window, "pageshow", (pageshowEvent) => {
|
|
167
|
+
// if (pageshowEvent.persisted === true) {
|
|
168
|
+
// removePageshowListener()
|
|
169
|
+
// removeResumeListener()
|
|
170
|
+
// callback(pageshowEvent)
|
|
171
|
+
// }
|
|
172
|
+
// })
|
|
173
|
+
// return () => {
|
|
174
|
+
// removeResumeListener()
|
|
175
|
+
// removePageshowListener()
|
|
176
|
+
// }
|
|
177
|
+
// }
|
|
178
|
+
|
|
179
|
+
const listenPageUnload = (callback) => {
|
|
180
|
+
const removePageHideListener = listenEvent(
|
|
181
|
+
window,
|
|
182
|
+
"pagehide",
|
|
183
|
+
(pageHideEvent) => {
|
|
184
|
+
if (pageHideEvent.persisted !== true) {
|
|
185
|
+
callback(pageHideEvent)
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
)
|
|
189
|
+
return removePageHideListener
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const listenEvent = (emitter, event, callback) => {
|
|
193
|
+
emitter.addEventListener(event, callback)
|
|
194
|
+
return () => {
|
|
195
|
+
emitter.removeEventListener(event, callback)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { createSSERoom } from "@jsenv/server"
|
|
2
|
+
import { createCallbackListNotifiedOnce } from "@jsenv/abort"
|
|
3
|
+
|
|
4
|
+
export const createSSEService = ({ serverEventCallbackList }) => {
|
|
5
|
+
const destroyCallbackList = createCallbackListNotifiedOnce()
|
|
6
|
+
|
|
7
|
+
const cache = []
|
|
8
|
+
const sseRoomLimit = 100
|
|
9
|
+
const getOrCreateSSERoom = (request) => {
|
|
10
|
+
const htmlFileRelativeUrl = request.ressource.slice(1)
|
|
11
|
+
const cacheEntry = cache.find(
|
|
12
|
+
(cacheEntryCandidate) =>
|
|
13
|
+
cacheEntryCandidate.htmlFileRelativeUrl === htmlFileRelativeUrl,
|
|
14
|
+
)
|
|
15
|
+
if (cacheEntry) {
|
|
16
|
+
return cacheEntry.sseRoom
|
|
17
|
+
}
|
|
18
|
+
const sseRoom = createSSERoom({
|
|
19
|
+
retryDuration: 2000,
|
|
20
|
+
historyLength: 100,
|
|
21
|
+
welcomeEventEnabled: true,
|
|
22
|
+
effect: () => {
|
|
23
|
+
return serverEventCallbackList.add((event) => {
|
|
24
|
+
sseRoom.sendEvent(event)
|
|
25
|
+
})
|
|
26
|
+
},
|
|
27
|
+
})
|
|
28
|
+
const removeSSECleanupCallback = destroyCallbackList.add(() => {
|
|
29
|
+
removeSSECleanupCallback()
|
|
30
|
+
sseRoom.close()
|
|
31
|
+
})
|
|
32
|
+
cache.push({
|
|
33
|
+
htmlFileRelativeUrl,
|
|
34
|
+
sseRoom,
|
|
35
|
+
cleanup: () => {
|
|
36
|
+
removeSSECleanupCallback()
|
|
37
|
+
sseRoom.close()
|
|
38
|
+
},
|
|
39
|
+
})
|
|
40
|
+
if (cache.length >= sseRoomLimit) {
|
|
41
|
+
const firstCacheEntry = cache.shift()
|
|
42
|
+
firstCacheEntry.cleanup()
|
|
43
|
+
}
|
|
44
|
+
return sseRoom
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
getOrCreateSSERoom,
|
|
49
|
+
destroy: () => {
|
|
50
|
+
destroyCallbackList.notify()
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url"
|
|
2
|
+
import { Worker, workerData } from "node:worker_threads"
|
|
3
|
+
|
|
4
|
+
// https://nodejs.org/api/worker_threads.html
|
|
5
|
+
export const createReloadableWorker = (workerFileUrl, options = {}) => {
|
|
6
|
+
const workerFilePath = fileURLToPath(workerFileUrl)
|
|
7
|
+
const isPrimary = !workerData || workerData.workerFilePath !== workerFilePath
|
|
8
|
+
let worker
|
|
9
|
+
|
|
10
|
+
const terminate = async () => {
|
|
11
|
+
if (worker) {
|
|
12
|
+
let _worker = worker
|
|
13
|
+
worker = null
|
|
14
|
+
const exitPromise = new Promise((resolve) => {
|
|
15
|
+
_worker.once("exit", resolve)
|
|
16
|
+
})
|
|
17
|
+
_worker.terminate()
|
|
18
|
+
await exitPromise
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const load = async () => {
|
|
23
|
+
if (!isPrimary) {
|
|
24
|
+
throw new Error(`worker can be loaded from primary file only`)
|
|
25
|
+
}
|
|
26
|
+
worker = new Worker(workerFilePath, {
|
|
27
|
+
...options,
|
|
28
|
+
workerData: {
|
|
29
|
+
...options.workerData,
|
|
30
|
+
workerFilePath,
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
worker.once("error", (error) => {
|
|
35
|
+
console.error(error)
|
|
36
|
+
})
|
|
37
|
+
worker.once("exit", () => {
|
|
38
|
+
worker = null
|
|
39
|
+
})
|
|
40
|
+
await new Promise((resolve) => {
|
|
41
|
+
worker.once("online", resolve)
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const reload = async () => {
|
|
46
|
+
await terminate()
|
|
47
|
+
await load()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
isPrimary,
|
|
52
|
+
load,
|
|
53
|
+
reload,
|
|
54
|
+
terminate,
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createEventSourceConnection } from "@jsenv/
|
|
1
|
+
import { createEventSourceConnection } from "@jsenv/core/src/helpers/event_source/event_source.js"
|
|
2
2
|
import { urlHotMetas } from "../../../import_meta_hot/client/import_meta_hot.js"
|
|
3
3
|
import {
|
|
4
4
|
isAutoreloadEnabled,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { urlToRelativeUrl } from "@jsenv/urls"
|
|
2
2
|
import { createCallbackList } from "@jsenv/abort"
|
|
3
3
|
|
|
4
|
-
import { createSSEService } from "@jsenv/
|
|
4
|
+
import { createSSEService } from "@jsenv/core/src/helpers/event_source/sse_service.js"
|
|
5
5
|
|
|
6
6
|
export const jsenvPluginDevSSEServer = ({
|
|
7
7
|
rootDirectoryUrl,
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { URL_META } from "@jsenv/url-meta"
|
|
2
|
+
import { fileSystemPathToUrl } from "@jsenv/urls"
|
|
3
|
+
|
|
4
|
+
import { requireFromJsenv } from "@jsenv/core/src/require_from_jsenv.js"
|
|
5
|
+
|
|
6
|
+
// https://github.com/istanbuljs/babel-plugin-istanbul/blob/321740f7b25d803f881466ea819d870f7ed6a254/src/index.js
|
|
7
|
+
|
|
8
|
+
export const babelPluginInstrument = (
|
|
9
|
+
api,
|
|
10
|
+
{
|
|
11
|
+
rootDirectoryUrl,
|
|
12
|
+
useInlineSourceMaps = false,
|
|
13
|
+
coverageConfig = { "./**/*": true },
|
|
14
|
+
},
|
|
15
|
+
) => {
|
|
16
|
+
const { programVisitor } = requireFromJsenv("istanbul-lib-instrument")
|
|
17
|
+
|
|
18
|
+
const { types } = api
|
|
19
|
+
|
|
20
|
+
const associations = URL_META.resolveAssociations(
|
|
21
|
+
{ cover: coverageConfig },
|
|
22
|
+
rootDirectoryUrl,
|
|
23
|
+
)
|
|
24
|
+
const shouldInstrument = (url) => {
|
|
25
|
+
return URL_META.applyAssociations({ url, associations }).cover
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
name: "transform-instrument",
|
|
30
|
+
visitor: {
|
|
31
|
+
Program: {
|
|
32
|
+
enter(path) {
|
|
33
|
+
const { file } = this
|
|
34
|
+
const { opts } = file
|
|
35
|
+
if (!opts.sourceFileName) {
|
|
36
|
+
console.warn(
|
|
37
|
+
`cannot instrument file when "sourceFileName" option is not set`,
|
|
38
|
+
)
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
const fileUrl = fileSystemPathToUrl(opts.sourceFileName)
|
|
42
|
+
if (!shouldInstrument(fileUrl)) {
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
this.__dv__ = null
|
|
47
|
+
|
|
48
|
+
let inputSourceMap
|
|
49
|
+
|
|
50
|
+
if (useInlineSourceMaps) {
|
|
51
|
+
// https://github.com/istanbuljs/babel-plugin-istanbul/commit/a9e15643d249a2985e4387e4308022053b2cd0ad#diff-1fdf421c05c1140f6d71444ea2b27638R65
|
|
52
|
+
inputSourceMap =
|
|
53
|
+
opts.inputSourceMap || file.inputMap
|
|
54
|
+
? file.inputMap.sourcemap
|
|
55
|
+
: null
|
|
56
|
+
} else {
|
|
57
|
+
inputSourceMap = opts.inputSourceMap
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
this.__dv__ = programVisitor(
|
|
61
|
+
types,
|
|
62
|
+
opts.filenameRelative || opts.filename,
|
|
63
|
+
{
|
|
64
|
+
coverageVariable: "__coverage__",
|
|
65
|
+
inputSourceMap,
|
|
66
|
+
},
|
|
67
|
+
)
|
|
68
|
+
this.__dv__.enter(path)
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
exit(path) {
|
|
72
|
+
if (!this.__dv__) {
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
const object = this.__dv__.exit(path)
|
|
76
|
+
// object got two properties: fileCoverage and sourceMappingURL
|
|
77
|
+
this.file.metadata.coverage = object.fileCoverage
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
}
|
|
82
|
+
}
|