akanjs 3.0.0-beta.24 → 3.0.0-beta.26

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.
Files changed (45) hide show
  1. package/common/hmrPhase.ts +2 -0
  2. package/package.json +1 -1
  3. package/server/SSR_MEMORY_DIAGNOSIS.md +2 -2
  4. package/server/akanApp.ts +1 -0
  5. package/server/akanServer.ts +7 -1
  6. package/server/artifact/builderRpc.ts +13 -2
  7. package/server/artifact/devBuildRecovery.ts +12 -0
  8. package/server/artifact/index.ts +1 -0
  9. package/server/artifact/ipcTypes.ts +46 -3
  10. package/server/artifact/routeClientCache.ts +128 -29
  11. package/server/di/diLifecycle.ts +1 -1
  12. package/server/hmr/clientScript.ts +148 -86
  13. package/server/hmr/csrDevManifest.ts +17 -0
  14. package/server/hmr/csrDevRuntime.ts +238 -18
  15. package/server/hmr/csrDevShell.ts +90 -10
  16. package/server/hmr/devArtifactPruner.ts +77 -0
  17. package/server/hmr/devHmrController.ts +233 -137
  18. package/server/hmr/ssrDevShim.ts +112 -0
  19. package/server/hmr/ssrUpdateQueue.ts +111 -0
  20. package/server/hmr/wsHub.ts +19 -4
  21. package/server/routing/apiRouter.ts +12 -0
  22. package/server/rscWorker.tsx +11 -1
  23. package/server/rscWorkerHost.ts +223 -59
  24. package/server/systemPageDocument.tsx +6 -0
  25. package/server/types.tsx +4 -0
  26. package/server/webRouter.ts +57 -22
  27. package/types/common/hmrPhase.d.ts +2 -0
  28. package/types/server/artifact/builderRpc.d.ts +3 -2
  29. package/types/server/artifact/devBuildRecovery.d.ts +4 -0
  30. package/types/server/artifact/index.d.ts +1 -0
  31. package/types/server/artifact/ipcTypes.d.ts +47 -2
  32. package/types/server/artifact/routeClientCache.d.ts +9 -2
  33. package/types/server/hmr/csrDevManifest.d.ts +15 -0
  34. package/types/server/hmr/csrDevRuntime.d.ts +34 -0
  35. package/types/server/hmr/csrDevShell.d.ts +9 -1
  36. package/types/server/hmr/devArtifactPruner.d.ts +9 -0
  37. package/types/server/hmr/devHmrController.d.ts +8 -5
  38. package/types/server/hmr/ssrDevShim.d.ts +7 -0
  39. package/types/server/hmr/ssrUpdateQueue.d.ts +23 -0
  40. package/types/server/hmr/wsHub.d.ts +16 -7
  41. package/types/server/routing/apiRouter.d.ts +8 -0
  42. package/types/server/rscWorkerHost.d.ts +14 -2
  43. package/types/server/systemPageDocument.d.ts +2 -0
  44. package/types/server/types.d.ts +4 -0
  45. package/types/server/webRouter.d.ts +8 -2
@@ -1,3 +1,4 @@
1
+ import { SSR_DEV_ROUTE_PREFIX } from "./csrDevManifest";
1
2
  import { isSyncNavigationEnabled } from "./wsHub";
2
3
 
