@jsenv/core 27.0.0-alpha.45 → 27.0.0-alpha.46
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/package.json +7 -6
- package/src/build/start_build_server.js +87 -37
- package/src/dev/start_dev_server.js +95 -21
- package/src/execute/execute.js +1 -2
- package/src/omega/kitchen.js +33 -2
- package/src/omega/omega_server.js +2 -3
- package/src/omega/server/file_service.js +11 -1
- package/src/omega/url_graph/url_info_transformations.js +3 -1
- package/src/omega/url_graph.js +47 -6
- package/src/plugins/autoreload/dev_sse/jsenv_plugin_dev_sse_server.js +27 -56
- package/src/plugins/autoreload/jsenv_plugin_autoreload.js +4 -4
- package/src/plugins/autoreload/jsenv_plugin_hmr.js +2 -2
- package/src/plugins/plugins.js +6 -6
- package/src/test/execute_plan.js +2 -7
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.46",
|
|
4
4
|
"description": "Tool to develop, test and build js projects",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"node": ">=16.13.0"
|
|
12
12
|
},
|
|
13
13
|
"publishConfig": {
|
|
14
|
-
"access": "public"
|
|
14
|
+
"access": "public",
|
|
15
|
+
"registry": "https://registry.npmjs.org"
|
|
15
16
|
},
|
|
16
17
|
"type": "module",
|
|
17
18
|
"imports": {},
|
|
@@ -59,15 +60,15 @@
|
|
|
59
60
|
"@financial-times/polyfill-useragent-normaliser": "2.0.1",
|
|
60
61
|
"@jsenv/abort": "4.1.2",
|
|
61
62
|
"@jsenv/babel-plugins": "1.0.2",
|
|
62
|
-
"@jsenv/filesystem": "3.1
|
|
63
|
+
"@jsenv/filesystem": "3.2.1",
|
|
63
64
|
"@jsenv/importmap": "1.2.0",
|
|
64
65
|
"@jsenv/integrity": "0.0.1",
|
|
65
66
|
"@jsenv/log": "1.5.2",
|
|
66
67
|
"@jsenv/logger": "4.0.1",
|
|
67
68
|
"@jsenv/node-esm-resolution": "0.0.6",
|
|
68
|
-
"@jsenv/server": "12.6.
|
|
69
|
+
"@jsenv/server": "12.6.2",
|
|
69
70
|
"@jsenv/uneval": "1.6.0",
|
|
70
|
-
"@jsenv/utils": "1.
|
|
71
|
+
"@jsenv/utils": "1.7.0",
|
|
71
72
|
"construct-style-sheets-polyfill": "3.1.0",
|
|
72
73
|
"cssnano": "5.1.7",
|
|
73
74
|
"cssnano-preset-default": "5.2.7",
|
|
@@ -107,4 +108,4 @@
|
|
|
107
108
|
"redux": "4.1.2",
|
|
108
109
|
"rollup": "2.70.1"
|
|
109
110
|
}
|
|
110
|
-
}
|
|
111
|
+
}
|
|
@@ -21,13 +21,15 @@ import {
|
|
|
21
21
|
pluginCORS,
|
|
22
22
|
fetchFileSystem,
|
|
23
23
|
} from "@jsenv/server"
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
assertAndNormalizeDirectoryUrl,
|
|
26
|
+
registerDirectoryLifecycle,
|
|
27
|
+
} from "@jsenv/filesystem"
|
|
25
28
|
import { createLogger } from "@jsenv/logger"
|
|
26
29
|
import { Abort } from "@jsenv/abort"
|
|
27
30
|
|
|
28
|
-
import {
|
|
31
|
+
import { initReloadableProcess } from "@jsenv/utils/process_reload/process_reload.js"
|
|
29
32
|
import { createTaskLog } from "@jsenv/utils/logs/task_log.js"
|
|
30
|
-
import { watchFiles } from "@jsenv/utils/file_watcher/file_watcher.js"
|
|
31
33
|
import { executeCommand } from "@jsenv/utils/command/command.js"
|
|
32
34
|
|
|
33
35
|
export const startBuildServer = async ({
|
|
@@ -45,43 +47,80 @@ export const startBuildServer = async ({
|
|
|
45
47
|
|
|
46
48
|
rootDirectoryUrl,
|
|
47
49
|
buildDirectoryUrl,
|
|
50
|
+
buildServerFiles = {
|
|
51
|
+
"./package.json": true,
|
|
52
|
+
"./jsenv.config.mjs": true,
|
|
53
|
+
},
|
|
54
|
+
buildServerMainFile,
|
|
55
|
+
buildServerAutoreload = false,
|
|
56
|
+
clientFiles = {
|
|
57
|
+
"./**": true,
|
|
58
|
+
"./**/.*/": false, // any folder starting with a dot is ignored (includes .git,.jsenv for instance)
|
|
59
|
+
"./dist/": false,
|
|
60
|
+
"./**/node_modules/": false,
|
|
61
|
+
},
|
|
62
|
+
cooldownBetweenFileEvents,
|
|
48
63
|
buildCommand,
|
|
49
64
|
mainBuildFileUrl = "/index.html",
|
|
50
|
-
watchedFilePatterns,
|
|
51
|
-
cooldownBetweenFileEvents,
|
|
52
|
-
autorestart,
|
|
53
65
|
}) => {
|
|
66
|
+
const logger = createLogger({ logLevel })
|
|
54
67
|
rootDirectoryUrl = assertAndNormalizeDirectoryUrl(rootDirectoryUrl)
|
|
55
68
|
buildDirectoryUrl = assertAndNormalizeDirectoryUrl(buildDirectoryUrl)
|
|
56
69
|
|
|
57
|
-
const
|
|
58
|
-
signal,
|
|
70
|
+
const reloadableProcess = await initReloadableProcess({
|
|
59
71
|
handleSIGINT,
|
|
60
|
-
...(
|
|
72
|
+
...(buildServerAutoreload
|
|
61
73
|
? {
|
|
62
74
|
enabled: true,
|
|
63
|
-
logLevel:
|
|
64
|
-
|
|
65
|
-
urlsToWatch: [
|
|
66
|
-
...(autorestart.urlsToWatch || []),
|
|
67
|
-
new URL("package.json", rootDirectoryUrl),
|
|
68
|
-
new URL("jsenv.config.mjs", rootDirectoryUrl),
|
|
69
|
-
],
|
|
75
|
+
logLevel: "info",
|
|
76
|
+
fileToRestart: buildServerMainFile,
|
|
70
77
|
}
|
|
71
78
|
: {
|
|
72
79
|
enabled: false,
|
|
73
80
|
}),
|
|
74
81
|
})
|
|
75
|
-
if (
|
|
82
|
+
if (reloadableProcess.isPrimary) {
|
|
83
|
+
const buildServerFileChangeCallback = ({ relativeUrl, event }) => {
|
|
84
|
+
const url = new URL(relativeUrl, rootDirectoryUrl).href
|
|
85
|
+
if (buildServerAutoreload) {
|
|
86
|
+
logger.info(`file ${event} ${url} -> restarting server...`)
|
|
87
|
+
reloadableProcess.reload()
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const stopWatchingBuildServerFiles = registerDirectoryLifecycle(
|
|
91
|
+
rootDirectoryUrl,
|
|
92
|
+
{
|
|
93
|
+
watchPatterns: {
|
|
94
|
+
[buildServerMainFile]: true,
|
|
95
|
+
...buildServerFiles,
|
|
96
|
+
},
|
|
97
|
+
cooldownBetweenFileEvents,
|
|
98
|
+
keepProcessAlive: false,
|
|
99
|
+
recursive: true,
|
|
100
|
+
added: ({ relativeUrl }) => {
|
|
101
|
+
buildServerFileChangeCallback({ relativeUrl, event: "added" })
|
|
102
|
+
},
|
|
103
|
+
updated: ({ relativeUrl }) => {
|
|
104
|
+
buildServerFileChangeCallback({ relativeUrl, event: "modified" })
|
|
105
|
+
},
|
|
106
|
+
removed: ({ relativeUrl }) => {
|
|
107
|
+
buildServerFileChangeCallback({ relativeUrl, event: "removed" })
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
)
|
|
111
|
+
signal.addEventListener("abort", () => {
|
|
112
|
+
stopWatchingBuildServerFiles()
|
|
113
|
+
})
|
|
76
114
|
return {
|
|
77
115
|
origin: `${protocol}://127.0.0.1:${port}`,
|
|
78
116
|
stop: () => {
|
|
79
|
-
|
|
117
|
+
stopWatchingBuildServerFiles()
|
|
118
|
+
|
|
119
|
+
reloadableProcess.stop()
|
|
80
120
|
},
|
|
81
121
|
}
|
|
82
122
|
}
|
|
83
|
-
signal =
|
|
84
|
-
const logger = createLogger({ logLevel })
|
|
123
|
+
signal = reloadableProcess.signal
|
|
85
124
|
|
|
86
125
|
let buildPromise
|
|
87
126
|
let buildAbortController
|
|
@@ -177,28 +216,39 @@ export const startBuildServer = async ({
|
|
|
177
216
|
})
|
|
178
217
|
logger.info(``)
|
|
179
218
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
219
|
+
runBuild()
|
|
220
|
+
const clientFileChangeCallback = ({ relativeUrl, event }) => {
|
|
221
|
+
const url = new URL(relativeUrl, rootDirectoryUrl).href
|
|
222
|
+
buildAbortController.abort()
|
|
223
|
+
// setTimeout is to ensure the abortController.abort() above
|
|
224
|
+
// is properly taken into account so that logs about abort comes first
|
|
225
|
+
// then logs about re-running the build happens
|
|
226
|
+
setTimeout(() => {
|
|
227
|
+
logger.info(`${url.slice(rootDirectoryUrl.length)} ${event} -> rebuild`)
|
|
228
|
+
runBuild()
|
|
229
|
+
})
|
|
230
|
+
}
|
|
231
|
+
const stopWatchingClientFiles = registerDirectoryLifecycle(rootDirectoryUrl, {
|
|
232
|
+
watchPatterns: clientFiles,
|
|
183
233
|
cooldownBetweenFileEvents,
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
234
|
+
keepProcessAlive: false,
|
|
235
|
+
recursive: true,
|
|
236
|
+
added: ({ relativeUrl }) => {
|
|
237
|
+
clientFileChangeCallback({ relativeUrl, event: "added" })
|
|
238
|
+
},
|
|
239
|
+
updated: ({ relativeUrl }) => {
|
|
240
|
+
clientFileChangeCallback({ relativeUrl, event: "modified" })
|
|
241
|
+
},
|
|
242
|
+
removed: ({ relativeUrl }) => {
|
|
243
|
+
clientFileChangeCallback({ relativeUrl, event: "removed" })
|
|
193
244
|
},
|
|
194
245
|
})
|
|
195
|
-
signal.addEventListener("abort", () => {
|
|
196
|
-
unregisterDirectoryLifecyle()
|
|
197
|
-
})
|
|
198
|
-
runBuild()
|
|
199
246
|
return {
|
|
200
247
|
origin: server.origin,
|
|
201
|
-
stop: () =>
|
|
248
|
+
stop: () => {
|
|
249
|
+
stopWatchingClientFiles()
|
|
250
|
+
server.stop()
|
|
251
|
+
},
|
|
202
252
|
}
|
|
203
253
|
}
|
|
204
254
|
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
assertAndNormalizeDirectoryUrl,
|
|
3
|
+
registerDirectoryLifecycle,
|
|
4
|
+
} from "@jsenv/filesystem"
|
|
2
5
|
import { createLogger } from "@jsenv/logger"
|
|
3
6
|
|
|
4
|
-
import {
|
|
7
|
+
import { initReloadableProcess } from "@jsenv/utils/process_reload/process_reload.js"
|
|
5
8
|
import { createTaskLog } from "@jsenv/utils/logs/task_log.js"
|
|
6
9
|
import { getCorePlugins } from "@jsenv/core/src/plugins/plugins.js"
|
|
7
10
|
import { createUrlGraph } from "@jsenv/core/src/omega/url_graph.js"
|
|
@@ -14,7 +17,8 @@ import { jsenvPluginToolbar } from "./plugins/toolbar/jsenv_plugin_toolbar.js"
|
|
|
14
17
|
export const startDevServer = async ({
|
|
15
18
|
signal = new AbortController().signal,
|
|
16
19
|
handleSIGINT,
|
|
17
|
-
logLevel,
|
|
20
|
+
logLevel = "info",
|
|
21
|
+
omegaServerLogLevel = "warn",
|
|
18
22
|
port = 3456,
|
|
19
23
|
protocol = "http",
|
|
20
24
|
listenAnyIp,
|
|
@@ -25,6 +29,20 @@ export const startDevServer = async ({
|
|
|
25
29
|
privateKey,
|
|
26
30
|
keepProcessAlive = true,
|
|
27
31
|
rootDirectoryUrl,
|
|
32
|
+
devServerFiles = {
|
|
33
|
+
"./package.json": true,
|
|
34
|
+
"./jsenv.config.mjs": true,
|
|
35
|
+
},
|
|
36
|
+
devServerMainFile,
|
|
37
|
+
devServerAutoreload = false,
|
|
38
|
+
clientFiles = {
|
|
39
|
+
"./**": true,
|
|
40
|
+
"./**/.*/": false, // any folder starting with a dot is ignored (includes .git,.jsenv for instance)
|
|
41
|
+
"./dist/": false,
|
|
42
|
+
"./**/node_modules/": false,
|
|
43
|
+
},
|
|
44
|
+
cooldownBetweenFileEvents,
|
|
45
|
+
clientAutoreload = true,
|
|
28
46
|
|
|
29
47
|
sourcemaps = "inline",
|
|
30
48
|
plugins = [],
|
|
@@ -33,7 +51,6 @@ export const startDevServer = async ({
|
|
|
33
51
|
nodeEsmResolution,
|
|
34
52
|
fileSystemMagicResolution,
|
|
35
53
|
transpilation,
|
|
36
|
-
autoreload = true,
|
|
37
54
|
explorerGroups = {
|
|
38
55
|
source: {
|
|
39
56
|
"./*.html": true,
|
|
@@ -44,40 +61,92 @@ export const startDevServer = async ({
|
|
|
44
61
|
},
|
|
45
62
|
},
|
|
46
63
|
toolbar = false,
|
|
47
|
-
autorestart,
|
|
48
64
|
}) => {
|
|
65
|
+
const logger = createLogger({ logLevel })
|
|
49
66
|
rootDirectoryUrl = assertAndNormalizeDirectoryUrl(rootDirectoryUrl)
|
|
50
|
-
const
|
|
67
|
+
const reloadableProcess = await initReloadableProcess({
|
|
51
68
|
signal,
|
|
52
69
|
handleSIGINT,
|
|
53
|
-
...(
|
|
70
|
+
...(devServerAutoreload
|
|
54
71
|
? {
|
|
55
72
|
enabled: true,
|
|
56
|
-
logLevel:
|
|
57
|
-
|
|
58
|
-
urlsToWatch: [
|
|
59
|
-
...(autorestart.urlsToWatch || []),
|
|
60
|
-
new URL("package.json", rootDirectoryUrl),
|
|
61
|
-
new URL("jsenv.config.mjs", rootDirectoryUrl),
|
|
62
|
-
],
|
|
73
|
+
logLevel: "warn",
|
|
74
|
+
fileToRestart: devServerMainFile,
|
|
63
75
|
}
|
|
64
76
|
: {
|
|
65
77
|
enabled: false,
|
|
66
78
|
}),
|
|
67
79
|
})
|
|
68
|
-
if (
|
|
80
|
+
if (reloadableProcess.isPrimary) {
|
|
81
|
+
const devServerFileChangeCallback = ({ relativeUrl, event }) => {
|
|
82
|
+
const url = new URL(relativeUrl, rootDirectoryUrl).href
|
|
83
|
+
if (devServerAutoreload) {
|
|
84
|
+
logger.info(`file ${event} ${url} -> restarting server...`)
|
|
85
|
+
reloadableProcess.reload()
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const unregisterDevServerFilesWatcher = registerDirectoryLifecycle(
|
|
89
|
+
rootDirectoryUrl,
|
|
90
|
+
{
|
|
91
|
+
watchPatterns: {
|
|
92
|
+
[devServerMainFile]: true,
|
|
93
|
+
...devServerFiles,
|
|
94
|
+
},
|
|
95
|
+
cooldownBetweenFileEvents,
|
|
96
|
+
keepProcessAlive: false,
|
|
97
|
+
recursive: true,
|
|
98
|
+
added: ({ relativeUrl }) => {
|
|
99
|
+
devServerFileChangeCallback({ relativeUrl, event: "added" })
|
|
100
|
+
},
|
|
101
|
+
updated: ({ relativeUrl }) => {
|
|
102
|
+
devServerFileChangeCallback({ relativeUrl, event: "modified" })
|
|
103
|
+
},
|
|
104
|
+
removed: ({ relativeUrl }) => {
|
|
105
|
+
devServerFileChangeCallback({ relativeUrl, event: "removed" })
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
)
|
|
109
|
+
signal.addEventListener("abort", () => {
|
|
110
|
+
unregisterDevServerFilesWatcher()
|
|
111
|
+
})
|
|
69
112
|
return {
|
|
70
113
|
origin: `${protocol}://127.0.0.1:${port}`,
|
|
71
114
|
stop: () => {
|
|
72
|
-
|
|
115
|
+
unregisterDevServerFilesWatcher()
|
|
116
|
+
reloadableProcess.stop()
|
|
73
117
|
},
|
|
74
118
|
}
|
|
75
119
|
}
|
|
76
120
|
|
|
77
|
-
const logger = createLogger({ logLevel })
|
|
78
121
|
const startServerTask = createTaskLog(logger, "start server")
|
|
79
122
|
|
|
80
|
-
const
|
|
123
|
+
const clientFileChangeCallbackList = []
|
|
124
|
+
const clientFilesPruneCallbackList = []
|
|
125
|
+
const clientFileChangeCallback = ({ relativeUrl, event }) => {
|
|
126
|
+
const url = new URL(relativeUrl, rootDirectoryUrl).href
|
|
127
|
+
clientFileChangeCallbackList.forEach((callback) => {
|
|
128
|
+
callback({ url, event })
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
const stopWatchingClientFiles = registerDirectoryLifecycle(rootDirectoryUrl, {
|
|
132
|
+
watchPatterns: clientFiles,
|
|
133
|
+
cooldownBetweenFileEvents,
|
|
134
|
+
keepProcessAlive: false,
|
|
135
|
+
recursive: true,
|
|
136
|
+
added: ({ relativeUrl }) => {
|
|
137
|
+
clientFileChangeCallback({ event: "added", relativeUrl })
|
|
138
|
+
},
|
|
139
|
+
updated: ({ relativeUrl }) => {
|
|
140
|
+
clientFileChangeCallback({ event: "modified", relativeUrl })
|
|
141
|
+
},
|
|
142
|
+
removed: ({ relativeUrl }) => {
|
|
143
|
+
clientFileChangeCallback({ event: "removed", relativeUrl })
|
|
144
|
+
},
|
|
145
|
+
})
|
|
146
|
+
const urlGraph = createUrlGraph({
|
|
147
|
+
clientFileChangeCallbackList,
|
|
148
|
+
clientFilesPruneCallbackList,
|
|
149
|
+
})
|
|
81
150
|
const kitchen = createKitchen({
|
|
82
151
|
signal,
|
|
83
152
|
logger,
|
|
@@ -97,7 +166,9 @@ export const startDevServer = async ({
|
|
|
97
166
|
nodeEsmResolution,
|
|
98
167
|
fileSystemMagicResolution,
|
|
99
168
|
transpilation,
|
|
100
|
-
|
|
169
|
+
clientAutoreload,
|
|
170
|
+
clientFileChangeCallbackList,
|
|
171
|
+
clientFilesPruneCallbackList,
|
|
101
172
|
}),
|
|
102
173
|
jsenvPluginExplorer({
|
|
103
174
|
groups: explorerGroups,
|
|
@@ -106,7 +177,7 @@ export const startDevServer = async ({
|
|
|
106
177
|
],
|
|
107
178
|
})
|
|
108
179
|
const server = await startOmegaServer({
|
|
109
|
-
|
|
180
|
+
logLevel: omegaServerLogLevel,
|
|
110
181
|
keepProcessAlive,
|
|
111
182
|
listenAnyIp,
|
|
112
183
|
port,
|
|
@@ -132,6 +203,9 @@ export const startDevServer = async ({
|
|
|
132
203
|
})
|
|
133
204
|
return {
|
|
134
205
|
origin: server.origin,
|
|
135
|
-
stop:
|
|
206
|
+
stop: () => {
|
|
207
|
+
stopWatchingClientFiles()
|
|
208
|
+
server.stop()
|
|
209
|
+
},
|
|
136
210
|
}
|
|
137
211
|
}
|
package/src/execute/execute.js
CHANGED
|
@@ -89,10 +89,9 @@ export const execute = async ({
|
|
|
89
89
|
}),
|
|
90
90
|
],
|
|
91
91
|
})
|
|
92
|
-
const serverLogger = createLogger({ logLevel: "warn" })
|
|
93
92
|
const server = await startOmegaServer({
|
|
94
93
|
signal: executeOperation.signal,
|
|
95
|
-
|
|
94
|
+
logLevel: "warn",
|
|
96
95
|
rootDirectoryUrl,
|
|
97
96
|
urlGraph,
|
|
98
97
|
kitchen,
|
package/src/omega/kitchen.js
CHANGED
|
@@ -619,7 +619,7 @@ export const createKitchen = ({
|
|
|
619
619
|
},
|
|
620
620
|
)
|
|
621
621
|
}
|
|
622
|
-
const cook = async ({ urlInfo, outDirectoryUrl, ...rest }) => {
|
|
622
|
+
const cook = memoizeCook(async ({ urlInfo, outDirectoryUrl, ...rest }) => {
|
|
623
623
|
outDirectoryUrl = outDirectoryUrl ? String(outDirectoryUrl) : undefined
|
|
624
624
|
|
|
625
625
|
const writeFiles = ({ gotError }) => {
|
|
@@ -654,7 +654,7 @@ export const createKitchen = ({
|
|
|
654
654
|
writeFiles({ gotError: true })
|
|
655
655
|
throw e
|
|
656
656
|
}
|
|
657
|
-
}
|
|
657
|
+
})
|
|
658
658
|
|
|
659
659
|
baseContext.cook = cook
|
|
660
660
|
|
|
@@ -684,6 +684,37 @@ export const createKitchen = ({
|
|
|
684
684
|
}
|
|
685
685
|
}
|
|
686
686
|
|
|
687
|
+
const memoizeCook = (cook) => {
|
|
688
|
+
const pendingDishes = new Map()
|
|
689
|
+
return async (params) => {
|
|
690
|
+
const { urlInfo } = params
|
|
691
|
+
const { url, modifiedTimestamp } = urlInfo
|
|
692
|
+
const pendingDish = pendingDishes.get(url)
|
|
693
|
+
if (pendingDish) {
|
|
694
|
+
if (!modifiedTimestamp) {
|
|
695
|
+
await pendingDish.promise
|
|
696
|
+
return
|
|
697
|
+
}
|
|
698
|
+
if (pendingDish.timestamp > modifiedTimestamp) {
|
|
699
|
+
await pendingDish.promise
|
|
700
|
+
return
|
|
701
|
+
}
|
|
702
|
+
pendingDishes.delete(url)
|
|
703
|
+
}
|
|
704
|
+
const timestamp = Date.now()
|
|
705
|
+
const promise = cook(params)
|
|
706
|
+
pendingDishes.set(url, {
|
|
707
|
+
timestamp,
|
|
708
|
+
promise,
|
|
709
|
+
})
|
|
710
|
+
try {
|
|
711
|
+
await promise
|
|
712
|
+
} finally {
|
|
713
|
+
pendingDishes.delete(url)
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
687
718
|
const applyReferenceEffectsOnUrlInfo = (reference, urlInfo, context) => {
|
|
688
719
|
Object.assign(urlInfo.data, reference.data)
|
|
689
720
|
Object.assign(urlInfo.timing, reference.timing)
|
|
@@ -8,14 +8,13 @@ import {
|
|
|
8
8
|
} from "@jsenv/server"
|
|
9
9
|
import { convertFileSystemErrorToResponseProperties } from "@jsenv/server/src/internal/convertFileSystemErrorToResponseProperties.js"
|
|
10
10
|
import { createCallbackListNotifiedOnce } from "@jsenv/abort"
|
|
11
|
-
import { loggerToLogLevel } from "@jsenv/logger"
|
|
12
11
|
|
|
13
12
|
import { createFileService } from "./server/file_service.js"
|
|
14
13
|
|
|
15
14
|
export const startOmegaServer = async ({
|
|
16
15
|
signal,
|
|
17
16
|
handleSIGINT,
|
|
18
|
-
|
|
17
|
+
logLevel,
|
|
19
18
|
protocol = "http",
|
|
20
19
|
http2 = protocol === "https",
|
|
21
20
|
privateKey,
|
|
@@ -48,7 +47,7 @@ export const startOmegaServer = async ({
|
|
|
48
47
|
stopOnSIGINT: handleSIGINT,
|
|
49
48
|
stopOnInternalError: false,
|
|
50
49
|
keepProcessAlive,
|
|
51
|
-
logLevel
|
|
50
|
+
logLevel,
|
|
52
51
|
startLog: false,
|
|
53
52
|
|
|
54
53
|
protocol,
|
|
@@ -47,6 +47,15 @@ export const createFileService = ({
|
|
|
47
47
|
type: "entry_point",
|
|
48
48
|
specifier: request.ressource,
|
|
49
49
|
})
|
|
50
|
+
const ifNoneMatch = request.headers["if-none-match"]
|
|
51
|
+
if (ifNoneMatch && urlInfo.contentEtag === ifNoneMatch) {
|
|
52
|
+
return {
|
|
53
|
+
status: 304,
|
|
54
|
+
headers: {
|
|
55
|
+
"cache-control": `private,max-age=0,must-revalidate`,
|
|
56
|
+
},
|
|
57
|
+
}
|
|
58
|
+
}
|
|
50
59
|
const referenceFromGraph = urlGraph.inferReference(
|
|
51
60
|
reference.url,
|
|
52
61
|
reference.parentUrl,
|
|
@@ -60,7 +69,7 @@ export const createFileService = ({
|
|
|
60
69
|
[runtimeName]: runtimeVersion,
|
|
61
70
|
},
|
|
62
71
|
})
|
|
63
|
-
let { response, contentType, content } = urlInfo
|
|
72
|
+
let { response, contentType, content, contentEtag } = urlInfo
|
|
64
73
|
if (response) {
|
|
65
74
|
return response
|
|
66
75
|
}
|
|
@@ -71,6 +80,7 @@ export const createFileService = ({
|
|
|
71
80
|
"content-type": contentType,
|
|
72
81
|
"content-length": Buffer.byteLength(content),
|
|
73
82
|
"cache-control": `private,max-age=0,must-revalidate`,
|
|
83
|
+
"eTag": contentEtag,
|
|
74
84
|
},
|
|
75
85
|
body: content,
|
|
76
86
|
timing: urlInfo.timing,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { urlToRelativeUrl } from "@jsenv/filesystem"
|
|
1
|
+
import { bufferToEtag, urlToRelativeUrl } from "@jsenv/filesystem"
|
|
2
|
+
|
|
2
3
|
import { composeTwoSourcemaps } from "@jsenv/utils/sourcemap/sourcemap_composition_v3.js"
|
|
3
4
|
import {
|
|
4
5
|
SOURCEMAP,
|
|
@@ -163,6 +164,7 @@ export const createUrlInfoTransformer = ({
|
|
|
163
164
|
})
|
|
164
165
|
}
|
|
165
166
|
}
|
|
167
|
+
urlInfo.contentEtag = bufferToEtag(Buffer.from(urlInfo.content))
|
|
166
168
|
}
|
|
167
169
|
|
|
168
170
|
return {
|
package/src/omega/url_graph.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { createCallbackList } from "@jsenv/abort"
|
|
2
1
|
import { urlToRelativeUrl } from "@jsenv/filesystem"
|
|
3
2
|
|
|
4
|
-
export const createUrlGraph = (
|
|
3
|
+
export const createUrlGraph = ({
|
|
4
|
+
clientFileChangeCallbackList,
|
|
5
|
+
clientFilesPruneCallbackList,
|
|
6
|
+
} = {}) => {
|
|
5
7
|
const urlInfos = {}
|
|
6
8
|
const getUrlInfo = (url) => urlInfos[url]
|
|
7
9
|
const deleteUrlInfo = (url) => {
|
|
@@ -49,7 +51,6 @@ export const createUrlGraph = () => {
|
|
|
49
51
|
return visitDependents(urlInfo)
|
|
50
52
|
}
|
|
51
53
|
|
|
52
|
-
const prunedCallbackList = createCallbackList()
|
|
53
54
|
const updateReferences = (urlInfo, references) => {
|
|
54
55
|
const dependencyUrls = []
|
|
55
56
|
references.forEach((reference) => {
|
|
@@ -101,7 +102,47 @@ export const createUrlGraph = () => {
|
|
|
101
102
|
if (prunedUrlInfos.length === 0) {
|
|
102
103
|
return
|
|
103
104
|
}
|
|
104
|
-
|
|
105
|
+
prunedUrlInfos.forEach((prunedUrlInfo) => {
|
|
106
|
+
prunedUrlInfo.modifiedTimestamp = Date.now()
|
|
107
|
+
// should we delete?
|
|
108
|
+
// delete urlInfos[prunedUrlInfo.url]
|
|
109
|
+
})
|
|
110
|
+
if (clientFilesPruneCallbackList) {
|
|
111
|
+
clientFilesPruneCallbackList.forEach((callback) => {
|
|
112
|
+
callback({
|
|
113
|
+
firstUrlInfo,
|
|
114
|
+
prunedUrlInfos,
|
|
115
|
+
})
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (clientFileChangeCallbackList) {
|
|
121
|
+
const updateModifiedTimestamp = (urlInfo, modifiedTimestamp) => {
|
|
122
|
+
const seen = []
|
|
123
|
+
const iterate = (urlInfo) => {
|
|
124
|
+
if (seen.includes(urlInfo.url)) {
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
seen.push(urlInfo.url)
|
|
128
|
+
urlInfo.modifiedTimestamp = modifiedTimestamp
|
|
129
|
+
urlInfo.dependents.forEach((dependentUrl) => {
|
|
130
|
+
const dependentUrlInfo = urlInfos[dependentUrl]
|
|
131
|
+
const { hotAcceptDependencies = [] } = dependentUrlInfo.data
|
|
132
|
+
if (!hotAcceptDependencies.includes(urlInfo.url)) {
|
|
133
|
+
iterate(dependentUrlInfo)
|
|
134
|
+
}
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
iterate(urlInfo)
|
|
138
|
+
}
|
|
139
|
+
clientFileChangeCallbackList.push(({ url }) => {
|
|
140
|
+
const urlInfo = urlInfos[url]
|
|
141
|
+
if (urlInfo) {
|
|
142
|
+
updateModifiedTimestamp(urlInfo, Date.now())
|
|
143
|
+
urlInfo.contentEtag = null
|
|
144
|
+
}
|
|
145
|
+
})
|
|
105
146
|
}
|
|
106
147
|
|
|
107
148
|
return {
|
|
@@ -111,8 +152,6 @@ export const createUrlGraph = () => {
|
|
|
111
152
|
deleteUrlInfo,
|
|
112
153
|
inferReference,
|
|
113
154
|
findDependent,
|
|
114
|
-
|
|
115
|
-
prunedCallbackList,
|
|
116
155
|
updateReferences,
|
|
117
156
|
|
|
118
157
|
toJSON: (rootDirectoryUrl) => {
|
|
@@ -133,6 +172,7 @@ export const createUrlGraph = () => {
|
|
|
133
172
|
|
|
134
173
|
const createUrlInfo = (url) => {
|
|
135
174
|
return {
|
|
175
|
+
modifiedTimestamp: 0,
|
|
136
176
|
data: {}, // plugins can put whatever they want here
|
|
137
177
|
references: [],
|
|
138
178
|
dependencies: new Set(),
|
|
@@ -148,6 +188,7 @@ const createUrlInfo = (url) => {
|
|
|
148
188
|
external: false,
|
|
149
189
|
originalContent: undefined,
|
|
150
190
|
content: undefined,
|
|
191
|
+
contentEtag: null,
|
|
151
192
|
sourcemap: null,
|
|
152
193
|
sourcemapReference: null,
|
|
153
194
|
timing: {},
|
|
@@ -6,10 +6,12 @@ import { createSSEService } from "@jsenv/utils/event_source/sse_service.js"
|
|
|
6
6
|
export const jsenvPluginDevSSEServer = ({
|
|
7
7
|
rootDirectoryUrl,
|
|
8
8
|
urlGraph,
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
clientFileChangeCallbackList,
|
|
10
|
+
clientFilesPruneCallbackList,
|
|
11
11
|
}) => {
|
|
12
12
|
const serverEventCallbackList = createCallbackList()
|
|
13
|
+
const sseService = createSSEService({ serverEventCallbackList })
|
|
14
|
+
|
|
13
15
|
const notifyDeclined = ({ cause, reason, declinedBy }) => {
|
|
14
16
|
serverEventCallbackList.notify({
|
|
15
17
|
type: "reload",
|
|
@@ -32,25 +34,6 @@ export const jsenvPluginDevSSEServer = ({
|
|
|
32
34
|
}),
|
|
33
35
|
})
|
|
34
36
|
}
|
|
35
|
-
const updateHmrTimestamp = (urlInfo, hmrTimestamp) => {
|
|
36
|
-
const urlInfos = urlGraph.urlInfos
|
|
37
|
-
const seen = []
|
|
38
|
-
const iterate = (urlInfo) => {
|
|
39
|
-
if (seen.includes(urlInfo.url)) {
|
|
40
|
-
return
|
|
41
|
-
}
|
|
42
|
-
seen.push(urlInfo.url)
|
|
43
|
-
urlInfo.data.hmrTimestamp = hmrTimestamp
|
|
44
|
-
urlInfo.dependents.forEach((dependentUrl) => {
|
|
45
|
-
const dependentUrlInfo = urlInfos[dependentUrl]
|
|
46
|
-
const { hotAcceptDependencies = [] } = dependentUrlInfo.data
|
|
47
|
-
if (!hotAcceptDependencies.includes(urlInfo.url)) {
|
|
48
|
-
iterate(dependentUrlInfo, hmrTimestamp)
|
|
49
|
-
}
|
|
50
|
-
})
|
|
51
|
-
}
|
|
52
|
-
iterate(urlInfo)
|
|
53
|
-
}
|
|
54
37
|
const propagateUpdate = (firstUrlInfo) => {
|
|
55
38
|
const urlInfos = urlGraph.urlInfos
|
|
56
39
|
const iterate = (urlInfo, trace) => {
|
|
@@ -129,41 +112,29 @@ export const jsenvPluginDevSSEServer = ({
|
|
|
129
112
|
const trace = []
|
|
130
113
|
return iterate(firstUrlInfo, trace)
|
|
131
114
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
notifyAccepted({
|
|
154
|
-
cause: `${relativeUrl} ${event}`,
|
|
155
|
-
reason: hotUpdate.reason,
|
|
156
|
-
instructions: hotUpdate.instructions,
|
|
157
|
-
})
|
|
158
|
-
}
|
|
159
|
-
},
|
|
115
|
+
clientFileChangeCallbackList.push(({ url, event }) => {
|
|
116
|
+
const urlInfo = urlGraph.urlInfos[url]
|
|
117
|
+
// file not part of dependency graph
|
|
118
|
+
if (!urlInfo) {
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
const relativeUrl = urlToRelativeUrl(url, rootDirectoryUrl)
|
|
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
|
+
}
|
|
160
136
|
})
|
|
161
|
-
|
|
162
|
-
prunedUrlInfos.forEach((prunedUrlInfo) => {
|
|
163
|
-
prunedUrlInfo.data.hmrTimestamp = Date.now()
|
|
164
|
-
// should we delete instead?
|
|
165
|
-
// delete urlGraph.urlInfos[prunedUrlInfo.url]
|
|
166
|
-
})
|
|
137
|
+
clientFilesPruneCallbackList.push(({ prunedUrlInfos, firstUrlInfo }) => {
|
|
167
138
|
const mainHotUpdate = propagateUpdate(firstUrlInfo)
|
|
168
139
|
const cause = `following files are no longer referenced: ${prunedUrlInfos.map(
|
|
169
140
|
(prunedUrlInfo) => urlToRelativeUrl(prunedUrlInfo.url, rootDirectoryUrl),
|
|
@@ -207,7 +178,7 @@ export const jsenvPluginDevSSEServer = ({
|
|
|
207
178
|
return {
|
|
208
179
|
name: "jsenv:sse_server",
|
|
209
180
|
appliesDuring: { dev: true },
|
|
210
|
-
serve: (request
|
|
181
|
+
serve: (request) => {
|
|
211
182
|
if (request.ressource === "/__graph__") {
|
|
212
183
|
const graphJson = JSON.stringify(urlGraph.toJSON(rootDirectoryUrl))
|
|
213
184
|
return {
|
|
@@ -6,8 +6,8 @@ export const jsenvPluginAutoreload = ({
|
|
|
6
6
|
rootDirectoryUrl,
|
|
7
7
|
urlGraph,
|
|
8
8
|
scenario,
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
clientFileChangeCallbackList,
|
|
10
|
+
clientFilesPruneCallbackList,
|
|
11
11
|
}) => {
|
|
12
12
|
if (scenario === "build") {
|
|
13
13
|
return []
|
|
@@ -20,8 +20,8 @@ export const jsenvPluginAutoreload = ({
|
|
|
20
20
|
jsenvPluginDevSSEServer({
|
|
21
21
|
rootDirectoryUrl,
|
|
22
22
|
urlGraph,
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
clientFileChangeCallbackList,
|
|
24
|
+
clientFilesPruneCallbackList,
|
|
25
25
|
}),
|
|
26
26
|
]
|
|
27
27
|
}
|
|
@@ -23,12 +23,12 @@ export const jsenvPluginHmr = () => {
|
|
|
23
23
|
return null
|
|
24
24
|
}
|
|
25
25
|
const urlInfo = context.urlGraph.getUrlInfo(reference.url)
|
|
26
|
-
if (!urlInfo.
|
|
26
|
+
if (!urlInfo.modifiedTimestamp) {
|
|
27
27
|
return null
|
|
28
28
|
}
|
|
29
29
|
return {
|
|
30
30
|
hmr: "",
|
|
31
|
-
v: urlInfo.
|
|
31
|
+
v: urlInfo.modifiedTimestamp,
|
|
32
32
|
}
|
|
33
33
|
},
|
|
34
34
|
}
|
package/src/plugins/plugins.js
CHANGED
|
@@ -31,14 +31,13 @@ export const getCorePlugins = ({
|
|
|
31
31
|
minification = false,
|
|
32
32
|
bundling = false,
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
clientAutoreload = false,
|
|
35
|
+
clientFileChangeCallbackList,
|
|
36
|
+
clientFilesPruneCallbackList,
|
|
35
37
|
} = {}) => {
|
|
36
38
|
if (htmlSupervisor === true) {
|
|
37
39
|
htmlSupervisor = {}
|
|
38
40
|
}
|
|
39
|
-
if (autoreload === true) {
|
|
40
|
-
autoreload = {}
|
|
41
|
-
}
|
|
42
41
|
if (nodeEsmResolution === true) {
|
|
43
42
|
nodeEsmResolution = {}
|
|
44
43
|
}
|
|
@@ -75,13 +74,14 @@ export const getCorePlugins = ({
|
|
|
75
74
|
jsenvPluginMinification(minification),
|
|
76
75
|
|
|
77
76
|
jsenvPluginImportMetaHot(),
|
|
78
|
-
...(
|
|
77
|
+
...(clientAutoreload
|
|
79
78
|
? [
|
|
80
79
|
jsenvPluginAutoreload({
|
|
81
80
|
rootDirectoryUrl,
|
|
82
81
|
urlGraph,
|
|
83
82
|
scenario,
|
|
84
|
-
|
|
83
|
+
clientFileChangeCallbackList,
|
|
84
|
+
clientFilesPruneCallbackList,
|
|
85
85
|
}),
|
|
86
86
|
]
|
|
87
87
|
: []),
|
package/src/test/execute_plan.js
CHANGED
|
@@ -11,11 +11,7 @@ import {
|
|
|
11
11
|
urlToMeta,
|
|
12
12
|
writeFileSync,
|
|
13
13
|
} from "@jsenv/filesystem"
|
|
14
|
-
import {
|
|
15
|
-
createLogger,
|
|
16
|
-
createDetailedMessage,
|
|
17
|
-
loggerToLevels,
|
|
18
|
-
} from "@jsenv/logger"
|
|
14
|
+
import { createDetailedMessage, loggerToLevels } from "@jsenv/logger"
|
|
19
15
|
import { createLog, startSpinner } from "@jsenv/log"
|
|
20
16
|
import { Abort, raceProcessTeardownEvents } from "@jsenv/abort"
|
|
21
17
|
|
|
@@ -173,10 +169,9 @@ export const executePlan = async (
|
|
|
173
169
|
}),
|
|
174
170
|
],
|
|
175
171
|
})
|
|
176
|
-
const serverLogger = createLogger({ logLevel: "warn" })
|
|
177
172
|
const server = await startOmegaServer({
|
|
178
173
|
signal: multipleExecutionsOperation.signal,
|
|
179
|
-
|
|
174
|
+
logLevel: "warn",
|
|
180
175
|
rootDirectoryUrl,
|
|
181
176
|
urlGraph,
|
|
182
177
|
kitchen,
|