@jsenv/core 27.0.0-alpha.82 → 27.0.0-alpha.85
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 +208 -4
- package/dist/js/s.js +2 -2
- package/dist/main.js +1430 -615
- package/dist/s.js +2 -2
- package/dist/s.js.map +2 -1
- package/package.json +6 -2
- package/src/build/build.js +5 -8
- package/src/build/build_urls_generator.js +1 -2
- package/src/build/inject_global_version_mappings.js +4 -4
- package/src/build/inject_service_worker_urls.js +2 -2
- package/src/build/resync_ressource_hints.js +17 -18
- package/src/build/start_build_server.js +33 -26
- package/src/dev/plugins/explorer/jsenv_plugin_explorer.js +1 -2
- package/src/dev/plugins/toolbar/client/util/fetching.js +1 -1
- package/src/dev/plugins/toolbar/jsenv_plugin_toolbar.js +3 -3
- package/src/dev/start_dev_server.js +38 -30
- package/src/execute/runtimes/browsers/from_playwright.js +5 -4
- package/src/execute/runtimes/node/node_process.js +2 -2
- package/src/helpers/command/command.js +73 -0
- 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 +57 -0
- package/src/omega/compat/runtime_compat.js +2 -1
- package/src/omega/kitchen.js +4 -1
- package/src/omega/server/user_agent.js +2 -1
- package/src/omega/url_graph/sort_by_dependencies.js +27 -0
- package/src/omega/url_graph/url_info_transformations.js +24 -14
- package/src/plugins/autoreload/dev_sse/client/event_source_client.js +1 -1
- package/src/plugins/autoreload/dev_sse/client/reload.js +6 -3
- package/src/plugins/autoreload/dev_sse/jsenv_plugin_dev_sse_client.js +3 -3
- package/src/plugins/autoreload/dev_sse/jsenv_plugin_dev_sse_server.js +1 -1
- package/src/plugins/bundling/css/bundle_css.js +4 -4
- package/src/plugins/bundling/js_module/bundle_js_module.js +86 -67
- package/src/plugins/commonjs_globals/jsenv_plugin_commonjs_globals.js +2 -2
- package/src/plugins/file_urls/jsenv_plugin_file_urls.js +4 -5
- package/src/plugins/html_supervisor/jsenv_plugin_html_supervisor.js +62 -74
- package/src/plugins/import_meta_hot/html_hot_dependencies.js +9 -15
- package/src/plugins/import_meta_hot/jsenv_plugin_import_meta_hot.js +3 -3
- package/src/plugins/import_meta_scenarios/jsenv_plugin_import_meta_scenarios.js +2 -2
- package/src/plugins/importmap/jsenv_plugin_importmap.js +25 -27
- package/src/plugins/inject_globals/inject_globals.js +4 -4
- package/src/plugins/inline/jsenv_plugin_data_urls.js +1 -1
- package/src/plugins/inline/jsenv_plugin_html_inline_content.js +41 -43
- package/src/plugins/inline/jsenv_plugin_js_inline_content.js +4 -4
- package/src/plugins/minification/css/minify_css.js +1 -1
- package/src/plugins/transpilation/as_js_classic/client/s.js +2 -2
- package/src/plugins/transpilation/as_js_classic/jsenv_plugin_as_js_classic.js +2 -4
- package/src/plugins/transpilation/as_js_classic/jsenv_plugin_as_js_classic_html.js +45 -67
- package/src/plugins/transpilation/babel/global_this/babel_plugin_global_this_as_jsenv_import.js +2 -3
- package/src/plugins/transpilation/babel/helpers/babel_plugin_babel_helpers_as_jsenv_imports.js +3 -4
- package/src/plugins/transpilation/babel/jsenv_plugin_babel.js +1 -1
- package/src/plugins/transpilation/babel/new_stylesheet/babel_plugin_new_stylesheet_as_jsenv_import.js +2 -3
- package/src/plugins/transpilation/babel/regenerator_runtime/babel_plugin_regenerator_runtime_as_jsenv_import.js +2 -3
- package/src/plugins/transpilation/css_parcel/jsenv_plugin_css_parcel.js +1 -1
- package/src/plugins/transpilation/import_assertions/jsenv_plugin_import_assertions.js +1 -1
- package/src/plugins/transpilation/jsenv_plugin_top_level_await.js +2 -1
- package/src/plugins/url_analysis/css/css_urls.js +2 -3
- package/src/plugins/url_analysis/html/html_urls.js +98 -113
- package/src/plugins/url_analysis/js/js_urls.js +3 -2
- 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 +25 -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
|
@@ -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,20 @@ export const startDevServer = async ({
|
|
|
122
124
|
},
|
|
123
125
|
},
|
|
124
126
|
)
|
|
125
|
-
|
|
126
|
-
|
|
127
|
+
operation.addAbortCallback(() => {
|
|
128
|
+
stopWatchingDevServerFiles()
|
|
129
|
+
reloadableWorker.terminate()
|
|
127
130
|
})
|
|
131
|
+
|
|
132
|
+
const worker = await reloadableWorker.load()
|
|
133
|
+
if (!keepProcessAlive) {
|
|
134
|
+
worker.unref()
|
|
135
|
+
}
|
|
128
136
|
return {
|
|
129
137
|
origin: `${protocol}://127.0.0.1:${port}`,
|
|
130
138
|
stop: () => {
|
|
131
|
-
|
|
132
|
-
|
|
139
|
+
stopWatchingDevServerFiles()
|
|
140
|
+
reloadableWorker.terminate()
|
|
133
141
|
},
|
|
134
142
|
}
|
|
135
143
|
}
|
|
@@ -8,10 +8,11 @@ import {
|
|
|
8
8
|
raceCallbacks,
|
|
9
9
|
} from "@jsenv/abort"
|
|
10
10
|
import { moveUrl } from "@jsenv/urls"
|
|
11
|
-
import { memoize } from "@jsenv/utils/memoize/memoize.js"
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
import {
|
|
11
|
+
import { memoize } from "@jsenv/utils/src/memoize/memoize.js"
|
|
12
|
+
import { escapeRegexpSpecialChars } from "@jsenv/utils/src/string/escape_regexp_special_chars.js"
|
|
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"
|
|
15
16
|
|
|
16
17
|
export const createRuntimeFromPlaywright = ({
|
|
17
18
|
browserName,
|
|
@@ -5,10 +5,10 @@ import {
|
|
|
5
5
|
createCallbackListNotifiedOnce,
|
|
6
6
|
} from "@jsenv/abort"
|
|
7
7
|
import { uneval } from "@jsenv/uneval"
|
|
8
|
-
|
|
9
8
|
import { urlToFileSystemPath } from "@jsenv/urls"
|
|
10
9
|
import { createDetailedMessage } from "@jsenv/log"
|
|
11
|
-
import { memoize } from "@jsenv/utils/memoize/memoize.js"
|
|
10
|
+
import { memoize } from "@jsenv/utils/src/memoize/memoize.js"
|
|
11
|
+
|
|
12
12
|
import { createChildExecOptions } from "./child_exec_options.js"
|
|
13
13
|
import { ExecOptions } from "./exec_options.js"
|
|
14
14
|
import { killProcessTree } from "./kill_process_tree.js"
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { exec } from "node:child_process"
|
|
2
|
+
import { createDetailedMessage, createLogger, UNICODE } from "@jsenv/log"
|
|
3
|
+
|
|
4
|
+
export const executeCommand = (
|
|
5
|
+
command,
|
|
6
|
+
{
|
|
7
|
+
logLevel = "info",
|
|
8
|
+
signal = new AbortController().signal,
|
|
9
|
+
onStdout = () => {},
|
|
10
|
+
onStderr = () => {},
|
|
11
|
+
cwd,
|
|
12
|
+
env,
|
|
13
|
+
timeout,
|
|
14
|
+
} = {},
|
|
15
|
+
) => {
|
|
16
|
+
const logger = createLogger({ logLevel })
|
|
17
|
+
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
logger.debug(`${UNICODE.COMMAND} ${command}`)
|
|
20
|
+
const commandProcess = exec(command, {
|
|
21
|
+
signal,
|
|
22
|
+
cwd:
|
|
23
|
+
cwd && typeof cwd === "string" && cwd.startsWith("file:")
|
|
24
|
+
? new URL(cwd)
|
|
25
|
+
: cwd,
|
|
26
|
+
env,
|
|
27
|
+
timeout,
|
|
28
|
+
silent: true,
|
|
29
|
+
})
|
|
30
|
+
commandProcess.on("error", (error) => {
|
|
31
|
+
if (error && error.code === "ETIMEDOUT") {
|
|
32
|
+
logger.error(`timeout after ${timeout} ms`)
|
|
33
|
+
reject(error)
|
|
34
|
+
} else {
|
|
35
|
+
reject(error)
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
const stdoutDatas = []
|
|
39
|
+
commandProcess.stdout.on("data", (data) => {
|
|
40
|
+
stdoutDatas.push(data)
|
|
41
|
+
logger.debug(data)
|
|
42
|
+
onStderr(data)
|
|
43
|
+
})
|
|
44
|
+
let stderrDatas = []
|
|
45
|
+
commandProcess.stderr.on("data", (data) => {
|
|
46
|
+
stderrDatas.push(data)
|
|
47
|
+
logger.debug(data)
|
|
48
|
+
onStdout(data)
|
|
49
|
+
})
|
|
50
|
+
if (commandProcess.stdin) {
|
|
51
|
+
commandProcess.stdin.on("error", (error) => {
|
|
52
|
+
reject(error)
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
commandProcess.on("exit", (exitCode, signal) => {
|
|
56
|
+
if (signal) {
|
|
57
|
+
reject(new Error(`killed with ${signal}`))
|
|
58
|
+
}
|
|
59
|
+
if (exitCode) {
|
|
60
|
+
reject(
|
|
61
|
+
new Error(
|
|
62
|
+
createDetailedMessage(`failed with exit code ${exitCode}`, {
|
|
63
|
+
"command stderr": stderrDatas.join(""),
|
|
64
|
+
// "command stdout": stdoutDatas.join(""),
|
|
65
|
+
}),
|
|
66
|
+
),
|
|
67
|
+
)
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
resolve({ exitCode, signal })
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
}
|
|
@@ -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,57 @@
|
|
|
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
|
+
await new Promise((resolve) => {
|
|
38
|
+
worker.once("online", resolve)
|
|
39
|
+
})
|
|
40
|
+
worker.once("exit", () => {
|
|
41
|
+
worker = null
|
|
42
|
+
})
|
|
43
|
+
return worker
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const reload = async () => {
|
|
47
|
+
await terminate()
|
|
48
|
+
await load()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
isPrimary,
|
|
53
|
+
load,
|
|
54
|
+
reload,
|
|
55
|
+
terminate,
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { findHighestVersion } from "@jsenv/utils/semantic_versioning/highest_version.js"
|
|
1
|
+
import { findHighestVersion } from "@jsenv/utils/src/semantic_versioning/highest_version.js"
|
|
2
|
+
|
|
2
3
|
import { featureCompats } from "./features_compats.js"
|
|
3
4
|
|
|
4
5
|
export const RUNTIME_COMPAT = {
|
package/src/omega/kitchen.js
CHANGED
|
@@ -8,7 +8,8 @@ import {
|
|
|
8
8
|
} from "@jsenv/urls"
|
|
9
9
|
import { writeFileSync, ensureWindowsDriveLetter } from "@jsenv/filesystem"
|
|
10
10
|
import { createDetailedMessage } from "@jsenv/log"
|
|
11
|
-
import { CONTENT_TYPE } from "@jsenv/utils/content_type/content_type.js"
|
|
11
|
+
import { CONTENT_TYPE } from "@jsenv/utils/src/content_type/content_type.js"
|
|
12
|
+
|
|
12
13
|
import { createPluginController } from "../plugins/plugin_controller.js"
|
|
13
14
|
import { urlSpecifierEncoding } from "./url_specifier_encoding.js"
|
|
14
15
|
import { createUrlInfoTransformer } from "./url_graph/url_info_transformations.js"
|
|
@@ -711,6 +712,8 @@ const memoizeCook = (cook) => {
|
|
|
711
712
|
const applyReferenceEffectsOnUrlInfo = (reference, urlInfo, context) => {
|
|
712
713
|
if (reference.shouldHandle) {
|
|
713
714
|
urlInfo.shouldHandle = true
|
|
715
|
+
} else {
|
|
716
|
+
urlInfo.shouldHandle = false
|
|
714
717
|
}
|
|
715
718
|
urlInfo.originalUrl = urlInfo.originalUrl || reference.url
|
|
716
719
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { memoizeByFirstArgument } from "@jsenv/utils/memoize/memoize_by_first_argument.js"
|
|
1
|
+
import { memoizeByFirstArgument } from "@jsenv/utils/src/memoize/memoize_by_first_argument.js"
|
|
2
|
+
|
|
2
3
|
import { requireFromJsenv } from "@jsenv/core/src/require_from_jsenv.js"
|
|
3
4
|
|
|
4
5
|
export const parseUserAgentHeader = memoizeByFirstArgument((userAgent) => {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export const sortByDependencies = (nodes) => {
|
|
2
|
+
const visited = []
|
|
3
|
+
const sorted = []
|
|
4
|
+
const circular = []
|
|
5
|
+
const visit = (url) => {
|
|
6
|
+
const isSorted = sorted.includes(url)
|
|
7
|
+
if (isSorted) {
|
|
8
|
+
return
|
|
9
|
+
}
|
|
10
|
+
const isVisited = visited.includes(url)
|
|
11
|
+
if (isVisited) {
|
|
12
|
+
circular.push(url)
|
|
13
|
+
sorted.push(url)
|
|
14
|
+
} else {
|
|
15
|
+
visited.push(url)
|
|
16
|
+
nodes[url].dependencies.forEach((dependencyUrl) => {
|
|
17
|
+
visit(dependencyUrl, url)
|
|
18
|
+
})
|
|
19
|
+
sorted.push(url)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
Object.keys(nodes).forEach((url) => {
|
|
23
|
+
visit(url)
|
|
24
|
+
})
|
|
25
|
+
sorted.circular = circular
|
|
26
|
+
return sorted
|
|
27
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
+
import { pathToFileURL } from "node:url"
|
|
1
2
|
import { bufferToEtag } from "@jsenv/filesystem"
|
|
2
|
-
import { urlToRelativeUrl } from "@jsenv/urls"
|
|
3
|
-
|
|
4
|
-
import { composeTwoSourcemaps } from "@jsenv/utils/sourcemap/sourcemap_composition_v3.js"
|
|
3
|
+
import { urlToRelativeUrl, isFileSystemPath } from "@jsenv/urls"
|
|
5
4
|
import {
|
|
5
|
+
composeTwoSourcemaps,
|
|
6
6
|
SOURCEMAP,
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
} from "@jsenv/
|
|
7
|
+
generateSourcemapFileUrl,
|
|
8
|
+
generateSourcemapDataUrl,
|
|
9
|
+
} from "@jsenv/sourcemap"
|
|
10
10
|
|
|
11
11
|
export const createUrlInfoTransformer = ({
|
|
12
12
|
logger,
|
|
@@ -23,18 +23,25 @@ export const createUrlInfoTransformer = ({
|
|
|
23
23
|
sourcemaps === "programmatic"
|
|
24
24
|
|
|
25
25
|
const normalizeSourcemap = (urlInfo, sourcemap) => {
|
|
26
|
+
let { sources } = sourcemap
|
|
27
|
+
if (sources) {
|
|
28
|
+
sources = sources.map((source) => {
|
|
29
|
+
if (source && isFileSystemPath(source)) {
|
|
30
|
+
return String(pathToFileURL(source))
|
|
31
|
+
}
|
|
32
|
+
return source
|
|
33
|
+
})
|
|
34
|
+
}
|
|
26
35
|
const wantSourcesContent =
|
|
27
36
|
// for inline content (<script> insdide html)
|
|
28
37
|
// chrome won't be able to fetch the file as it does not exists
|
|
29
38
|
// so sourcemap must contain sources
|
|
30
39
|
sourcemapsSourcesContent ||
|
|
31
40
|
urlInfo.isInline ||
|
|
32
|
-
(
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (sourcemap.sources && sourcemap.sources.length > 1) {
|
|
37
|
-
sourcemap.sources = sourcemap.sources.map(
|
|
41
|
+
(sources &&
|
|
42
|
+
sources.some((source) => !source || !source.startsWith("file:")))
|
|
43
|
+
if (sources && sources.length > 1) {
|
|
44
|
+
sourcemap.sources = sources.map(
|
|
38
45
|
(source) => new URL(source, urlInfo.originalUrl).href,
|
|
39
46
|
)
|
|
40
47
|
if (!wantSourcesContent) {
|
|
@@ -65,7 +72,9 @@ export const createUrlInfoTransformer = ({
|
|
|
65
72
|
// when jsenv is done cooking the file
|
|
66
73
|
// during build it's urlInfo.url to be inside the build
|
|
67
74
|
// but otherwise it's generatedUrl to be inside .jsenv/ directory
|
|
68
|
-
urlInfo.sourcemapGeneratedUrl =
|
|
75
|
+
urlInfo.sourcemapGeneratedUrl = generateSourcemapFileUrl(
|
|
76
|
+
urlInfo.generatedUrl,
|
|
77
|
+
)
|
|
69
78
|
const [sourcemapReference, sourcemapUrlInfo] = injectSourcemapPlaceholder({
|
|
70
79
|
urlInfo,
|
|
71
80
|
specifier: urlInfo.sourcemapGeneratedUrl,
|
|
@@ -154,7 +163,8 @@ export const createUrlInfoTransformer = ({
|
|
|
154
163
|
}
|
|
155
164
|
sourcemapUrlInfo.content = JSON.stringify(sourcemap, null, " ")
|
|
156
165
|
if (sourcemaps === "inline") {
|
|
157
|
-
sourcemapReference.generatedSpecifier =
|
|
166
|
+
sourcemapReference.generatedSpecifier =
|
|
167
|
+
generateSourcemapDataUrl(sourcemap)
|
|
158
168
|
}
|
|
159
169
|
if (sourcemaps === "file" || sourcemaps === "inline") {
|
|
160
170
|
urlInfo.content = SOURCEMAP.writeComment({
|
|
@@ -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,
|