@jsenv/core 27.3.3 → 27.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/js/autoreload.js +359 -0
- package/dist/js/execute_using_dynamic_import.js +1 -1
- package/dist/js/html_supervisor_installer.js +524 -147
- package/dist/js/html_supervisor_setup.js +3 -4
- package/dist/js/new_stylesheet.js +26 -58
- package/dist/js/server_events_client.js +307 -0
- package/dist/main.js +7699 -7307
- package/package.json +15 -15
- package/{README.md → readme.md} +18 -7
- package/src/build/build.js +16 -18
- package/src/build/start_build_server.js +24 -28
- package/src/dev/start_dev_server.js +30 -94
- package/src/execute/execute.js +17 -35
- package/src/execute/run.js +2 -0
- package/src/omega/errors.js +43 -9
- package/src/omega/kitchen.js +42 -25
- package/src/omega/omega_server.js +96 -74
- package/src/omega/server/file_service.js +256 -28
- package/src/omega/url_graph.js +39 -20
- package/src/plugins/autoreload/client/autoreload.js +201 -0
- package/src/plugins/autoreload/{dev_sse/client → client}/autoreload_preference.js +0 -0
- package/src/plugins/autoreload/{dev_sse/client → client}/reload.js +29 -10
- package/src/plugins/autoreload/{dev_sse/client → client}/url_helpers.js +0 -0
- package/src/plugins/autoreload/jsenv_plugin_autoreload.js +4 -8
- package/src/plugins/autoreload/{dev_sse/jsenv_plugin_dev_sse_client.js → jsenv_plugin_autoreload_client.js} +8 -8
- package/src/plugins/autoreload/jsenv_plugin_autoreload_server.js +196 -0
- package/src/{dev/plugins → plugins}/explorer/client/explorer.html +0 -0
- package/src/{dev/plugins → plugins}/explorer/client/jsenv.png +0 -0
- package/src/{dev/plugins → plugins}/explorer/jsenv_plugin_explorer.js +1 -3
- package/src/plugins/html_supervisor/client/error_overlay.js +401 -0
- package/src/plugins/html_supervisor/client/html_supervisor_installer.js +138 -23
- package/src/plugins/html_supervisor/client/html_supervisor_setup.js +3 -4
- package/src/plugins/html_supervisor/jsenv_plugin_html_supervisor.js +55 -23
- package/src/plugins/inline/jsenv_plugin_html_inline_content.js +97 -117
- package/src/plugins/node_esm_resolution/jsenv_plugin_node_esm_resolution.js +66 -58
- package/src/plugins/plugin_controller.js +102 -67
- package/src/plugins/plugins.js +10 -10
- package/src/{helpers/event_source/event_source.js → plugins/server_events/client/event_source_connection.js} +125 -33
- package/src/plugins/server_events/client/server_events_client.js +17 -0
- package/src/plugins/server_events/jsenv_plugin_server_events_client_injection.js +48 -0
- package/src/plugins/server_events/server_events_dispatcher.js +69 -0
- package/src/{dev/plugins → plugins}/toolbar/client/animation/toolbar_animation.js +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/eventsource/eventsource.css +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/eventsource/toolbar_eventsource.js +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/execution/execution.css +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/execution/toolbar_execution.js +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/focus/focus.css +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/focus/toolbar_focus.js +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/jsenv_logo.svg +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/notification/toolbar_notification.js +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/responsive/overflow_menu.css +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/responsive/toolbar_responsive.js +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/settings/settings.css +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/settings/toolbar_settings.js +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/theme/jsenv_theme.css +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/theme/light_theme.css +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/theme/toolbar_theme.js +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/toolbar.html +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/toolbar_injector.js +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/toolbar_main.css +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/toolbar_main.js +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/tooltip/tooltip.css +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/tooltip/tooltip.js +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/util/animation.js +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/util/dom.js +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/util/fetch_using_xhr.js +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/util/fetching.js +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/util/iframe_to_parent_href.js +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/util/jsenv_logger.js +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/util/preferences.js +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/util/responsive.js +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/util/util.js +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/variant/variant.js +0 -0
- package/src/{dev/plugins → plugins}/toolbar/jsenv_plugin_toolbar.js +0 -0
- package/src/plugins/transpilation/as_js_classic/jsenv_plugin_as_js_classic_html.js +4 -3
- package/src/plugins/transpilation/babel/new_stylesheet/client/new_stylesheet.js +25 -55
- package/src/plugins/transpilation/import_assertions/jsenv_plugin_import_assertions.js +44 -24
- package/src/plugins/transpilation/jsenv_plugin_transpilation.js +6 -1
- package/src/plugins/url_analysis/html/html_urls.js +8 -8
- package/src/plugins/url_analysis/jsenv_plugin_url_analysis.js +3 -1
- package/src/test/execute_plan.js +36 -54
- package/src/test/execute_test_plan.js +2 -2
- package/src/test/logs_file_execution.js +60 -27
- package/src/test/logs_file_execution.test.mjs +41 -0
- package/dist/js/event_source_client.js +0 -528
- package/src/helpers/event_source/sse_service.js +0 -53
- package/src/plugins/autoreload/dev_sse/client/event_source_client.js +0 -193
- package/src/plugins/autoreload/dev_sse/jsenv_plugin_dev_sse_server.js +0 -203
- package/src/plugins/html_supervisor/client/error_in_document.js +0 -198
|
@@ -3,43 +3,242 @@ import {
|
|
|
3
3
|
serveDirectory,
|
|
4
4
|
composeTwoResponses,
|
|
5
5
|
} from "@jsenv/server"
|
|
6
|
+
import { registerDirectoryLifecycle } from "@jsenv/filesystem"
|
|
6
7
|
import { urlIsInsideOf, moveUrl } from "@jsenv/urls"
|
|
8
|
+
import { URL_META } from "@jsenv/url-meta"
|
|
7
9
|
|
|
10
|
+
import { getCorePlugins } from "@jsenv/core/src/plugins/plugins.js"
|
|
11
|
+
import { createUrlGraph } from "@jsenv/core/src/omega/url_graph.js"
|
|
12
|
+
import { createKitchen } from "@jsenv/core/src/omega/kitchen.js"
|
|
13
|
+
import { createServerEventsDispatcher } from "@jsenv/core/src/plugins/server_events/server_events_dispatcher.js"
|
|
14
|
+
import { jsenvPluginServerEventsClientInjection } from "@jsenv/core/src/plugins/server_events/jsenv_plugin_server_events_client_injection.js"
|
|
8
15
|
import { parseUserAgentHeader } from "./user_agent.js"
|
|
9
16
|
|
|
10
17
|
export const createFileService = ({
|
|
18
|
+
signal,
|
|
19
|
+
logLevel,
|
|
20
|
+
serverStopCallbacks,
|
|
21
|
+
|
|
11
22
|
rootDirectoryUrl,
|
|
12
|
-
urlGraph,
|
|
13
|
-
kitchen,
|
|
14
23
|
scenario,
|
|
24
|
+
runtimeCompat,
|
|
25
|
+
|
|
26
|
+
plugins,
|
|
27
|
+
urlAnalysis,
|
|
28
|
+
htmlSupervisor,
|
|
29
|
+
nodeEsmResolution,
|
|
30
|
+
fileSystemMagicResolution,
|
|
31
|
+
transpilation,
|
|
32
|
+
clientAutoreload,
|
|
33
|
+
clientFiles,
|
|
34
|
+
cooldownBetweenFileEvents,
|
|
35
|
+
explorer,
|
|
36
|
+
sourcemaps,
|
|
37
|
+
writeGeneratedFiles,
|
|
15
38
|
}) => {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
39
|
+
const jsenvDirectoryUrl = new URL(".jsenv/", rootDirectoryUrl).href
|
|
40
|
+
const onErrorWhileServingFileCallbacks = []
|
|
41
|
+
const onErrorWhileServingFile = (data) => {
|
|
42
|
+
onErrorWhileServingFileCallbacks.forEach(
|
|
43
|
+
(onErrorWhileServingFileCallback) => {
|
|
44
|
+
onErrorWhileServingFileCallback(data)
|
|
45
|
+
},
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const clientFileChangeCallbackList = []
|
|
50
|
+
const clientFilesPruneCallbackList = []
|
|
51
|
+
const clientFileChangeCallback = ({ relativeUrl, event }) => {
|
|
52
|
+
const url = new URL(relativeUrl, rootDirectoryUrl).href
|
|
53
|
+
clientFileChangeCallbackList.forEach((callback) => {
|
|
54
|
+
callback({ url, event })
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
const clientFilePatterns = {
|
|
58
|
+
...clientFiles,
|
|
59
|
+
".jsenv/": false,
|
|
22
60
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
61
|
+
|
|
62
|
+
if (scenario === "dev") {
|
|
63
|
+
const stopWatchingClientFiles = registerDirectoryLifecycle(
|
|
64
|
+
rootDirectoryUrl,
|
|
65
|
+
{
|
|
66
|
+
watchPatterns: clientFilePatterns,
|
|
67
|
+
cooldownBetweenFileEvents,
|
|
68
|
+
keepProcessAlive: false,
|
|
69
|
+
recursive: true,
|
|
70
|
+
added: ({ relativeUrl }) => {
|
|
71
|
+
clientFileChangeCallback({ event: "added", relativeUrl })
|
|
72
|
+
},
|
|
73
|
+
updated: ({ relativeUrl }) => {
|
|
74
|
+
clientFileChangeCallback({ event: "modified", relativeUrl })
|
|
75
|
+
},
|
|
76
|
+
removed: ({ relativeUrl }) => {
|
|
77
|
+
clientFileChangeCallback({ event: "removed", relativeUrl })
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
)
|
|
81
|
+
serverStopCallbacks.push(stopWatchingClientFiles)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const contextCache = new Map()
|
|
85
|
+
const getOrCreateContext = (request) => {
|
|
86
|
+
const { runtimeName, runtimeVersion } = parseUserAgentHeader(
|
|
87
|
+
request.headers["user-agent"],
|
|
88
|
+
)
|
|
89
|
+
const runtimeId = `${runtimeName}@${runtimeVersion}`
|
|
90
|
+
const existingContext = contextCache.get(runtimeId)
|
|
91
|
+
if (existingContext) {
|
|
92
|
+
return existingContext
|
|
93
|
+
}
|
|
94
|
+
const watchAssociations = URL_META.resolveAssociations(
|
|
95
|
+
{ watch: clientFilePatterns },
|
|
96
|
+
rootDirectoryUrl,
|
|
97
|
+
)
|
|
98
|
+
const urlGraph = createUrlGraph({
|
|
99
|
+
clientFileChangeCallbackList,
|
|
100
|
+
clientFilesPruneCallbackList,
|
|
101
|
+
onCreateUrlInfo: (urlInfo) => {
|
|
102
|
+
const { watch } = URL_META.applyAssociations({
|
|
103
|
+
url: urlInfo.url,
|
|
104
|
+
associations: watchAssociations,
|
|
105
|
+
})
|
|
106
|
+
urlInfo.isValid = () => watch
|
|
107
|
+
},
|
|
108
|
+
includeOriginalUrls: scenario === "dev",
|
|
109
|
+
})
|
|
110
|
+
const kitchen = createKitchen({
|
|
111
|
+
signal,
|
|
112
|
+
logLevel,
|
|
113
|
+
rootDirectoryUrl,
|
|
114
|
+
scenario,
|
|
115
|
+
runtimeCompat,
|
|
116
|
+
urlGraph,
|
|
117
|
+
plugins: [
|
|
118
|
+
...plugins,
|
|
119
|
+
...getCorePlugins({
|
|
120
|
+
rootDirectoryUrl,
|
|
121
|
+
scenario,
|
|
122
|
+
runtimeCompat,
|
|
123
|
+
|
|
124
|
+
urlAnalysis,
|
|
125
|
+
htmlSupervisor,
|
|
126
|
+
nodeEsmResolution,
|
|
127
|
+
fileSystemMagicResolution,
|
|
128
|
+
transpilation,
|
|
129
|
+
|
|
130
|
+
clientAutoreload,
|
|
131
|
+
clientFileChangeCallbackList,
|
|
132
|
+
clientFilesPruneCallbackList,
|
|
133
|
+
explorer,
|
|
134
|
+
}),
|
|
135
|
+
],
|
|
136
|
+
sourcemaps,
|
|
137
|
+
writeGeneratedFiles,
|
|
138
|
+
})
|
|
139
|
+
serverStopCallbacks.push(() => {
|
|
140
|
+
kitchen.pluginController.callHooks("destroy", kitchen.kitchenContext)
|
|
141
|
+
})
|
|
142
|
+
server_events: {
|
|
143
|
+
const allServerEvents = {}
|
|
144
|
+
kitchen.pluginController.plugins.forEach((plugin) => {
|
|
145
|
+
const { serverEvents } = plugin
|
|
146
|
+
if (serverEvents) {
|
|
147
|
+
Object.keys(serverEvents).forEach((serverEventName) => {
|
|
148
|
+
// we could throw on serverEvent name conflict
|
|
149
|
+
// we could throw if serverEvents[serverEventName] is not a function
|
|
150
|
+
allServerEvents[serverEventName] = serverEvents[serverEventName]
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
})
|
|
154
|
+
const serverEventNames = Object.keys(allServerEvents)
|
|
155
|
+
if (serverEventNames.length > 0) {
|
|
156
|
+
const serverEventsDispatcher = createServerEventsDispatcher()
|
|
157
|
+
serverStopCallbacks.push(() => {
|
|
158
|
+
serverEventsDispatcher.destroy()
|
|
159
|
+
})
|
|
160
|
+
Object.keys(allServerEvents).forEach((serverEventName) => {
|
|
161
|
+
allServerEvents[serverEventName]({
|
|
162
|
+
rootDirectoryUrl,
|
|
163
|
+
urlGraph,
|
|
164
|
+
scenario,
|
|
165
|
+
sendServerEvent: (data) => {
|
|
166
|
+
serverEventsDispatcher.dispatch({
|
|
167
|
+
type: serverEventName,
|
|
168
|
+
data,
|
|
169
|
+
})
|
|
170
|
+
},
|
|
171
|
+
})
|
|
172
|
+
})
|
|
173
|
+
onErrorWhileServingFileCallbacks.push((data) => {
|
|
174
|
+
serverEventsDispatcher.dispatchToRoomsMatching(
|
|
175
|
+
{
|
|
176
|
+
type: "error_while_serving_file",
|
|
177
|
+
data,
|
|
178
|
+
},
|
|
179
|
+
(room) => {
|
|
180
|
+
// send only to page depending on this file
|
|
181
|
+
const errorFileUrl = data.url
|
|
182
|
+
const roomEntryPointUrl = new URL(
|
|
183
|
+
room.request.ressource.slice(1),
|
|
184
|
+
rootDirectoryUrl,
|
|
185
|
+
).href
|
|
186
|
+
const isErrorRelatedToEntryPoint = Boolean(
|
|
187
|
+
urlGraph.findDependent(errorFileUrl, (dependentUrlInfo) => {
|
|
188
|
+
return dependentUrlInfo.url === roomEntryPointUrl
|
|
189
|
+
}),
|
|
190
|
+
)
|
|
191
|
+
return isErrorRelatedToEntryPoint
|
|
192
|
+
},
|
|
193
|
+
)
|
|
194
|
+
})
|
|
195
|
+
// "unshift" because serve must come first to catch event source client request
|
|
196
|
+
kitchen.pluginController.unshiftPlugin({
|
|
197
|
+
name: "jsenv:provide_server_events",
|
|
198
|
+
serve: (request) => {
|
|
199
|
+
const { accept } = request.headers
|
|
200
|
+
if (accept && accept.includes("text/event-stream")) {
|
|
201
|
+
const room = serverEventsDispatcher.addRoom(request)
|
|
202
|
+
return room.join(request)
|
|
203
|
+
}
|
|
204
|
+
return null
|
|
205
|
+
},
|
|
206
|
+
})
|
|
207
|
+
// "push" so that event source client connection can be put as early as possible in html
|
|
208
|
+
kitchen.pluginController.pushPlugin(
|
|
209
|
+
jsenvPluginServerEventsClientInjection(),
|
|
210
|
+
)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const context = {
|
|
215
|
+
rootDirectoryUrl,
|
|
216
|
+
scenario,
|
|
217
|
+
runtimeName,
|
|
218
|
+
runtimeVersion,
|
|
219
|
+
urlGraph,
|
|
220
|
+
kitchen,
|
|
221
|
+
}
|
|
222
|
+
contextCache.set(runtimeId, context)
|
|
223
|
+
return context
|
|
27
224
|
}
|
|
28
225
|
|
|
29
|
-
|
|
226
|
+
return async (request) => {
|
|
30
227
|
// serve file inside ".jsenv" directory
|
|
31
228
|
const requestFileUrl = new URL(request.ressource.slice(1), rootDirectoryUrl)
|
|
32
229
|
.href
|
|
33
|
-
if (urlIsInsideOf(requestFileUrl,
|
|
230
|
+
if (urlIsInsideOf(requestFileUrl, jsenvDirectoryUrl)) {
|
|
34
231
|
return fetchFileSystem(requestFileUrl, {
|
|
35
232
|
headers: request.headers,
|
|
36
233
|
})
|
|
37
234
|
}
|
|
235
|
+
const { runtimeName, runtimeVersion, urlGraph, kitchen } =
|
|
236
|
+
getOrCreateContext(request)
|
|
38
237
|
const responseFromPlugin =
|
|
39
238
|
await kitchen.pluginController.callAsyncHooksUntil(
|
|
40
239
|
"serve",
|
|
41
240
|
request,
|
|
42
|
-
|
|
241
|
+
kitchen.kitchenContext,
|
|
43
242
|
)
|
|
44
243
|
if (responseFromPlugin) {
|
|
45
244
|
return responseFromPlugin
|
|
@@ -51,7 +250,7 @@ export const createFileService = ({
|
|
|
51
250
|
}
|
|
52
251
|
if (!reference) {
|
|
53
252
|
const entryPoint = kitchen.injectReference({
|
|
54
|
-
trace: parentUrl || rootDirectoryUrl,
|
|
253
|
+
trace: { message: parentUrl || rootDirectoryUrl },
|
|
55
254
|
parentUrl: parentUrl || rootDirectoryUrl,
|
|
56
255
|
type: "http_request",
|
|
57
256
|
specifier: request.ressource,
|
|
@@ -64,8 +263,12 @@ export const createFileService = ({
|
|
|
64
263
|
if (
|
|
65
264
|
ifNoneMatch &&
|
|
66
265
|
urlInfo.contentEtag === ifNoneMatch &&
|
|
67
|
-
//
|
|
68
|
-
// -
|
|
266
|
+
// urlInfo.isValid
|
|
267
|
+
// - is false by default because there must be some logic capable
|
|
268
|
+
// to invalidate the url (otherwise server would return 304 forever)
|
|
269
|
+
// - is set to a function returning true if the file is watched
|
|
270
|
+
// in start_dev_server.js
|
|
271
|
+
// - is set to a custom function by cjs_to_esm in compiled_file_cache.js
|
|
69
272
|
urlInfo.isValid()
|
|
70
273
|
) {
|
|
71
274
|
return {
|
|
@@ -92,9 +295,6 @@ export const createFileService = ({
|
|
|
92
295
|
urlInfo.dependsOnPackageJson = false
|
|
93
296
|
urlInfo.timing = {}
|
|
94
297
|
}
|
|
95
|
-
const { runtimeName, runtimeVersion } = parseUserAgentHeader(
|
|
96
|
-
request.headers["user-agent"],
|
|
97
|
-
)
|
|
98
298
|
await kitchen.cook(urlInfo, {
|
|
99
299
|
request,
|
|
100
300
|
reference,
|
|
@@ -126,7 +326,7 @@ export const createFileService = ({
|
|
|
126
326
|
kitchen.pluginController.callHooks(
|
|
127
327
|
"augmentResponse",
|
|
128
328
|
{ reference, urlInfo },
|
|
129
|
-
|
|
329
|
+
kitchen.kitchenContext,
|
|
130
330
|
(returnValue) => {
|
|
131
331
|
response = composeTwoResponses(response, returnValue)
|
|
132
332
|
},
|
|
@@ -135,10 +335,19 @@ export const createFileService = ({
|
|
|
135
335
|
} catch (e) {
|
|
136
336
|
const code = e.code
|
|
137
337
|
if (code === "PARSE_ERROR") {
|
|
138
|
-
|
|
338
|
+
onErrorWhileServingFile({
|
|
339
|
+
requestedRessource: request.ressource,
|
|
340
|
+
code: "PARSE_ERROR",
|
|
341
|
+
message: e.reason,
|
|
342
|
+
url: e.url,
|
|
343
|
+
traceUrl: e.traceUrl,
|
|
344
|
+
traceLine: e.traceLine,
|
|
345
|
+
traceColumn: e.traceColumn,
|
|
346
|
+
traceMessage: e.traceMessage,
|
|
347
|
+
})
|
|
139
348
|
return {
|
|
140
349
|
url: reference.url,
|
|
141
|
-
status: 200,
|
|
350
|
+
status: 200, // let the browser re-throw the syntax error
|
|
142
351
|
statusText: e.reason,
|
|
143
352
|
statusMessage: e.message,
|
|
144
353
|
headers: {
|
|
@@ -166,6 +375,19 @@ export const createFileService = ({
|
|
|
166
375
|
}
|
|
167
376
|
}
|
|
168
377
|
if (code === "NOT_FOUND") {
|
|
378
|
+
onErrorWhileServingFile({
|
|
379
|
+
requestedRessource: request.ressource,
|
|
380
|
+
isFaviconAutoRequest:
|
|
381
|
+
request.ressource === "/favicon.ico" &&
|
|
382
|
+
reference.type === "http_request",
|
|
383
|
+
code: "NOT_FOUND",
|
|
384
|
+
message: e.reason,
|
|
385
|
+
url: e.url,
|
|
386
|
+
traceUrl: e.traceUrl,
|
|
387
|
+
traceLine: e.traceLine,
|
|
388
|
+
traceColumn: e.traceColumn,
|
|
389
|
+
traceMessage: e.traceMessage,
|
|
390
|
+
})
|
|
169
391
|
return {
|
|
170
392
|
url: reference.url,
|
|
171
393
|
status: 404,
|
|
@@ -173,6 +395,16 @@ export const createFileService = ({
|
|
|
173
395
|
statusMessage: e.message,
|
|
174
396
|
}
|
|
175
397
|
}
|
|
398
|
+
onErrorWhileServingFile({
|
|
399
|
+
requestedRessource: request.ressource,
|
|
400
|
+
code: "UNEXPECTED",
|
|
401
|
+
stack: e.stack,
|
|
402
|
+
url: e.url,
|
|
403
|
+
traceUrl: e.traceUrl,
|
|
404
|
+
traceLine: e.traceLine,
|
|
405
|
+
traceColumn: e.traceColumn,
|
|
406
|
+
traceMessage: e.traceMessage,
|
|
407
|
+
})
|
|
176
408
|
return {
|
|
177
409
|
url: reference.url,
|
|
178
410
|
status: 500,
|
|
@@ -181,10 +413,6 @@ export const createFileService = ({
|
|
|
181
413
|
}
|
|
182
414
|
}
|
|
183
415
|
}
|
|
184
|
-
return async (request) => {
|
|
185
|
-
let response = await getResponse(request)
|
|
186
|
-
return response
|
|
187
|
-
}
|
|
188
416
|
}
|
|
189
417
|
|
|
190
418
|
const inferParentFromRequest = (request, rootDirectoryUrl) => {
|
package/src/omega/url_graph.js
CHANGED
|
@@ -4,6 +4,8 @@ import { urlSpecifierEncoding } from "./url_specifier_encoding.js"
|
|
|
4
4
|
export const createUrlGraph = ({
|
|
5
5
|
clientFileChangeCallbackList,
|
|
6
6
|
clientFilesPruneCallbackList,
|
|
7
|
+
onCreateUrlInfo = () => {},
|
|
8
|
+
includeOriginalUrls,
|
|
7
9
|
} = {}) => {
|
|
8
10
|
const urlInfoMap = new Map()
|
|
9
11
|
const getUrlInfo = (url) => urlInfoMap.get(url)
|
|
@@ -22,6 +24,7 @@ export const createUrlGraph = ({
|
|
|
22
24
|
if (existingUrlInfo) return existingUrlInfo
|
|
23
25
|
const urlInfo = createUrlInfo(url)
|
|
24
26
|
urlInfoMap.set(url, urlInfo)
|
|
27
|
+
onCreateUrlInfo(urlInfo)
|
|
25
28
|
return urlInfo
|
|
26
29
|
}
|
|
27
30
|
const inferReference = (specifier, parentUrl) => {
|
|
@@ -55,7 +58,17 @@ export const createUrlGraph = ({
|
|
|
55
58
|
}
|
|
56
59
|
|
|
57
60
|
const updateReferences = (urlInfo, references) => {
|
|
58
|
-
const
|
|
61
|
+
const setOfDependencyUrls = new Set()
|
|
62
|
+
|
|
63
|
+
// for import assertion "file.css?as_css_module" depends on "file.css"
|
|
64
|
+
// this is enabled only for dev where there is autoreload
|
|
65
|
+
// during build the css file must be considered as not referenced
|
|
66
|
+
// (except if referenced explicitely by something else) so that
|
|
67
|
+
// the css file does not appear in the build directory
|
|
68
|
+
if (includeOriginalUrls && urlInfo.originalUrl !== urlInfo.url) {
|
|
69
|
+
setOfDependencyUrls.add(urlInfo.originalUrl)
|
|
70
|
+
}
|
|
71
|
+
|
|
59
72
|
references.forEach((reference) => {
|
|
60
73
|
if (reference.isRessourceHint) {
|
|
61
74
|
// ressource hint are a special kind of reference.
|
|
@@ -66,19 +79,14 @@ export const createUrlGraph = ({
|
|
|
66
79
|
// by <link> as dependency and it's fine
|
|
67
80
|
return
|
|
68
81
|
}
|
|
69
|
-
|
|
70
|
-
return
|
|
71
|
-
}
|
|
72
|
-
dependencyUrls.push(reference.url)
|
|
82
|
+
setOfDependencyUrls.add(reference.url)
|
|
73
83
|
})
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
Array.from(urlInfo.dependencies).filter(
|
|
77
|
-
(dep) => !dependencyUrls.includes(dep),
|
|
78
|
-
),
|
|
84
|
+
const urlsToRemove = Array.from(urlInfo.dependencies).filter(
|
|
85
|
+
(dep) => !setOfDependencyUrls.has(dep),
|
|
79
86
|
)
|
|
87
|
+
pruneDependencies(urlInfo, urlsToRemove)
|
|
80
88
|
urlInfo.references = references
|
|
81
|
-
|
|
89
|
+
setOfDependencyUrls.forEach((dependencyUrl) => {
|
|
82
90
|
const dependencyUrlInfo = reuseOrCreateUrlInfo(dependencyUrl)
|
|
83
91
|
urlInfo.dependencies.add(dependencyUrl)
|
|
84
92
|
dependencyUrlInfo.dependents.add(urlInfo.url)
|
|
@@ -90,14 +98,17 @@ export const createUrlGraph = ({
|
|
|
90
98
|
const removeDependencies = (urlInfo, urlsToPrune) => {
|
|
91
99
|
urlsToPrune.forEach((urlToPrune) => {
|
|
92
100
|
urlInfo.dependencies.delete(urlToPrune)
|
|
93
|
-
const
|
|
94
|
-
if (!
|
|
101
|
+
const dependencyUrlInfo = getUrlInfo(urlToPrune)
|
|
102
|
+
if (!dependencyUrlInfo) {
|
|
95
103
|
return
|
|
96
104
|
}
|
|
97
|
-
|
|
98
|
-
if (
|
|
99
|
-
removeDependencies(
|
|
100
|
-
|
|
105
|
+
dependencyUrlInfo.dependents.delete(urlInfo.url)
|
|
106
|
+
if (dependencyUrlInfo.dependents.size === 0) {
|
|
107
|
+
removeDependencies(
|
|
108
|
+
dependencyUrlInfo,
|
|
109
|
+
Array.from(dependencyUrlInfo.dependencies),
|
|
110
|
+
)
|
|
111
|
+
prunedUrlInfos.push(dependencyUrlInfo)
|
|
101
112
|
}
|
|
102
113
|
})
|
|
103
114
|
}
|
|
@@ -107,8 +118,10 @@ export const createUrlGraph = ({
|
|
|
107
118
|
}
|
|
108
119
|
prunedUrlInfos.forEach((prunedUrlInfo) => {
|
|
109
120
|
prunedUrlInfo.modifiedTimestamp = Date.now()
|
|
110
|
-
|
|
111
|
-
|
|
121
|
+
if (prunedUrlInfo.isInline) {
|
|
122
|
+
// should we always delete?
|
|
123
|
+
deleteUrlInfo(prunedUrlInfo.url)
|
|
124
|
+
}
|
|
112
125
|
})
|
|
113
126
|
if (clientFilesPruneCallbackList) {
|
|
114
127
|
clientFilesPruneCallbackList.forEach((callback) => {
|
|
@@ -145,6 +158,12 @@ export const createUrlGraph = ({
|
|
|
145
158
|
iterate(dependentUrlInfo)
|
|
146
159
|
}
|
|
147
160
|
})
|
|
161
|
+
urlInfo.dependencies.forEach((dependencyUrl) => {
|
|
162
|
+
const dependencyUrlInfo = getUrlInfo(dependencyUrl)
|
|
163
|
+
if (dependencyUrlInfo.isInline) {
|
|
164
|
+
iterate(dependencyUrlInfo)
|
|
165
|
+
}
|
|
166
|
+
})
|
|
148
167
|
}
|
|
149
168
|
iterate(urlInfo)
|
|
150
169
|
}
|
|
@@ -229,4 +248,4 @@ const createUrlInfo = (url) => {
|
|
|
229
248
|
}
|
|
230
249
|
}
|
|
231
250
|
|
|
232
|
-
const isValid = () =>
|
|
251
|
+
const isValid = () => false
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { urlHotMetas } from "../../import_meta_hot/client/import_meta_hot.js"
|
|
2
|
+
import {
|
|
3
|
+
isAutoreloadEnabled,
|
|
4
|
+
setAutoreloadPreference,
|
|
5
|
+
} from "./autoreload_preference.js"
|
|
6
|
+
import { compareTwoUrlPaths } from "./url_helpers.js"
|
|
7
|
+
import {
|
|
8
|
+
reloadHtmlPage,
|
|
9
|
+
reloadJsImport,
|
|
10
|
+
getDOMNodesUsingUrl,
|
|
11
|
+
} from "./reload.js"
|
|
12
|
+
|
|
13
|
+
const reloader = {
|
|
14
|
+
urlHotMetas,
|
|
15
|
+
isAutoreloadEnabled,
|
|
16
|
+
setAutoreloadPreference,
|
|
17
|
+
status: "idle",
|
|
18
|
+
onstatuschange: () => {},
|
|
19
|
+
setStatus: (status) => {
|
|
20
|
+
reloader.status = status
|
|
21
|
+
reloader.onstatuschange()
|
|
22
|
+
},
|
|
23
|
+
messages: [],
|
|
24
|
+
addMessage: (reloadMessage) => {
|
|
25
|
+
reloader.messages.push(reloadMessage)
|
|
26
|
+
if (isAutoreloadEnabled()) {
|
|
27
|
+
reloader.reload()
|
|
28
|
+
} else {
|
|
29
|
+
reloader.setStatus("can_reload")
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
reload: () => {
|
|
33
|
+
const someEffectIsFullReload = reloader.messages.some(
|
|
34
|
+
(reloadMessage) => reloadMessage.type === "full",
|
|
35
|
+
)
|
|
36
|
+
if (someEffectIsFullReload) {
|
|
37
|
+
reloadHtmlPage()
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
reloader.setStatus("reloading")
|
|
41
|
+
const onApplied = (reloadMessage) => {
|
|
42
|
+
const index = reloader.messages.indexOf(reloadMessage)
|
|
43
|
+
reloader.messages.splice(index, 1)
|
|
44
|
+
if (reloader.messages.length === 0) {
|
|
45
|
+
reloader.setStatus("idle")
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const setReloadMessagePromise = (reloadMessage, promise) => {
|
|
49
|
+
promise.then(
|
|
50
|
+
() => {
|
|
51
|
+
onApplied(reloadMessage)
|
|
52
|
+
},
|
|
53
|
+
(e) => {
|
|
54
|
+
reloader.setStatus("failed")
|
|
55
|
+
if (typeof window.reportError === "function") {
|
|
56
|
+
window.reportError(e)
|
|
57
|
+
} else {
|
|
58
|
+
console.error(e)
|
|
59
|
+
}
|
|
60
|
+
console.error(
|
|
61
|
+
`[jsenv] Hot reload failed after ${reloadMessage.reason}.
|
|
62
|
+
This could be due to syntax errors or importing non-existent modules (see errors in console)`,
|
|
63
|
+
)
|
|
64
|
+
},
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
reloader.messages.forEach((reloadMessage) => {
|
|
68
|
+
if (reloadMessage.type === "hot") {
|
|
69
|
+
const promise = addToHotQueue(() => {
|
|
70
|
+
return applyHotReload(reloadMessage)
|
|
71
|
+
})
|
|
72
|
+
setReloadMessagePromise(reloadMessage, promise)
|
|
73
|
+
} else {
|
|
74
|
+
setReloadMessagePromise(reloadMessage, Promise.resolve())
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
},
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let pendingCallbacks = []
|
|
81
|
+
let running = false
|
|
82
|
+
const addToHotQueue = async (callback) => {
|
|
83
|
+
pendingCallbacks.push(callback)
|
|
84
|
+
dequeue()
|
|
85
|
+
}
|
|
86
|
+
const dequeue = async () => {
|
|
87
|
+
if (running) {
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
const callbacks = pendingCallbacks.slice()
|
|
91
|
+
pendingCallbacks = []
|
|
92
|
+
running = true
|
|
93
|
+
try {
|
|
94
|
+
await callbacks.reduce(async (previous, callback) => {
|
|
95
|
+
await previous
|
|
96
|
+
await callback()
|
|
97
|
+
}, Promise.resolve())
|
|
98
|
+
} finally {
|
|
99
|
+
running = false
|
|
100
|
+
if (pendingCallbacks.length) {
|
|
101
|
+
dequeue()
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const applyHotReload = async ({ hotInstructions }) => {
|
|
107
|
+
await hotInstructions.reduce(
|
|
108
|
+
async (previous, { type, boundary, acceptedBy }) => {
|
|
109
|
+
await previous
|
|
110
|
+
|
|
111
|
+
const urlToFetch = new URL(boundary, `${window.location.origin}/`).href
|
|
112
|
+
const urlHotMeta = urlHotMetas[urlToFetch]
|
|
113
|
+
// there is no url hot meta when:
|
|
114
|
+
// - code was not executed (code splitting with dynamic import)
|
|
115
|
+
// - import.meta.hot.accept() is not called (happens for HTML and CSS)
|
|
116
|
+
|
|
117
|
+
if (type === "prune") {
|
|
118
|
+
if (urlHotMeta) {
|
|
119
|
+
delete urlHotMetas[urlToFetch]
|
|
120
|
+
if (urlHotMeta.disposeCallback) {
|
|
121
|
+
console.groupCollapsed(
|
|
122
|
+
`[jsenv] cleanup ${boundary} (previously used in ${acceptedBy})`,
|
|
123
|
+
)
|
|
124
|
+
console.log(`call dispose callback`)
|
|
125
|
+
await urlHotMeta.disposeCallback()
|
|
126
|
+
console.groupEnd()
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return null
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (acceptedBy === boundary) {
|
|
133
|
+
console.groupCollapsed(`[jsenv] hot reloading ${boundary}`)
|
|
134
|
+
} else {
|
|
135
|
+
console.groupCollapsed(
|
|
136
|
+
`[jsenv] hot reloading ${acceptedBy} usage in ${boundary}`,
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
if (type === "js_module") {
|
|
140
|
+
if (!urlHotMeta) {
|
|
141
|
+
// code was not executed, no need to re-execute it
|
|
142
|
+
return null
|
|
143
|
+
}
|
|
144
|
+
if (urlHotMeta.disposeCallback) {
|
|
145
|
+
console.log(`call dispose callback`)
|
|
146
|
+
await urlHotMeta.disposeCallback()
|
|
147
|
+
}
|
|
148
|
+
console.log(`importing js module`)
|
|
149
|
+
const namespace = await reloadJsImport(urlToFetch)
|
|
150
|
+
if (urlHotMeta.acceptCallback) {
|
|
151
|
+
await urlHotMeta.acceptCallback(namespace)
|
|
152
|
+
}
|
|
153
|
+
console.log(`js module import done`)
|
|
154
|
+
console.groupEnd()
|
|
155
|
+
return namespace
|
|
156
|
+
}
|
|
157
|
+
if (type === "html") {
|
|
158
|
+
if (!compareTwoUrlPaths(urlToFetch, window.location.href)) {
|
|
159
|
+
// we are not in that HTML page
|
|
160
|
+
return null
|
|
161
|
+
}
|
|
162
|
+
const urlToReload = new URL(acceptedBy, `${window.location.origin}/`)
|
|
163
|
+
.href
|
|
164
|
+
const domNodesUsingUrl = getDOMNodesUsingUrl(urlToReload)
|
|
165
|
+
const domNodesCount = domNodesUsingUrl.length
|
|
166
|
+
if (domNodesCount === 0) {
|
|
167
|
+
console.log(`no dom node using ${acceptedBy}`)
|
|
168
|
+
} else if (domNodesCount === 1) {
|
|
169
|
+
console.log(`reloading`, domNodesUsingUrl[0].node)
|
|
170
|
+
domNodesUsingUrl[0].reload()
|
|
171
|
+
} else {
|
|
172
|
+
console.log(`reloading ${domNodesCount} nodes using ${acceptedBy}`)
|
|
173
|
+
domNodesUsingUrl.forEach((domNodesUsingUrl) => {
|
|
174
|
+
domNodesUsingUrl.reload()
|
|
175
|
+
})
|
|
176
|
+
}
|
|
177
|
+
console.groupEnd()
|
|
178
|
+
return null
|
|
179
|
+
}
|
|
180
|
+
console.warn(`unknown update type: "${type}"`)
|
|
181
|
+
return null
|
|
182
|
+
},
|
|
183
|
+
Promise.resolve(),
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
window.__reloader__ = reloader
|
|
188
|
+
window.__server_events__.addEventCallbacks({
|
|
189
|
+
reload: ({ data }) => {
|
|
190
|
+
const reloadMessage = JSON.parse(data)
|
|
191
|
+
reloader.addMessage(reloadMessage)
|
|
192
|
+
},
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
// const findHotMetaUrl = (originalFileRelativeUrl) => {
|
|
196
|
+
// return Object.keys(urlHotMetas).find((compileUrl) => {
|
|
197
|
+
// return (
|
|
198
|
+
// parseCompiledUrl(compileUrl).fileRelativeUrl === originalFileRelativeUrl
|
|
199
|
+
// )
|
|
200
|
+
// })
|
|
201
|
+
// }
|
|
File without changes
|