@jsenv/core 27.4.0 → 27.5.2
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 +469 -254
- 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 +7558 -7311
- package/package.json +12 -10
- package/{README.md → readme.md} +8 -9
- package/src/build/build.js +12 -16
- package/src/build/start_build_server.js +24 -28
- package/src/dev/start_dev_server.js +34 -96
- package/src/execute/execute.js +17 -35
- package/src/omega/errors.js +20 -18
- package/src/omega/kitchen.js +7 -6
- package/src/omega/omega_server.js +96 -127
- package/src/omega/server/file_service.js +247 -46
- package/src/omega/url_graph.js +33 -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 -4
- 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_formatter.js +300 -0
- package/src/plugins/html_supervisor/client/error_overlay.js +172 -0
- package/src/plugins/html_supervisor/client/html_supervisor_installer.js +124 -54
- package/src/plugins/html_supervisor/client/html_supervisor_setup.js +3 -4
- package/src/plugins/html_supervisor/jsenv_plugin_html_supervisor.js +72 -27
- 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 -8
- package/src/{helpers/event_source/event_source.js → plugins/server_events/client/event_source_connection.js} +102 -31
- 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/test/execute_plan.js +36 -54
- package/src/test/execute_test_plan.js +2 -2
- package/dist/js/event_source_client.js +0 -549
- 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 -192
- package/src/plugins/html_supervisor/client/error_in_document.js +0 -345
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) => {
|
|
@@ -235,4 +248,4 @@ const createUrlInfo = (url) => {
|
|
|
235
248
|
}
|
|
236
249
|
}
|
|
237
250
|
|
|
238
|
-
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
|
|
@@ -13,8 +13,8 @@ export const reloadHtmlPage = () => {
|
|
|
13
13
|
// - no need to check [hot-accept]and [hot-decline] attributes for instance
|
|
14
14
|
// This is because if something should full reload, we receive "full_reload"
|
|
15
15
|
// from server and this function is not called
|
|
16
|
-
export const
|
|
17
|
-
const
|
|
16
|
+
export const getDOMNodesUsingUrl = (urlToReload) => {
|
|
17
|
+
const nodes = []
|
|
18
18
|
const shouldReloadUrl = (urlCandidate) => {
|
|
19
19
|
return compareTwoUrlPaths(urlCandidate, urlToReload)
|
|
20
20
|
}
|
|
@@ -29,8 +29,11 @@ export const reloadDOMNodesUsingUrl = (urlToReload) => {
|
|
|
29
29
|
if (!shouldReloadUrl(attribute)) {
|
|
30
30
|
return
|
|
31
31
|
}
|
|
32
|
-
|
|
33
|
-
node
|
|
32
|
+
nodes.push({
|
|
33
|
+
node,
|
|
34
|
+
reload: () => {
|
|
35
|
+
node[attributeName] = injectQuery(attribute, { hmr: Date.now() })
|
|
36
|
+
},
|
|
34
37
|
})
|
|
35
38
|
}
|
|
36
39
|
Array.from(document.querySelectorAll(`link[rel="stylesheet"]`)).forEach(
|
|
@@ -41,8 +44,23 @@ export const reloadDOMNodesUsingUrl = (urlToReload) => {
|
|
|
41
44
|
Array.from(document.querySelectorAll(`link[rel="icon"]`)).forEach((link) => {
|
|
42
45
|
visitNodeAttributeAsUrl(link, "href")
|
|
43
46
|
})
|
|
44
|
-
Array.from(document.
|
|
47
|
+
Array.from(document.querySelectorAll("script")).forEach((script) => {
|
|
45
48
|
visitNodeAttributeAsUrl(script, "src")
|
|
49
|
+
const generatedFromSrc = script.getAttribute("generated-from-src")
|
|
50
|
+
if (generatedFromSrc) {
|
|
51
|
+
const generatedFromUrl = new URL(generatedFromSrc, window.location.origin)
|
|
52
|
+
.href
|
|
53
|
+
if (shouldReloadUrl(generatedFromUrl)) {
|
|
54
|
+
nodes.push({
|
|
55
|
+
node: script,
|
|
56
|
+
reload: () =>
|
|
57
|
+
window.__html_supervisor__.reloadSupervisedScript({
|
|
58
|
+
type: script.type,
|
|
59
|
+
src: generatedFromSrc,
|
|
60
|
+
}),
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
}
|
|
46
64
|
})
|
|
47
65
|
// There is no real need to update a.href because the ressource will be fetched when clicked.
|
|
48
66
|
// But in a scenario where the ressource was already visited and is in browser cache, adding
|
|
@@ -67,8 +85,11 @@ export const reloadDOMNodesUsingUrl = (urlToReload) => {
|
|
|
67
85
|
srcCandidate.specifier = injectQuery(url, { hmr: Date.now() })
|
|
68
86
|
}
|
|
69
87
|
})
|
|
70
|
-
|
|
71
|
-
img
|
|
88
|
+
nodes.push({
|
|
89
|
+
node: img,
|
|
90
|
+
reload: () => {
|
|
91
|
+
img.srcset = stringifySrcSet(srcCandidates)
|
|
92
|
+
},
|
|
72
93
|
})
|
|
73
94
|
}
|
|
74
95
|
})
|
|
@@ -83,9 +104,7 @@ export const reloadDOMNodesUsingUrl = (urlToReload) => {
|
|
|
83
104
|
Array.from(document.querySelectorAll("use")).forEach((use) => {
|
|
84
105
|
visitNodeAttributeAsUrl(use, "href")
|
|
85
106
|
})
|
|
86
|
-
|
|
87
|
-
mutation()
|
|
88
|
-
})
|
|
107
|
+
return nodes
|
|
89
108
|
}
|
|
90
109
|
|
|
91
110
|
export const reloadJsImport = async (url) => {
|
|
File without changes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsenvPluginHmr } from "./jsenv_plugin_hmr.js"
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { jsenvPluginAutoreloadClient } from "./jsenv_plugin_autoreload_client.js"
|
|
3
|
+
import { jsenvPluginAutoreloadServer } from "./jsenv_plugin_autoreload_server.js"
|
|
4
4
|
|
|
5
5
|
export const jsenvPluginAutoreload = ({
|
|
6
6
|
scenario,
|
|
@@ -12,8 +12,8 @@ export const jsenvPluginAutoreload = ({
|
|
|
12
12
|
}
|
|
13
13
|
return [
|
|
14
14
|
jsenvPluginHmr(),
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
jsenvPluginAutoreloadClient(),
|
|
16
|
+
jsenvPluginAutoreloadServer({
|
|
17
17
|
clientFileChangeCallbackList,
|
|
18
18
|
clientFilesPruneCallbackList,
|
|
19
19
|
}),
|
|
@@ -5,30 +5,30 @@ import {
|
|
|
5
5
|
createHtmlNode,
|
|
6
6
|
} from "@jsenv/ast"
|
|
7
7
|
|
|
8
|
-
export const
|
|
9
|
-
const
|
|
10
|
-
"./client/
|
|
8
|
+
export const jsenvPluginAutoreloadClient = () => {
|
|
9
|
+
const autoreloadClientFileUrl = new URL(
|
|
10
|
+
"./client/autoreload.js",
|
|
11
11
|
import.meta.url,
|
|
12
12
|
).href
|
|
13
13
|
|
|
14
14
|
return {
|
|
15
|
-
name: "jsenv:
|
|
15
|
+
name: "jsenv:autoreload_client",
|
|
16
16
|
appliesDuring: { dev: true },
|
|
17
17
|
transformUrlContent: {
|
|
18
18
|
html: (htmlUrlInfo, context) => {
|
|
19
19
|
const htmlAst = parseHtmlString(htmlUrlInfo.content)
|
|
20
|
-
const [
|
|
20
|
+
const [autoreloadClientReference] = context.referenceUtils.inject({
|
|
21
21
|
type: "script_src",
|
|
22
22
|
expectedType: "js_module",
|
|
23
|
-
specifier:
|
|
23
|
+
specifier: autoreloadClientFileUrl,
|
|
24
24
|
})
|
|
25
25
|
injectScriptNodeAsEarlyAsPossible(
|
|
26
26
|
htmlAst,
|
|
27
27
|
createHtmlNode({
|
|
28
28
|
"tagName": "script",
|
|
29
29
|
"type": "module",
|
|
30
|
-
"src":
|
|
31
|
-
"injected-by": "jsenv:
|
|
30
|
+
"src": autoreloadClientReference.generatedSpecifier,
|
|
31
|
+
"injected-by": "jsenv:autoreload_client",
|
|
32
32
|
}),
|
|
33
33
|
)
|
|
34
34
|
const htmlModified = stringifyHtmlAst(htmlAst)
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { urlIsInsideOf, urlToRelativeUrl } from "@jsenv/urls"
|
|
2
|
+
|
|
3
|
+
export const jsenvPluginAutoreloadServer = ({
|
|
4
|
+
clientFileChangeCallbackList,
|
|
5
|
+
clientFilesPruneCallbackList,
|
|
6
|
+
}) => {
|
|
7
|
+
return {
|
|
8
|
+
name: "jsenv:autoreload_server",
|
|
9
|
+
appliesDuring: { dev: true },
|
|
10
|
+
serverEvents: {
|
|
11
|
+
reload: ({ sendServerEvent, rootDirectoryUrl, urlGraph }) => {
|
|
12
|
+
const formatUrlForClient = (url) => {
|
|
13
|
+
if (urlIsInsideOf(url, rootDirectoryUrl)) {
|
|
14
|
+
return urlToRelativeUrl(url, rootDirectoryUrl)
|
|
15
|
+
}
|
|
16
|
+
if (url.startsWith("file:")) {
|
|
17
|
+
return `/@fs/${url.slice("file:///".length)}`
|
|
18
|
+
}
|
|
19
|
+
return url
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const notifyDeclined = ({ cause, reason, declinedBy }) => {
|
|
23
|
+
sendServerEvent({
|
|
24
|
+
cause,
|
|
25
|
+
type: "full",
|
|
26
|
+
typeReason: reason,
|
|
27
|
+
declinedBy,
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
const notifyAccepted = ({ cause, reason, instructions }) => {
|
|
31
|
+
sendServerEvent({
|
|
32
|
+
cause,
|
|
33
|
+
type: "hot",
|
|
34
|
+
typeReason: reason,
|
|
35
|
+
hotInstructions: instructions,
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
const propagateUpdate = (firstUrlInfo) => {
|
|
39
|
+
const iterate = (urlInfo, seen) => {
|
|
40
|
+
if (urlInfo.data.hotAcceptSelf) {
|
|
41
|
+
return {
|
|
42
|
+
accepted: true,
|
|
43
|
+
reason:
|
|
44
|
+
urlInfo === firstUrlInfo
|
|
45
|
+
? `file accepts hot reload`
|
|
46
|
+
: `a dependent file accepts hot reload`,
|
|
47
|
+
instructions: [
|
|
48
|
+
{
|
|
49
|
+
type: urlInfo.type,
|
|
50
|
+
boundary: formatUrlForClient(urlInfo.url),
|
|
51
|
+
acceptedBy: formatUrlForClient(urlInfo.url),
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const { dependents } = urlInfo
|
|
57
|
+
const instructions = []
|
|
58
|
+
for (const dependentUrl of dependents) {
|
|
59
|
+
const dependentUrlInfo = urlGraph.getUrlInfo(dependentUrl)
|
|
60
|
+
if (dependentUrlInfo.data.hotDecline) {
|
|
61
|
+
return {
|
|
62
|
+
declined: true,
|
|
63
|
+
reason: `a dependent file declines hot reload`,
|
|
64
|
+
declinedBy: dependentUrl,
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const { hotAcceptDependencies = [] } = dependentUrlInfo.data
|
|
68
|
+
if (hotAcceptDependencies.includes(urlInfo.url)) {
|
|
69
|
+
instructions.push({
|
|
70
|
+
type: dependentUrlInfo.type,
|
|
71
|
+
boundary: formatUrlForClient(dependentUrl),
|
|
72
|
+
acceptedBy: formatUrlForClient(urlInfo.url),
|
|
73
|
+
})
|
|
74
|
+
continue
|
|
75
|
+
}
|
|
76
|
+
if (seen.includes(dependentUrl)) {
|
|
77
|
+
return {
|
|
78
|
+
declined: true,
|
|
79
|
+
reason: "circular dependency",
|
|
80
|
+
declinedBy: formatUrlForClient(dependentUrl),
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const dependentPropagationResult = iterate(dependentUrlInfo, [
|
|
84
|
+
...seen,
|
|
85
|
+
dependentUrl,
|
|
86
|
+
])
|
|
87
|
+
if (dependentPropagationResult.accepted) {
|
|
88
|
+
instructions.push(...dependentPropagationResult.instructions)
|
|
89
|
+
continue
|
|
90
|
+
}
|
|
91
|
+
if (
|
|
92
|
+
// declined explicitely by an other file, it must decline the whole update
|
|
93
|
+
dependentPropagationResult.declinedBy
|
|
94
|
+
) {
|
|
95
|
+
return dependentPropagationResult
|
|
96
|
+
}
|
|
97
|
+
// declined by absence of boundary, we can keep searching
|
|
98
|
+
continue
|
|
99
|
+
}
|
|
100
|
+
if (instructions.length === 0) {
|
|
101
|
+
return {
|
|
102
|
+
declined: true,
|
|
103
|
+
reason: `there is no file accepting hot reload while propagating update`,
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
accepted: true,
|
|
108
|
+
reason: `${instructions.length} dependent file(s) accepts hot reload`,
|
|
109
|
+
instructions,
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const seen = []
|
|
113
|
+
return iterate(firstUrlInfo, seen)
|
|
114
|
+
}
|
|
115
|
+
clientFileChangeCallbackList.push(({ url, event }) => {
|
|
116
|
+
const urlInfo = urlGraph.getUrlInfo(url)
|
|
117
|
+
// file not part of dependency graph
|
|
118
|
+
if (!urlInfo) {
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
const relativeUrl = formatUrlForClient(url)
|
|
122
|
+
const hotUpdate = propagateUpdate(urlInfo)
|
|
123
|
+
if (hotUpdate.declined) {
|
|
124
|
+
notifyDeclined({
|
|
125
|
+
cause: `${relativeUrl} ${event}`,
|
|
126
|
+
reason: hotUpdate.reason,
|
|
127
|
+
declinedBy: hotUpdate.declinedBy,
|
|
128
|
+
})
|
|
129
|
+
} else {
|
|
130
|
+
notifyAccepted({
|
|
131
|
+
cause: `${relativeUrl} ${event}`,
|
|
132
|
+
reason: hotUpdate.reason,
|
|
133
|
+
instructions: hotUpdate.instructions,
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
})
|
|
137
|
+
clientFilesPruneCallbackList.push(
|
|
138
|
+
({ prunedUrlInfos, firstUrlInfo }) => {
|
|
139
|
+
const mainHotUpdate = propagateUpdate(firstUrlInfo)
|
|
140
|
+
const cause = `following files are no longer referenced: ${prunedUrlInfos.map(
|
|
141
|
+
(prunedUrlInfo) => formatUrlForClient(prunedUrlInfo.url),
|
|
142
|
+
)}`
|
|
143
|
+
// now check if we can hot update the main ressource
|
|
144
|
+
// then if we can hot update all dependencies
|
|
145
|
+
if (mainHotUpdate.declined) {
|
|
146
|
+
notifyDeclined({
|
|
147
|
+
cause,
|
|
148
|
+
reason: mainHotUpdate.reason,
|
|
149
|
+
declinedBy: mainHotUpdate.declinedBy,
|
|
150
|
+
})
|
|
151
|
+
return
|
|
152
|
+
}
|
|
153
|
+
// main can hot update
|
|
154
|
+
let i = 0
|
|
155
|
+
const instructions = []
|
|
156
|
+
while (i < prunedUrlInfos.length) {
|
|
157
|
+
const prunedUrlInfo = prunedUrlInfos[i++]
|
|
158
|
+
if (prunedUrlInfo.data.hotDecline) {
|
|
159
|
+
notifyDeclined({
|
|
160
|
+
cause,
|
|
161
|
+
reason: `a pruned file declines hot reload`,
|
|
162
|
+
declinedBy: formatUrlForClient(prunedUrlInfo.url),
|
|
163
|
+
})
|
|
164
|
+
return
|
|
165
|
+
}
|
|
166
|
+
instructions.push({
|
|
167
|
+
type: "prune",
|
|
168
|
+
boundary: formatUrlForClient(prunedUrlInfo.url),
|
|
169
|
+
acceptedBy: formatUrlForClient(firstUrlInfo.url),
|
|
170
|
+
})
|
|
171
|
+
}
|
|
172
|
+
notifyAccepted({
|
|
173
|
+
cause,
|
|
174
|
+
reason: mainHotUpdate.reason,
|
|
175
|
+
instructions,
|
|
176
|
+
})
|
|
177
|
+
},
|
|
178
|
+
)
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
serve: (request, { rootDirectoryUrl, urlGraph }) => {
|
|
182
|
+
if (request.ressource === "/__graph__") {
|
|
183
|
+
const graphJson = JSON.stringify(urlGraph.toJSON(rootDirectoryUrl))
|
|
184
|
+
return {
|
|
185
|
+
status: 200,
|
|
186
|
+
headers: {
|
|
187
|
+
"content-type": "application/json",
|
|
188
|
+
"content-length": Buffer.byteLength(graphJson),
|
|
189
|
+
},
|
|
190
|
+
body: graphJson,
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return null
|
|
194
|
+
},
|
|
195
|
+
}
|
|
196
|
+
}
|
|
File without changes
|
|
File without changes
|
|
@@ -9,9 +9,7 @@ export const jsenvPluginExplorer = ({ groups }) => {
|
|
|
9
9
|
|
|
10
10
|
return {
|
|
11
11
|
name: "jsenv:explorer",
|
|
12
|
-
appliesDuring:
|
|
13
|
-
dev: true,
|
|
14
|
-
},
|
|
12
|
+
appliesDuring: "dev",
|
|
15
13
|
serve: async (request, { rootDirectoryUrl }) => {
|
|
16
14
|
if (request.ressource !== "/") {
|
|
17
15
|
return null
|