@jsenv/core 27.0.0-alpha.44 → 27.0.0-alpha.47

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.
@@ -5,27 +5,24 @@
5
5
  "../src/plugins/html_supervisor/client/html_supervisor_setup.js"
6
6
  ],
7
7
  "sourcesContent": [
8
- "window.__html_supervisor__ = {\n // \"html_supervisor_installer.js\" will implement\n // - \"addExecution\"\n // - \"collectScriptResults\"\n // - \"superviseScriptTypeModule\"\n // and take all executions in \"executions\" and implement their supervision\n executions: [],\n addExecution: (execution) => {\n window.__html_supervisor__.executions.push(execution)\n },\n collectScriptResults: () => {\n throw new Error(\"htmlSupervisor not installed\")\n },\n superviseScriptTypeModule: () => {\n throw new Error(\"htmlSupervisor not installed\")\n },\n superviseScript: ({ src, crossorigin, integrity }) => {\n window.__html_supervisor__.addExecution({\n type: \"js_classic\",\n improveErrorWithFetch: true,\n currentScript: document.currentScript,\n src,\n promise: new Promise((resolve, reject) => {\n const script = document.createElement(\"script\")\n if (crossorigin) {\n script.crossorigin = crossorigin\n }\n if (integrity) {\n script.integrity = integrity\n }\n script.src = src\n const scriptUrl = new URL(src, window.location).href\n let lastWindowErrorUrl\n let lastWindowError\n const windowErrorCallback = (e) => {\n lastWindowErrorUrl = e.filename\n lastWindowError = e.error\n }\n const cleanup = () => {\n document.body.removeChild(script)\n window.removeEventListener(\"error\", windowErrorCallback)\n }\n window.addEventListener(\"error\", windowErrorCallback)\n script.addEventListener(\"error\", () => {\n cleanup()\n reject(src)\n })\n script.addEventListener(\"load\", () => {\n cleanup()\n if (lastWindowErrorUrl === scriptUrl) {\n reject(lastWindowError)\n } else {\n resolve()\n }\n })\n document.body.appendChild(script)\n }),\n })\n },\n getScriptExecutionResults: () => {\n // wait for page to load before collecting script execution results\n const htmlReadyPromise = new Promise((resolve) => {\n if (document.readyState === \"complete\") {\n resolve()\n return\n }\n const loadCallback = () => {\n window.removeEventListener(\"load\", loadCallback)\n resolve()\n }\n window.addEventListener(\"load\", loadCallback)\n })\n return htmlReadyPromise.then(() => {\n return window.__html_supervisor__.collectScriptResults()\n })\n },\n}\n"
8
+ "window.__html_supervisor__ = {\n // \"html_supervisor_installer.js\" will implement\n // - \"addScriptToExecute\"\n // - \"superviseScriptTypeModule\"\n // - \"collectScriptResults\"\n // and take all executions in \"scriptsToExecute\" and implement their supervision\n scriptsToExecute: [],\n addScriptToExecute: (scriptToExecute) => {\n window.__html_supervisor__.scriptsToExecute.push(scriptToExecute)\n },\n superviseScript: ({ src, isInline, crossorigin, integrity }) => {\n window.__html_supervisor__.addScriptToExecute({\n src,\n type: \"js_classic\",\n isInline,\n currentScript: document.currentScript,\n execute: () => {\n return new Promise((resolve, reject) => {\n const script = document.createElement(\"script\")\n if (crossorigin) {\n script.crossorigin = crossorigin\n }\n if (integrity) {\n script.integrity = integrity\n }\n script.src = src\n const scriptUrl = new URL(src, window.location).href\n let lastWindowErrorUrl\n let lastWindowError\n const windowErrorCallback = (e) => {\n lastWindowErrorUrl = e.filename\n lastWindowError = e.error\n }\n const cleanup = () => {\n document.body.removeChild(script)\n window.removeEventListener(\"error\", windowErrorCallback)\n }\n window.addEventListener(\"error\", windowErrorCallback)\n script.addEventListener(\"error\", () => {\n cleanup()\n reject(src)\n })\n script.addEventListener(\"load\", () => {\n cleanup()\n if (lastWindowErrorUrl === scriptUrl) {\n reject(lastWindowError)\n } else {\n resolve()\n }\n })\n document.body.appendChild(script)\n })\n },\n })\n },\n superviseScriptTypeModule: () => {\n throw new Error(\"htmlSupervisor not installed\")\n },\n getScriptExecutionResults: () => {\n // wait for page to load before collecting script execution results\n const htmlReadyPromise = new Promise((resolve) => {\n if (document.readyState === \"complete\") {\n resolve()\n return\n }\n const loadCallback = () => {\n window.removeEventListener(\"load\", loadCallback)\n resolve()\n }\n window.addEventListener(\"load\", loadCallback)\n })\n return htmlReadyPromise.then(() => {\n return window.__html_supervisor__.collectScriptResults()\n })\n },\n collectScriptResults: () => {\n throw new Error(\"htmlSupervisor not installed\")\n },\n}\n"
9
9
  ],
