veryfront 0.1.1100 → 0.1.1101
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/esm/deno.js +1 -1
- package/esm/src/routing/api/context-builder.d.ts +23 -1
- package/esm/src/routing/api/context-builder.d.ts.map +1 -1
- package/esm/src/routing/api/context-builder.js +22 -1
- package/esm/src/routing/api/handler.d.ts +11 -1
- package/esm/src/routing/api/handler.d.ts.map +1 -1
- package/esm/src/routing/api/handler.js +46 -12
- package/esm/src/routing/api/module-loader/loader.d.ts +5 -0
- package/esm/src/routing/api/module-loader/loader.d.ts.map +1 -1
- package/esm/src/routing/api/module-loader/loader.js +37 -2
- package/esm/src/server/dev-server/middleware.d.ts.map +1 -1
- package/esm/src/server/dev-server/middleware.js +35 -7
- package/esm/src/server/handlers/request/ssr/error-page-fallback.js +20 -3
- package/esm/src/server/handlers/request/ssr/ssr.handler.d.ts +0 -11
- package/esm/src/server/handlers/request/ssr/ssr.handler.d.ts.map +1 -1
- package/esm/src/server/handlers/request/ssr/ssr.handler.js +23 -1
- package/esm/src/transforms/esm/degraded-artifact.d.ts +15 -0
- package/esm/src/transforms/esm/degraded-artifact.d.ts.map +1 -0
- package/esm/src/transforms/esm/degraded-artifact.js +22 -0
- package/esm/src/transforms/esm/http-cache.d.ts.map +1 -1
- package/esm/src/transforms/esm/http-cache.js +47 -17
- package/esm/src/transforms/esm/import-parser.d.ts.map +1 -1
- package/esm/src/transforms/esm/import-parser.js +47 -1
- package/esm/src/transforms/esm/specifier-resolver.d.ts +21 -4
- package/esm/src/transforms/esm/specifier-resolver.d.ts.map +1 -1
- package/esm/src/transforms/esm/specifier-resolver.js +43 -23
- package/esm/src/transforms/import-rewriter/strategies/asset-strategy.d.ts +23 -0
- package/esm/src/transforms/import-rewriter/strategies/asset-strategy.d.ts.map +1 -0
- package/esm/src/transforms/import-rewriter/strategies/asset-strategy.js +49 -0
- package/esm/src/transforms/import-rewriter/strategies/index.d.ts +1 -0
- package/esm/src/transforms/import-rewriter/strategies/index.d.ts.map +1 -1
- package/esm/src/transforms/import-rewriter/strategies/index.js +1 -0
- package/esm/src/transforms/import-rewriter/unified-rewriter.d.ts.map +1 -1
- package/esm/src/transforms/import-rewriter/unified-rewriter.js +2 -0
- package/esm/src/transforms/mdx/esm-module-loader/module-fetcher/index.d.ts.map +1 -1
- package/esm/src/transforms/mdx/esm-module-loader/module-fetcher/index.js +2 -1
- package/esm/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.d.ts +19 -0
- package/esm/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.d.ts.map +1 -1
- package/esm/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.js +23 -1
- package/esm/src/utils/version-constant.d.ts +1 -1
- package/esm/src/utils/version-constant.js +1 -1
- package/package.json +5 -5
package/esm/deno.js
CHANGED
|
@@ -11,10 +11,32 @@ export interface APIContext {
|
|
|
11
11
|
cookies: Record<string, string>;
|
|
12
12
|
headers: Headers;
|
|
13
13
|
url: URL;
|
|
14
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Read the request body as JSON, or build a JSON response.
|
|
16
|
+
*
|
|
17
|
+
* `ctx.json()` with no arguments parses and returns the request body.
|
|
18
|
+
* `ctx.json(data, init?)` returns a JSON `Response`.
|
|
19
|
+
*/
|
|
20
|
+
json: {
|
|
21
|
+
(): Promise<unknown>;
|
|
22
|
+
(data: unknown, init?: ResponseInit): Response;
|
|
23
|
+
};
|
|
15
24
|
text: (data: string, init?: ResponseInit) => Response;
|
|
16
25
|
fs: FileSystemAdapter;
|
|
17
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Build the `ctx.json` helper for a request.
|
|
29
|
+
*
|
|
30
|
+
* Overloaded on arity. `ctx.json(data)` builds a response; `ctx.json()` reads
|
|
31
|
+
* the request body, which is what handlers reach for, and what the zero-arg
|
|
32
|
+
* form was previously misread as. It used to stringify `undefined` into a
|
|
33
|
+
* Response, so `await ctx.json()` yielded a Response object that serialised
|
|
34
|
+
* back out as `{}` and silently dropped every posted payload.
|
|
35
|
+
*
|
|
36
|
+
* Exported because the isolation Worker builds its own context and has to
|
|
37
|
+
* behave identically. Handlers must not care which one ran them.
|
|
38
|
+
*/
|
|
39
|
+
export declare function createJsonHelper(request: Request): APIContext["json"];
|
|
18
40
|
export declare function createContext(request: Request, match: RouteMatch, fs: FileSystemAdapter): APIContext;
|
|
19
41
|
/**
|
|
20
42
|
* @deprecated Use {@link flattenRouteParams} directly. Kept as a thin alias so
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context-builder.d.ts","sourceRoot":"","sources":["../../../../src/src/routing/api/context-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAG3D,OAAO,EAAE,YAAY,EAAE,CAAC;AAExB,mDAAmD;AACnD,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,OAAO,CAAC;IACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAC1C,KAAK,EAAE,eAAe,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,GAAG,CAAC;IACT,IAAI,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,YAAY,
|
|
1
|
+
{"version":3,"file":"context-builder.d.ts","sourceRoot":"","sources":["../../../../src/src/routing/api/context-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAG3D,OAAO,EAAE,YAAY,EAAE,CAAC;AAExB,mDAAmD;AACnD,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,OAAO,CAAC;IACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAC1C,KAAK,EAAE,eAAe,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,GAAG,CAAC;IACT;;;;;OAKG;IACH,IAAI,EAAE;QACJ,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;QACrB,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,YAAY,GAAG,QAAQ,CAAC;KAChD,CAAC;IACF,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,YAAY,KAAK,QAAQ,CAAC;IACtD,EAAE,EAAE,iBAAiB,CAAC;CACvB;AAgBD;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,UAAU,CAAC,MAAM,CAAC,CAUrE;AAED,wBAAgB,aAAa,CAC3B,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,UAAU,EACjB,EAAE,EAAE,iBAAiB,GACpB,UAAU,CAmBZ;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,GACxC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAExB"}
|
|
@@ -10,9 +10,30 @@ function createResponse(body, contentType, init) {
|
|
|
10
10
|
},
|
|
11
11
|
});
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Build the `ctx.json` helper for a request.
|
|
15
|
+
*
|
|
16
|
+
* Overloaded on arity. `ctx.json(data)` builds a response; `ctx.json()` reads
|
|
17
|
+
* the request body, which is what handlers reach for, and what the zero-arg
|
|
18
|
+
* form was previously misread as. It used to stringify `undefined` into a
|
|
19
|
+
* Response, so `await ctx.json()` yielded a Response object that serialised
|
|
20
|
+
* back out as `{}` and silently dropped every posted payload.
|
|
21
|
+
*
|
|
22
|
+
* Exported because the isolation Worker builds its own context and has to
|
|
23
|
+
* behave identically. Handlers must not care which one ran them.
|
|
24
|
+
*/
|
|
25
|
+
export function createJsonHelper(request) {
|
|
26
|
+
function json(...args) {
|
|
27
|
+
if (args.length === 0)
|
|
28
|
+
return request.json();
|
|
29
|
+
const [data, init] = args;
|
|
30
|
+
return createResponse(JSON.stringify(data), "application/json", init);
|
|
31
|
+
}
|
|
32
|
+
return json;
|
|
33
|
+
}
|
|
13
34
|
export function createContext(request, match, fs) {
|
|
14
35
|
const url = new URL(request.url);
|
|
15
|
-
const json = (
|
|
36
|
+
const json = createJsonHelper(request);
|
|
16
37
|
const text = (data, init) => createResponse(data, "text/plain", init);
|
|
17
38
|
return {
|
|
18
39
|
request,
|
|
@@ -6,6 +6,17 @@ import { loadHandlerModule } from "./module-loader/loader.js";
|
|
|
6
6
|
import { discoverAppRoutes, discoverPagesRoutes } from "./route-discovery.js";
|
|
7
7
|
import type { HandlerContext } from "../../types/index.js";
|
|
8
8
|
export type { APIContext, APIRoute };
|
|
9
|
+
/**
|
|
10
|
+
* Reduce a module load error to something safe to put in a response body.
|
|
11
|
+
*
|
|
12
|
+
* The message is worth returning, since it usually names a syntax or import
|
|
13
|
+
* error the developer can act on. The rest of it is not: a raw load error
|
|
14
|
+
* carries the temp directory the bundle was written to, absolute paths into the
|
|
15
|
+
* framework install, and a full stack trace. A path inside the project survives
|
|
16
|
+
* as a project-relative one, because that is the part that identifies the file
|
|
17
|
+
* to fix.
|
|
18
|
+
*/
|
|
19
|
+
export declare function sanitizeLoadErrorForResponse(message: string, projectDir?: string): string;
|
|
9
20
|
/**
|
|
10
21
|
* Injection interface for testing APIRouteHandler dependencies
|
|
11
22
|
*/
|
|
@@ -31,7 +42,6 @@ export declare class APIRouteHandler {
|
|
|
31
42
|
private projectDir;
|
|
32
43
|
private router;
|
|
33
44
|
private routeCache;
|
|
34
|
-
private lastErrorMessage;
|
|
35
45
|
private activeRequests;
|
|
36
46
|
private destroyRequested;
|
|
37
47
|
private destroyed;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../../src/src/routing/api/handler.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAMlD,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAG9E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAK3D,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../../src/src/routing/api/handler.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAMlD,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAG9E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAK3D,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC;AAYrC;;;;;;;;;GASG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAYzF;AAED;;GAEG;AACH,UAAU,mBAAmB;IAC3B,iBAAiB,CAAC,EAAE,OAAO,iBAAiB,CAAC;IAC7C,mBAAmB,CAAC,EAAE,OAAO,mBAAmB,CAAC;IACjD,iBAAiB,CAAC,EAAE,OAAO,iBAAiB,CAAC;IAC7C,SAAS,CAAC,EAAE,OAAO,SAAS,CAAC;CAC9B;AAID;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,mBAAmB,GAAG,IAAI,GAAG,IAAI,CAE3E;AAWD,uDAAuD;AACvD,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,WAAW,CAAC;CACvB;AAED,iDAAiD;AACjD,MAAM,MAAM,UAAU,GAAG,CAAC,GAAG,EAAE,UAAU,KAAK,OAAO,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAAC;AAW3E,qBAAa,eAAe;IAkBxB,OAAO,CAAC,UAAU;IAjBpB,OAAO,CAAC,MAAM,CAAyB;IACvC,OAAO,CAAC,UAAU,CAA6E;IAC/F,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,SAAS,CAAS;IAE1B,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,cAAc,CAAwC;IAE9D,OAAO,CAAC,UAAU,CAAqC;IACvD,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,iBAAiB,CAA8B;IAEvD,OAAO,CAAC,MAAM,CAAsD;IACpE,OAAO,CAAC,aAAa,CAA8B;gBAGzC,UAAU,EAAE,MAAM,EAC1B,OAAO,CAAC,EAAE,cAAc;IAM1B,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAgD3B,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAwGxE,OAAO,CAAC,WAAW;IAsCnB,UAAU,IAAI,IAAI;IAKlB,OAAO,IAAI,IAAI;IAWf,OAAO,CAAC,eAAe;IAKvB,OAAO,CAAC,UAAU;YAQJ,aAAa;YAsBb,gBAAgB;YAOhB,cAAc;YAcd,YAAY;YAOZ,cAAc;CAW7B;AAED,OAAO,EACL,UAAU,EACV,SAAS,EACT,mBAAmB,IAAI,WAAW,EAClC,YAAY,IAAI,IAAI,EACpB,QAAQ,EACR,gBAAgB,IAAI,QAAQ,EAC5B,YAAY,GACb,MAAM,yCAAyC,CAAC"}
|
|
@@ -12,6 +12,33 @@ import { executeAppRoute, executePagesRoute } from "./route-executor.js";
|
|
|
12
12
|
import { withSpan } from "../../observability/tracing/otlp-setup.js";
|
|
13
13
|
/** Max entries in the loaded-handler LRU cache */
|
|
14
14
|
const HANDLER_CACHE_MAX_ENTRIES = 256;
|
|
15
|
+
/** Longest sanitised load error a response body carries. */
|
|
16
|
+
const MAX_LOAD_ERROR_LENGTH = 300;
|
|
17
|
+
/**
|
|
18
|
+
* Roots that only ever identify the machine running the server: home
|
|
19
|
+
* directories and the temp directories bundling writes to.
|
|
20
|
+
*/
|
|
21
|
+
const MACHINE_PATH_PATTERN = /(?:file:\/\/)?\/(?:private\/)?(?:Users|home|root|var\/folders|var\/tmp|tmp)\/\S*/g;
|
|
22
|
+
/**
|
|
23
|
+
* Reduce a module load error to something safe to put in a response body.
|
|
24
|
+
*
|
|
25
|
+
* The message is worth returning, since it usually names a syntax or import
|
|
26
|
+
* error the developer can act on. The rest of it is not: a raw load error
|
|
27
|
+
* carries the temp directory the bundle was written to, absolute paths into the
|
|
28
|
+
* framework install, and a full stack trace. A path inside the project survives
|
|
29
|
+
* as a project-relative one, because that is the part that identifies the file
|
|
30
|
+
* to fix.
|
|
31
|
+
*/
|
|
32
|
+
export function sanitizeLoadErrorForResponse(message, projectDir) {
|
|
33
|
+
// A stack trace is never the actionable part.
|
|
34
|
+
let text = (message.split("\n")[0] ?? "").trim();
|
|
35
|
+
if (projectDir) {
|
|
36
|
+
const root = projectDir.replace(/\/+$/, "");
|
|
37
|
+
text = text.replaceAll(`file://${root}/`, "").replaceAll(`${root}/`, "");
|
|
38
|
+
}
|
|
39
|
+
text = text.replace(MACHINE_PATH_PATTERN, "<PATH>");
|
|
40
|
+
return text.length > MAX_LOAD_ERROR_LENGTH ? `${text.slice(0, MAX_LOAD_ERROR_LENGTH)}...` : text;
|
|
41
|
+
}
|
|
15
42
|
let injectedDeps = null;
|
|
16
43
|
/**
|
|
17
44
|
* Inject dependencies for testing. Pass null to reset to defaults.
|
|
@@ -31,7 +58,6 @@ export class APIRouteHandler {
|
|
|
31
58
|
projectDir;
|
|
32
59
|
router = new ApiRouteMatcher();
|
|
33
60
|
routeCache = new LRUCache({ maxEntries: HANDLER_CACHE_MAX_ENTRIES });
|
|
34
|
-
lastErrorMessage = null;
|
|
35
61
|
activeRequests = 0;
|
|
36
62
|
destroyRequested = false;
|
|
37
63
|
destroyed = false;
|
|
@@ -116,18 +142,27 @@ export class APIRouteHandler {
|
|
|
116
142
|
page: match.route.page,
|
|
117
143
|
params: match.params,
|
|
118
144
|
});
|
|
119
|
-
const handler = await this.loadHandler(match);
|
|
145
|
+
const { handler, errorMessage } = await this.loadHandler(match);
|
|
120
146
|
if (!handler) {
|
|
147
|
+
const msg = errorMessage ?? "Handler not found";
|
|
121
148
|
try {
|
|
122
|
-
|
|
149
|
+
// The full detail, paths and all, belongs in the log.
|
|
150
|
+
logger.error(`handler module failed to load: ${match.route.page}`, { reason: msg });
|
|
123
151
|
}
|
|
124
152
|
catch (e) {
|
|
125
153
|
logger.warn("API error log failed", e);
|
|
126
154
|
}
|
|
127
|
-
const msg = this.lastErrorMessage ?? "Handler not found";
|
|
128
155
|
if (msg.includes("Remote import blocked by allow-list"))
|
|
129
156
|
return badGateway(msg);
|
|
130
|
-
|
|
157
|
+
// The reason the module failed to load is the only useful thing here.
|
|
158
|
+
// Reporting a flat "Handler not found" for what is usually a syntax or
|
|
159
|
+
// import error sends people hunting for a routing problem that does
|
|
160
|
+
// not exist. Local development only, and sanitised: a raw load error
|
|
161
|
+
// carries temp directories, absolute paths and a stack trace, none of
|
|
162
|
+
// which belong in a response body.
|
|
163
|
+
return internalServerError(ctx?.isLocalProject
|
|
164
|
+
? sanitizeLoadErrorForResponse(msg, this.projectDir)
|
|
165
|
+
: "Handler not found");
|
|
131
166
|
}
|
|
132
167
|
// App Router routes are always named route.ts/js/tsx/jsx
|
|
133
168
|
// Pages Router routes have descriptive names like articles.ts
|
|
@@ -156,7 +191,7 @@ export class APIRouteHandler {
|
|
|
156
191
|
await this.ensureConfig(adapter);
|
|
157
192
|
const cached = this.routeCache.get(modulePath);
|
|
158
193
|
if (cached)
|
|
159
|
-
return cached;
|
|
194
|
+
return { handler: cached, errorMessage: null };
|
|
160
195
|
try {
|
|
161
196
|
const deps = getDeps();
|
|
162
197
|
const handler = await deps.loadHandlerModule({
|
|
@@ -168,16 +203,15 @@ export class APIRouteHandler {
|
|
|
168
203
|
// Only cache handlers that export at least one HTTP method.
|
|
169
204
|
// Empty objects ({}) from failed imports are truthy but useless —
|
|
170
205
|
// caching them would prevent retry after the user fixes the import.
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
return
|
|
206
|
+
const usable = handler && Object.keys(handler).length > 0 ? handler : null;
|
|
207
|
+
if (usable)
|
|
208
|
+
this.routeCache.set(modulePath, usable);
|
|
209
|
+
return { handler: usable, errorMessage: null };
|
|
175
210
|
}
|
|
176
211
|
catch (error) {
|
|
177
212
|
const msg = error instanceof Error ? error.message : String(error);
|
|
178
|
-
this.lastErrorMessage = msg;
|
|
179
213
|
logger.error(`[API] Failed to load handler for ${modulePath}: ${msg}`);
|
|
180
|
-
return null;
|
|
214
|
+
return { handler: null, errorMessage: msg };
|
|
181
215
|
}
|
|
182
216
|
}, { "api.modulePath": modulePath });
|
|
183
217
|
}
|
|
@@ -2,4 +2,9 @@ import type { APIRoute, LoadModuleOptions } from "./types.js";
|
|
|
2
2
|
export { generateCompiledBinaryRequireShim, getNodeExternalPackagesToResolve, loadVeryfrontExportsMap, resolveEsmUserDependencies, resolveNodePackageToFileUrl, rewriteCompiledBinaryUserDependencyImports, rewriteCompiledBinaryVeryfrontImports, rewriteDenoNodeBuiltinImports, rewriteDenoNpmDependencyImports, rewriteNodeExternalImports, } from "./external-import-rewriter.js";
|
|
3
3
|
export { toCjsDestructureBindings } from "./loader-helpers.js";
|
|
4
4
|
export declare function loadHandlerModule(options: LoadModuleOptions): Promise<APIRoute | null>;
|
|
5
|
+
/**
|
|
6
|
+
* True when a module failed to load because Deno could not resolve one of its
|
|
7
|
+
* import specifiers, rather than because the module itself is broken.
|
|
8
|
+
*/
|
|
9
|
+
export declare function isSpecifierResolutionError(error: unknown): boolean;
|
|
5
10
|
//# sourceMappingURL=loader.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../../../../src/src/routing/api/module-loader/loader.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAkB9D,OAAO,EACL,iCAAiC,EACjC,gCAAgC,EAChC,uBAAuB,EACvB,0BAA0B,EAC1B,2BAA2B,EAC3B,0CAA0C,EAC1C,qCAAqC,EACrC,6BAA6B,EAC7B,+BAA+B,EAC/B,0BAA0B,GAC3B,MAAM,+BAA+B,CAAC;AAIvC,OAAO,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AAE/D,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAyBtF"}
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../../../../src/src/routing/api/module-loader/loader.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAkB9D,OAAO,EACL,iCAAiC,EACjC,gCAAgC,EAChC,uBAAuB,EACvB,0BAA0B,EAC1B,2BAA2B,EAC3B,0CAA0C,EAC1C,qCAAqC,EACrC,6BAA6B,EAC7B,+BAA+B,EAC/B,0BAA0B,GAC3B,MAAM,+BAA+B,CAAC;AAIvC,OAAO,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AAE/D,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAyBtF;AA0DD;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAIlE"}
|
|
@@ -45,8 +45,25 @@ async function loadModule(args) {
|
|
|
45
45
|
return loadAndTranspileModule(modulePath, projectDir, adapter, fs, config);
|
|
46
46
|
}
|
|
47
47
|
const fileExistsLocally = await fs.exists(modulePath);
|
|
48
|
-
if (fileExistsLocally)
|
|
49
|
-
|
|
48
|
+
if (fileExistsLocally) {
|
|
49
|
+
try {
|
|
50
|
+
return await loadTSModuleDirect(modulePath);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
// A direct import shares the dev server's runtime context, which is what
|
|
54
|
+
// makes auto-discovery (agentRegistry and friends) work — but it leaves
|
|
55
|
+
// specifier resolution to Deno, which knows nothing about the project's
|
|
56
|
+
// `@/` alias. Bundling can resolve that import map path, so fall back to
|
|
57
|
+
// it rather than reporting a routing-shaped 500.
|
|
58
|
+
if (!isSpecifierResolutionError(error))
|
|
59
|
+
throw error;
|
|
60
|
+
logger.debug("Direct import could not resolve a specifier, bundling instead", {
|
|
61
|
+
modulePath,
|
|
62
|
+
error: error instanceof Error ? error.message : String(error),
|
|
63
|
+
});
|
|
64
|
+
return loadAndTranspileModule(modulePath, projectDir, adapter, fs, config);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
50
67
|
logger.debug(`File not local, using adapter-based loading: ${modulePath}`);
|
|
51
68
|
return loadAndTranspileModule(modulePath, projectDir, adapter, fs, config);
|
|
52
69
|
}
|
|
@@ -55,6 +72,24 @@ async function loadModule(args) {
|
|
|
55
72
|
* This allows the module to share the same runtime context as the dev server,
|
|
56
73
|
* enabling auto-discovery features like agentRegistry to work.
|
|
57
74
|
*/
|
|
75
|
+
/**
|
|
76
|
+
* Deno's resolver reports an unresolvable import as a TypeError whose message
|
|
77
|
+
* opens by naming the specifier it could not resolve. Anchoring on that opening
|
|
78
|
+
* is what keeps a module's own error out: a route that throws
|
|
79
|
+
* `Error("Cannot find module x")` at evaluation time is broken, and re-running
|
|
80
|
+
* it under bundling would evaluate broken code a second time.
|
|
81
|
+
*/
|
|
82
|
+
const SPECIFIER_RESOLUTION_MESSAGE = /^(?:Import "[^"]+" not a dependency|Module not found "[^"]+"|Relative import path "[^"]+" not prefixed)/;
|
|
83
|
+
/**
|
|
84
|
+
* True when a module failed to load because Deno could not resolve one of its
|
|
85
|
+
* import specifiers, rather than because the module itself is broken.
|
|
86
|
+
*/
|
|
87
|
+
export function isSpecifierResolutionError(error) {
|
|
88
|
+
// Every other error shape the direct import can produce belongs to the module.
|
|
89
|
+
if (!(error instanceof TypeError))
|
|
90
|
+
return false;
|
|
91
|
+
return SPECIFIER_RESOLUTION_MESSAGE.test(error.message.trimStart());
|
|
92
|
+
}
|
|
58
93
|
function loadTSModuleDirect(modulePath) {
|
|
59
94
|
const cacheBuster = `?v=${Date.now()}`;
|
|
60
95
|
const url = modulePath.startsWith("file://")
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../../../src/src/server/dev-server/middleware.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AAGxE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAGtE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAM7D,MAAM,MAAM,kBAAkB,GAAG,iBAAiB,CAAC;AAEnD,UAAU,qBAAqB;IAC7B,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAkFD,wBAAsB,kBAAkB,CACtC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,cAAc,EACvB,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,kBAAkB,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../../../src/src/server/dev-server/middleware.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AAGxE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAGtE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAM7D,MAAM,MAAM,kBAAkB,GAAG,iBAAiB,CAAC;AAEnD,UAAU,qBAAqB;IAC7B,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAkFD,wBAAsB,kBAAkB,CACtC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,cAAc,EACvB,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAkC/B;AAqHD,wBAAsB,eAAe,CACnC,QAAQ,EAAE,kBAAkB,EAC5B,MAAM,EAAE,eAAe,EACvB,cAAc,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,GAClD,OAAO,CAAC,IAAI,CAAC,CASf"}
|
|
@@ -86,11 +86,11 @@ export async function loadMiddlewareFile(projectDir, adapter, options = {}) {
|
|
|
86
86
|
try {
|
|
87
87
|
logger.debug(`Loading ${middlewareFile}`);
|
|
88
88
|
if (isVirtualFilesystem(adapter.fs)) {
|
|
89
|
-
return await loadMiddlewareFromVirtualFS(middlewarePath, adapter, options.throwOnError === true);
|
|
89
|
+
return await loadMiddlewareFromVirtualFS(middlewarePath, adapter, options.throwOnError === true, middlewareFile);
|
|
90
90
|
}
|
|
91
91
|
const middlewareUrl = `file://${middlewarePath}?t=${Date.now()}-${dntShim.crypto.randomUUID()}`;
|
|
92
92
|
const middlewareModule = await import(middlewareUrl);
|
|
93
|
-
return normalizeMiddlewareExport(middlewareModule, options.throwOnError === true);
|
|
93
|
+
return normalizeMiddlewareExport(middlewareModule, options.throwOnError === true, middlewareFile);
|
|
94
94
|
}
|
|
95
95
|
catch (error) {
|
|
96
96
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -101,7 +101,7 @@ export async function loadMiddlewareFile(projectDir, adapter, options = {}) {
|
|
|
101
101
|
}
|
|
102
102
|
return [];
|
|
103
103
|
}
|
|
104
|
-
async function loadMiddlewareFromVirtualFS(middlewarePath, adapter, strictExport) {
|
|
104
|
+
async function loadMiddlewareFromVirtualFS(middlewarePath, adapter, strictExport, sourceFile = "middleware.ts") {
|
|
105
105
|
const fs = createFileSystem();
|
|
106
106
|
const content = await adapter.fs.readFile(middlewarePath);
|
|
107
107
|
const source = typeof content === "string" ? content : new TextDecoder().decode(content);
|
|
@@ -130,20 +130,48 @@ async function loadMiddlewareFromVirtualFS(middlewarePath, adapter, strictExport
|
|
|
130
130
|
try {
|
|
131
131
|
await fs.writeTextFile(tempFile, js);
|
|
132
132
|
const middlewareModule = await import(`file://${tempFile}?v=${Date.now()}`);
|
|
133
|
-
return normalizeMiddlewareExport(middlewareModule, strictExport);
|
|
133
|
+
return normalizeMiddlewareExport(middlewareModule, strictExport, sourceFile);
|
|
134
134
|
}
|
|
135
135
|
finally {
|
|
136
136
|
await fs.remove(tempDir, { recursive: true });
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
|
-
|
|
139
|
+
/**
|
|
140
|
+
* Explain what a root middleware file must export. When the module looks like it
|
|
141
|
+
* was written for Next.js, say so, because that is overwhelmingly why this
|
|
142
|
+
* fails. A bad root middleware takes down every route, so the message has
|
|
143
|
+
* to be enough to fix the file without reading the source.
|
|
144
|
+
*/
|
|
145
|
+
function invalidMiddlewareExport(middlewareModule, sourceFile) {
|
|
146
|
+
const named = middlewareModule && typeof middlewareModule === "object"
|
|
147
|
+
? Object.keys(middlewareModule)
|
|
148
|
+
: [];
|
|
149
|
+
const looksLikeNext = named.includes("middleware") &&
|
|
150
|
+
typeof middlewareModule.middleware === "function";
|
|
151
|
+
const detail = looksLikeNext
|
|
152
|
+
? `Found a named "middleware" export, which is the Next.js convention. ` +
|
|
153
|
+
`Veryfront expects a default export, and its middleware receives ` +
|
|
154
|
+
`(c, next), where c is a context carrying c.req, not the Request itself.`
|
|
155
|
+
: named.length > 0
|
|
156
|
+
? `Found export(s): ${named.join(", ")}.`
|
|
157
|
+
: `The module has no usable export.`;
|
|
158
|
+
return new TypeError(`Invalid middleware export in ${sourceFile}. ${detail}\n` +
|
|
159
|
+
`Expected a default export that is a middleware function, or a non-empty ` +
|
|
160
|
+
`array of them:\n\n` +
|
|
161
|
+
` export default async function middleware(c, next) {\n` +
|
|
162
|
+
` const response = await next();\n` +
|
|
163
|
+
` return response;\n` +
|
|
164
|
+
` }\n\n` +
|
|
165
|
+
`See docs/guides/middleware.md.`);
|
|
166
|
+
}
|
|
167
|
+
function normalizeMiddlewareExport(middlewareModule, strict = false, sourceFile = "middleware.ts") {
|
|
140
168
|
const exported = middlewareModule && typeof middlewareModule === "object" &&
|
|
141
169
|
"default" in middlewareModule
|
|
142
170
|
? middlewareModule.default
|
|
143
171
|
: middlewareModule;
|
|
144
172
|
if (Array.isArray(exported)) {
|
|
145
173
|
if (strict && (exported.length === 0 || exported.some((value) => typeof value !== "function"))) {
|
|
146
|
-
throw
|
|
174
|
+
throw invalidMiddlewareExport(middlewareModule, sourceFile);
|
|
147
175
|
}
|
|
148
176
|
return exported.filter((middleware) => typeof middleware === "function");
|
|
149
177
|
}
|
|
@@ -151,7 +179,7 @@ function normalizeMiddlewareExport(middlewareModule, strict = false) {
|
|
|
151
179
|
return [exported];
|
|
152
180
|
}
|
|
153
181
|
if (strict) {
|
|
154
|
-
throw
|
|
182
|
+
throw invalidMiddlewareExport(middlewareModule, sourceFile);
|
|
155
183
|
}
|
|
156
184
|
return [];
|
|
157
185
|
}
|
|
@@ -78,6 +78,23 @@ async function setCachedPath(cacheKey, path) {
|
|
|
78
78
|
}
|
|
79
79
|
errorPagePathCache.set(cacheKey, path);
|
|
80
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* Whether a "this project has no error page" answer may be cached.
|
|
83
|
+
*
|
|
84
|
+
* In dev it may not. Nothing invalidates this cache when the filesystem
|
|
85
|
+
* changes, so caching the miss means a project that adds `pages/500.tsx` while
|
|
86
|
+
* the server is running keeps getting the dev overlay until restart, with no
|
|
87
|
+
* indication why. A miss costs one file probe on an error render, which is not
|
|
88
|
+
* a path worth optimising in dev.
|
|
89
|
+
*/
|
|
90
|
+
function canCacheMiss(ctx) {
|
|
91
|
+
return !ctx.isLocalProject;
|
|
92
|
+
}
|
|
93
|
+
async function setCachedMiss(cacheKey, ctx) {
|
|
94
|
+
if (!canCacheMiss(ctx))
|
|
95
|
+
return;
|
|
96
|
+
await setCachedPath(cacheKey, null);
|
|
97
|
+
}
|
|
81
98
|
async function deleteCachedPath(cacheKey) {
|
|
82
99
|
if (injectedCacheRepo) {
|
|
83
100
|
await injectedCacheRepo.delete(cacheKey);
|
|
@@ -104,7 +121,7 @@ async function tryLoadErrorPage(pagesDir, pageType, ctx, reactVersion) {
|
|
|
104
121
|
try {
|
|
105
122
|
const resolvedPath = await ctx.adapter.fs.resolveFile(basePath);
|
|
106
123
|
if (!resolvedPath) {
|
|
107
|
-
await
|
|
124
|
+
await setCachedMiss(cacheKey, ctx);
|
|
108
125
|
return null;
|
|
109
126
|
}
|
|
110
127
|
const fullPath = joinPath(ctx.projectDir, resolvedPath);
|
|
@@ -117,7 +134,7 @@ async function tryLoadErrorPage(pagesDir, pageType, ctx, reactVersion) {
|
|
|
117
134
|
catch (_) {
|
|
118
135
|
// expected: resolveFile may fail, fall through to extension probing
|
|
119
136
|
}
|
|
120
|
-
await
|
|
137
|
+
await setCachedMiss(cacheKey, ctx);
|
|
121
138
|
return null;
|
|
122
139
|
}
|
|
123
140
|
for (const ext of ERROR_PAGE_EXTENSIONS) {
|
|
@@ -136,7 +153,7 @@ async function tryLoadErrorPage(pagesDir, pageType, ctx, reactVersion) {
|
|
|
136
153
|
// expected: file with this extension doesn't exist
|
|
137
154
|
}
|
|
138
155
|
}
|
|
139
|
-
await
|
|
156
|
+
await setCachedMiss(cacheKey, ctx);
|
|
140
157
|
return null;
|
|
141
158
|
}
|
|
142
159
|
async function loadErrorComponent(filePath, ctx, reactVersion) {
|
|
@@ -15,17 +15,6 @@ import { type SSRServiceLike } from "../../../services/rendering/ssr.service.js"
|
|
|
15
15
|
* Config override (PRODUCTION_MODE) takes precedence.
|
|
16
16
|
*/
|
|
17
17
|
export declare function isProductionMode(ctx: HandlerContext, _url?: URL): boolean;
|
|
18
|
-
/**
|
|
19
|
-
* SSR Handler - Thin orchestration layer
|
|
20
|
-
*
|
|
21
|
-
* Responsibilities:
|
|
22
|
-
* - Route matching and quick rejections
|
|
23
|
-
* - Multi-project context setup
|
|
24
|
-
* - Response building with headers
|
|
25
|
-
* - Custom fallback handling (not-found, error pages)
|
|
26
|
-
*
|
|
27
|
-
* Business logic is delegated to SSRService.
|
|
28
|
-
*/
|
|
29
18
|
export declare class SSRHandler extends BaseHandler {
|
|
30
19
|
metadata: HandlerMetadata;
|
|
31
20
|
private ssrService;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ssr.handler.d.ts","sourceRoot":"","sources":["../../../../../../src/src/server/handlers/request/ssr/ssr.handler.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,KAAK,EACV,cAAc,EACd,eAAe,EAEf,aAAa,EACd,MAAM,gBAAgB,CAAC;AAWxB,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,4CAA4C,CAAC;
|
|
1
|
+
{"version":3,"file":"ssr.handler.d.ts","sourceRoot":"","sources":["../../../../../../src/src/server/handlers/request/ssr/ssr.handler.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,KAAK,EACV,cAAc,EACd,eAAe,EAEf,aAAa,EACd,MAAM,gBAAgB,CAAC;AAWxB,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,4CAA4C,CAAC;AAOpD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,CAAC,EAAE,GAAG,GAAG,OAAO,CAKzE;AA+BD,qBAAa,UAAW,SAAQ,WAAW;IACzC,QAAQ,EAAE,eAAe,CAIvB;IAEF,OAAO,CAAC,UAAU,CAAiB;gBAEvB,UAAU,CAAC,EAAE,cAAc;IAKvC,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;IA8BjE,OAAO,CAAC,qBAAqB;IAmF7B,OAAO,CAAC,iBAAiB;IAkEzB,OAAO,CAAC,cAAc;YAgBR,cAAc;YA6Bd,sBAAsB;YAgBtB,aAAa;CAU5B"}
|
|
@@ -19,6 +19,7 @@ import { tryNotFoundFallback } from "./not-found-fallback.js";
|
|
|
19
19
|
import { tryErrorPageFallback } from "./error-page-fallback.js";
|
|
20
20
|
import { SSRService, } from "../../../services/rendering/ssr.service.js";
|
|
21
21
|
import { ErrorPages } from "../../../utils/error-html.js";
|
|
22
|
+
import { VeryfrontError } from "../../../../errors/index.js";
|
|
22
23
|
import { buildSSRResponse } from "./ssr-response-builder.js";
|
|
23
24
|
const logger = serverLogger.component("ssr");
|
|
24
25
|
/**
|
|
@@ -43,6 +44,23 @@ export function isProductionMode(ctx, _url) {
|
|
|
43
44
|
*
|
|
44
45
|
* Business logic is delegated to SSRService.
|
|
45
46
|
*/
|
|
47
|
+
/**
|
|
48
|
+
* True for errors raised while compiling or resolving project source, as
|
|
49
|
+
* opposed to errors thrown by the running application.
|
|
50
|
+
*
|
|
51
|
+
* Module-load failures arrive wrapped in a RUNTIME-category `render-error`,
|
|
52
|
+
* which loses the original category, so they are identified by their
|
|
53
|
+
* `criticalFailures` context instead: a page module that could not be *loaded*
|
|
54
|
+
* always failed to compile or resolve.
|
|
55
|
+
*/
|
|
56
|
+
function isBuildError(error) {
|
|
57
|
+
if (!(error instanceof VeryfrontError))
|
|
58
|
+
return false;
|
|
59
|
+
if (error.category === "BUILD" || error.category === "MODULE")
|
|
60
|
+
return true;
|
|
61
|
+
const context = error.context;
|
|
62
|
+
return Array.isArray(context?.criticalFailures) && context.criticalFailures.length > 0;
|
|
63
|
+
}
|
|
46
64
|
export class SSRHandler extends BaseHandler {
|
|
47
65
|
metadata = {
|
|
48
66
|
name: "SSRHandler",
|
|
@@ -182,7 +200,11 @@ export class SSRHandler extends BaseHandler {
|
|
|
182
200
|
if (result.errorType === "not-found") {
|
|
183
201
|
return this.handleNotFound(req, ctx, slug, nonce);
|
|
184
202
|
}
|
|
185
|
-
|
|
203
|
+
const isServerError = result.errorType === "server-error" ||
|
|
204
|
+
result.errorType === "runtime";
|
|
205
|
+
// Project error pages should beat the dev overlay for runtime errors.
|
|
206
|
+
// Build/import errors stay visible because their overlay is actionable.
|
|
207
|
+
if (isServerError && !(result.showDevOverlay && isBuildError(result.error))) {
|
|
186
208
|
const customResponse = await this.tryCustomErrorFallback(req, ctx, result, nonce);
|
|
187
209
|
if (customResponse)
|
|
188
210
|
return customResponse;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Degradation marking for cached HTTP module artifacts.
|
|
3
|
+
*
|
|
4
|
+
* A module whose lazy dependency could not be prefetched is still usable for
|
|
5
|
+
* the render that produced it, but it must never be mistaken for a complete
|
|
6
|
+
* artifact on a later render. The marker travels with the code, so any reader
|
|
7
|
+
* of the on-disk cache can tell the two apart.
|
|
8
|
+
*
|
|
9
|
+
* @module transforms/esm/degraded-artifact
|
|
10
|
+
*/
|
|
11
|
+
/** Mark bundle code as produced from an incomplete dependency prefetch. */
|
|
12
|
+
export declare function markDegradedArtifact(code: string): string;
|
|
13
|
+
/** Return whether bundle code was produced from an incomplete prefetch. */
|
|
14
|
+
export declare function isDegradedArtifact(code: string): boolean;
|
|
15
|
+
//# sourceMappingURL=degraded-artifact.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"degraded-artifact.d.ts","sourceRoot":"","sources":["../../../../src/src/transforms/esm/degraded-artifact.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,2EAA2E;AAC3E,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGzD;AAED,2EAA2E;AAC3E,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAExD"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Degradation marking for cached HTTP module artifacts.
|
|
3
|
+
*
|
|
4
|
+
* A module whose lazy dependency could not be prefetched is still usable for
|
|
5
|
+
* the render that produced it, but it must never be mistaken for a complete
|
|
6
|
+
* artifact on a later render. The marker travels with the code, so any reader
|
|
7
|
+
* of the on-disk cache can tell the two apart.
|
|
8
|
+
*
|
|
9
|
+
* @module transforms/esm/degraded-artifact
|
|
10
|
+
*/
|
|
11
|
+
/** Preserved comment format that survives minification */
|
|
12
|
+
const VF_DEGRADED_MARKER = "\n/*! @vf-degraded */\n";
|
|
13
|
+
/** Mark bundle code as produced from an incomplete dependency prefetch. */
|
|
14
|
+
export function markDegradedArtifact(code) {
|
|
15
|
+
if (isDegradedArtifact(code))
|
|
16
|
+
return code;
|
|
17
|
+
return `${code}${VF_DEGRADED_MARKER}`;
|
|
18
|
+
}
|
|
19
|
+
/** Return whether bundle code was produced from an incomplete prefetch. */
|
|
20
|
+
export function isDegradedArtifact(code) {
|
|
21
|
+
return code.endsWith(VF_DEGRADED_MARKER);
|
|
22
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-cache.d.ts","sourceRoot":"","sources":["../../../../src/src/transforms/esm/http-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAeH,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,oBAAoB,EACpB,qBAAqB,EACrB,kBAAkB,EACnB,MAAM,sBAAsB,CAAC;AAI9B,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"http-cache.d.ts","sourceRoot":"","sources":["../../../../src/src/transforms/esm/http-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAeH,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,oBAAoB,EACpB,qBAAqB,EACrB,kBAAkB,EACnB,MAAM,sBAAsB,CAAC;AAI9B,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEzE,OAAO,EAGL,KAAK,YAAY,EAKjB,wBAAwB,EACxB,KAAK,wBAAwB,EAC7B,KAAK,aAAa,EAElB,gBAAgB,EAEhB,KAAK,OAAO,EACb,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,iBAAiB,EAA2B,MAAM,4BAA4B,CAAC;AACxF,OAAO,EACL,0BAA0B,EAO3B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,sBAAsB,EAKvB,MAAM,uBAAuB,CAAC;AAe/B,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,oBAAoB,EACpB,qBAAqB,EACrB,kBAAkB,GACnB,CAAC;AAGF,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,OAAO,EAAE,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,gBAAgB,EAAE,CAAC;AACzE,OAAO,EAAE,0BAA0B,EAAE,CAAC;AACtC,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,CAAC;AAC5C,OAAO,EAAE,sBAAsB,EAAE,CAAC;AAwTlC,wEAAwE;AACxE,UAAU,sBAAsB;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,sBAAsB,CAAC,CAkCjC;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,MAAM,CAAC,CAOjB;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,UAAU,CAAC,EAAE,MAAM,EACnB,QAAQ,CAAC,EAAE,wBAAwB,GAClC,OAAO,CAAC,OAAO,CAAC,CAElB;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,EAClD,QAAQ,EAAE,MAAM,EAChB,QAAQ,CAAC,EAAE,wBAAwB,GAClC,OAAO,CAAC,MAAM,EAAE,CAAC,CAEnB;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAErF;AAGD,eAAO,MAAM,wBAAwB,0BAAoB,CAAC"}
|
|
@@ -23,6 +23,7 @@ import { CACHE_DIR_TOKEN, detokenizeAllCachePaths, detokenizeCachePaths, tokeniz
|
|
|
23
23
|
import { looksLikeHtmlContent as looksLikeHtmlNotJs } from "./html-content.js";
|
|
24
24
|
// Extracted modules
|
|
25
25
|
import { embedSourceUrl, extractSourceUrl } from "./source-url-embed.js";
|
|
26
|
+
import { isDegradedArtifact, markDegradedArtifact } from "./degraded-artifact.js";
|
|
26
27
|
import { buildHttpCacheIdentity, buildHttpCacheIdentityMetadata, ensureAbsoluteDir, ensurePreparedHttpCacheRequestOptions, getEffectiveHttpCacheRequest, hashHttpCacheIdentity, hasIncompatibleFilePaths, isHttpUrl, normalizeHttpUrl, prepareHttpCacheRequestOptions, } from "./http-cache-helpers.js";
|
|
27
28
|
import { extractBundleDeps, validateBundleDepsExist } from "./bundle-deps-validator.js";
|
|
28
29
|
import { __clearInFlightHttpFetches, bundleAccumulatorStorage, inFlightHttpFetches, processingStackStorage, refreshDistributedCacheAsync, trackBundleAccumulator, waitForInFlightFetch, } from "./in-flight-manager.js";
|
|
@@ -56,15 +57,32 @@ async function cacheHttpModuleInternal(url, options) {
|
|
|
56
57
|
const fs = createFileSystem();
|
|
57
58
|
if (await exists(cachePath)) {
|
|
58
59
|
const code = await fs.readTextFile(cachePath);
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
60
|
+
if (isDegradedArtifact(code)) {
|
|
61
|
+
// The artifact on disk is the fallback a previous render wrote when a
|
|
62
|
+
// dependency could not be prefetched. Retry the prefetch instead of
|
|
63
|
+
// handing the degradation on.
|
|
64
|
+
httpCacheLog.debug("Local cache holds a degraded artifact, will re-fetch", {
|
|
65
|
+
url: normalizedUrl,
|
|
66
|
+
hash,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
const deps = extractBundleDeps(code);
|
|
71
|
+
if (deps.length > 0) {
|
|
72
|
+
const depsValid = await validateBundleDepsExist(deps, cacheDir);
|
|
73
|
+
if (!depsValid) {
|
|
74
|
+
httpCacheLog.debug("Local cache has missing deps, will re-fetch", {
|
|
75
|
+
url: normalizedUrl,
|
|
76
|
+
hash,
|
|
77
|
+
missingDeps: deps.length,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
getCachedPaths().set(cacheKey, cachePath);
|
|
82
|
+
refreshDistributedCacheAsync(hash, code, cacheDir, normalizedUrl, identityMetadata, getLastDistributedRefresh);
|
|
83
|
+
trackBundleAccumulator(hash, normalizedUrl, cachePath);
|
|
84
|
+
return cachePath;
|
|
85
|
+
}
|
|
68
86
|
}
|
|
69
87
|
else {
|
|
70
88
|
getCachedPaths().set(cacheKey, cachePath);
|
|
@@ -73,12 +91,6 @@ async function cacheHttpModuleInternal(url, options) {
|
|
|
73
91
|
return cachePath;
|
|
74
92
|
}
|
|
75
93
|
}
|
|
76
|
-
else {
|
|
77
|
-
getCachedPaths().set(cacheKey, cachePath);
|
|
78
|
-
refreshDistributedCacheAsync(hash, code, cacheDir, normalizedUrl, identityMetadata, getLastDistributedRefresh);
|
|
79
|
-
trackBundleAccumulator(hash, normalizedUrl, cachePath);
|
|
80
|
-
return cachePath;
|
|
81
|
-
}
|
|
82
94
|
}
|
|
83
95
|
const processingStack = getProcessingStack();
|
|
84
96
|
if (processingStack.has(cacheIdentity)) {
|
|
@@ -198,13 +210,18 @@ async function cacheHttpModuleInternal(url, options) {
|
|
|
198
210
|
});
|
|
199
211
|
}
|
|
200
212
|
processingStack.add(cacheIdentity);
|
|
213
|
+
let degraded = [];
|
|
201
214
|
try {
|
|
202
|
-
|
|
215
|
+
const rewritten = await rewriteModuleImports(code, normalizedUrl, options, cacheHttpModule);
|
|
216
|
+
code = rewritten.code;
|
|
217
|
+
degraded = rewritten.degraded;
|
|
203
218
|
}
|
|
204
219
|
finally {
|
|
205
220
|
processingStack.delete(cacheIdentity);
|
|
206
221
|
}
|
|
207
222
|
code = embedSourceUrl(code, normalizedUrl);
|
|
223
|
+
if (degraded.length > 0)
|
|
224
|
+
code = markDegradedArtifact(code);
|
|
208
225
|
await fs.mkdir(cacheDir, { recursive: true });
|
|
209
226
|
await fs.writeTextFile(cachePath, code);
|
|
210
227
|
if (!(await exists(cachePath))) {
|
|
@@ -212,6 +229,19 @@ async function cacheHttpModuleInternal(url, options) {
|
|
|
212
229
|
detail: `[HTTP-CACHE] INVARIANT VIOLATION: File write succeeded but file does not exist: ${cachePath}`,
|
|
213
230
|
});
|
|
214
231
|
}
|
|
232
|
+
if (degraded.length > 0) {
|
|
233
|
+
// The file on disk carries this render through, but the artifact is not
|
|
234
|
+
// the one this URL is supposed to produce. Keeping it out of the
|
|
235
|
+
// distributed cache and the in-memory path map means the next render
|
|
236
|
+
// retries the prefetch instead of inheriting one upstream blip for the
|
|
237
|
+
// lifetime of the distributed entry.
|
|
238
|
+
httpCacheLog.warn("Not caching a module with unresolved dynamic imports", {
|
|
239
|
+
url: normalizedUrl,
|
|
240
|
+
hash,
|
|
241
|
+
degraded,
|
|
242
|
+
});
|
|
243
|
+
return cachePath;
|
|
244
|
+
}
|
|
215
245
|
try {
|
|
216
246
|
await httpBundleCache.setCode(String(hash), asLocalModuleCode(code), normalizedUrl, HTTP_MODULE_DISTRIBUTED_TTL_SEC, identityMetadata);
|
|
217
247
|
}
|
|
@@ -256,7 +286,7 @@ async function cacheHttpModule(url, options) {
|
|
|
256
286
|
export function cacheHttpImportsToLocal(code, options) {
|
|
257
287
|
const requestOptions = prepareHttpCacheRequestOptions(options);
|
|
258
288
|
return bundleAccumulatorStorage.run([], async () => {
|
|
259
|
-
const replacements = await buildReplacements(code, undefined, requestOptions, cacheHttpModule);
|
|
289
|
+
const { replacements } = await buildReplacements(code, undefined, requestOptions, cacheHttpModule);
|
|
260
290
|
if (replacements.size === 0)
|
|
261
291
|
return { code };
|
|
262
292
|
httpCacheLog.debug("Cached HTTP imports", { count: replacements.size });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"import-parser.d.ts","sourceRoot":"","sources":["../../../../src/src/transforms/esm/import-parser.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"import-parser.d.ts","sourceRoot":"","sources":["../../../../src/src/transforms/esm/import-parser.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAKtE,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,uBAAuB;IAC/B,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,UAAU,EAAE,WAAW,EAAE,CAAC;IAC1B,mBAAmB,EAAE,kBAAkB,EAAE,CAAC;IAC1C,OAAO,EAAE,aAAa,EAAE,CAAC;CAC1B;AAKD,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,uBAAuB,CAAC,CA0HlC"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { compileContent } from "../mdx/compiler/index.js";
|
|
1
2
|
import { getEsbuild } from "../../platform/compat/esbuild.js";
|
|
2
3
|
import { join } from "../../platform/compat/path/index.js";
|
|
3
4
|
import { createFileSystem } from "../../platform/compat/fs.js";
|
|
@@ -11,8 +12,18 @@ export async function parseLocalImports(code, filePath, projectDir, adapter) {
|
|
|
11
12
|
if (filePath.endsWith(".css") || filePath.endsWith(".json")) {
|
|
12
13
|
return { imports: [], cssImports: [], crossProjectImports: [], missing: [] };
|
|
13
14
|
}
|
|
15
|
+
// MDX/Markdown is not JSX, so handing the raw source to esbuild under the
|
|
16
|
+
// `jsx` loader fails with "<stdin>:1:1: ERROR: Syntax error" — which surfaced
|
|
17
|
+
// to users as "Component has missing dependencies" for a file that exists.
|
|
18
|
+
// Compile content to JSX first, exactly as the transform pipeline's parse
|
|
19
|
+
// stage does, then read the imports out of that.
|
|
20
|
+
let parseSource = code;
|
|
21
|
+
if (/\.mdx?$/i.test(filePath)) {
|
|
22
|
+
const compiled = await compileContent("development", projectDir, code, undefined, filePath, "server");
|
|
23
|
+
parseSource = compiled.compiledCode;
|
|
24
|
+
}
|
|
14
25
|
const esbuild = await getEsbuild();
|
|
15
|
-
const result = await esbuild.transform(
|
|
26
|
+
const result = await esbuild.transform(parseSource, {
|
|
16
27
|
loader: getLoaderFromPath(filePath),
|
|
17
28
|
format: "esm",
|
|
18
29
|
target: "esnext",
|
|
@@ -32,6 +43,28 @@ export async function parseLocalImports(code, filePath, projectDir, adapter) {
|
|
|
32
43
|
const specifier = imp.n;
|
|
33
44
|
if (!specifier)
|
|
34
45
|
continue;
|
|
46
|
+
// The content compile above runs with the "server" target, which rewrites a
|
|
47
|
+
// relative specifier to an absolute `file://` URL before the lexer ever
|
|
48
|
+
// sees it. Without this branch those dependencies match none of the shapes
|
|
49
|
+
// below and are dropped without even being reported as missing, so an MDX
|
|
50
|
+
// file's sibling components are never recursively transformed.
|
|
51
|
+
if (specifier.startsWith("file://")) {
|
|
52
|
+
const absolutePath = fileUrlToPath(specifier);
|
|
53
|
+
if (absolutePath && await checkFileExists(absolutePath, adapter)) {
|
|
54
|
+
const entry = { specifier, absolutePath };
|
|
55
|
+
if (absolutePath.endsWith(".css"))
|
|
56
|
+
cssImports.push(entry);
|
|
57
|
+
else
|
|
58
|
+
localImports.push(entry);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
missingImports.push({
|
|
62
|
+
specifier,
|
|
63
|
+
fromFile: filePath,
|
|
64
|
+
reason: `File not found: ${absolutePath ?? specifier}`,
|
|
65
|
+
});
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
35
68
|
if (specifier.startsWith("./") || specifier.startsWith("../")) {
|
|
36
69
|
const resolved = await resolveLocalImportPath(filePath, specifier, adapter);
|
|
37
70
|
if (resolved) {
|
|
@@ -83,6 +116,19 @@ export async function parseLocalImports(code, filePath, projectDir, adapter) {
|
|
|
83
116
|
}
|
|
84
117
|
return { imports: localImports, cssImports, crossProjectImports, missing: missingImports };
|
|
85
118
|
}
|
|
119
|
+
/** Filesystem path behind a `file://` specifier, or null when it is not one. */
|
|
120
|
+
function fileUrlToPath(specifier) {
|
|
121
|
+
try {
|
|
122
|
+
const url = new URL(specifier);
|
|
123
|
+
if (url.protocol !== "file:")
|
|
124
|
+
return null;
|
|
125
|
+
return decodeURIComponent(url.pathname);
|
|
126
|
+
}
|
|
127
|
+
catch (_) {
|
|
128
|
+
/* expected: not a well-formed URL */
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
86
132
|
async function checkFileExists(path, adapter) {
|
|
87
133
|
try {
|
|
88
134
|
const fs = adapter?.fs.stat ? adapter.fs : createFileSystem();
|
|
@@ -9,15 +9,32 @@
|
|
|
9
9
|
import { type CacheOptions } from "./http-cache-helpers.js";
|
|
10
10
|
/** Function signature for caching an HTTP module and returning its local path. */
|
|
11
11
|
export type CacheHttpModuleFn = (url: string, options: CacheOptions) => Promise<string | null>;
|
|
12
|
+
/** Specifier replacements plus the specifiers that were left unresolved. */
|
|
13
|
+
export interface SpecifierReplacements {
|
|
14
|
+
readonly replacements: ReadonlyMap<string, string>;
|
|
15
|
+
/** Specifiers left in place because prefetching them failed. */
|
|
16
|
+
readonly degraded: readonly string[];
|
|
17
|
+
}
|
|
18
|
+
/** Rewritten module code plus the specifiers that were left unresolved. */
|
|
19
|
+
export interface RewrittenModule {
|
|
20
|
+
readonly code: string;
|
|
21
|
+
/** Specifiers left in place because prefetching them failed. */
|
|
22
|
+
readonly degraded: readonly string[];
|
|
23
|
+
}
|
|
12
24
|
/**
|
|
13
25
|
* Build a map of specifier replacements by resolving all imports in the code.
|
|
14
26
|
*
|
|
15
|
-
* Resolution failure is fatal
|
|
16
|
-
*
|
|
27
|
+
* Resolution failure is fatal unless the runtime can resolve the specifier on
|
|
28
|
+
* its own. See {@link runtimeResolvableSpecifiers}. Every specifier left in
|
|
29
|
+
* place is reported as degraded so callers can decide whether the resulting
|
|
30
|
+
* code is fit to cache.
|
|
17
31
|
*/
|
|
18
|
-
export declare function buildReplacements(code: string, baseUrl: string | undefined, options: CacheOptions, cacheHttpModule: CacheHttpModuleFn): Promise<
|
|
32
|
+
export declare function buildReplacements(code: string, baseUrl: string | undefined, options: CacheOptions, cacheHttpModule: CacheHttpModuleFn): Promise<SpecifierReplacements>;
|
|
19
33
|
/**
|
|
20
34
|
* Rewrite all HTTP/npm/bare import specifiers in module code to local cached paths.
|
|
35
|
+
*
|
|
36
|
+
* Reports any specifier left in place, so the caller can keep the resulting
|
|
37
|
+
* code out of the caches that outlive this render.
|
|
21
38
|
*/
|
|
22
|
-
export declare function rewriteModuleImports(code: string, moduleUrl: string, options: CacheOptions, cacheHttpModule: CacheHttpModuleFn): Promise<
|
|
39
|
+
export declare function rewriteModuleImports(code: string, moduleUrl: string, options: CacheOptions, cacheHttpModule: CacheHttpModuleFn): Promise<RewrittenModule>;
|
|
23
40
|
//# sourceMappingURL=specifier-resolver.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"specifier-resolver.d.ts","sourceRoot":"","sources":["../../../../src/src/transforms/esm/specifier-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAQH,OAAO,EACL,KAAK,YAAY,EAQlB,MAAM,yBAAyB,CAAC;AAEjC,kFAAkF;AAClF,MAAM,MAAM,iBAAiB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"specifier-resolver.d.ts","sourceRoot":"","sources":["../../../../src/src/transforms/esm/specifier-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAQH,OAAO,EACL,KAAK,YAAY,EAQlB,MAAM,yBAAyB,CAAC;AAEjC,kFAAkF;AAClF,MAAM,MAAM,iBAAiB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;AAoH/F,4EAA4E;AAC5E,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,YAAY,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnD,gEAAgE;IAChE,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC;AAED,2EAA2E;AAC3E,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,gEAAgE;IAChE,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,OAAO,EAAE,YAAY,EACrB,eAAe,EAAE,iBAAiB,GACjC,OAAO,CAAC,qBAAqB,CAAC,CAuChC;AAED;;;;;GAKG;AACH,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,YAAY,EACrB,eAAe,EAAE,iBAAiB,GACjC,OAAO,CAAC,eAAe,CAAC,CAa1B"}
|
|
@@ -83,21 +83,26 @@ async function resolveSpecifier(specifier, baseUrl, options, cacheHttpModule) {
|
|
|
83
83
|
return resolveSpecifier(mapped, baseUrl, options, cacheHttpModule);
|
|
84
84
|
}
|
|
85
85
|
/**
|
|
86
|
-
* Specifiers
|
|
86
|
+
* Specifiers the runtime can still resolve on its own if prefetching fails.
|
|
87
87
|
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
* handed to the runtime loader, and a failure to do that
|
|
92
|
-
* it was before graceful degradation existed.
|
|
88
|
+
* Two conditions must hold together. The specifier must be reached only
|
|
89
|
+
* through `import(...)`, because a static import is part of the emitted
|
|
90
|
+
* module's own import graph: every static dependency resolves to a local path
|
|
91
|
+
* before the module is handed to the runtime loader, and a failure to do that
|
|
92
|
+
* is fatal, exactly as it was before graceful degradation existed. A dynamic
|
|
93
|
+
* specifier is resolved by the runtime at call time and is routinely guarded by
|
|
94
|
+
* the caller (`platform/adapters/redis/modules.js` only calls
|
|
95
|
+
* `await import("redis")` when the redis adapter is actually used), so failing
|
|
96
|
+
* to prefetch it leaves the specifier in place rather than taking down a render
|
|
97
|
+
* that would never have imported it.
|
|
93
98
|
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
+
* The specifier must also be an absolute http(s) URL, because that is the only
|
|
100
|
+
* form the runtime can resolve without the transform. A relative specifier left
|
|
101
|
+
* in place resolves against the local bundle cache directory, where the chunk
|
|
102
|
+
* was never written; `npm:` and bare specifiers need the import map the cached
|
|
103
|
+
* module no longer carries. Those failures stay fatal.
|
|
99
104
|
*/
|
|
100
|
-
function
|
|
105
|
+
function runtimeResolvableSpecifiers(imports) {
|
|
101
106
|
const dynamic = new Set();
|
|
102
107
|
const staticSpecifiers = new Set();
|
|
103
108
|
for (const imp of imports) {
|
|
@@ -107,23 +112,30 @@ function isDynamicOnly(imports) {
|
|
|
107
112
|
}
|
|
108
113
|
for (const specifier of staticSpecifiers)
|
|
109
114
|
dynamic.delete(specifier);
|
|
115
|
+
for (const specifier of [...dynamic]) {
|
|
116
|
+
if (!isHttpUrl(specifier))
|
|
117
|
+
dynamic.delete(specifier);
|
|
118
|
+
}
|
|
110
119
|
return dynamic;
|
|
111
120
|
}
|
|
112
121
|
/**
|
|
113
122
|
* Build a map of specifier replacements by resolving all imports in the code.
|
|
114
123
|
*
|
|
115
|
-
* Resolution failure is fatal
|
|
116
|
-
*
|
|
124
|
+
* Resolution failure is fatal unless the runtime can resolve the specifier on
|
|
125
|
+
* its own. See {@link runtimeResolvableSpecifiers}. Every specifier left in
|
|
126
|
+
* place is reported as degraded so callers can decide whether the resulting
|
|
127
|
+
* code is fit to cache.
|
|
117
128
|
*/
|
|
118
129
|
export async function buildReplacements(code, baseUrl, options, cacheHttpModule) {
|
|
119
130
|
const imports = await parseImports(code);
|
|
120
131
|
const uniqueSpecifiers = [...new Set(imports.map((imp) => imp.n).filter(Boolean))];
|
|
121
|
-
const
|
|
132
|
+
const runtimeResolvable = runtimeResolvableSpecifiers(imports);
|
|
122
133
|
const settled = await Promise.allSettled(uniqueSpecifiers.map(async (specifier) => ({
|
|
123
134
|
specifier,
|
|
124
135
|
resolved: await resolveSpecifier(specifier, baseUrl, options, cacheHttpModule),
|
|
125
136
|
})));
|
|
126
137
|
const replacements = new Map();
|
|
138
|
+
const degraded = [];
|
|
127
139
|
for (let i = 0; i < settled.length; i++) {
|
|
128
140
|
const outcome = settled[i];
|
|
129
141
|
const specifier = uniqueSpecifiers[i];
|
|
@@ -135,24 +147,32 @@ export async function buildReplacements(code, baseUrl, options, cacheHttpModule)
|
|
|
135
147
|
replacements.set(resolvedFor, resolved);
|
|
136
148
|
continue;
|
|
137
149
|
}
|
|
138
|
-
//
|
|
139
|
-
//
|
|
140
|
-
// the runtime loader is handed
|
|
141
|
-
|
|
150
|
+
// Anything the runtime cannot resolve on its own must resolve here.
|
|
151
|
+
// Leaving one unresolved would emit a module whose own import graph reaches
|
|
152
|
+
// outside the local cache, which is not what the runtime loader is handed
|
|
153
|
+
// anywhere else.
|
|
154
|
+
if (!runtimeResolvable.has(specifier))
|
|
142
155
|
throw outcome.reason;
|
|
156
|
+
degraded.push(specifier);
|
|
143
157
|
logger.warn("Leaving an unresolvable dynamic specifier for runtime resolution", {
|
|
144
158
|
specifier,
|
|
145
159
|
error: outcome.reason instanceof Error ? outcome.reason.message : String(outcome.reason),
|
|
146
160
|
});
|
|
147
161
|
}
|
|
148
|
-
return replacements;
|
|
162
|
+
return { replacements, degraded };
|
|
149
163
|
}
|
|
150
164
|
/**
|
|
151
165
|
* Rewrite all HTTP/npm/bare import specifiers in module code to local cached paths.
|
|
166
|
+
*
|
|
167
|
+
* Reports any specifier left in place, so the caller can keep the resulting
|
|
168
|
+
* code out of the caches that outlive this render.
|
|
152
169
|
*/
|
|
153
170
|
export async function rewriteModuleImports(code, moduleUrl, options, cacheHttpModule) {
|
|
154
|
-
const replacements = await buildReplacements(code, moduleUrl, options, cacheHttpModule);
|
|
171
|
+
const { replacements, degraded } = await buildReplacements(code, moduleUrl, options, cacheHttpModule);
|
|
155
172
|
if (replacements.size === 0)
|
|
156
|
-
return code;
|
|
157
|
-
return
|
|
173
|
+
return { code, degraded };
|
|
174
|
+
return {
|
|
175
|
+
code: await replaceSpecifiers(code, (specifier) => replacements.get(specifier) ?? null),
|
|
176
|
+
degraded,
|
|
177
|
+
};
|
|
158
178
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ImportRewriteStrategy, ImportSpecifierInfo, RewriteContext, RewriteResult } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Reject `import logo from "@/assets/logo.svg"`.
|
|
4
|
+
*
|
|
5
|
+
* Veryfront serves static assets from `public/` at the root path; it has no
|
|
6
|
+
* asset-import model, so there is nothing to rewrite such a specifier to.
|
|
7
|
+
* Previously the alias and relative strategies fell through to their
|
|
8
|
+
* "no extension, add .js" branch and emitted `assets/logo.svg.js`, so the
|
|
9
|
+
* failure surfaced as:
|
|
10
|
+
*
|
|
11
|
+
* Module not found "file:///_vf_modules/assets/logo.svg.js"
|
|
12
|
+
*
|
|
13
|
+
* naming a file the author never wrote, in a directory that does not exist.
|
|
14
|
+
* Failing here instead names the real file and the supported alternative.
|
|
15
|
+
*/
|
|
16
|
+
export declare class AssetStrategy implements ImportRewriteStrategy {
|
|
17
|
+
readonly name = "asset";
|
|
18
|
+
readonly priority = 0.6;
|
|
19
|
+
matches(specifier: string, _ctx: RewriteContext): boolean;
|
|
20
|
+
rewrite(info: ImportSpecifierInfo, ctx: RewriteContext): RewriteResult;
|
|
21
|
+
}
|
|
22
|
+
export declare const assetStrategy: AssetStrategy;
|
|
23
|
+
//# sourceMappingURL=asset-strategy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asset-strategy.d.ts","sourceRoot":"","sources":["../../../../../src/src/transforms/import-rewriter/strategies/asset-strategy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,qBAAqB,EACrB,mBAAmB,EACnB,cAAc,EACd,aAAa,EACd,MAAM,aAAa,CAAC;AAarB;;;;;;;;;;;;;GAaG;AACH,qBAAa,aAAc,YAAW,qBAAqB;IACzD,QAAQ,CAAC,IAAI,WAAW;IAGxB,QAAQ,CAAC,QAAQ,OAAO;IAExB,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,GAAG,OAAO;IAIzD,OAAO,CAAC,IAAI,EAAE,mBAAmB,EAAE,GAAG,EAAE,cAAc,GAAG,aAAa;CAiBvE;AAED,eAAO,MAAM,aAAa,eAAsB,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { COMPILATION_ERROR } from "../../../errors/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Static asset extensions that are not JavaScript modules.
|
|
4
|
+
*
|
|
5
|
+
* `.css` is deliberately absent — CSS imports are supported and are stripped
|
|
6
|
+
* by the pipeline's CSS stage before import resolution runs, so they never
|
|
7
|
+
* reach this strategy.
|
|
8
|
+
*/
|
|
9
|
+
const ASSET_EXTENSION_PATTERN = /\.(?:svg|png|jpe?g|gif|webp|avif|ico|bmp|woff2?|ttf|otf|eot|mp4|webm|mp3|wav|pdf)(?:\?.*)?$/i;
|
|
10
|
+
/**
|
|
11
|
+
* Reject `import logo from "@/assets/logo.svg"`.
|
|
12
|
+
*
|
|
13
|
+
* Veryfront serves static assets from `public/` at the root path; it has no
|
|
14
|
+
* asset-import model, so there is nothing to rewrite such a specifier to.
|
|
15
|
+
* Previously the alias and relative strategies fell through to their
|
|
16
|
+
* "no extension, add .js" branch and emitted `assets/logo.svg.js`, so the
|
|
17
|
+
* failure surfaced as:
|
|
18
|
+
*
|
|
19
|
+
* Module not found "file:///_vf_modules/assets/logo.svg.js"
|
|
20
|
+
*
|
|
21
|
+
* naming a file the author never wrote, in a directory that does not exist.
|
|
22
|
+
* Failing here instead names the real file and the supported alternative.
|
|
23
|
+
*/
|
|
24
|
+
export class AssetStrategy {
|
|
25
|
+
name = "asset";
|
|
26
|
+
// Ahead of the alias (1) and relative strategies so it sees both
|
|
27
|
+
// `@/assets/logo.svg` and `./logo.svg`.
|
|
28
|
+
priority = 0.6;
|
|
29
|
+
matches(specifier, _ctx) {
|
|
30
|
+
return ASSET_EXTENSION_PATTERN.test(specifier);
|
|
31
|
+
}
|
|
32
|
+
rewrite(info, ctx) {
|
|
33
|
+
// Name the importing file relative to the project. A path outside it is a
|
|
34
|
+
// machine path, so only the file name goes in the message.
|
|
35
|
+
const importer = ctx.filePath.startsWith(ctx.projectDir)
|
|
36
|
+
? ctx.filePath.slice(ctx.projectDir.length).replace(/^\/+/, "")
|
|
37
|
+
: ctx.filePath.split("/").pop() ?? ctx.filePath;
|
|
38
|
+
const fileName = (info.specifier.split("/").pop() ?? info.specifier).split(/[?#]/, 1)[0] ??
|
|
39
|
+
info.specifier;
|
|
40
|
+
throw COMPILATION_ERROR.create({
|
|
41
|
+
detail: `Cannot import the static asset "${info.specifier}" as a module (in ${importer}). ` +
|
|
42
|
+
`Veryfront serves static assets from public/ at the root path. ` +
|
|
43
|
+
`Move the file to public/${fileName} and reference it by URL instead, ` +
|
|
44
|
+
`for example <img src="/${fileName}" />. ` +
|
|
45
|
+
`See docs/guides/project-structure.md.`,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export const assetStrategy = new AssetStrategy();
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* @module transforms/import-rewriter/strategies
|
|
5
5
|
*/
|
|
6
6
|
export { AliasStrategy, aliasStrategy } from "./alias-strategy.js";
|
|
7
|
+
export { AssetStrategy, assetStrategy } from "./asset-strategy.js";
|
|
7
8
|
export { BareStrategy, bareStrategy } from "./bare-strategy.js";
|
|
8
9
|
export { NodeBuiltinStrategy, nodeBuiltinStrategy } from "./node-builtin-strategy.js";
|
|
9
10
|
export { CrossProjectStrategy, crossProjectStrategy, isCrossProjectImport, parseCrossProjectImport, } from "./cross-project-strategy.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/src/transforms/import-rewriter/strategies/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACnE,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACtF,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,oBAAoB,EACpB,uBAAuB,GACxB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC5E,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtE,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/src/transforms/import-rewriter/strategies/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACnE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACnE,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACtF,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,oBAAoB,EACpB,uBAAuB,GACxB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC5E,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtE,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC"}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* @module transforms/import-rewriter/strategies
|
|
5
5
|
*/
|
|
6
6
|
export { AliasStrategy, aliasStrategy } from "./alias-strategy.js";
|
|
7
|
+
export { AssetStrategy, assetStrategy } from "./asset-strategy.js";
|
|
7
8
|
export { BareStrategy, bareStrategy } from "./bare-strategy.js";
|
|
8
9
|
export { NodeBuiltinStrategy, nodeBuiltinStrategy } from "./node-builtin-strategy.js";
|
|
9
10
|
export { CrossProjectStrategy, crossProjectStrategy, isCrossProjectImport, parseCrossProjectImport, } from "./cross-project-strategy.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"unified-rewriter.d.ts","sourceRoot":"","sources":["../../../../src/src/transforms/import-rewriter/unified-rewriter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"unified-rewriter.d.ts","sourceRoot":"","sources":["../../../../src/src/transforms/import-rewriter/unified-rewriter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAiBH,OAAO,KAAK,EAAE,qBAAqB,EAAE,cAAc,EAAiB,MAAM,YAAY,CAAC;AAmBvF,MAAM,WAAW,cAAc;IAC7B,mDAAmD;IACnD,UAAU,CAAC,EAAE,qBAAqB,EAAE,CAAC;IACrC,2BAA2B;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,qBAAa,qBAAqB;IAChC,OAAO,CAAC,UAAU,CAA0B;gBAEhC,OAAO,CAAC,EAAE,cAAc;IAIpC;;OAEG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC;IA4B3D;;OAEG;IACH,OAAO,CAAC,aAAa;CAoCtB;AAED;;GAEG;AACH,eAAO,MAAM,eAAe,uBAA8B,CAAC;AAE3D;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAEjF"}
|
|
@@ -7,11 +7,13 @@
|
|
|
7
7
|
import { withSpan } from "../../observability/tracing/otlp-setup.js";
|
|
8
8
|
import { applyRewrites, parseAllImports } from "./parse-cache.js";
|
|
9
9
|
import { aliasStrategy, bareStrategy, crossProjectStrategy, importMapStrategy, nodeBuiltinStrategy, reactStrategy, relativeStrategy, urlStrategy, vendorStrategy, veryfrontStrategy, } from "./strategies/index.js";
|
|
10
|
+
import { assetStrategy } from "./strategies/asset-strategy.js";
|
|
10
11
|
/**
|
|
11
12
|
* Default strategy execution order by priority.
|
|
12
13
|
*/
|
|
13
14
|
const DEFAULT_STRATEGIES = [
|
|
14
15
|
nodeBuiltinStrategy, // 0.5 - Node.js built-ins (noop for browser)
|
|
16
|
+
assetStrategy, // 0.6 - static assets are not modules; reject with a pointer
|
|
15
17
|
reactStrategy, // 0 - React packages first
|
|
16
18
|
aliasStrategy, // 1 - @/ aliases
|
|
17
19
|
veryfrontStrategy, // 1.5 - #veryfront/*, veryfront/*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../src/src/transforms/mdx/esm-module-loader/module-fetcher/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,KAAK,MAAM,EAAkC,MAAM,4BAA4B,CAAC;AACzF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AAG5E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAcxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,sBAAsB,CAAC;AAS9B;;GAEG;AACH,qBAAa,yBAA0B,SAAQ,KAAK;gBACtC,cAAc,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;CAOtD;AAED;;GAEG;AACH,qBAAa,6BAA8B,SAAQ,KAAK;gBAC1C,SAAS,EAAE,MAAM;CAI9B;AAcD;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,oBAAoB,EAC7B,gBAAgB,CAAC,EAAE,MAAM,EACzB,OAAO,GAAE,GAAG,CAAC,MAAM,CAAa,GAC/B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAwFxB;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../src/src/transforms/mdx/esm-module-loader/module-fetcher/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,KAAK,MAAM,EAAkC,MAAM,4BAA4B,CAAC;AACzF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AAG5E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAcxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,sBAAsB,CAAC;AAS9B;;GAEG;AACH,qBAAa,yBAA0B,SAAQ,KAAK;gBACtC,cAAc,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;CAOtD;AAED;;GAEG;AACH,qBAAa,6BAA8B,SAAQ,KAAK;gBAC1C,SAAS,EAAE,MAAM;CAI9B;AAcD;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,oBAAoB,EAC7B,gBAAgB,CAAC,EAAE,MAAM,EACzB,OAAO,GAAE,GAAG,CAAC,MAAM,CAAa,GAC/B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAwFxB;AA+ID;;GAEG;AACH,wBAAgB,0BAA0B,CACxC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,cAAc,EACvB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE;IACR,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC,GACA,oBAAoB,CAUtB"}
|
|
@@ -18,7 +18,7 @@ import { getModulePathCache } from "../cache/index.js";
|
|
|
18
18
|
import { hashString } from "../utils/hash.js";
|
|
19
19
|
import { resolveModuleFile } from "../resolution/file-finder.js";
|
|
20
20
|
import { getTransformCacheKey, getVersionedPathCacheKey } from "./cache-keys.js";
|
|
21
|
-
import { resolveNestedModuleImports } from "./nested-imports.js";
|
|
21
|
+
import { resolveNestedImportBase, resolveNestedModuleImports } from "./nested-imports.js";
|
|
22
22
|
import { readDistributedCache } from "./distributed-cache.js";
|
|
23
23
|
import { resolveUnresolvedModuleViaHttpFallback } from "./http-fallback.js";
|
|
24
24
|
import { normalizePath } from "./module-cache.js";
|
|
@@ -215,6 +215,7 @@ async function doFetchAndCacheModule(normalizedPath, context, fetchAndCacheModul
|
|
|
215
215
|
moduleCode,
|
|
216
216
|
esmCacheDir,
|
|
217
217
|
normalizedPath,
|
|
218
|
+
parentBasePath: resolveNestedImportBase(normalizedPath, actualFilePath),
|
|
218
219
|
strictMissingModules: context.strictMissingModules ?? true,
|
|
219
220
|
projectSlug,
|
|
220
221
|
fetchAndCacheModule: fetchAndCacheModuleFn,
|
|
@@ -42,7 +42,26 @@ export interface ResolveNestedModuleImportsInput {
|
|
|
42
42
|
strictMissingModules: boolean;
|
|
43
43
|
fetchAndCacheModule: (path: string, parent?: string) => Promise<string | null>;
|
|
44
44
|
log?: Logger;
|
|
45
|
+
/**
|
|
46
|
+
* Path this module's relative imports resolve against. Defaults to
|
|
47
|
+
* `normalizedPath`; see {@link resolveNestedImportBase}.
|
|
48
|
+
*/
|
|
49
|
+
parentBasePath?: string;
|
|
45
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* The path a module's own relative imports should resolve against.
|
|
53
|
+
*
|
|
54
|
+
* A directory barrel lives at `lib/index.ts` but is addressed as
|
|
55
|
+
* `_vf_modules/lib`. Resolving its children against `_vf_modules/lib.js` drops
|
|
56
|
+
* the trailing segment as if it were a filename, so `./constants.js` becomes
|
|
57
|
+
* `_vf_modules/constants.js` — one directory too high. The file is then not
|
|
58
|
+
* found and gets replaced by a stub, and the barrel silently stops re-exporting
|
|
59
|
+
* anything: `does not provide an export named 'COLORS'`.
|
|
60
|
+
*
|
|
61
|
+
* When the module actually resolved to an index file, keep the directory
|
|
62
|
+
* segment by addressing it as `<dir>/index.js`.
|
|
63
|
+
*/
|
|
64
|
+
export declare function resolveNestedImportBase(normalizedPath: string, actualFilePath?: string): string;
|
|
46
65
|
/**
|
|
47
66
|
* Resolve nested /_vf_modules and relative imports into local file:// cache paths.
|
|
48
67
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nested-imports.d.ts","sourceRoot":"","sources":["../../../../../../src/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAQtD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAC;AAMzD;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,MAAM,GACjB;IACD,SAAS,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjF,QAAQ,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACjF,CAkCA;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,CAM3F;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,kBAAkB,EAAE,EAC7B,WAAW,EAAE,MAAM,EACnB,oBAAoB,EAAE,OAAO,EAC7B,gBAAgB,CAAC,EAAE,MAAM,EACzB,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,MAAM,CAAC,CAqCjB;AAED,MAAM,WAAW,+BAA+B;IAC9C,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,oBAAoB,EAAE,OAAO,CAAC;IAC9B,mBAAmB,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC/E,GAAG,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"nested-imports.d.ts","sourceRoot":"","sources":["../../../../../../src/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAQtD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAC;AAMzD;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,MAAM,GACjB;IACD,SAAS,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjF,QAAQ,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACjF,CAkCA;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,CAM3F;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,kBAAkB,EAAE,EAC7B,WAAW,EAAE,MAAM,EACnB,oBAAoB,EAAE,OAAO,EAC7B,gBAAgB,CAAC,EAAE,MAAM,EACzB,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,MAAM,CAAC,CAqCjB;AAED,MAAM,WAAW,+BAA+B;IAC9C,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,oBAAoB,EAAE,OAAO,CAAC;IAC9B,mBAAmB,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC/E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CACrC,cAAc,EAAE,MAAM,EACtB,cAAc,CAAC,EAAE,MAAM,GACtB,MAAM,CASR;AAED;;GAEG;AACH,wBAAsB,0BAA0B,CAC9C,KAAK,EAAE,+BAA+B,GACrC,OAAO,CAAC,MAAM,CAAC,CA0DjB"}
|
|
@@ -83,6 +83,28 @@ export async function processNestedImports(moduleCode, results, esmCacheDir, str
|
|
|
83
83
|
}
|
|
84
84
|
return replaceSourceSpans(moduleCode, replacements);
|
|
85
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* The path a module's own relative imports should resolve against.
|
|
88
|
+
*
|
|
89
|
+
* A directory barrel lives at `lib/index.ts` but is addressed as
|
|
90
|
+
* `_vf_modules/lib`. Resolving its children against `_vf_modules/lib.js` drops
|
|
91
|
+
* the trailing segment as if it were a filename, so `./constants.js` becomes
|
|
92
|
+
* `_vf_modules/constants.js` — one directory too high. The file is then not
|
|
93
|
+
* found and gets replaced by a stub, and the barrel silently stops re-exporting
|
|
94
|
+
* anything: `does not provide an export named 'COLORS'`.
|
|
95
|
+
*
|
|
96
|
+
* When the module actually resolved to an index file, keep the directory
|
|
97
|
+
* segment by addressing it as `<dir>/index.js`.
|
|
98
|
+
*/
|
|
99
|
+
export function resolveNestedImportBase(normalizedPath, actualFilePath) {
|
|
100
|
+
if (!actualFilePath || !/(?:^|\/)index\.(?:tsx?|jsx?|mdx|md)$/.test(actualFilePath)) {
|
|
101
|
+
return normalizedPath;
|
|
102
|
+
}
|
|
103
|
+
const withoutExt = normalizedPath.replace(/\.(?:js|mjs)$/, "");
|
|
104
|
+
if (withoutExt.endsWith("/index"))
|
|
105
|
+
return normalizedPath;
|
|
106
|
+
return `${withoutExt}/index.js`;
|
|
107
|
+
}
|
|
86
108
|
/**
|
|
87
109
|
* Resolve nested /_vf_modules and relative imports into local file:// cache paths.
|
|
88
110
|
*/
|
|
@@ -111,7 +133,7 @@ export async function resolveNestedModuleImports(input) {
|
|
|
111
133
|
original,
|
|
112
134
|
start,
|
|
113
135
|
end,
|
|
114
|
-
nestedFilePath: await input.fetchAndCacheModule(path, input.normalizedPath),
|
|
136
|
+
nestedFilePath: await input.fetchAndCacheModule(path, input.parentBasePath ?? input.normalizedPath),
|
|
115
137
|
[key]: path,
|
|
116
138
|
})));
|
|
117
139
|
input.log?.debug(`${LOG_PREFIX_MDX_LOADER} [fetchAndCacheModule] processing vfModules DONE`, {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "veryfront",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1101",
|
|
4
4
|
"description": "The simplest way to build AI-powered apps",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -328,10 +328,10 @@
|
|
|
328
328
|
"@types/react": "19.2.14",
|
|
329
329
|
"@types/react-dom": "19.2.3",
|
|
330
330
|
"ws": "8.21.0",
|
|
331
|
-
"@veryfront/ext-bundler-esbuild": "0.1.
|
|
332
|
-
"@veryfront/ext-content-mdx": "0.1.
|
|
333
|
-
"@veryfront/ext-css-tailwind": "0.1.
|
|
334
|
-
"@veryfront/ext-parser-babel": "0.1.
|
|
331
|
+
"@veryfront/ext-bundler-esbuild": "0.1.1101",
|
|
332
|
+
"@veryfront/ext-content-mdx": "0.1.1101",
|
|
333
|
+
"@veryfront/ext-css-tailwind": "0.1.1101",
|
|
334
|
+
"@veryfront/ext-parser-babel": "0.1.1101"
|
|
335
335
|
},
|
|
336
336
|
"devDependencies": {
|
|
337
337
|
"@types/node": "20.9.0"
|