veryfront 0.1.1102 → 0.1.1103

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 (82) hide show
  1. package/esm/deno.js +1 -1
  2. package/esm/src/data/helpers.d.ts +20 -3
  3. package/esm/src/data/helpers.d.ts.map +1 -1
  4. package/esm/src/data/helpers.js +46 -12
  5. package/esm/src/data/static-data-fetcher.d.ts.map +1 -1
  6. package/esm/src/data/static-data-fetcher.js +15 -0
  7. package/esm/src/html/hydration-script-builder/templates/renderer.d.ts.map +1 -1
  8. package/esm/src/html/hydration-script-builder/templates/renderer.js +52 -28
  9. package/esm/src/modules/server/module-server.d.ts.map +1 -1
  10. package/esm/src/modules/server/module-server.js +15 -9
  11. package/esm/src/rendering/orchestrator/module-loader/build-failure.d.ts +20 -0
  12. package/esm/src/rendering/orchestrator/module-loader/build-failure.d.ts.map +1 -0
  13. package/esm/src/rendering/orchestrator/module-loader/build-failure.js +26 -0
  14. package/esm/src/rendering/orchestrator/module-loader/index.d.ts +2 -1
  15. package/esm/src/rendering/orchestrator/module-loader/index.d.ts.map +1 -1
  16. package/esm/src/rendering/orchestrator/module-loader/index.js +69 -7
  17. package/esm/src/rendering/orchestrator/module-loader/module-persistence.d.ts +6 -0
  18. package/esm/src/rendering/orchestrator/module-loader/module-persistence.d.ts.map +1 -1
  19. package/esm/src/rendering/orchestrator/module-loader/module-persistence.js +49 -0
  20. package/esm/src/rendering/orchestrator/pipeline.d.ts.map +1 -1
  21. package/esm/src/rendering/orchestrator/pipeline.js +10 -1
  22. package/esm/src/routing/api/context-builder.d.ts +26 -15
  23. package/esm/src/routing/api/context-builder.d.ts.map +1 -1
  24. package/esm/src/routing/api/context-builder.js +34 -15
  25. package/esm/src/routing/api/module-loader/loader.js +57 -1
  26. package/esm/src/server/dev-server/middleware.d.ts.map +1 -1
  27. package/esm/src/server/dev-server/middleware.js +29 -6
  28. package/esm/src/server/handlers/request/ssr/ssr.handler.d.ts.map +1 -1
  29. package/esm/src/server/handlers/request/ssr/ssr.handler.js +6 -4
  30. package/esm/src/transforms/esm/import-attributes.d.ts +32 -0
  31. package/esm/src/transforms/esm/import-attributes.d.ts.map +1 -0
  32. package/esm/src/transforms/esm/import-attributes.js +116 -0
  33. package/esm/src/transforms/esm/import-parser.d.ts.map +1 -1
  34. package/esm/src/transforms/esm/import-parser.js +68 -18
  35. package/esm/src/transforms/esm/lexer.d.ts +18 -0
  36. package/esm/src/transforms/esm/lexer.d.ts.map +1 -1
  37. package/esm/src/transforms/esm/lexer.js +21 -0
  38. package/esm/src/transforms/esm/specifier-resolver.d.ts.map +1 -1
  39. package/esm/src/transforms/esm/specifier-resolver.js +13 -0
  40. package/esm/src/transforms/esm/transform-utils.d.ts +8 -0
  41. package/esm/src/transforms/esm/transform-utils.d.ts.map +1 -1
  42. package/esm/src/transforms/esm/transform-utils.js +11 -1
  43. package/esm/src/transforms/import-rewriter/project-paths.d.ts +26 -0
  44. package/esm/src/transforms/import-rewriter/project-paths.d.ts.map +1 -0
  45. package/esm/src/transforms/import-rewriter/project-paths.js +49 -0
  46. package/esm/src/transforms/import-rewriter/strategies/alias-strategy.d.ts +0 -1
  47. package/esm/src/transforms/import-rewriter/strategies/alias-strategy.d.ts.map +1 -1
  48. package/esm/src/transforms/import-rewriter/strategies/alias-strategy.js +5 -20
  49. package/esm/src/transforms/import-rewriter/strategies/asset-strategy.d.ts +1 -1
  50. package/esm/src/transforms/import-rewriter/strategies/asset-strategy.d.ts.map +1 -1
  51. package/esm/src/transforms/import-rewriter/strategies/asset-strategy.js +62 -12
  52. package/esm/src/transforms/import-rewriter/strategies/bare-strategy.d.ts.map +1 -1
  53. package/esm/src/transforms/import-rewriter/strategies/bare-strategy.js +19 -3
  54. package/esm/src/transforms/import-rewriter/strategies/node-builtin-strategy.d.ts +11 -0
  55. package/esm/src/transforms/import-rewriter/strategies/node-builtin-strategy.d.ts.map +1 -1
  56. package/esm/src/transforms/import-rewriter/strategies/node-builtin-strategy.js +13 -0
  57. package/esm/src/transforms/import-rewriter/strategies/relative-strategy.d.ts +0 -1
  58. package/esm/src/transforms/import-rewriter/strategies/relative-strategy.d.ts.map +1 -1
  59. package/esm/src/transforms/import-rewriter/strategies/relative-strategy.js +2 -16
  60. package/esm/src/transforms/import-rewriter/strategies/veryfront-strategy.d.ts.map +1 -1
  61. package/esm/src/transforms/import-rewriter/strategies/veryfront-strategy.js +4 -3
  62. package/esm/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.d.ts +4 -2
  63. package/esm/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.d.ts.map +1 -1
  64. package/esm/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.js +23 -7
  65. package/esm/src/transforms/pipeline/stages/browser-node-builtin-imports.d.ts.map +1 -1
  66. package/esm/src/transforms/pipeline/stages/browser-node-builtin-imports.js +5 -0
  67. package/esm/src/transforms/pipeline/stages/browser-server-exports-strip.d.ts +25 -3
  68. package/esm/src/transforms/pipeline/stages/browser-server-exports-strip.d.ts.map +1 -1
  69. package/esm/src/transforms/pipeline/stages/browser-server-exports-strip.js +285 -44
  70. package/esm/src/transforms/pipeline/stages/compile.d.ts.map +1 -1
  71. package/esm/src/transforms/pipeline/stages/compile.js +6 -1
  72. package/esm/src/transforms/pipeline/stages/ssr-vf-modules/transform.d.ts +9 -5
  73. package/esm/src/transforms/pipeline/stages/ssr-vf-modules/transform.d.ts.map +1 -1
  74. package/esm/src/transforms/pipeline/stages/ssr-vf-modules/transform.js +23 -9
  75. package/esm/src/transforms/shared/server-only-packages.d.ts +30 -0
  76. package/esm/src/transforms/shared/server-only-packages.d.ts.map +1 -0
  77. package/esm/src/transforms/shared/server-only-packages.js +47 -0
  78. package/esm/src/types/entities/getEntityInfo.d.ts.map +1 -1
  79. package/esm/src/types/entities/getEntityInfo.js +10 -2
  80. package/esm/src/utils/version-constant.d.ts +1 -1
  81. package/esm/src/utils/version-constant.js +1 -1
  82. package/package.json +5 -5