10
10
  "names": [
11
11
  "window",
12
12
  "__html_supervisor__",
13
- "executions",
14
- "addExecution",
15
- "execution",
13
+ "scriptsToExecute",
14
+ "addScriptToExecute",
15
+ "scriptToExecute",
16
16
  "push",
17
- "collectScriptResults",
18
- "Error",
19
- "superviseScriptTypeModule",
20
17
  "superviseScript",
21
18
  "src",
19
+ "isInline",
22
20
  "crossorigin",
23
21
  "integrity",
24
22
  "type",
25
- "improveErrorWithFetch",
26
23
  "currentScript",
27
24
  "document",
28
- "promise",
25
+ "execute",
29
26
  "Promise",
30
27
  "resolve",
31
28
  "reject",
@@ -47,11 +44,14 @@
47
44
  "removeEventListener",
48
45
  "addEventListener",
49
46
  "appendChild",
47
+ "superviseScriptTypeModule",
48
+ "Error",
50
49
  "getScriptExecutionResults",
51
50
  "htmlReadyPromise",
52
51
  "readyState",
53
52
  "loadCallback",
54
- "then"
53
+ "then",
54
+ "collectScriptResults"
55
55
  ],
56
- "mappings": "AAAAA,MAAM,CAACC,mBAAP,GAA6B;AAC3B;AACA;AACA;AACA;AACA;AACAC,EAAAA,UAAU,EAAE,EANe;EAO3BC,YAAY,EAAGC,SAAD,IAAe;AAC3BJ,IAAAA,MAAM,CAACC,mBAAP,CAA2BC,UAA3B,CAAsCG,IAAtC,CAA2CD,SAA3C,CAAA,CAAA;GARyB;AAU3BE,EAAAA,oBAAoB,EAAE,MAAM;AAC1B,IAAA,MAAM,IAAIC,KAAJ,CAAU,8BAAV,CAAN,CAAA;GAXyB;AAa3BC,EAAAA,yBAAyB,EAAE,MAAM;AAC/B,IAAA,MAAM,IAAID,KAAJ,CAAU,8BAAV,CAAN,CAAA;GAdyB;AAgB3BE,EAAAA,eAAe,EAAE,CAAC;IAAEC,GAAF;IAAOC,WAAP;AAAoBC,IAAAA,SAAAA;AAApB,GAAD,KAAqC;AACpDZ,IAAAA,MAAM,CAACC,mBAAP,CAA2BE,YAA3B,CAAwC;AACtCU,MAAAA,IAAI,EAAE,YADgC;AAEtCC,MAAAA,qBAAqB,EAAE,IAFe;MAGtCC,aAAa,EAAEC,QAAQ,CAACD,aAHc;MAItCL,GAJsC;MAKtCO,OAAO,EAAE,IAAIC,OAAJ,CAAY,CAACC,OAAD,EAAUC,MAAV,KAAqB;AACxC,QAAA,MAAMC,MAAM,GAAGL,QAAQ,CAACM,aAAT,CAAuB,QAAvB,CAAf,CAAA;;AACA,QAAA,IAAIX,WAAJ,EAAiB;UACfU,MAAM,CAACV,WAAP,GAAqBA,WAArB,CAAA;AACD,SAAA;;AACD,QAAA,IAAIC,SAAJ,EAAe;UACbS,MAAM,CAACT,SAAP,GAAmBA,SAAnB,CAAA;AACD,SAAA;;QACDS,MAAM,CAACX,GAAP,GAAaA,GAAb,CAAA;QACA,MAAMa,SAAS,GAAG,IAAIC,GAAJ,CAAQd,GAAR,EAAaV,MAAM,CAACyB,QAApB,CAAA,CAA8BC,IAAhD,CAAA;AACA,QAAA,IAAIC,kBAAJ,CAAA;AACA,QAAA,IAAIC,eAAJ,CAAA;;QACA,MAAMC,mBAAmB,GAAIC,CAAD,IAAO;UACjCH,kBAAkB,GAAGG,CAAC,CAACC,QAAvB,CAAA;UACAH,eAAe,GAAGE,CAAC,CAACE,KAApB,CAAA;SAFF,CAAA;;QAIA,MAAMC,OAAO,GAAG,MAAM;AACpBjB,UAAAA,QAAQ,CAACkB,IAAT,CAAcC,WAAd,CAA0Bd,MAA1B,CAAA,CAAA;AACArB,UAAAA,MAAM,CAACoC,mBAAP,CAA2B,OAA3B,EAAoCP,mBAApC,CAAA,CAAA;SAFF,CAAA;;AAIA7B,QAAAA,MAAM,CAACqC,gBAAP,CAAwB,OAAxB,EAAiCR,mBAAjC,CAAA,CAAA;AACAR,QAAAA,MAAM,CAACgB,gBAAP,CAAwB,OAAxB,EAAiC,MAAM;UACrCJ,OAAO,EAAA,CAAA;UACPb,MAAM,CAACV,GAAD,CAAN,CAAA;SAFF,CAAA,CAAA;AAIAW,QAAAA,MAAM,CAACgB,gBAAP,CAAwB,MAAxB,EAAgC,MAAM;UACpCJ,OAAO,EAAA,CAAA;;UACP,IAAIN,kBAAkB,KAAKJ,SAA3B,EAAsC;YACpCH,MAAM,CAACQ,eAAD,CAAN,CAAA;AACD,WAFD,MAEO;YACLT,OAAO,EAAA,CAAA;AACR,WAAA;SANH,CAAA,CAAA;AAQAH,QAAAA,QAAQ,CAACkB,IAAT,CAAcI,WAAd,CAA0BjB,MAA1B,CAAA,CAAA;OAjCO,CAAA;KALX,CAAA,CAAA;GAjByB;AA2D3BkB,EAAAA,yBAAyB,EAAE,MAAM;AAC/B;AACA,IAAA,MAAMC,gBAAgB,GAAG,IAAItB,OAAJ,CAAaC,OAAD,IAAa;AAChD,MAAA,IAAIH,QAAQ,CAACyB,UAAT,KAAwB,UAA5B,EAAwC;QACtCtB,OAAO,EAAA,CAAA;AACP,QAAA,OAAA;AACD,OAAA;;MACD,MAAMuB,YAAY,GAAG,MAAM;AACzB1C,QAAAA,MAAM,CAACoC,mBAAP,CAA2B,MAA3B,EAAmCM,YAAnC,CAAA,CAAA;QACAvB,OAAO,EAAA,CAAA;OAFT,CAAA;;AAIAnB,MAAAA,MAAM,CAACqC,gBAAP,CAAwB,MAAxB,EAAgCK,YAAhC,CAAA,CAAA;AACD,KAVwB,CAAzB,CAAA;AAWA,IAAA,OAAOF,gBAAgB,CAACG,IAAjB,CAAsB,MAAM;AACjC,MAAA,OAAO3C,MAAM,CAACC,mBAAP,CAA2BK,oBAA3B,EAAP,CAAA;AACD,KAFM,CAAP,CAAA;AAGD,GAAA;AA3E0B,CAA7B"
56
+ "mappings": "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"
57
57
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/core",
3
- "version": "27.0.0-alpha.44",
3
+ "version": "27.0.0-alpha.47",
4
4
  "description": "Tool to develop, test and build js projects",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -60,15 +60,15 @@