3
4
  export const HMR_CLIENT_SCRIPT = `(function(){
@@ -6,6 +7,7 @@ export const HMR_CLIENT_SCRIPT = `(function(){
6
7
  var syncNavigationEnabled = ${JSON.stringify(isSyncNavigationEnabled())};
7
8
  var syncNavigationClientId = Math.random().toString(36).slice(2) + Date.now().toString(36);
8
9
  var clientKind = self.__AKAN_HMR_CLIENT__ === "csr" ? "csr" : "ssr";
10
+ var systemPage = !!self.__AKAN_HMR_SYSTEM_PAGE__;
9
11
  var proto = location.protocol === "https:" ? "wss:" : "ws:";
10
12
  var url = proto + "//" + location.host + "/_akan/hmr" + (clientKind === "csr" ? "?client=csr" : "");
11
13
  var attempts = 0;
@@ -14,7 +16,6 @@ export const HMR_CLIENT_SCRIPT = `(function(){
14
16
  var refreshRuntimePromise = null;
15
17
  var refreshRuntime = null;
16
18
  var pendingRefreshRegistrations = [];
17
- var refreshQueue = Promise.resolve();
18
19
  var overlayEl = null;
19
20
  var overlayLabelEl = null;
20
21
  var overlayDetailEl = null;
@@ -24,7 +25,7 @@ export const HMR_CLIENT_SCRIPT = `(function(){
24
25
  var overlayNextToken = 1;
25
26
  var overlayJobs = {};
26
27
  var buildErrorStates = {};
27
- self.__AKAN_HMR_PHASE__ = null;
28
+ var traces = self.__AKAN_HMR_TRACES__ = self.__AKAN_HMR_TRACES__ || [];
28
29
  self.__AKAN_DEV_SYNC_NAVIGATION__ = function(href, kind){
29
30
  if (self.__AKAN_DEV_SYNC_NAVIGATION_APPLYING__ || !syncNavigationEnabled || !socket || socket.readyState !== WebSocket.OPEN) return;
30
31
  try {
@@ -44,28 +45,54 @@ export const HMR_CLIENT_SCRIPT = `(function(){
44
45
  else pendingRefreshRegistrations.push([type, id]);
45
46
  };
46
47
  self.$RefreshSig$ = self.$RefreshSig$ || function(){ return function(type){ return type; }; };
47
- if (clientKind === "ssr") ensureRefreshRuntime().catch(function(err){
48
+ if (clientKind === "ssr" && !systemPage) ensureRefreshRuntime().catch(function(err){
48
49
  console.warn("[akan-hmr] React Refresh runtime preload failed", err);
49
50
  });
50
51
 
51
52
  function connect(){
52
53
  try { socket = new WebSocket(url); }
53
54
  catch(e){ console.error("[akan-hmr] ws init failed", e); schedule(); return; }
54
- socket.addEventListener("open", function(){ attempts = 0; });
55
55
  socket.addEventListener("message", function(ev){
56
56
  var msg;
57
57
  try { msg = JSON.parse(ev.data); } catch (e){ return; }
58
58
  if (!msg || typeof msg.type !== "string") return;
59
59
  if (msg.type === "hello") {
60
+ attempts = 0;
61
+ var dropped = Array.isArray(msg.failingPhases) && dropBuildErrorsExcept(msg.failingPhases);
62
+ if (dropped && systemPage) {
63
+ reloadForUpdate("the build that failed to render this page recovered");
64
+ return;
65
+ }
60
66
  if (clientKind === "csr") {
61
- if (csrGenerationMoved(msg.csrGeneration)) reloadForCsr("missed a CSR update while disconnected");
67
+ if (csrGenerationMoved(msg.csrGeneration)) reloadForUpdate("missed a CSR update while disconnected");
62
68
  return;
63
69
  }
64
- if (lastBuildId !== null && msg.buildId !== lastBuildId) {
65
- location.reload();
70
+ if (systemPage) {
71
+ if (lastBuildId !== null && msg.buildId !== lastBuildId) location.reload();
72
+ lastBuildId = msg.buildId;
66
73
  return;
67
74
  }
68
- lastBuildId = msg.buildId;
75
+ if (ssrRegistryReplaced(msg.ssrEpoch)) {
76
+ reloadForUpdate("the dev server rebuilt the SSR registry while this tab held the previous one");
77
+ return;
78
+ }
79
+ if (ssrRegistryGone(msg.ssrEpoch)) {
80
+ reloadForUpdate("the dev server has no SSR registry yet: a new session or a config restart is building one");
81
+ return;
82
+ }
83
+ if (ssrBootFailedBefore(msg.ssrGeneration, msg.ssrEpoch)) {
84
+ reloadForUpdate("the SSR registry this tab failed to load was rebuilt");
85
+ return;
86
+ }
87
+ if (typeof msg.ssrGeneration === "number") self.__AKAN_SSR_HELLO_GENERATION__ = msg.ssrGeneration;
88
+ var sameRegistry = holdsSsrRegistry(msg.ssrEpoch);
89
+ if (sameRegistry) catchUpSsrRegistry(msg.ssrGeneration);
90
+ if (lastBuildId === null || msg.buildId === lastBuildId) {
91
+ lastBuildId = msg.buildId;
92
+ return;
93
+ }
94
+ if (sameRegistry) refreshRsc({ buildId: msg.buildId });
95
+ else location.reload();
69
96
  return;
70
97
  }
71
98
  if (msg.type === "reload") {
@@ -78,14 +105,14 @@ export const HMR_CLIENT_SCRIPT = `(function(){
78
105
  refreshRsc(msg);
79
106
  return;
80
107
  }
81
- if (msg.type === "client-refresh") {
82
- refreshClient(msg);
83
- return;
84
- }
85
108
  if (msg.type === "csr-update") {
86
109
  applyCsrUpdate(msg);
87
110
  return;
88
111
  }
112
+ if (msg.type === "ssr-update") {
113
+ applySsrUpdate(msg);
114
+ return;
115
+ }
89
116
  if (msg.type === "css-update") {
90
117
  var cssUrl = selectCssUrl(msg.cssAssets);
91
118
  if (cssUrl) swapCss(cssUrl);
@@ -127,20 +154,75 @@ export const HMR_CLIENT_SCRIPT = `(function(){
127
154
  return typeof current === "number" && current !== generation;
128
155
  }
129
156
 
130
- function reloadForCsr(reason){
131
- console.warn("[akan-hmr] reloading the CSR page: " + reason);
157
+ function reloadForUpdate(reason){
158
+ console.warn("[akan-hmr] reloading the page: " + reason);
132
159
  beginHmrOverlay("Reloading...", true);
133
160
  setTimeout(function(){ location.reload(); }, 30);
134
161
  }
135
162
 
163
+ function recordTrace(kind, msg, receivedAt, appliedAt){
164
+ traces.push({ kind: kind, generation: msg.generation, trace: msg.trace || null, receivedAt: receivedAt, appliedAt: appliedAt });
165
+ if (traces.length > 64) traces.shift();
166
+ }
167
+
136
168
  function applyCsrUpdate(msg){
169
+ recordTrace("csr", msg, Date.now(), null);
137
170
  if (msg.reload || !self.__akan || typeof self.__akan.hot !== "function") {
138
- reloadForCsr(msg.reason || "the CSR bundle was rebuilt");
171
+ reloadForUpdate(msg.reason || "the CSR bundle was rebuilt");
139
172
  return;
140
173
  }
141
174
  self.__akan.hot(msg);
142
175
  }
143
176
 
177
+ function ssrRegistryReplaced(epoch){
178
+ var own = self.__AKAN_SSR_EPOCH__;
179
+ return typeof epoch === "number" && typeof own === "number" && own !== epoch;
180
+ }
181
+
182
+ function holdsSsrRegistry(epoch){
183
+ return typeof epoch === "number" && self.__AKAN_SSR_EPOCH__ === epoch;
184
+ }
185
+
186
+ function ssrRegistryGone(epoch){
187
+ return typeof self.__AKAN_SSR_EPOCH__ === "number" && typeof epoch !== "number";
188
+ }
189
+
190
+ function catchUpSsrRegistry(generation){
191
+ var registry = self.__akan;
192
+ if (typeof generation !== "number" || !registry || typeof registry.catchUp !== "function") return;
193
+ if (registry.inspect().target < generation) registry.catchUp(generation, ${JSON.stringify(SSR_DEV_ROUTE_PREFIX)});
194
+ }
195
+
196
+ function ssrBootFailedBefore(generation, epoch){
197
+ var failed = self.__AKAN_SSR_BOOT_FAILED__;
198
+ if (!failed || typeof generation !== "number") return false;
199
+ return generation > failed.generation || (typeof epoch === "number" && typeof failed.epoch === "number" && epoch !== failed.epoch);
200
+ }
201
+
202
+ function applySsrUpdate(msg){
203
+ recordTrace("ssr", msg, Date.now(), null);
204
+ if (systemPage) {
205
+ reloadForUpdate("a save changed the client code of the page that failed to render");
206
+ return;
207
+ }
208
+ if (ssrBootFailedBefore(msg.generation)) {
209
+ reloadForUpdate("the SSR registry this tab failed to load was updated");
210
+ return;
211
+ }
212
+ var startFailed = !!(self.__akan && typeof self.__akan.inspect === "function" && self.__akan.inspect().failed);
213
+ if (msg.reload && self.__akan && (!(msg.generation <= self.__akan.generation) || (startFailed && msg.generation >= self.__akan.generation))) {
214
+ reloadForUpdate(msg.reason || "the SSR registry was rebuilt");
215
+ return;
216
+ }
217
+ if (self.__akan && typeof self.__akan.hot === "function") {
218
+ self.__akan.hot(msg);
219
+ return;
220
+ }
221
+ var early = self.__AKAN_SSR_EARLY_UPDATES__ = self.__AKAN_SSR_EARLY_UPDATES__ || [];
222
+ early.push(msg);
223
+ if (early.length > 64) early.shift();
224
+ }
225
+
144
226
  function schedule(){
145
227
  attempts = Math.min(attempts + 1, 6);
146
228
  var delay = Math.min(30000, 250 * Math.pow(2, attempts - 1));
@@ -259,6 +341,10 @@ export const HMR_CLIENT_SCRIPT = `(function(){
259
341
  showBuildErrorOverlay(msg);
260
342
  return;
261
343
  }
344
+ if (msg.status === "ok" && systemPage) {
345
+ reloadForUpdate("the build that failed to render this page recovered");
346
+ return;
347
+ }
262
348
  if (msg.status === "ok") clearBuildErrorOverlay(msg);
263
349
  }
264
350
 
@@ -301,9 +387,31 @@ export const HMR_CLIENT_SCRIPT = `(function(){
301
387
  var current = buildErrorStates[phase];
302
388
  if (!current) return;
303
389
  var generation = typeof msg.generation === "number" ? msg.generation : 0;
304
- var recovered = phase === "backend" ? generation >= current.generation : generation > current.generation;
390
+ var sameGeneration = phase === "backend" || phase === "route";
391
+ var recovered = sameGeneration ? generation >= current.generation : generation > current.generation;
305
392
  if (!recovered) return;
306
393
  delete buildErrorStates[phase];
394
+ showBuildRecovered(msg);
395
+ }
396
+
397
+ function dropBuildErrorsExcept(failing){
398
+ var dropped = false;
399
+ for (var phase in buildErrorStates) {
400
+ if (failing.indexOf(phase) >= 0) continue;
401
+ delete buildErrorStates[phase];
402
+ dropped = true;
403
+ }
404
+ if (dropped && !systemPage) showBuildRecovered({});
405
+ return dropped;
406
+ }
407
+
408
+ function clearRenderError(){
409
+ if (!buildErrorStates.build) return;
410
+ delete buildErrorStates.build;
411
+ showBuildRecovered({});
412
+ }
413
+
414
+ function showBuildRecovered(msg){
307
415
  if (hasBuildErrors()) {
308
416
  renderBuildErrorOverlay();
309
417
  return;
@@ -360,7 +468,25 @@ export const HMR_CLIENT_SCRIPT = `(function(){
360
468
  return prefix ? prefix + "\\n" + msg.message : msg.message;
361
469
  }
362
470
 
471
+ var rscRefreshSeq = 0;
472
+ var rscRefreshedSeq = 0;
363
473
  function refreshRsc(msg){
474
+ var receivedAt = Date.now();
475
+ var seq = ++rscRefreshSeq;
476
+ var refresh = function(){ if (seq === rscRefreshSeq) doRefreshRsc(msg, receivedAt, seq); };
477
+ var settle = function(){
478
+ return self.__akan && typeof self.__akan.whenSettled === "function" ? self.__akan.whenSettled() : null;
479
+ };
480
+ var booting = self.__AKAN_SSR_BOOT__;
481
+ var settled = booting ? Promise.resolve(booting).then(settle, function(){ return null; }) : settle();
482
+ if (!settled) {
483
+ refresh();
484
+ return;
485
+ }
486
+ settled.then(refresh, refresh);
487
+ }
488
+
489
+ function doRefreshRsc(msg, receivedAt, seq){
364
490
  var started = performance.now();
365
491
  var overlayToken = beginHmrOverlay("Refreshing page...");
366
492
  try { self.__AKAN_RSC_CLEAR_CACHE__ && self.__AKAN_RSC_CLEAR_CACHE__(); } catch(e){}
@@ -371,8 +497,13 @@ export const HMR_CLIENT_SCRIPT = `(function(){
371
497
  return;
372
498
  }
373
499
  Promise.resolve(self.__AKAN_RSC_REFRESH__({ buildId: msg.buildId })).then(function(){
374
- lastBuildId = msg.buildId;
500
+ if (!(seq < rscRefreshedSeq)) {
501
+ rscRefreshedSeq = seq;
502
+ lastBuildId = msg.buildId;
503
+ }
504
+ recordTrace("rsc-refresh", msg, receivedAt, Date.now());
375
505
  endHmrOverlay(overlayToken);
506
+ clearRenderError();
376
507
  console.debug && console.debug("[akan-hmr] RSC refreshed", {
377
508
  buildId: msg.buildId,
378
509
  generation: msg.generation,
@@ -408,75 +539,6 @@ export const HMR_CLIENT_SCRIPT = `(function(){
408
539
  return refreshRuntimePromise;
409
540
  }
410
541
 
411
- function refreshClient(msg){
412
- refreshQueue = refreshQueue.then(function(){ return doRefreshClient(msg); }, function(){ return doRefreshClient(msg); });
413
- }
414
-
415
- function setHmrPhase(phase){
416
- self.__AKAN_HMR_PHASE__ = phase;
417
- }
418
-
419
- function doRefreshClient(msg){
420
- var started = performance.now();
421
- var metadataAt = started;
422
- var importAt = started;
423
- var refreshAt = started;
424
- var overlayToken = beginHmrOverlay("Updating...");
425
- var fallbackToRsc = false;
426
- return ensureRefreshRuntime().then(function(runtime){
427
- setHmrOverlayLabel(overlayToken, "Fetching update...");
428
- var endpoint = new URL("/_akan/hmr/client-refresh", location.origin);
429
- endpoint.searchParams.set("url", location.href);
430
- if (msg.buildId != null) endpoint.searchParams.set("buildId", String(msg.buildId));
431
- return fetch(endpoint, { credentials: "same-origin", cache: "no-store" })
432
- .then(function(res){
433
- if (!res.ok) throw new Error("client-refresh metadata failed " + res.status + " " + res.statusText);
434
- return res.json();
435
- })
436
- .then(function(info){
437
- metadataAt = performance.now();
438
- var chunks = Array.isArray(info.chunks) ? info.chunks : [];
439
- if (chunks.length === 0) throw new Error("no client chunks returned");
440
- setHmrPhase("refresh-import");
441
- setHmrOverlayLabel(overlayToken, "Importing update...");
442
- return Promise.all(chunks.map(function(chunk){ return import(chunk); })).then(function(){
443
- importAt = performance.now();
444
- setHmrPhase("react-refresh");
445
- setHmrOverlayLabel(overlayToken, "Applying update...");
446
- try {
447
- runtime.performReactRefresh();
448
- } finally {
449
- setHmrPhase(null);
450
- }
451
- refreshAt = performance.now();
452
- lastBuildId = msg.buildId;
453
- console.debug && console.debug("[akan-hmr] React Fast Refresh applied", {
454
- buildId: msg.buildId,
455
- generation: msg.generation,
456
- chunks: chunks.length,
457
- routeIds: info.routeIds || msg.routeIds,
458
- changedFiles: msg.changedFiles && msg.changedFiles.length,
459
- metadataMs: Math.round(metadataAt - started),
460
- importMs: Math.round(importAt - metadataAt),
461
- refreshMs: Math.round(refreshAt - importAt),
462
- durationMs: Math.round(refreshAt - started)
463
- });
464
- endHmrOverlay(overlayToken);
465
- }, function(err){
466
- setHmrPhase(null);
467
- throw err;
468
- });
469
- });
470
- }).catch(function(err){
471
- console.warn("[akan-hmr] React Fast Refresh failed, falling back to RSC refresh", err);
472
- fallbackToRsc = true;
473
- endHmrOverlay(overlayToken);
474
- refreshRsc(msg);
475
- }).finally(function(){
476
- if (!fallbackToRsc) endHmrOverlay(overlayToken);
477
- });
478
- }
479
-
480
542
  function swapCss(href){
481
543
  var overlayToken = beginHmrOverlay("Updating styles...");
482
544
  var link = document.createElement("link");
@@ -2,16 +2,33 @@ export const CSR_DEV_DIRNAME = "csr-dev";
2
2
  export const CSR_DEV_ROUTE_PREFIX = "/_akan/csr-dev/";
3
3
  export const CSR_DEV_APP_FILE = "app.js";
4
4
  export const CSR_DEV_MANIFEST_FILE = "manifest.json";
5
+ /** Present only while the resident builder patches: a builder that dies holding it takes the patcher off. */
6
+ export const CSR_DEV_PATCHING_MARKER = ".patching";
7
+ /** Patches kept on disk behind the newest, and as many as a reconnecting tab catches up on before it reloads instead. */
8
+ export const CSR_DEV_KEPT_PATCHES = 40;
9
+
10
+ export const SSR_DEV_DIRNAME = "ssr-dev";
11
+ export const SSR_DEV_ROUTE_PREFIX = "/_akan/ssr-dev/";
12
+ /** The one chunk every SSR registry row names: loading it boots the registry in the tab. */
13
+ export const SSR_DEV_CHUNK = "ssr-dev";
14
+ /** Marks a row id as a registry module, so the dev shim tells it from the runtime rows that stay chunk URLs. */
15
+ export const SSR_DEV_ID_PREFIX = "ssr-dev:";
5
16
 
6
17
  export type DevCsrMode = "registry" | "artifact";
7
18
 
8
19
  export interface CsrDevManifest {
9
20
  version: 1;
10
21
  generation: number;
22
+ /** The generation `app.js` holds; a patch is announced before `app.js` catches up, so it may trail `generation`. */
23
+ appGeneration?: number;
11
24
  vendorFile: string;
12
25
  entries: Record<string, string>;
26
+ /** When the registry was last built whole: a tab from an earlier registry must reload, whatever its generation. */
27
+ epoch?: number;
13
28
  }
14
29
 
30
+ export const appGenerationOf = (manifest: CsrDevManifest): number => manifest.appGeneration ?? manifest.generation;
31
+
15
32
  /** Where each module's generated code starts in a combined file (`app.js`, a patch), for its source map. */
16
33
  export interface CsrDevLayout {
17
34
  lineCount: number;