package/esm/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "veryfront",
3
- "version": "0.1.1102",
3
+ "version": "0.1.1103",
4
4
  "license": "Apache-2.0",
5
5
  "nodeModulesDir": "auto",
6
6
  "minimumDependencyAge": {
@@ -1,7 +1,18 @@
1
1
  import type { DataResult } from "./types.js";
2
- /** Return a redirect result from a data loader. */
2
+ /**
3
+ * Redirect the request from a data loader.
4
+ *
5
+ * Return it or throw it. `throw redirect("/login")` behaves exactly like
6
+ * `return redirect("/login")`.
7
+ */
3
8
  export declare function redirect(destination: string, permanent?: boolean): DataResult;
4
- /** Return a 404 result from a data loader. */
9
+ /**
10
+ * Render the 404 page from a data loader.
11
+ *
12
+ * Return it or throw it. `throw notFound()` behaves exactly like
13
+ * `return notFound()`, which is useful deep inside a helper that has no clean
14
+ * way to return to the loader.
15
+ */
5
16
  export declare function notFound(): DataResult;
6
17
  /**
7
18
  * True when `value` is a control-flow result produced by {@link notFound} or
@@ -11,8 +22,14 @@ export declare function notFound(): DataResult;
11
22
  * naturally and is what people coming from other frameworks reach for. Thrown,
12
23
  * the plain object is not an `Error`, so the SSR error handler stringified it
13
24
  * to `[object Object]` and returned a 500 instead of the intended 404 or
14
- * redirect. Recognising the shape lets a thrown result behave like a returned
25
+ * redirect. Recognising the brand lets a thrown result behave like a returned
15
26
  * one.
27
+ *
28
+ * The check is on the brand, never on the shape. A loader that does
29
+ * `throw await response.json()` against an upstream answering
30
+ * `{ notFound: true, message: "record locked" }` is reporting a failure, and
31
+ * reading that as a 404 would render the wrong page, log nothing, and cache a
32
+ * 404 the site never asked for.
16
33
  */
17
34
  export declare function isDataControlResult(value: unknown): value is DataResult;
18
35
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../src/src/data/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,mDAAmD;AACnD,wBAAgB,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,UAAQ,GAAG,UAAU,CAE3E;AAED,8CAA8C;AAC9C,wBAAgB,QAAQ,IAAI,UAAU,CAErC;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,UAAU,CAUvE;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,UAAU,GAAG,UAAU,CAGlE"}
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../src/src/data/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AA2B7C;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,UAAQ,GAAG,UAAU,CAE3E;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,IAAI,UAAU,CAErC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,UAAU,CAIvE;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,UAAU,GAAG,UAAU,CAGlE"}
@@ -1,10 +1,44 @@
1
- /** Return a redirect result from a data loader. */
1
+ /**
2
+ * Brand marking an object as produced by {@link notFound} or {@link redirect}.
3
+ *
4
+ * A registered symbol, so a result built by one copy of this module is
5
+ * recognised by another. Project code and the framework do not always share a
6
+ * module instance, and isolated data fetching crosses a realm boundary.
7
+ *
8
+ * Symbols are dropped by `structuredClone`, so the brand does not survive
9
+ * `postMessage`. Worker-side code normalises a thrown control result before it
10
+ * is posted back, while the object is still in-realm.
11
+ */
12
+ const DATA_CONTROL_RESULT = Symbol.for("veryfront.dataControlResult");
13
+ /**
14
+ * Mark a result as framework-produced control flow.
15
+ *
16
+ * The brand is non-enumerable, so it stays out of `Object.keys`,
17
+ * `JSON.stringify`, and the `DataResult` schema. A returned control result
18
+ * behaves exactly as it did before the brand existed.
19
+ */
20
+ function brandDataControlResult(result) {
21
+ Object.defineProperty(result, DATA_CONTROL_RESULT, { value: true });
22
+ return result;
23
+ }
24
+ /**
25
+ * Redirect the request from a data loader.
26
+ *
27
+ * Return it or throw it. `throw redirect("/login")` behaves exactly like
28
+ * `return redirect("/login")`.
29
+ */
2
30
  export function redirect(destination, permanent = false) {
3
- return { redirect: { destination, permanent } };
31
+ return brandDataControlResult({ redirect: { destination, permanent } });
4
32
  }
5
- /** Return a 404 result from a data loader. */
33
+ /**
34
+ * Render the 404 page from a data loader.
35
+ *
36
+ * Return it or throw it. `throw notFound()` behaves exactly like
37
+ * `return notFound()`, which is useful deep inside a helper that has no clean
38
+ * way to return to the loader.
39
+ */
6
40
  export function notFound() {
7
- return { notFound: true };
41
+ return brandDataControlResult({ notFound: true });
8
42
  }
9
43
  /**
10
44
  * True when `value` is a control-flow result produced by {@link notFound} or
@@ -14,19 +48,19 @@ export function notFound() {
14
48
  * naturally and is what people coming from other frameworks reach for. Thrown,
15
49
  * the plain object is not an `Error`, so the SSR error handler stringified it
16
50
  * to `[object Object]` and returned a 500 instead of the intended 404 or
17
- * redirect. Recognising the shape lets a thrown result behave like a returned
51
+ * redirect. Recognising the brand lets a thrown result behave like a returned
18
52
  * one.
53
+ *
54
+ * The check is on the brand, never on the shape. A loader that does
55
+ * `throw await response.json()` against an upstream answering
56
+ * `{ notFound: true, message: "record locked" }` is reporting a failure, and
57
+ * reading that as a 404 would render the wrong page, log nothing, and cache a
58
+ * 404 the site never asked for.
19
59
  */
20
60
  export function isDataControlResult(value) {
21
61
  if (value === null || typeof value !== "object")
22
62
  return false;
23
- if (value instanceof Error)
24
- return false;
25
- const candidate = value;
26
- if (candidate.notFound === true)
27
- return true;
28
- const destination = candidate.redirect?.destination;
29
- return typeof destination === "string";
63
+ return value[DATA_CONTROL_RESULT] === true;
30
64
  }
31
65
  /**
32
66
  * Reduce a thrown control result to the shape a returned one produces.
@@ -1 +1 @@
1
- {"version":3,"file":"static-data-fetcher.d.ts","sourceRoot":"","sources":["../../../src/src/data/static-data-fetcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAE7D,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AA2DxE,qBAAa,iBAAiB;IAGhB,OAAO,CAAC,YAAY;IAFhC,OAAO,CAAC,oBAAoB,CAAoC;gBAE5C,YAAY,EAAE,YAAY;IAExC,KAAK,CAAC,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;IA4ChF,OAAO,CAAC,uBAAuB;YAMjB,iBAAiB;IAuB/B,OAAO,CAAC,eAAe;YAQT,iBAAiB;YA+BjB,UAAU;YAqEV,sBAAsB;IAoEpC;;;;;OAKG;IACH,OAAO,CAAC,QAAQ;CAIjB"}
1
+ {"version":3,"file":"static-data-fetcher.d.ts","sourceRoot":"","sources":["../../../src/src/data/static-data-fetcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAE7D,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AA2DxE,qBAAa,iBAAiB;IAGhB,OAAO,CAAC,YAAY;IAFhC,OAAO,CAAC,oBAAoB,CAAoC;gBAE5C,YAAY,EAAE,YAAY;IAExC,KAAK,CAAC,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;IA4ChF,OAAO,CAAC,uBAAuB;YAMjB,iBAAiB;IAuB/B,OAAO,CAAC,eAAe;YAQT,iBAAiB;YA+BjB,UAAU;YAqEV,sBAAsB;IAuFpC;;;;;OAKG;IACH,OAAO,CAAC,QAAQ;CAIjB"}
@@ -188,6 +188,21 @@ export class StaticDataFetcher {
188
188
  const start = performance.now();
189
189
  try {
190
190
  const result = await this.executeStaticData(getStaticData, context, REVALIDATION_TIMEOUT_MS, `getStaticData revalidation for ${pathname}`);
191
+ // A background revalidation refreshes a page that is already being
192
+ // served. A notFound or redirect is not a refreshed page, so keep
193
+ // the entry that is live and let the next revalidation try again.
194
+ // Storing it would serve a 404 for the previously healthy page, and
195
+ // a control result carries no revalidate interval, so the entry
196
+ // would never revalidate again either.
197
+ if (result.notFound || result.redirect) {
198
+ serverLogger.warn("DATA_REVALIDATION_CONTROL_RESULT background revalidation returned a control result, keeping the cached entry", {
199
+ pathname,
200
+ durationMs: Math.round(performance.now() - start),
201
+ cacheKey,
202
+ control: result.notFound ? "notFound" : "redirect",
203
+ });
204
+ return;
205
+ }
191
206
  this.storeCacheEntry(cacheKey, result);
192
207
  }
193
208
  catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../../../../src/src/html/hydration-script-builder/templates/renderer.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,iBAAiB,cA4R7B,CAAC"}
1
+ {"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../../../../src/src/html/hydration-script-builder/templates/renderer.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,iBAAiB,cAoT7B,CAAC"}
@@ -3,17 +3,59 @@ export const getRendererScript = () => `
3
3
 
4
4
  // True when a dynamic import failed because the module could not be
5
5
  // fetched (404 / network), as opposed to being fetched and then failing to
6
- // link or evaluate. Browsers report the former as a TypeError with a
7
- // "dynamically imported module" message; link failures are SyntaxErrors and
8
- // evaluation failures are whatever the module threw.
6
+ // link or evaluate. Browsers word this as a TypeError naming the dynamic
7
+ // import itself; link failures are SyntaxErrors and evaluation failures are
8
+ // whatever the module threw. The wording is matched against the dynamic
9
+ // import phrases only, so app code throwing "Failed to load user profile"
10
+ // at module scope is not mistaken for a missing module.
9
11
  function isModuleNotFoundError(error) {
10
12
  if (!error) return false;
11
13
  if (error instanceof SyntaxError) return false;
12
14
  const message = String((error && error.message) || error);
13
- return /(?:Failed to fetch|error loading|Importing a module script failed|Failed to load)/i
15
+ return /(?:dynamically imported module|Importing a module script failed|Failed to load module script)/i
14
16
  .test(message);
15
17
  }
16
18
 
19
+ // Picks the error that describes the failure best. An error that proves a
20
+ // module was reached (link or evaluation) always beats one that only proves
21
+ // a URL could not be fetched, because a 404 on a path that was never
22
+ // expected to exist explains nothing. Otherwise the earlier error wins: it
23
+ // names the module the router actually intended to load.
24
+ function preferReachedModuleError(earlier, later) {
25
+ if (!earlier) return later;
26
+ if (!later) return earlier;
27
+ if (isModuleNotFoundError(earlier) && !isModuleNotFoundError(later)) return later;
28
+ return earlier;
29
+ }
30
+
31
+ // Loads a Pages Router page, retrying at <route>/index.js because both
32
+ // pages/about.tsx and pages/about/index.tsx are valid sources for the same
33
+ // route. The retry is unconditional: gating it on the wording of the
34
+ // first rejection turned any unrecognized wording into a blank page. Error
35
+ // selection, not the retry, is what must stay precise.
36
+ async function loadPageModuleWithIndexFallback(
37
+ basePath,
38
+ pageSlug,
39
+ pageModuleError,
40
+ importModule,
41
+ ) {
42
+ try {
43
+ return await importModule(basePath + '.js');
44
+ } catch (error) {
45
+ const routeError = preferReachedModuleError(pageModuleError, error);
46
+
47
+ // An index slug already resolves to <route>/index.js, so the retry
48
+ // would ask for <route>/index/index.js.
49
+ if (pageSlug === 'index' || pageSlug.endsWith('/index')) throw routeError;
50
+
51
+ try {
52
+ return await importModule(basePath + '/index.js');
53
+ } catch (indexError) {
54
+ throw preferReachedModuleError(routeError, indexError);
55
+ }
56
+ }
57
+ }
58
+
17
59
  async function renderPage(pathname) {
18
60
  const resolvedPathname = (() => {
19
61
  const input = typeof pathname === 'string' ? pathname : window.location.pathname;
@@ -128,30 +170,12 @@ export const getRendererScript = () => `
128
170
  const prefix = pageSlug.startsWith('@/') ? '' : '/pages';
129
171
  const basePath = MODULE_SERVER_URL + prefix + '/' + pageSlug;
130
172
 
131
- try {
132
- pageModule = await import(basePath + '.js');
133
- } catch (error) {
134
- pageModuleError = pageModuleError || error;
135
-
136
- // Only retry at <route>/index.js when the module genuinely could not
137
- // be found. If it was found but failed to link or evaluate, retrying
138
- // a path that does not exist replaces a precise error ("does not
139
- // provide an export named 'createHash'") with a misleading 404.
140
- const canRetryAsIndex = isModuleNotFoundError(error) &&
141
- pageSlug !== 'index' && !pageSlug.endsWith('/index');
142
-
143
- if (!canRetryAsIndex) throw pageModuleError;
144
-
145
- try {
146
- pageModule = await import(basePath + '/index.js');
147
- } catch (indexError) {
148
- // For a real <route>/index.tsx page, the first 404 was expected
149
- // and this retry is the path that matters: if it reached a module
150
- // and failed to link or evaluate, its error is the real one. Only
151
- // when the retry 404s as well does the original describe more.
152
- throw isModuleNotFoundError(indexError) ? pageModuleError : indexError;
153
- }
154
- }
173
+ pageModule = await loadPageModuleWithIndexFallback(
174
+ basePath,
175
+ pageSlug,
176
+ pageModuleError,
177
+ (moduleUrl) => import(moduleUrl),
178
+ );
155
179
  }
156
180
 
157
181
  if (!pageModule) {
@@ -1 +1 @@
1
- {"version":3,"file":"module-server.d.ts","sourceRoot":"","sources":["../../../../src/src/modules/server/module-server.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAG5E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AA2DtE;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAwDrD,CAAC;AAyEF,MAAM,WAAW,mBAAmB;IAClC,yDAAyD;IACzD,SAAS,EAAE,MAAM,CAAC;IAClB,6BAA6B;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,sBAAsB;IACtB,OAAO,EAAE,cAAc,CAAC;IACxB,uBAAuB;IACvB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,+DAA+D;IAC/D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gFAAgF;IAChF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mDAAmD;IACnD,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,uDAAuD;IACvD,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,yDAAyD;IACzD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sFAAsF;IACtF,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,sDAAsD;AACtD,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAqczF;AAwWD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAGrD;AAiBD;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAyBlE"}
1
+ {"version":3,"file":"module-server.d.ts","sourceRoot":"","sources":["../../../../src/src/modules/server/module-server.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAG5E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AA2DtE;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAyDrD,CAAC;AAyEF,MAAM,WAAW,mBAAmB;IAClC,yDAAyD;IACzD,SAAS,EAAE,MAAM,CAAC;IAClB,6BAA6B;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,sBAAsB;IACtB,OAAO,EAAE,cAAc,CAAC;IACxB,uBAAuB;IACvB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,+DAA+D;IAC/D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gFAAgF;IAChF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mDAAmD;IACnD,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,uDAAuD;IACvD,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,yDAAyD;IACzD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sFAAsF;IACtF,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,sDAAsD;AACtD,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAqczF;AAwWD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAGrD;AAiBD;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CA8BlE"}
@@ -87,8 +87,9 @@ export default {};
87
87
  ].join("\n") + "\n",
88
88
  "_dnt.polyfills": `export default {};\n`,
89
89
  // Deno import-map alias stub for browser/HTTP-served framework modules.
90
- // Must be a JS module (not JSON) because esbuild strips `with { type: "json" }`
91
- // at es2020 target, and browsers reject JSON MIME type without the assertion.
90
+ // Must be a JS module (not JSON): a browser refuses a JSON module unless the
91
+ // importer carries `with { type: "json" }`, so serving JS keeps the stub
92
+ // independent of how far import attribute support has reached the browser.
92
93
  "_veryfront/_deno-config": `export default ${JSON.stringify({ version: VERSION })};\n`,
93
94
  // dnt rewrites #deno-config to relative deno.js in npm framework modules.
94
95
  "deno": `export default ${JSON.stringify({ version: VERSION })};\n`,
@@ -795,17 +796,22 @@ const COMPILED_TO_JS_EXTENSIONS = /\.(?:tsx?|jsx|mdx|md)$/;
795
796
  */
796
797
  export function getDevModuleContentType(modulePath) {
797
798
  const normalizedPath = modulePath.toLowerCase();
798
- if (normalizedPath.endsWith(".map") || normalizedPath.endsWith(".json")) {
799
+ // The import rewriter appends `.js` to any specifier whose extension it does
800
+ // not recognise, so `@/lib/data.json` arrives here as `lib/data.json.js`
801
+ // while the source file, and therefore the body, is still raw JSON. Resolve
802
+ // the source extension the same way the module lookup does before deciding.
803
+ const sourcePath = normalizedPath.replace(/\.(?:mjs|js)$/, "");
804
+ if (sourcePath.endsWith(".map") || sourcePath.endsWith(".json")) {
799
805
  return "application/json; charset=utf-8";
800
806
  }
801
- if (normalizedPath.endsWith(".css")) {
807
+ if (sourcePath.endsWith(".css")) {
802
808
  return "text/css; charset=utf-8";
803
809
  }
804
- // The request path carries the *source* extension, but the body we serve is
805
- // always the compiled JavaScript. Typing the response from the source
806
- // extension yields `application/typescript`, which browsers refuse to execute
807
- // as a module under strict MIME checking.
808
- if (COMPILED_TO_JS_EXTENSIONS.test(normalizedPath)) {
810
+ // The request path can carry a source extension, but the body served for one
811
+ // is the compiled JavaScript. Typing the response from the source extension
812
+ // yields `application/typescript`, which browsers refuse to execute as a
813
+ // module under strict MIME checking.
814
+ if (COMPILED_TO_JS_EXTENSIONS.test(sourcePath)) {
809
815
  return "application/javascript; charset=utf-8";
810
816
  }
811
817
  const detected = getContentTypeForPath(normalizedPath);
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Build-failure tagging for module loads.
3
+ *
4
+ * A page module can fail for two very different reasons, and callers need to
5
+ * tell them apart:
6
+ *
7
+ * - The source could not be compiled or resolved. That is a developer-facing
8
+ * build failure, and the message says how to fix it.
9
+ * - The module compiled, ran, and threw at module scope (a missing environment
10
+ * variable, a rejected top-level `await`). That is an ordinary application
11
+ * error, and a project's own error page should present it.
12
+ *
13
+ * Only the loader is in a position to know which happened, so it tags the
14
+ * error at the point of failure instead of leaving later layers to infer it.
15
+ */
16
+ /** Tag `error` as a build failure and return it. */
17
+ export declare function markBuildFailure(error: unknown): unknown;
18
+ /** True when `error` was raised while compiling or resolving project source. */
19
+ export declare function isBuildFailure(error: unknown): boolean;
20
+ //# sourceMappingURL=build-failure.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-failure.d.ts","sourceRoot":"","sources":["../../../../../src/src/rendering/orchestrator/module-loader/build-failure.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAMH,oDAAoD;AACpD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAGxD;AAED,gFAAgF;AAChF,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAEtD"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Build-failure tagging for module loads.
3
+ *
4
+ * A page module can fail for two very different reasons, and callers need to
5
+ * tell them apart:
6
+ *
7
+ * - The source could not be compiled or resolved. That is a developer-facing
8
+ * build failure, and the message says how to fix it.
9
+ * - The module compiled, ran, and threw at module scope (a missing environment
10
+ * variable, a rejected top-level `await`). That is an ordinary application
11
+ * error, and a project's own error page should present it.
12
+ *
13
+ * Only the loader is in a position to know which happened, so it tags the
14
+ * error at the point of failure instead of leaving later layers to infer it.
15
+ */
16
+ const BUILD_FAILURE = Symbol.for("veryfront.module-loader.build-failure");
17
+ /** Tag `error` as a build failure and return it. */
18
+ export function markBuildFailure(error) {
19
+ if (error instanceof Error)
20
+ error[BUILD_FAILURE] = true;
21
+ return error;
22
+ }
23
+ /** True when `error` was raised while compiling or resolving project source. */
24
+ export function isBuildFailure(error) {
25
+ return error instanceof Error && error[BUILD_FAILURE] === true;
26
+ }
@@ -7,6 +7,7 @@
7
7
  * @module rendering/orchestrator/module-loader
8
8
  */
9
9
  import type { RuntimeAdapter } from "../../../platform/adapters/base.js";
10
+ export { isBuildFailure } from "./build-failure.js";
10
11
  export { createEsmCache, createModuleCache, generateHash } from "./cache.js";
11
12
  export { fetchEsmModule, rewriteEsmPaths } from "./esm-rewriter.js";
12
13
  /**
@@ -19,7 +20,7 @@ export { fetchEsmModule, rewriteEsmPaths } from "./esm-rewriter.js";
19
20
  * @param useLocalAdapter - Whether to use local adapter for reading
20
21
  * @returns Path to the transformed module file
21
22
  */
22
- export declare function transformModuleWithDeps(filePath: string, tmpDir: string, localAdapter: RuntimeAdapter, config: ModuleLoaderConfig, useLocalAdapter?: boolean): Promise<string>;
23
+ export declare function transformModuleWithDeps(filePath: string, tmpDir: string, localAdapter: RuntimeAdapter, config: ModuleLoaderConfig, useLocalAdapter?: boolean, lineage?: ReadonlySet<string>, cycleTargets?: Set<string>): Promise<string>;
23
24
  export interface ModuleLoaderConfig {
24
25
  projectDir: string;
25
26
  projectId?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/src/rendering/orchestrator/module-loader/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAiBzE,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC7E,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAOpE;;;;;;;;;GASG;AACH,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,cAAc,EAC5B,MAAM,EAAE,kBAAkB,EAC1B,eAAe,UAAQ,GACtB,OAAO,CAAC,MAAM,CAAC,CA6FjB;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,cAAc,CAAC;IACxB,IAAI,EAAE,aAAa,GAAG,YAAY,CAAC;IACnC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,yDAAyD;IACzD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAyBD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAI5D;AAED;;;;;;GAMG;AACH,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,kBAAkB,GACzB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAyElC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/src/rendering/orchestrator/module-loader/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAgBzE,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAKpD,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC7E,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAOpE;;;;;;;;;GASG;AACH,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,cAAc,EAC5B,MAAM,EAAE,kBAAkB,EAC1B,eAAe,UAAQ,EACvB,OAAO,GAAE,WAAW,CAAC,MAAM,CAAa,EAKxC,YAAY,GAAE,GAAG,CAAC,MAAM,CAAa,GACpC,OAAO,CAAC,MAAM,CAAC,CAyIjB;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,cAAc,CAAC;IACxB,IAAI,EAAE,aAAa,GAAG,YAAY,CAAC;IACnC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,yDAAyD;IACzD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAyBD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAI5D;AAED;;;;;;GAMG;AACH,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,kBAAkB,GACzB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAuFlC"}
@@ -16,6 +16,8 @@ import { resolveModuleDependencies, rewriteResolvedDependencyImports, } from "./
16
16
  import { persistTransformedModule } from "./module-persistence.js";
17
17
  import { transformModuleCodeWithCache } from "./module-transform-cache.js";
18
18
  import { getModuleCacheKey, resolveCachedModulePath } from "./module-cache-lookup.js";
19
+ import { markBuildFailure } from "./build-failure.js";
20
+ export { isBuildFailure } from "./build-failure.js";
19
21
  const logger = rendererLogger.component("module-loader");
20
22
  // Re-export utilities
21
23
  export { createEsmCache, createModuleCache, generateHash } from "./cache.js";
@@ -35,7 +37,12 @@ function decodeFileContent(fileContent) {
35
37
  * @param useLocalAdapter - Whether to use local adapter for reading
36
38
  * @returns Path to the transformed module file
37
39
  */
38
- export async function transformModuleWithDeps(filePath, tmpDir, localAdapter, config, useLocalAdapter = false) {
40
+ export async function transformModuleWithDeps(filePath, tmpDir, localAdapter, config, useLocalAdapter = false, lineage = new Set(),
41
+ // Shared by reference across the whole transform tree (unlike `lineage`, which
42
+ // is copied per level): a descendant records a cycle target here, and the
43
+ // ancestor that eventually persists that target reads it to write a stable
44
+ // alias the left-as-authored cycle edge can resolve to.
45
+ cycleTargets = new Set()) {
39
46
  const { moduleCache, projectDir, projectId, contentSourceId, adapter, mode } = config;
40
47
  const cacheKey = getModuleCacheKey(filePath, projectId, projectDir, contentSourceId, config.reactVersion, mode);
41
48
  const cachedPath = await resolveCachedModulePath({
@@ -58,15 +65,55 @@ export async function transformModuleWithDeps(filePath, tmpDir, localAdapter, co
58
65
  filePath,
59
66
  projectDir,
60
67
  });
61
- const transformedDeps = await Promise.all(resolvedDeps.filter((d) => d.depFilePath).map(async (dep) => {
68
+ // The module cache is only written once a transform completes, so it cannot
69
+ // break a cycle that is still in progress. Carry the chain instead.
70
+ const nextLineage = new Set(lineage).add(filePath);
71
+ const transformedDeps = (await Promise.all(resolvedDeps.filter((d) => d.depFilePath).map(async (dep) => {
72
+ // `await import()` is how a module graph legitimately breaks an import
73
+ // cycle, so following one eagerly can lead straight back to a module
74
+ // further up this chain and recurse until the worker dies. Leave the
75
+ // specifier as authored so the recursion terminates.
76
+ //
77
+ // The cycle target is persisted as a content-hashed artifact whose hash
78
+ // is derived from transformed output we do not produce here (producing it
79
+ // is the recursion we are breaking), so the edge cannot be rewritten to
80
+ // that hashed path. Instead we record the target: when its ancestor
81
+ // persists it, a stable non-hashed alias is written next to the hashed
82
+ // artifact so the relative `.js` specifier esbuild leaves behind resolves.
83
+ // NOTE: this alias path is not yet runtime-verified end to end; if it does
84
+ // not resolve in a real runtime the cycle branch stays broken, which is no
85
+ // worse than before (and still a strict improvement over hanging).
86
+ if (nextLineage.has(dep.depFilePath)) {
87
+ cycleTargets.add(dep.depFilePath);
88
+ logger.debug("Skipping dependency already in the transform chain:", {
89
+ path: dep.path,
90
+ depFilePath: dep.depFilePath,
91
+ });
92
+ return null;
93
+ }
62
94
  logger.debug("Found dependency:", {
63
95
  path: dep.path,
64
96
  depFilePath: dep.depFilePath,
65
97
  isLocalLib: dep.isLocalLib,
66
98
  });
67
- const depTempPath = await transformModuleWithDeps(dep.depFilePath, tmpDir, localAdapter, config, dep.isLocalLib);
68
- return { ...dep, depTempPath };
69
- }));
99
+ try {
100
+ const depTempPath = await transformModuleWithDeps(dep.depFilePath, tmpDir, localAdapter, config, dep.isLocalLib, nextLineage, cycleTargets);
101
+ return { ...dep, depTempPath };
102
+ }
103
+ catch (error) {
104
+ // A static import has to resolve for the importer to run at all. A
105
+ // dynamic one may never be evaluated, so a module behind an untaken
106
+ // branch must not fail the page that merely mentions it.
107
+ if (!dep.isDynamic)
108
+ throw error;
109
+ logger.warn("Leaving an unresolvable dynamic dependency as authored:", {
110
+ path: dep.path,
111
+ depFilePath: dep.depFilePath,
112
+ reason: error instanceof Error ? error.message : String(error),
113
+ });
114
+ return null;
115
+ }
116
+ }))).filter((dep) => dep !== null);
70
117
  fileContent = rewriteResolvedDependencyImports(fileContent, transformedDeps);
71
118
  for (const dep of transformedDeps) {
72
119
  logger.debug("Replaced import:", {
@@ -103,6 +150,7 @@ export async function transformModuleWithDeps(filePath, tmpDir, localAdapter, co
103
150
  cacheKey,
104
151
  contentSourceId,
105
152
  reactVersion: config.reactVersion,
153
+ isCycleTarget: cycleTargets.has(filePath),
106
154
  });
107
155
  }
108
156
  /**
@@ -146,7 +194,15 @@ export function isMissingModuleError(error) {
146
194
  export async function loadModule(filePath, config) {
147
195
  const tmpDir = await getModuleCacheDir(config);
148
196
  const localAdapter = await getLocalAdapter();
149
- const tempFilePath = await transformModuleWithDeps(filePath, tmpDir, localAdapter, config);
197
+ // Everything up to here compiles and resolves source, so a failure is a build
198
+ // failure. Everything after it is the module running.
199
+ let tempFilePath;
200
+ try {
201
+ tempFilePath = await transformModuleWithDeps(filePath, tmpDir, localAdapter, config);
202
+ }
203
+ catch (error) {
204
+ throw markBuildFailure(error);
205
+ }
150
206
  const moduleUrl = `file://${tempFilePath}`;
151
207
  try {
152
208
  return await import(moduleUrl);
@@ -189,7 +245,13 @@ export async function loadModule(filePath, config) {
189
245
  // invalidation stays scoped to this tenant (the path-cache key is not
190
246
  // project-scoped — see invalidateMdxEsmModule).
191
247
  invalidateMdxEsmModule(tmpDir, filePath, config.projectDir, config.reactVersion);
192
- const rebuiltPath = await transformModuleWithDeps(filePath, tmpDir, localAdapter, config);
248
+ let rebuiltPath;
249
+ try {
250
+ rebuiltPath = await transformModuleWithDeps(filePath, tmpDir, localAdapter, config);
251
+ }
252
+ catch (rebuildError) {
253
+ throw markBuildFailure(rebuildError);
254
+ }
193
255
  return await import(`file://${rebuiltPath}?t=${Date.now()}&rebuilt=1`);
194
256
  }
195
257
  logger.error("Failed to import module:", {
@@ -14,6 +14,12 @@ export interface PersistTransformedModuleInput {
14
14
  cacheKey: string;
15
15
  contentSourceId?: string;
16
16
  reactVersion?: string;
17
+ /**
18
+ * True when a dynamic import elsewhere closes a cycle back onto this module.
19
+ * Such an edge is left as authored (`import("../app/page.js")`), so it needs a
20
+ * stable, non-hashed alias next to the content-hashed artifact to resolve to.
21
+ */
22
+ isCycleTarget?: boolean;
17
23
  }
18
24
  /** Write a transformed module artifact and register cache pointers. */
19
25
  export declare function persistTransformedModule(input: PersistTransformedModuleInput): Promise<string>;
@@ -1 +1 @@
1
- {"version":3,"file":"module-persistence.d.ts","sourceRoot":"","sources":["../../../../../src/src/rendering/orchestrator/module-loader/module-persistence.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAuDzE,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,cAAc,CAAC;IAC7B,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,uEAAuE;AACvE,wBAAsB,wBAAwB,CAC5C,KAAK,EAAE,6BAA6B,GACnC,OAAO,CAAC,MAAM,CAAC,CA+DjB"}
1
+ {"version":3,"file":"module-persistence.d.ts","sourceRoot":"","sources":["../../../../../src/src/rendering/orchestrator/module-loader/module-persistence.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAuDzE,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,cAAc,CAAC;IAC7B,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAqDD,uEAAuE;AACvE,wBAAsB,wBAAwB,CAC5C,KAAK,EAAE,6BAA6B,GACnC,OAAO,CAAC,MAAM,CAAC,CAqEjB"}
@@ -44,6 +44,51 @@ async function ensureDir(adapter, dir, force = false) {
44
44
  createdDirs.add(dir);
45
45
  pruneCreatedDirs();
46
46
  }
47
+ /**
48
+ * Whether transformed output exposes a default export, so a cycle alias knows
49
+ * to re-export it. Covers esbuild's `export default …`, `… as default`, and
50
+ * `export { default } from …` forms.
51
+ */
52
+ function hasDefaultExport(code) {
53
+ return /\bexport\s+default\b/.test(code) ||
54
+ /\bas\s+default\b/.test(code) ||
55
+ /\bexport\s*\{[^}]*\bdefault\b[^}]*\}/.test(code);
56
+ }
57
+ /**
58
+ * Write a stable, non-hashed alias next to a cycle target's hashed artifact.
59
+ *
60
+ * A dynamic import that closes an import cycle is left as the author wrote it
61
+ * (see the module loader), so esbuild normalises it to a relative `.js` path
62
+ * (`../app/page.js`) that does not match the content-hashed artifact
63
+ * (`../app/page.<hash>.js`). The alias sits at that relative path and re-exports
64
+ * the real artifact, so the edge resolves if the branch runs. Best-effort: a
65
+ * failed alias just leaves the pre-existing (unresolved) cycle edge in place.
66
+ */
67
+ async function writeCycleTargetAlias(input, relativePath, hashedFileName) {
68
+ const aliasRelativePath = relativePath.replace(/\.(tsx?|jsx|mdx)$/, ".js");
69
+ // Same extension in and out means nothing was renamed (already `.js`): the
70
+ // authored edge already points at the real artifact, so no alias is needed.
71
+ if (aliasRelativePath === relativePath)
72
+ return;
73
+ const aliasPath = join(input.tmpDir, aliasRelativePath);
74
+ const lines = [`export * from "./${hashedFileName}";`];
75
+ if (hasDefaultExport(input.transformedCode)) {
76
+ lines.push(`export { default } from "./${hashedFileName}";`);
77
+ }
78
+ try {
79
+ await input.localAdapter.fs.writeFile(aliasPath, lines.join("\n"));
80
+ logger.debug("Wrote cycle-target alias", {
81
+ alias: aliasRelativePath,
82
+ target: hashedFileName,
83
+ });
84
+ }
85
+ catch (error) {
86
+ logger.warn("Failed to write cycle-target alias", {
87
+ filePath: input.filePath.slice(-40),
88
+ error: error instanceof Error ? error.message : String(error),
89
+ });
90
+ }
91
+ }
47
92
  /** Write a transformed module artifact and register cache pointers. */
48
93
  export async function persistTransformedModule(input) {
49
94
  const transformedHash = hashCodeHex(input.transformedCode).slice(0, 8);
@@ -100,5 +145,9 @@ export async function persistTransformedModule(input) {
100
145
  });
101
146
  }
102
147
  input.moduleCache.set(input.cacheKey, tempFilePath);
148
+ if (input.isCycleTarget) {
149
+ const hashedFileName = jsPath.slice(jsPath.lastIndexOf("/") + 1);
150
+ await writeCycleTargetAlias(input, relativePath, hashedFileName);
151
+ }
103
152
  return tempFilePath;
104
153
  }
@@ -1 +1 @@
1
- {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../../../src/src/rendering/orchestrator/pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAUH,OAAO,EAIL,KAAK,iBAAiB,EACvB,MAAM,iCAAiC,CAAC;AASzC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAqDhF,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAE1D;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACvC,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACxE,aAAa,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrF;AAED,MAAM,WAAW,oBAAoB;IACnC,YAAY,EAAE,YAAY,CAAC;IAC3B,gBAAgB,EAAE,wBAAwB,CAAC;IAC3C,YAAY,EAAE,YAAY,CAAC;IAC3B,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,eAAe,EAAE,eAAe,CAAC;IACjC,OAAO,EAAE,cAAc,CAAC;IACxB,IAAI,EAAE,aAAa,GAAG,YAAY,CAAC;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,yEAAyE;IACzE,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2EAA2E;IAC3E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,wEAAwE;IACxE,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB,6CAA6C;IAC7C,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAChC,8EAA8E;IAC9E,iBAAiB,CAAC,EAAE,OAAO,qBAAqB,EAAE,sBAAsB,CAAC;CAC1E;AA0BD,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,kBAAkB,CAAqB;IAC/C,OAAO,CAAC,mBAAmB,CAAgC;gBAE/C,MAAM,EAAE,oBAAoB;IAcxC,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,oBAAoB;IA0B5B;;;;OAIG;YACW,yBAAyB;IAWvC;;;OAGG;IACH,gBAAgB,IAAI,IAAI;IAKxB,OAAO,CAAC,UAAU;YAOJ,0BAA0B;IAaxC;;;;;;;;;OASG;YACW,qBAAqB;IA6DnC;;;OAGG;YACW,mBAAmB;IA6GjC,OAAO,CAAC,uBAAuB;IAkCzB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC;IAyT9E,+EAA+E;IACzE,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,gBAAgB,CAAC;YAsHzE,kBAAkB;YA2ClB,cAAc;IAW5B,OAAO,CAAC,uBAAuB;YAYjB,kBAAkB;IAyFhC,OAAO,CAAC,kBAAkB;YAQZ,uBAAuB;IAmBrC;;;;;;OAMG;IACH,OAAO,CAAC,aAAa;CAStB"}
1
+ {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../../../src/src/rendering/orchestrator/pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAUH,OAAO,EAIL,KAAK,iBAAiB,EACvB,MAAM,iCAAiC,CAAC;AASzC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAsDhF,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAE1D;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACvC,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACxE,aAAa,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrF;AAED,MAAM,WAAW,oBAAoB;IACnC,YAAY,EAAE,YAAY,CAAC;IAC3B,gBAAgB,EAAE,wBAAwB,CAAC;IAC3C,YAAY,EAAE,YAAY,CAAC;IAC3B,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,eAAe,EAAE,eAAe,CAAC;IACjC,OAAO,EAAE,cAAc,CAAC;IACxB,IAAI,EAAE,aAAa,GAAG,YAAY,CAAC;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,yEAAyE;IACzE,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2EAA2E;IAC3E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,wEAAwE;IACxE,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB,6CAA6C;IAC7C,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAChC,8EAA8E;IAC9E,iBAAiB,CAAC,EAAE,OAAO,qBAAqB,EAAE,sBAAsB,CAAC;CAC1E;AA0BD,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,kBAAkB,CAAqB;IAC/C,OAAO,CAAC,mBAAmB,CAAgC;gBAE/C,MAAM,EAAE,oBAAoB;IAcxC,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,oBAAoB;IA0B5B;;;;OAIG;YACW,yBAAyB;IAWvC;;;OAGG;IACH,gBAAgB,IAAI,IAAI;IAKxB,OAAO,CAAC,UAAU;YAOJ,0BAA0B;IAaxC;;;;;;;;;OASG;YACW,qBAAqB;IAqEnC;;;OAGG;YACW,mBAAmB;IA6GjC,OAAO,CAAC,uBAAuB;IAkCzB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC;IAyT9E,+EAA+E;IACzE,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,gBAAgB,CAAC;YAsHzE,kBAAkB;YA2ClB,cAAc;IAW5B,OAAO,CAAC,uBAAuB;YAYjB,kBAAkB;IAyFhC,OAAO,CAAC,kBAAkB;YAQZ,uBAAuB;IAmBrC;;;;;;OAMG;IACH,OAAO,CAAC,aAAa;CAStB"}