60
60
  "@financial-times/polyfill-useragent-normaliser": "2.0.1",
61
61
  "@jsenv/abort": "4.1.2",
62
62
  "@jsenv/babel-plugins": "1.0.2",
63
- "@jsenv/filesystem": "3.1.0",
63
+ "@jsenv/filesystem": "3.2.2",
64
64
  "@jsenv/importmap": "1.2.0",
65
65
  "@jsenv/integrity": "0.0.1",
66
66
  "@jsenv/log": "1.5.2",
67
67
  "@jsenv/logger": "4.0.1",
68
68
  "@jsenv/node-esm-resolution": "0.0.6",
69
- "@jsenv/server": "12.6.1",
69
+ "@jsenv/server": "12.6.2",
70
70
  "@jsenv/uneval": "1.6.0",
71
- "@jsenv/utils": "1.6.2",
71
+ "@jsenv/utils": "1.7.0",
72
72
  "construct-style-sheets-polyfill": "3.1.0",
73
73
  "cssnano": "5.1.7",
74
74
  "cssnano-preset-default": "5.2.7",
@@ -21,13 +21,15 @@ import {
21
21
  pluginCORS,
22
22
  fetchFileSystem,
23
23
  } from "@jsenv/server"
24
- import { assertAndNormalizeDirectoryUrl } from "@jsenv/filesystem"
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 { initProcessAutorestart } from "@jsenv/utils/file_watcher/process_auto_restart.js"
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 autorestartProcess = await initProcessAutorestart({
58
- signal,
70
+ const reloadableProcess = await initReloadableProcess({
59
71
  handleSIGINT,
60
- ...(autorestart
72
+ ...(buildServerAutoreload
61
73
  ? {
62
74
  enabled: true,
63
- logLevel: autorestart.logLevel,
64
- urlToRestart: autorestart.url,
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 (autorestartProcess.isPrimary) {
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
- autorestartProcess.stop()
117
+ stopWatchingBuildServerFiles()
118
+
119
+ reloadableProcess.stop()
80
120
  },
81
121
  }
82
122
  }
83
- signal = autorestartProcess.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
- const unregisterDirectoryLifecyle = watchFiles({
181
- rootDirectoryUrl,
182
- watchedFilePatterns,
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
- fileChangeCallback: ({ url, event }) => {
185
- buildAbortController.abort()
186
- // setTimeout is to ensure the abortController.abort() above
187
- // is properly taken into account so that logs about abort comes first
188
- // then logs about re-running the build happens
189
- setTimeout(() => {
190
- logger.info(`${url.slice(rootDirectoryUrl.length)} ${event} -> rebuild`)
191
- runBuild()
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: () => server.stop(),
248
+ stop: () => {
249
+ stopWatchingClientFiles()
250
+ server.stop()
251
+ },
202
252
  }
203
253
  }
204
254
 
@@ -1,7 +1,10 @@
1
- import { assertAndNormalizeDirectoryUrl } from "@jsenv/filesystem"
1
+ import {
2
+ assertAndNormalizeDirectoryUrl,
3
+ registerDirectoryLifecycle,
4
+ } from "@jsenv/filesystem"
2
5
  import { createLogger } from "@jsenv/logger"
3
6
 
4
- import { initProcessAutorestart } from "@jsenv/utils/file_watcher/process_auto_restart.js"
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 autorestartProcess = await initProcessAutorestart({
67
+ const reloadableProcess = await initReloadableProcess({
51
68
  signal,
52
69
  handleSIGINT,
53
- ...(autorestart
70
+ ...(devServerAutoreload
54
71
  ? {
55
72
  enabled: true,
56
- logLevel: autorestart.logLevel,
57
- urlToRestart: autorestart.url,
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 (autorestartProcess.isPrimary) {
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
- autorestartProcess.stop()
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 urlGraph = createUrlGraph()
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
- autoreload,
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
- logger,
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: server.stop,
206
+ stop: () => {
207
+ stopWatchingClientFiles()
208
+ server.stop()
209
+ },
136
210
  }
137
211
  }
@@ -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
- logger: serverLogger,
94
+ logLevel: "warn",
96
95
  rootDirectoryUrl,
97
96
  urlGraph,
98
97
  kitchen,
@@ -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
- logger,
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: loggerToLogLevel(logger),
50
+ logLevel,
52
51
  startLog: false,
53
52
 
54
53
  protocol,
@@ -47,11 +47,24 @@ 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,
53
62
  )
54
63
  try {
64
+ // urlInfo objects are reused, they must be "reset" before cooking then again
65
+ if (!urlInfo.isInline) {
66
+ urlGraph.resetUrlInfo(urlInfo)
67
+ }
55
68
  await kitchen.cook({
56
69
  reference: referenceFromGraph || reference,
57
70
  urlInfo,
@@ -60,7 +73,7 @@ export const createFileService = ({
60
73
  [runtimeName]: runtimeVersion,
61
74
  },
62
75
  })
63
- let { response, contentType, content } = urlInfo
76
+ let { response, contentType, content, contentEtag } = urlInfo
64
77
  if (response) {
65
78
  return response
66
79
  }
@@ -71,6 +84,7 @@ export const createFileService = ({
71
84
  "content-type": contentType,
72
85
  "content-length": Buffer.byteLength(content),
73
86
  "cache-control": `private,max-age=0,must-revalidate`,
87
+ "eTag": contentEtag,
74
88
  },
75
89
  body: content,
76
90
  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 {