veryfront 0.1.1099 → 0.1.1100
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/data/helpers.d.ts +21 -0
- package/esm/src/data/helpers.d.ts.map +1 -1
- package/esm/src/data/helpers.js +35 -0
- package/esm/src/data/server-data-fetcher.d.ts.map +1 -1
- package/esm/src/data/server-data-fetcher.js +17 -3
- package/esm/src/data/static-data-fetcher.d.ts.map +1 -1
- package/esm/src/data/static-data-fetcher.js +15 -2
- package/esm/src/html/hydration-script-builder/templates/renderer.d.ts.map +1 -1
- package/esm/src/html/hydration-script-builder/templates/renderer.js +36 -2
- package/esm/src/internal-agents/run-stream.d.ts.map +1 -1
- package/esm/src/internal-agents/run-stream.js +8 -6
- package/esm/src/rendering/orchestrator/module-loader/dependency-resolver.d.ts +2 -0
- package/esm/src/rendering/orchestrator/module-loader/dependency-resolver.d.ts.map +1 -1
- package/esm/src/rendering/orchestrator/module-loader/dependency-resolver.js +17 -16
- package/esm/src/tool/host-tools.d.ts.map +1 -1
- package/esm/src/tool/host-tools.js +3 -1
- package/esm/src/transforms/esm/transform-utils.d.ts +12 -0
- package/esm/src/transforms/esm/transform-utils.d.ts.map +1 -1
- package/esm/src/transforms/esm/transform-utils.js +10 -0
- package/esm/src/transforms/mdx/esm-module-loader/utils/source-spans.d.ts +11 -0
- package/esm/src/transforms/mdx/esm-module-loader/utils/source-spans.d.ts.map +1 -1
- package/esm/src/transforms/mdx/esm-module-loader/utils/source-spans.js +74 -0
- package/esm/src/transforms/pipeline/index.d.ts.map +1 -1
- package/esm/src/transforms/pipeline/index.js +3 -1
- package/esm/src/transforms/pipeline/stages/browser-node-builtin-imports.d.ts +33 -0
- package/esm/src/transforms/pipeline/stages/browser-node-builtin-imports.d.ts.map +1 -0
- package/esm/src/transforms/pipeline/stages/browser-node-builtin-imports.js +116 -0
- package/esm/src/transforms/pipeline/stages/browser-server-exports-strip.d.ts +45 -0
- package/esm/src/transforms/pipeline/stages/browser-server-exports-strip.d.ts.map +1 -0
- package/esm/src/transforms/pipeline/stages/browser-server-exports-strip.js +300 -0
- package/esm/src/transforms/pipeline/stages/compile.d.ts.map +1 -1
- package/esm/src/transforms/pipeline/stages/compile.js +2 -1
- package/esm/src/transforms/pipeline/stages/index.d.ts +2 -0
- package/esm/src/transforms/pipeline/stages/index.d.ts.map +1 -1
- package/esm/src/transforms/pipeline/stages/index.js +2 -0
- package/esm/src/transforms/pipeline/stages/ssr-vf-modules/transform.d.ts +11 -0
- package/esm/src/transforms/pipeline/stages/ssr-vf-modules/transform.d.ts.map +1 -1
- package/esm/src/transforms/pipeline/stages/ssr-vf-modules/transform.js +17 -0
- 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
|
@@ -3,4 +3,25 @@ import type { DataResult } from "./types.js";
|
|
|
3
3
|
export declare function redirect(destination: string, permanent?: boolean): DataResult;
|
|
4
4
|
/** Return a 404 result from a data loader. */
|
|
5
5
|
export declare function notFound(): DataResult;
|
|
6
|
+
/**
|
|
7
|
+
* True when `value` is a control-flow result produced by {@link notFound} or
|
|
8
|
+
* {@link redirect}.
|
|
9
|
+
*
|
|
10
|
+
* These helpers are documented as return values, but `throw notFound()` reads
|
|
11
|
+
* naturally and is what people coming from other frameworks reach for. Thrown,
|
|
12
|
+
* the plain object is not an `Error`, so the SSR error handler stringified it
|
|
13
|
+
* 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
|
|
15
|
+
* one.
|
|
16
|
+
*/
|
|
17
|
+
export declare function isDataControlResult(value: unknown): value is DataResult;
|
|
18
|
+
/**
|
|
19
|
+
* Reduce a thrown control result to the shape a returned one produces.
|
|
20
|
+
*
|
|
21
|
+
* Callers apply this inside whatever wraps the data loader, not in an outer
|
|
22
|
+
* `catch`. A 404 is a routing decision, and a circuit breaker that sees it as a
|
|
23
|
+
* failure will open on the fifth legitimate one and fail every later data route
|
|
24
|
+
* for the project.
|
|
25
|
+
*/
|
|
26
|
+
export declare function toDataControlResult(result: DataResult): DataResult;
|
|
6
27
|
//# sourceMappingURL=helpers.d.ts.map
|
|
@@ -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"}
|
|
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"}
|
package/esm/src/data/helpers.js
CHANGED
|
@@ -6,3 +6,38 @@ export function redirect(destination, permanent = false) {
|
|
|
6
6
|
export function notFound() {
|
|
7
7
|
return { notFound: true };
|
|
8
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* True when `value` is a control-flow result produced by {@link notFound} or
|
|
11
|
+
* {@link redirect}.
|
|
12
|
+
*
|
|
13
|
+
* These helpers are documented as return values, but `throw notFound()` reads
|
|
14
|
+
* naturally and is what people coming from other frameworks reach for. Thrown,
|
|
15
|
+
* the plain object is not an `Error`, so the SSR error handler stringified it
|
|
16
|
+
* 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
|
|
18
|
+
* one.
|
|
19
|
+
*/
|
|
20
|
+
export function isDataControlResult(value) {
|
|
21
|
+
if (value === null || typeof value !== "object")
|
|
22
|
+
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";
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Reduce a thrown control result to the shape a returned one produces.
|
|
33
|
+
*
|
|
34
|
+
* Callers apply this inside whatever wraps the data loader, not in an outer
|
|
35
|
+
* `catch`. A 404 is a routing decision, and a circuit breaker that sees it as a
|
|
36
|
+
* failure will open on the fifth legitimate one and fail every later data route
|
|
37
|
+
* for the project.
|
|
38
|
+
*/
|
|
39
|
+
export function toDataControlResult(result) {
|
|
40
|
+
if (result.redirect)
|
|
41
|
+
return { redirect: result.redirect };
|
|
42
|
+
return { notFound: true };
|
|
43
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-data-fetcher.d.ts","sourceRoot":"","sources":["../../../src/src/data/server-data-fetcher.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"server-data-fetcher.d.ts","sourceRoot":"","sources":["../../../src/src/data/server-data-fetcher.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAcxE;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,2DAA2D;IAC3D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2CAA2C;IAC3C,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,iBAAiB;IAC5B,KAAK,CACH,UAAU,EAAE,YAAY,EACxB,OAAO,EAAE,WAAW,EACpB,OAAO,CAAC,EAAE,sBAAsB,GAC/B,OAAO,CAAC,UAAU,CAAC;IAuFtB;;OAEG;YACW,aAAa;IAqE3B;;;OAGG;IACH,OAAO,CAAC,QAAQ;CAGjB"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as dntShim from "../../_dnt.shims.js";
|
|
2
|
+
import { isDataControlResult, toDataControlResult } from "./helpers.js";
|
|
2
3
|
import { serverLogger } from "../utils/index.js";
|
|
3
4
|
import { DATA_FETCH_TIMEOUT_MS } from "../config/defaults.js";
|
|
4
5
|
import { TimeoutError, withTimeoutThrow } from "../rendering/utils/stream-utils.js";
|
|
@@ -26,9 +27,22 @@ export class ServerDataFetcher {
|
|
|
26
27
|
return withSpan("data.fetch_server", async () => {
|
|
27
28
|
const start = performance.now();
|
|
28
29
|
try {
|
|
29
|
-
const result = await circuitBreaker.execute(() =>
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
const result = await circuitBreaker.execute(async () => {
|
|
31
|
+
try {
|
|
32
|
+
return await withTimeoutThrow(useIsolation
|
|
33
|
+
? this.fetchIsolated(options.modulePath, options.projectDir, context)
|
|
34
|
+
: Promise.resolve(pageModule.getServerData(context)), DATA_FETCH_TIMEOUT_MS, `getServerData for ${pathname}`);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
// `throw notFound()` / `throw redirect(...)`: treat a thrown
|
|
38
|
+
// control result exactly like a returned one. This has to happen
|
|
39
|
+
// inside the breaker, or five legitimate 404s on the same project
|
|
40
|
+
// open it and every data route after that fails fast for 30s.
|
|
41
|
+
if (isDataControlResult(error))
|
|
42
|
+
return toDataControlResult(error);
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
});
|
|
32
46
|
if (result.redirect)
|
|
33
47
|
return { redirect: result.redirect };
|
|
34
48
|
if (result.notFound)
|
|
@@ -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;
|
|
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,3 +1,4 @@
|
|
|
1
|
+
import { isDataControlResult, toDataControlResult } from "./helpers.js";
|
|
1
2
|
import { serverLogger } from "../utils/index.js";
|
|
2
3
|
import { DATA_FETCH_TIMEOUT_MS } from "../config/defaults.js";
|
|
3
4
|
import { TimeoutError, withTimeoutThrow } from "../rendering/utils/stream-utils.js";
|
|
@@ -78,8 +79,20 @@ export class StaticDataFetcher {
|
|
|
78
79
|
createStaticDataContext(context) {
|
|
79
80
|
return { params: context.params, url: context.url };
|
|
80
81
|
}
|
|
81
|
-
executeStaticData(getStaticData, context, timeoutMs, label) {
|
|
82
|
-
|
|
82
|
+
async executeStaticData(getStaticData, context, timeoutMs, label) {
|
|
83
|
+
try {
|
|
84
|
+
return await withTimeoutThrow(Promise.resolve(getStaticData(this.createStaticDataContext(context))), timeoutMs, label);
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
// `throw notFound()` / `throw redirect(...)`: treat a thrown control
|
|
88
|
+
// result exactly like a returned one. Normalising at the one place every
|
|
89
|
+
// path runs the handler covers the cached path as well as the preview
|
|
90
|
+
// one, and keeps a 404 from counting against the caller's circuit
|
|
91
|
+
// breaker.
|
|
92
|
+
if (isDataControlResult(error))
|
|
93
|
+
return toDataControlResult(error);
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
83
96
|
}
|
|
84
97
|
storeCacheEntry(cacheKey, result) {
|
|
85
98
|
this.cacheManager.set(cacheKey, {
|
|
@@ -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,
|
|
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,6 +1,19 @@
|
|
|
1
1
|
export const getRendererScript = () => `
|
|
2
2
|
// Note: DEBUG, log, logError are defined in router.ts which loads first
|
|
3
3
|
|
|
4
|
+
// True when a dynamic import failed because the module could not be
|
|
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.
|
|
9
|
+
function isModuleNotFoundError(error) {
|
|
10
|
+
if (!error) return false;
|
|
11
|
+
if (error instanceof SyntaxError) return false;
|
|
12
|
+
const message = String((error && error.message) || error);
|
|
13
|
+
return /(?:Failed to fetch|error loading|Importing a module script failed|Failed to load)/i
|
|
14
|
+
.test(message);
|
|
15
|
+
}
|
|
16
|
+
|
|
4
17
|
async function renderPage(pathname) {
|
|
5
18
|
const resolvedPathname = (() => {
|
|
6
19
|
const input = typeof pathname === 'string' ? pathname : window.location.pathname;
|
|
@@ -92,6 +105,8 @@ export const getRendererScript = () => `
|
|
|
92
105
|
};
|
|
93
106
|
}
|
|
94
107
|
|
|
108
|
+
let pageModuleError = null;
|
|
109
|
+
|
|
95
110
|
if (data.pagePath) {
|
|
96
111
|
const moduleUrl = shouldRenderRscClientPage
|
|
97
112
|
? '/_veryfront/rsc/module?rel=' + encodeURIComponent(data.pagePath)
|
|
@@ -101,6 +116,7 @@ export const getRendererScript = () => `
|
|
|
101
116
|
try {
|
|
102
117
|
pageModule = await import(moduleUrl);
|
|
103
118
|
} catch (error) {
|
|
119
|
+
pageModuleError = error;
|
|
104
120
|
logError('Failed to load page from hydration data:', error);
|
|
105
121
|
}
|
|
106
122
|
}
|
|
@@ -115,8 +131,26 @@ export const getRendererScript = () => `
|
|
|
115
131
|
try {
|
|
116
132
|
pageModule = await import(basePath + '.js');
|
|
117
133
|
} catch (error) {
|
|
118
|
-
|
|
119
|
-
|
|
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
|
+
}
|
|
120
154
|
}
|
|
121
155
|
}
|
|
122
156
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-stream.d.ts","sourceRoot":"","sources":["../../../src/src/internal-agents/run-stream.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,KAAK,EACV,KAAK,YAAY,IAAI,OAAO,EAC5B,KAAK,aAAa,EAEnB,MAAM,mBAAmB,CAAC;AAY3B,OAAO,KAAK,EACV,+BAA+B,EAC/B,8BAA8B,EAC/B,MAAM,qBAAqB,CAAC;AAU7B,OAAO,
|
|
1
|
+
{"version":3,"file":"run-stream.d.ts","sourceRoot":"","sources":["../../../src/src/internal-agents/run-stream.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,KAAK,EACV,KAAK,YAAY,IAAI,OAAO,EAC5B,KAAK,aAAa,EAEnB,MAAM,mBAAmB,CAAC;AAY3B,OAAO,KAAK,EACV,+BAA+B,EAC/B,8BAA8B,EAC/B,MAAM,qBAAqB,CAAC;AAU7B,OAAO,EAGL,KAAK,IAAI,EAGV,MAAM,kBAAkB,CAAC;AAe1B,OAAO,EAA0B,KAAK,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC3F,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAsCxD,MAAM,WAAW,+BAA+B;IAC9C,cAAc,EAAE,sBAAsB,CAAC;IACvC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,GAAG,OAAO,CAAC,CAAC;IAC5C,mBAAmB,CAAC,EAAE;QACpB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;KAC1B,CAAC;IACF,cAAc,CAAC,EAAE,+BAA+B,CAAC,gBAAgB,CAAC,CAAC;IACnE,8BAA8B,CAAC,EAAE,CAC/B,KAAK,EAAE,+BAA+B,KACnC,OAAO,CAAC,8BAA8B,CAAC,CAAC;IAC7C,aAAa,CAAC,EAAE,CACd,KAAK,EAAE,KAAK,EACZ,WAAW,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,KAClC;QACH,MAAM,EAAE,CACN,QAAQ,EAAE,OAAO,EAAE,EACnB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,SAAS,CAAC,EAAE;YACV,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,aAAa,KAAK,IAAI,CAAC;SAC9C,EACD,aAAa,CAAC,EAAE,MAAM,EACtB,uBAAuB,CAAC,EAAE,MAAM,EAChC,WAAW,CAAC,EAAE,WAAW,KACtB,OAAO,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC,CAAC;KAC1C,CAAC;CACH;AAoCD,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,oBAAoB,EAC3B,cAAc,EAAE,sBAAsB,EACtC,2BAA2B,CAAC,EAAE,MAAM,EAAE,EACtC,mBAAmB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,GAAG,OAAO,CAAC;;cAsFrD;AA6VD,wBAAsB,gCAAgC,CACpD,KAAK,EAAE,oBAAoB,EAC3B,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,+BAA+B,GACpC,OAAO,CAAC,QAAQ,CAAC,CA2anB"}
|
|
@@ -10,7 +10,7 @@ import { importFirstPartyExtensionModule } from "../extensions/first-party-impor
|
|
|
10
10
|
import { SandboxShellToolsProviderName, } from "../extensions/sandbox/index.js";
|
|
11
11
|
import { resolveHostedRuntimeAllowedToolNames } from "../agent/hosted/runtime-essential-tools.js";
|
|
12
12
|
import { SKILL_TOOL_IDS } from "../skill/types.js";
|
|
13
|
-
import { isToolVisibleTo, toolRegistry, } from "../tool/index.js";
|
|
13
|
+
import { createToolsFromHostDefinitions, isToolVisibleTo, toolRegistry, } from "../tool/index.js";
|
|
14
14
|
import { addSpanEvent, setSpanAttributes, withSpan, } from "../observability/tracing/otlp-setup.js";
|
|
15
15
|
import { defineSchema, lazySchema } from "../schemas/index.js";
|
|
16
16
|
import { createStreamTransformState, finalizeRunEvents, formatAgUiEvent, mapRuntimeEventToAgUi, parseSseJsonEvents, } from "./ag-ui-sse.js";
|
|
@@ -210,15 +210,17 @@ async function buildProjectAgentSandboxTools(input) {
|
|
|
210
210
|
: {}),
|
|
211
211
|
getProjectId: () => sandboxConfig.projectId ?? input.deps.projectAgentSandbox?.projectId,
|
|
212
212
|
});
|
|
213
|
-
const
|
|
214
|
-
|
|
213
|
+
const declaredTools = input.agent.config.tools;
|
|
214
|
+
const materializedTools = createToolsFromHostDefinitions(sandboxResult.tools);
|
|
215
|
+
const configuredTools = isRecord(declaredTools)
|
|
216
|
+
? Object.fromEntries(Object.entries(materializedTools).filter(([toolName]) => declaredTools[toolName] === true))
|
|
217
|
+
: {};
|
|
218
|
+
if (!configuredTools[PROJECT_AGENT_SANDBOX_BASH_TOOL_NAME]) {
|
|
215
219
|
await sandboxResult.closeSandbox();
|
|
216
220
|
return {};
|
|
217
221
|
}
|
|
218
222
|
return {
|
|
219
|
-
tools:
|
|
220
|
-
[PROJECT_AGENT_SANDBOX_BASH_TOOL_NAME]: bash,
|
|
221
|
-
},
|
|
223
|
+
tools: configuredTools,
|
|
222
224
|
closeSandbox: sandboxResult.closeSandbox,
|
|
223
225
|
};
|
|
224
226
|
}
|
|
@@ -8,6 +8,8 @@ export type ResolvedModuleDependency = {
|
|
|
8
8
|
relativePath: string;
|
|
9
9
|
depFilePath: string | null;
|
|
10
10
|
isLocalLib: boolean;
|
|
11
|
+
/** True when discovered inside `import("…")` rather than a static import. */
|
|
12
|
+
isDynamic: boolean;
|
|
11
13
|
};
|
|
12
14
|
/** Resolved dependency after its source module has been transformed to a temp file. */
|
|
13
15
|
export type TransformedModuleDependency = ResolvedModuleDependency & {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dependency-resolver.d.ts","sourceRoot":"","sources":["../../../../../src/src/rendering/orchestrator/module-loader/dependency-resolver.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;
|
|
1
|
+
{"version":3,"file":"dependency-resolver.d.ts","sourceRoot":"","sources":["../../../../../src/src/rendering/orchestrator/module-loader/dependency-resolver.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AA4BzE,sEAAsE;AACtE,MAAM,MAAM,wBAAwB,GAAG;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,UAAU,EAAE,OAAO,CAAC;IACpB,6EAA6E;IAC7E,SAAS,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,uFAAuF;AACvF,MAAM,MAAM,2BAA2B,GAAG,wBAAwB,GAAG;IACnE,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,sEAAsE;AACtE,MAAM,WAAW,8BAA8B;IAC7C,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,cAAc,CAAC;CACzB;AA4GD,yEAAyE;AACzE,wBAAsB,yBAAyB,CAC7C,KAAK,EAAE,8BAA8B,GACpC,OAAO,CAAC,wBAAwB,EAAE,CAAC,CAuBrC;AAED,2EAA2E;AAC3E,wBAAgB,gCAAgC,CAC9C,WAAW,EAAE,MAAM,EACnB,eAAe,EAAE,2BAA2B,EAAE,GAC7C,MAAM,CAUR"}
|
|
@@ -1,25 +1,23 @@
|
|
|
1
1
|
import { dirname, join, normalize } from "../../../platform/compat/path/index.js";
|
|
2
2
|
import { parallelMap, rendererLogger } from "../../../utils/index.js";
|
|
3
|
-
import { findStaticImportFromSpans, replaceSourceSpans, } from "../../../transforms/mdx/esm-module-loader/utils/source-spans.js";
|
|
3
|
+
import { findDynamicImportSpans, findStaticImportFromSpans, replaceSourceSpans, } from "../../../transforms/mdx/esm-module-loader/utils/source-spans.js";
|
|
4
4
|
import { findSourceFile } from "../file-resolver/index.js";
|
|
5
5
|
const logger = rendererLogger.component("module-loader");
|
|
6
|
+
const matchAlias = (specifier) => specifier.startsWith("@/") ? specifier : null;
|
|
7
|
+
const matchRelative = (specifier) => specifier.match(/^(\.\.?\/[^?]+)(?:\?.*)?$/)?.[1];
|
|
6
8
|
function collectAliasImports(fileContent) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}));
|
|
9
|
+
const toAlias = (isDynamic) => ({ original, path, start, end }) => ({ full: original, path, start, end, isDynamic });
|
|
10
|
+
return [
|
|
11
|
+
...findStaticImportFromSpans(fileContent, matchAlias).map(toAlias(false)),
|
|
12
|
+
...findDynamicImportSpans(fileContent, matchAlias).map(toAlias(true)),
|
|
13
|
+
];
|
|
13
14
|
}
|
|
14
15
|
function collectRelativeImports(fileContent, fileDir) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
start,
|
|
21
|
-
end,
|
|
22
|
-
}))
|
|
16
|
+
const toRelative = (isDynamic) => ({ original, path, start, end }) => ({ full: original, path, fromDir: fileDir, start, end, isDynamic });
|
|
17
|
+
return [
|
|
18
|
+
...findStaticImportFromSpans(fileContent, matchRelative).map(toRelative(false)),
|
|
19
|
+
...findDynamicImportSpans(fileContent, matchRelative).map(toRelative(true)),
|
|
20
|
+
]
|
|
23
21
|
// Ignore already-transformed file:// imports.
|
|
24
22
|
.filter((imp) => !imp.path.includes("file://"));
|
|
25
23
|
}
|
|
@@ -70,6 +68,7 @@ async function resolveRelativeImport(imp, adapter) {
|
|
|
70
68
|
relativePath: imp.path,
|
|
71
69
|
depFilePath,
|
|
72
70
|
isLocalLib: false,
|
|
71
|
+
isDynamic: imp.isDynamic,
|
|
73
72
|
};
|
|
74
73
|
}
|
|
75
74
|
/** Resolves @/ alias and relative local imports from a source module. */
|
|
@@ -94,7 +93,9 @@ export function rewriteResolvedDependencyImports(fileContent, transformedDeps) {
|
|
|
94
93
|
start: dep.start,
|
|
95
94
|
end: dep.end,
|
|
96
95
|
expected: dep.full,
|
|
97
|
-
|
|
96
|
+
// A dynamic span covers only the quoted specifier; a static one covers the
|
|
97
|
+
// whole `from "…"` clause.
|
|
98
|
+
replacement: dep.isDynamic ? `"file://${dep.depTempPath}"` : `from "file://${dep.depTempPath}"`,
|
|
98
99
|
}));
|
|
99
100
|
return replaceSourceSpans(fileContent, replacements);
|
|
100
101
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host-tools.d.ts","sourceRoot":"","sources":["../../../src/src/tool/host-tools.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAU,MAAM,+BAA+B,CAAC;AACxE,OAAO,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,oBAAoB,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAElF,KAAK,eAAe,GAAG;IACrB,cAAc,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,oBAAoB,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;CAChG,CAAC,gBAAgB,CAAC,CAAC;AAEpB,gCAAgC;AAChC,MAAM,MAAM,kBAAkB,GAAG;IAC/B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IACpB,sEAAsE;IACtE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,eAAe,CAAC,EAAE,UAAU,CAAC;IAC7B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,OAAO,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,eAAe,CAAC;IAC5C,GAAG,CAAC,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC;CACzB,CAAC;AAEF,6CAA6C;AAC7C,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"host-tools.d.ts","sourceRoot":"","sources":["../../../src/src/tool/host-tools.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAU,MAAM,+BAA+B,CAAC;AACxE,OAAO,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,oBAAoB,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAElF,KAAK,eAAe,GAAG;IACrB,cAAc,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,oBAAoB,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;CAChG,CAAC,gBAAgB,CAAC,CAAC;AAEpB,gCAAgC;AAChC,MAAM,MAAM,kBAAkB,GAAG;IAC/B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IACpB,sEAAsE;IACtE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,eAAe,CAAC,EAAE,UAAU,CAAC;IAC7B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,OAAO,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,eAAe,CAAC;IAC5C,GAAG,CAAC,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC;CACzB,CAAC;AAEF,6CAA6C;AAC7C,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;AAO7D,qDAAqD;AACrD,MAAM,WAAW,8BAA8B;IAC7C,kBAAkB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAC;CACnD;AA2DD,0CAA0C;AAC1C,wBAAgB,8BAA8B,CAC5C,WAAW,EAAE,WAAW,EACxB,OAAO,CAAC,EAAE,8BAA8B,GACvC,OAAO,CAAC;AACX,0CAA0C;AAC1C,wBAAgB,8BAA8B,CAC5C,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpC,OAAO,CAAC,EAAE,8BAA8B,GACvC,OAAO,CAAC"}
|
|
@@ -24,7 +24,9 @@ function isParserBackedPrecomputedSchema(input) {
|
|
|
24
24
|
function isHostToolDefinition(value) {
|
|
25
25
|
return (isRecord(value) &&
|
|
26
26
|
typeof value.description === "string" &&
|
|
27
|
-
(isSchemaLike(value.inputSchema) ||
|
|
27
|
+
(isSchemaLike(value.inputSchema) ||
|
|
28
|
+
isParserBackedPrecomputedSchema(value) ||
|
|
29
|
+
(value.inputSchema === undefined && isRecord(value.inputSchemaJson))) &&
|
|
28
30
|
typeof value.execute === "function");
|
|
29
31
|
}
|
|
30
32
|
function defaultToolCallId(toolName) {
|
|
@@ -4,6 +4,18 @@ import type { Loader } from "../../extensions/bundler/index.js";
|
|
|
4
4
|
* Use this for transform cache keys where a compact hash is preferred.
|
|
5
5
|
*/
|
|
6
6
|
export declare function computeShortContentHash(content: string): Promise<string>;
|
|
7
|
+
/**
|
|
8
|
+
* esbuild feature overrides shared by every source transform.
|
|
9
|
+
*
|
|
10
|
+
* Import attributes post-date the `es20xx` targets we lower to, so esbuild
|
|
11
|
+
* would silently *drop* `with { type: "json" }` rather than fail. Every runtime
|
|
12
|
+
* that consumes this output requires the attribute to load a JSON module, so
|
|
13
|
+
* dropping it turns a working import into a load-time error:
|
|
14
|
+
* `Attempted to load JSON module without specifying "type": "json"`.
|
|
15
|
+
*/
|
|
16
|
+
export declare const ESBUILD_SUPPORTED_FEATURES: {
|
|
17
|
+
readonly "import-attributes": true;
|
|
18
|
+
};
|
|
7
19
|
export declare function getLoaderFromPath(filePath: string): Loader;
|
|
8
20
|
export declare function needsTransform(filePath: string): boolean;
|
|
9
21
|
//# sourceMappingURL=transform-utils.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transform-utils.d.ts","sourceRoot":"","sources":["../../../../src/src/transforms/esm/transform-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAC;AAGhE;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAExE;
|
|
1
|
+
{"version":3,"file":"transform-utils.d.ts","sourceRoot":"","sources":["../../../../src/src/transforms/esm/transform-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAC;AAGhE;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAExE;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,0BAA0B;;CAAyC,CAAC;AAajF,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAG1D;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAExD"}
|
|
@@ -6,6 +6,16 @@ import { shortHash } from "../../utils/hash-utils.js";
|
|
|
6
6
|
export function computeShortContentHash(content) {
|
|
7
7
|
return shortHash(content);
|
|
8
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* esbuild feature overrides shared by every source transform.
|
|
11
|
+
*
|
|
12
|
+
* Import attributes post-date the `es20xx` targets we lower to, so esbuild
|
|
13
|
+
* would silently *drop* `with { type: "json" }` rather than fail. Every runtime
|
|
14
|
+
* that consumes this output requires the attribute to load a JSON module, so
|
|
15
|
+
* dropping it turns a working import into a load-time error:
|
|
16
|
+
* `Attempted to load JSON module without specifying "type": "json"`.
|
|
17
|
+
*/
|
|
18
|
+
export const ESBUILD_SUPPORTED_FEATURES = { "import-attributes": true };
|
|
9
19
|
const EXTENSION_LOADERS = {
|
|
10
20
|
".tsx": "tsx",
|
|
11
21
|
".ts": "ts",
|
|
@@ -18,6 +18,17 @@ export interface StaticImportSpan {
|
|
|
18
18
|
type SpecifierMatcher = (specifier: string) => string | null | undefined;
|
|
19
19
|
export declare function replaceSourceSpans(source: string, replacements: SourceSpanReplacement[]): string;
|
|
20
20
|
export declare function findStaticImportFromSpans(source: string, matcher: SpecifierMatcher): StaticImportSpan[];
|
|
21
|
+
/**
|
|
22
|
+
* Find `import("…")` expressions with a literal specifier.
|
|
23
|
+
*
|
|
24
|
+
* The returned span covers the quoted specifier itself (quotes included), not
|
|
25
|
+
* the surrounding `import(...)`, so a replacement is a bare quoted string.
|
|
26
|
+
* Dynamic imports whose argument is not a string literal are skipped, since
|
|
27
|
+
* their target is only known at runtime. That includes an argument the literal
|
|
28
|
+
* merely starts: rewriting the `"./foo"` in `import("./foo" + suffix)` would
|
|
29
|
+
* build a path out of a resolved prefix and an unresolved tail.
|
|
30
|
+
*/
|
|
31
|
+
export declare function findDynamicImportSpans(source: string, matcher: SpecifierMatcher): StaticImportSpan[];
|
|
21
32
|
export declare function findStaticSideEffectImportSpans(source: string, matcher: SpecifierMatcher): StaticImportSpan[];
|
|
22
33
|
export {};
|
|
23
34
|
//# sourceMappingURL=source-spans.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"source-spans.d.ts","sourceRoot":"","sources":["../../../../../../src/src/transforms/mdx/esm-module-loader/utils/source-spans.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AAEH,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED,KAAK,gBAAgB,GAAG,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;AAEzE,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,qBAAqB,EAAE,GACpC,MAAM,CAkCR;
|
|
1
|
+
{"version":3,"file":"source-spans.d.ts","sourceRoot":"","sources":["../../../../../../src/src/transforms/mdx/esm-module-loader/utils/source-spans.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AAEH,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED,KAAK,gBAAgB,GAAG,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;AAEzE,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,qBAAqB,EAAE,GACpC,MAAM,CAkCR;AAyJD,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,gBAAgB,GACxB,gBAAgB,EAAE,CAoCpB;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,gBAAgB,GACxB,gBAAgB,EAAE,CAwDpB;AAED,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,gBAAgB,GACxB,gBAAgB,EAAE,CAqCpB"}
|
|
@@ -72,6 +72,23 @@ function skipWhitespace(source, index) {
|
|
|
72
72
|
cursor++;
|
|
73
73
|
return cursor;
|
|
74
74
|
}
|
|
75
|
+
// Comments are legal wherever whitespace is, so a dynamic import can carry a
|
|
76
|
+
// bundler hint between the keyword, the parentheses and the specifier. Treating
|
|
77
|
+
// the comment as an unexpected character would leave the specifier unresolved.
|
|
78
|
+
function skipWhitespaceAndComments(source, index) {
|
|
79
|
+
let cursor = index;
|
|
80
|
+
while (cursor < source.length) {
|
|
81
|
+
const afterWhitespace = skipWhitespace(source, cursor);
|
|
82
|
+
const char = source[afterWhitespace];
|
|
83
|
+
const next = source[afterWhitespace + 1];
|
|
84
|
+
if (char === "/" && (next === "/" || next === "*")) {
|
|
85
|
+
cursor = skipIgnored(source, afterWhitespace);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
return afterWhitespace;
|
|
89
|
+
}
|
|
90
|
+
return cursor;
|
|
91
|
+
}
|
|
75
92
|
function nextStatementCursor(source, index) {
|
|
76
93
|
const semicolon = source.indexOf(";", index);
|
|
77
94
|
const newline = source.indexOf("\n", index);
|
|
@@ -164,6 +181,63 @@ export function findStaticImportFromSpans(source, matcher) {
|
|
|
164
181
|
}
|
|
165
182
|
return spans;
|
|
166
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* Find `import("…")` expressions with a literal specifier.
|
|
186
|
+
*
|
|
187
|
+
* The returned span covers the quoted specifier itself (quotes included), not
|
|
188
|
+
* the surrounding `import(...)`, so a replacement is a bare quoted string.
|
|
189
|
+
* Dynamic imports whose argument is not a string literal are skipped, since
|
|
190
|
+
* their target is only known at runtime. That includes an argument the literal
|
|
191
|
+
* merely starts: rewriting the `"./foo"` in `import("./foo" + suffix)` would
|
|
192
|
+
* build a path out of a resolved prefix and an unresolved tail.
|
|
193
|
+
*/
|
|
194
|
+
export function findDynamicImportSpans(source, matcher) {
|
|
195
|
+
const spans = [];
|
|
196
|
+
let cursor = 0;
|
|
197
|
+
while (cursor < source.length) {
|
|
198
|
+
const skipped = skipIgnored(source, cursor);
|
|
199
|
+
if (skipped !== cursor) {
|
|
200
|
+
cursor = skipped;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
// `import` used as an expression: not preceded by an identifier char or a
|
|
204
|
+
// dot (which would make it `foo.import` or part of a longer word).
|
|
205
|
+
if (!source.startsWith("import", cursor) ||
|
|
206
|
+
isIdentifierChar(source[cursor - 1]) ||
|
|
207
|
+
source[cursor - 1] === "." ||
|
|
208
|
+
isIdentifierChar(source[cursor + "import".length])) {
|
|
209
|
+
cursor++;
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
const parenIndex = skipWhitespaceAndComments(source, cursor + "import".length);
|
|
213
|
+
if (source[parenIndex] !== "(") {
|
|
214
|
+
cursor++;
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
const quoteIndex = skipWhitespaceAndComments(source, parenIndex + 1);
|
|
218
|
+
const quoted = readQuotedSpecifier(source, quoteIndex);
|
|
219
|
+
if (!quoted) {
|
|
220
|
+
cursor = parenIndex + 1;
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
// The literal must be the whole first argument. `)` closes the call and `,`
|
|
224
|
+
// starts the import-attributes argument; anything else (`+`, a template
|
|
225
|
+
// continuation, a ternary) means the runtime specifier is not this string.
|
|
226
|
+
const afterSpecifier = skipWhitespaceAndComments(source, quoted.end);
|
|
227
|
+
const isWholeArgument = source[afterSpecifier] === ")" || source[afterSpecifier] === ",";
|
|
228
|
+
const matchedPath = isWholeArgument ? matcher(quoted.specifier) : null;
|
|
229
|
+
if (matchedPath) {
|
|
230
|
+
spans.push({
|
|
231
|
+
original: source.slice(quoteIndex, quoted.end),
|
|
232
|
+
path: matchedPath,
|
|
233
|
+
start: quoteIndex,
|
|
234
|
+
end: quoted.end,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
cursor = quoted.end;
|
|
238
|
+
}
|
|
239
|
+
return spans;
|
|
240
|
+
}
|
|
167
241
|
export function findStaticSideEffectImportSpans(source, matcher) {
|
|
168
242
|
const spans = [];
|
|
169
243
|
let cursor = 0;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/src/transforms/pipeline/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAYH,OAAO,KAAK,EACV,cAAc,EACd,gBAAgB,EAEhB,eAAe,EAChB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/src/transforms/pipeline/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAYH,OAAO,KAAK,EACV,cAAc,EACd,gBAAgB,EAEhB,eAAe,EAChB,MAAM,YAAY,CAAC;AA8HpB,wBAAgB,WAAW,CACzB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,gBAAgB,EACzB,MAAM,CAAC,EAAE,cAAc,GACtB,OAAO,CAAC,eAAe,CAAC,CAyI1B;AAqBD,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,MAAM,CAAC,CASjB;AAmCD,YAAY,EACV,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,eAAe,GAChB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5C,OAAO,EACL,sBAAsB,EACtB,0BAA0B,EAC1B,SAAS,EACT,KAAK,EACL,KAAK,EACL,YAAY,GACb,MAAM,cAAc,CAAC"}
|
|
@@ -9,7 +9,7 @@ import { createTransformContext, formatTimingLog, recordStageTiming } from "./co
|
|
|
9
9
|
import { withSpan } from "../../observability/tracing/otlp-setup.js";
|
|
10
10
|
import { computeConfigHash } from "../../cache/config-hash.js";
|
|
11
11
|
import { computeDepsHash } from "../../cache/dependency-graph.js";
|
|
12
|
-
import { compilePlugin, cssStripPlugin, finalizePlugin, parsePlugin, resolveImportsPlugin, ssrHttpCachePlugin, ssrHttpStubPlugin, ssrVfModulesPlugin, } from "./stages/index.js";
|
|
12
|
+
import { browserNodeBuiltinImportsPlugin, browserServerExportsStripPlugin, compilePlugin, cssStripPlugin, finalizePlugin, parsePlugin, resolveImportsPlugin, ssrHttpCachePlugin, ssrHttpStubPlugin, ssrVfModulesPlugin, } from "./stages/index.js";
|
|
13
13
|
import { createFileSystem, exists } from "../../platform/compat/fs.js";
|
|
14
14
|
import { getHttpBundleCacheDir } from "../../utils/cache-dir.js";
|
|
15
15
|
import { validateCachedBundlesByManifestOrCode } from "../esm/cached-bundle-validation.js";
|
|
@@ -28,6 +28,8 @@ const BROWSER_PIPELINE = [
|
|
|
28
28
|
parsePlugin,
|
|
29
29
|
compilePlugin,
|
|
30
30
|
cssStripPlugin, // Strip CSS imports before they hit import resolution
|
|
31
|
+
browserServerExportsStripPlugin, // Drop server-only hooks + their now-unused imports
|
|
32
|
+
browserNodeBuiltinImportsPlugin, // node:* named imports -> namespace + destructure
|
|
31
33
|
resolveImportsPlugin, // Unified import resolution
|
|
32
34
|
finalizePlugin,
|
|
33
35
|
];
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser Node-Builtin Imports Stage — converts named imports of Node built-ins
|
|
3
|
+
* into namespace imports plus a destructure.
|
|
4
|
+
*
|
|
5
|
+
* Browser-bound imports of `node:*` are pointed at a noop polyfill whose whole
|
|
6
|
+
* job, per its own documentation, is that "the import succeeds, and any actual
|
|
7
|
+
* usage of the imported API fails at the call site, which surfaces the problem
|
|
8
|
+
* clearly instead of a cryptic module resolution error".
|
|
9
|
+
*
|
|
10
|
+
* That contract cannot hold for a *named* import. ESM resolves named imports at
|
|
11
|
+
* link time, so `import { createHash } from "node:crypto"` fails the whole
|
|
12
|
+
* module graph before a line of it runs:
|
|
13
|
+
*
|
|
14
|
+
* SyntaxError: The requested module '.../node-noop.js' does not provide an
|
|
15
|
+
* export named 'createHash'
|
|
16
|
+
*
|
|
17
|
+
* One leaked builtin therefore takes down hydration for the entire page, and the
|
|
18
|
+
* error names the polyfill rather than the offending import.
|
|
19
|
+
*
|
|
20
|
+
* Rewriting to a namespace import restores the intended behaviour: linking
|
|
21
|
+
* succeeds, the binding is `undefined`, and the failure happens where the API is
|
|
22
|
+
* actually called. It also makes the two node-noop variants (source and compiled
|
|
23
|
+
* binary, which export different names) behave identically.
|
|
24
|
+
*/
|
|
25
|
+
import type { TransformPlugin } from "../types.js";
|
|
26
|
+
/**
|
|
27
|
+
* Rewrite `import { a, b as c } from "node:x"` to a namespace import plus a
|
|
28
|
+
* destructure. Default and namespace imports already link successfully and are
|
|
29
|
+
* left untouched.
|
|
30
|
+
*/
|
|
31
|
+
export declare function rewriteNodeBuiltinNamedImports(code: string): Promise<string>;
|
|
32
|
+
export declare const browserNodeBuiltinImportsPlugin: TransformPlugin;
|
|
33
|
+
//# sourceMappingURL=browser-node-builtin-imports.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-node-builtin-imports.d.ts","sourceRoot":"","sources":["../../../../../src/src/transforms/pipeline/stages/browser-node-builtin-imports.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAoB,eAAe,EAAE,MAAM,aAAa,CAAC;AA+CrE;;;;GAIG;AACH,wBAAsB,8BAA8B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA2ClF;AAED,eAAO,MAAM,+BAA+B,EAAE,eAM7C,CAAC"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser Node-Builtin Imports Stage — converts named imports of Node built-ins
|
|
3
|
+
* into namespace imports plus a destructure.
|
|
4
|
+
*
|
|
5
|
+
* Browser-bound imports of `node:*` are pointed at a noop polyfill whose whole
|
|
6
|
+
* job, per its own documentation, is that "the import succeeds, and any actual
|
|
7
|
+
* usage of the imported API fails at the call site, which surfaces the problem
|
|
8
|
+
* clearly instead of a cryptic module resolution error".
|
|
9
|
+
*
|
|
10
|
+
* That contract cannot hold for a *named* import. ESM resolves named imports at
|
|
11
|
+
* link time, so `import { createHash } from "node:crypto"` fails the whole
|
|
12
|
+
* module graph before a line of it runs:
|
|
13
|
+
*
|
|
14
|
+
* SyntaxError: The requested module '.../node-noop.js' does not provide an
|
|
15
|
+
* export named 'createHash'
|
|
16
|
+
*
|
|
17
|
+
* One leaked builtin therefore takes down hydration for the entire page, and the
|
|
18
|
+
* error names the polyfill rather than the offending import.
|
|
19
|
+
*
|
|
20
|
+
* Rewriting to a namespace import restores the intended behaviour: linking
|
|
21
|
+
* succeeds, the binding is `undefined`, and the failure happens where the API is
|
|
22
|
+
* actually called. It also makes the two node-noop variants (source and compiled
|
|
23
|
+
* binary, which export different names) behave identically.
|
|
24
|
+
*/
|
|
25
|
+
import { TransformStage } from "../types.js";
|
|
26
|
+
import { rewriteImports } from "../../esm/lexer.js";
|
|
27
|
+
const NAMESPACE_PREFIX = "__vf_node_builtin_";
|
|
28
|
+
/**
|
|
29
|
+
* Pick a namespace prefix the module has never used. A source file is free to
|
|
30
|
+
* declare or import `__vf_node_builtin_0` itself, and shadowing it would either
|
|
31
|
+
* duplicate the binding or silently redirect the user's own one.
|
|
32
|
+
*
|
|
33
|
+
* Substring containment is the right test even though it over-matches: every
|
|
34
|
+
* name we go on to generate starts with the prefix, so a module that never
|
|
35
|
+
* mentions the prefix cannot collide with any of them. That keeps this cheap on
|
|
36
|
+
* post-esbuild output, where parsing for real bindings is not an option.
|
|
37
|
+
*/
|
|
38
|
+
function pickNamespacePrefix(code) {
|
|
39
|
+
let prefix = NAMESPACE_PREFIX;
|
|
40
|
+
while (code.includes(prefix))
|
|
41
|
+
prefix += "_";
|
|
42
|
+
return prefix;
|
|
43
|
+
}
|
|
44
|
+
/** Split a named-import clause into `local: imported` destructure pairs. */
|
|
45
|
+
function toDestructurePairs(namedClause) {
|
|
46
|
+
const pairs = [];
|
|
47
|
+
for (const rawPart of namedClause.split(",")) {
|
|
48
|
+
const part = rawPart.trim();
|
|
49
|
+
if (!part)
|
|
50
|
+
continue;
|
|
51
|
+
const alias = part.match(/^([_$a-zA-Z][\w$]*)\s+as\s+([_$a-zA-Z][\w$]*)$/);
|
|
52
|
+
if (alias?.[1] && alias[2]) {
|
|
53
|
+
pairs.push(`${alias[1]}: ${alias[2]}`);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (/^[_$a-zA-Z][\w$]*$/.test(part)) {
|
|
57
|
+
pairs.push(part);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
return null; // Unrecognised clause — leave the statement alone.
|
|
61
|
+
}
|
|
62
|
+
return pairs.length > 0 ? pairs : null;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Rewrite `import { a, b as c } from "node:x"` to a namespace import plus a
|
|
66
|
+
* destructure. Default and namespace imports already link successfully and are
|
|
67
|
+
* left untouched.
|
|
68
|
+
*/
|
|
69
|
+
export async function rewriteNodeBuiltinNamedImports(code) {
|
|
70
|
+
if (!code.includes("node:"))
|
|
71
|
+
return code;
|
|
72
|
+
let counter = 0;
|
|
73
|
+
const prefix = pickNamespacePrefix(code);
|
|
74
|
+
return await rewriteImports(code, (imp, statement) => {
|
|
75
|
+
if (!imp.n?.startsWith("node:"))
|
|
76
|
+
return null;
|
|
77
|
+
if (imp.d > -1)
|
|
78
|
+
return null; // dynamic import resolves lazily already
|
|
79
|
+
if (!statement.startsWith("import"))
|
|
80
|
+
return null;
|
|
81
|
+
// The clause is everything between the `import` keyword and the specifier.
|
|
82
|
+
// Take it from the lexer's own offsets rather than by searching for
|
|
83
|
+
// " from ": esbuild emits `import{createHash as h}from"node:crypto";` when
|
|
84
|
+
// the build is minified, and there is no such substring in it.
|
|
85
|
+
const specifierStart = imp.s - imp.ss;
|
|
86
|
+
if (specifierStart <= "import".length || specifierStart > statement.length)
|
|
87
|
+
return null;
|
|
88
|
+
const clause = statement
|
|
89
|
+
.slice("import".length, specifierStart - 1) // -1 for the opening quote
|
|
90
|
+
.replace(/\bfrom\s*$/, "")
|
|
91
|
+
.trim();
|
|
92
|
+
if (clause === "")
|
|
93
|
+
return null; // side-effect-only import
|
|
94
|
+
const namedMatch = clause.match(/\{([^}]*)\}/);
|
|
95
|
+
if (!namedMatch?.[1])
|
|
96
|
+
return null; // no named bindings
|
|
97
|
+
const pairs = toDestructurePairs(namedMatch[1]);
|
|
98
|
+
if (!pairs)
|
|
99
|
+
return null;
|
|
100
|
+
const namespace = `${prefix}${counter++}`;
|
|
101
|
+
const source = JSON.stringify(imp.n);
|
|
102
|
+
// Anything left of the named clause (a default binding) keeps its own import.
|
|
103
|
+
const leading = clause.replace(/\{[^}]*\}/, "").replace(/,\s*$/, "").replace(/^\s*,/, "")
|
|
104
|
+
.trim();
|
|
105
|
+
const defaultImport = leading ? `import ${leading} from ${source};` : "";
|
|
106
|
+
return `${defaultImport}import * as ${namespace} from ${source};` +
|
|
107
|
+
`const { ${pairs.join(", ")} } = ${namespace};`;
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
export const browserNodeBuiltinImportsPlugin = {
|
|
111
|
+
name: "browser-node-builtin-imports",
|
|
112
|
+
// Before import resolution, while the specifier is still `node:*`.
|
|
113
|
+
stage: TransformStage.COMPILE + 0.7,
|
|
114
|
+
condition: (ctx) => ctx.target === "browser",
|
|
115
|
+
transform: (ctx) => rewriteNodeBuiltinNamedImports(ctx.code),
|
|
116
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser Server-Exports Strip Stage: empties server-only data hooks in the
|
|
3
|
+
* client artifact, then drops the import bindings that only they used.
|
|
4
|
+
*
|
|
5
|
+
* `getServerData`, `getStaticData` and `getStaticPaths` run exclusively on the
|
|
6
|
+
* server, but the browser artifact is compiled from the same source file. Their
|
|
7
|
+
* bodies therefore ship to the client along with everything they import, so a
|
|
8
|
+
* page whose loader reaches `node:crypto` links against the node-builtin noop
|
|
9
|
+
* polyfill and hydration dies with:
|
|
10
|
+
*
|
|
11
|
+
* The requested module '.../node-noop.js' does not provide an export
|
|
12
|
+
* named 'createHash'
|
|
13
|
+
*
|
|
14
|
+
* esbuild cannot solve this for us: in transform mode (as opposed to bundle
|
|
15
|
+
* mode) it never drops an import, because it cannot prove the module is free of
|
|
16
|
+
* side effects.
|
|
17
|
+
*
|
|
18
|
+
* The pass runs on the AST from the `CodeParser` contract, for the same reason
|
|
19
|
+
* `rendering/rsc/export-extractor.ts` does: a module is not text. Matching
|
|
20
|
+
* declarations by hand means a private function that shares a hook's name gets
|
|
21
|
+
* emptied, a `}` inside a regular expression literal ends a body early, and a
|
|
22
|
+
* minified statement parses differently from the one a developer wrote.
|
|
23
|
+
*
|
|
24
|
+
* Two rules keep it conservative:
|
|
25
|
+
*
|
|
26
|
+
* - Only an exported declaration is emptied. A private helper called
|
|
27
|
+
* `getServerData` is ordinary client code.
|
|
28
|
+
* - An import whose bindings all fall out of use is reduced to a side-effect
|
|
29
|
+
* import rather than deleted, because this pass knows nothing about the
|
|
30
|
+
* top-level code of the module it points at. Node built-ins are the
|
|
31
|
+
* exception: in the browser they resolve to a noop polyfill, so there is no
|
|
32
|
+
* side effect to keep. This matches what esbuild does with an external
|
|
33
|
+
* import whose bindings go unused.
|
|
34
|
+
*
|
|
35
|
+
* Anything that cannot be parsed leaves the module exactly as it was.
|
|
36
|
+
*/
|
|
37
|
+
import type { TransformPlugin } from "../types.js";
|
|
38
|
+
/**
|
|
39
|
+
* Empty the server-only hooks in `code` and drop the import bindings they were
|
|
40
|
+
* the last user of. Returns `code` unchanged when there is nothing to do, when
|
|
41
|
+
* no parser is registered, or when the module does not parse.
|
|
42
|
+
*/
|
|
43
|
+
export declare function stripServerOnlyExports(code: string, filePath?: string): Promise<string>;
|
|
44
|
+
export declare const browserServerExportsStripPlugin: TransformPlugin;
|
|
45
|
+
//# sourceMappingURL=browser-server-exports-strip.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-server-exports-strip.d.ts","sourceRoot":"","sources":["../../../../../src/src/transforms/pipeline/stages/browser-server-exports-strip.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAKH,OAAO,KAAK,EAAoB,eAAe,EAAE,MAAM,aAAa,CAAC;AAoPrE;;;;GAIG;AACH,wBAAsB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA2B7F;AAED,eAAO,MAAM,+BAA+B,EAAE,eAO7C,CAAC"}
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser Server-Exports Strip Stage: empties server-only data hooks in the
|
|
3
|
+
* client artifact, then drops the import bindings that only they used.
|
|
4
|
+
*
|
|
5
|
+
* `getServerData`, `getStaticData` and `getStaticPaths` run exclusively on the
|
|
6
|
+
* server, but the browser artifact is compiled from the same source file. Their
|
|
7
|
+
* bodies therefore ship to the client along with everything they import, so a
|
|
8
|
+
* page whose loader reaches `node:crypto` links against the node-builtin noop
|
|
9
|
+
* polyfill and hydration dies with:
|
|
10
|
+
*
|
|
11
|
+
* The requested module '.../node-noop.js' does not provide an export
|
|
12
|
+
* named 'createHash'
|
|
13
|
+
*
|
|
14
|
+
* esbuild cannot solve this for us: in transform mode (as opposed to bundle
|
|
15
|
+
* mode) it never drops an import, because it cannot prove the module is free of
|
|
16
|
+
* side effects.
|
|
17
|
+
*
|
|
18
|
+
* The pass runs on the AST from the `CodeParser` contract, for the same reason
|
|
19
|
+
* `rendering/rsc/export-extractor.ts` does: a module is not text. Matching
|
|
20
|
+
* declarations by hand means a private function that shares a hook's name gets
|
|
21
|
+
* emptied, a `}` inside a regular expression literal ends a body early, and a
|
|
22
|
+
* minified statement parses differently from the one a developer wrote.
|
|
23
|
+
*
|
|
24
|
+
* Two rules keep it conservative:
|
|
25
|
+
*
|
|
26
|
+
* - Only an exported declaration is emptied. A private helper called
|
|
27
|
+
* `getServerData` is ordinary client code.
|
|
28
|
+
* - An import whose bindings all fall out of use is reduced to a side-effect
|
|
29
|
+
* import rather than deleted, because this pass knows nothing about the
|
|
30
|
+
* top-level code of the module it points at. Node built-ins are the
|
|
31
|
+
* exception: in the browser they resolve to a noop polyfill, so there is no
|
|
32
|
+
* side effect to keep. This matches what esbuild does with an external
|
|
33
|
+
* import whose bindings go unused.
|
|
34
|
+
*
|
|
35
|
+
* Anything that cannot be parsed leaves the module exactly as it was.
|
|
36
|
+
*/
|
|
37
|
+
import { tryResolve } from "../../../extensions/contracts.js";
|
|
38
|
+
import { rendererLogger as logger } from "../../../utils/index.js";
|
|
39
|
+
import { TransformStage } from "../types.js";
|
|
40
|
+
/** Exports that only ever execute on the server. */
|
|
41
|
+
const SERVER_ONLY_EXPORTS = ["getServerData", "getStaticData", "getStaticPaths"];
|
|
42
|
+
/** Source the stub nodes are lifted from, so no node shape is hand-built. */
|
|
43
|
+
const STUB_SOURCE = `function __vfStub() { throw new Error("server-only"); }
|
|
44
|
+
const __vfStubInit = function () { throw new Error("server-only"); };`;
|
|
45
|
+
function isNode(value) {
|
|
46
|
+
return typeof value === "object" && value !== null &&
|
|
47
|
+
typeof value.type === "string";
|
|
48
|
+
}
|
|
49
|
+
function children(node) {
|
|
50
|
+
const found = [];
|
|
51
|
+
for (const [key, value] of Object.entries(node)) {
|
|
52
|
+
if (key === "loc" || key === "leadingComments" || key === "trailingComments")
|
|
53
|
+
continue;
|
|
54
|
+
if (Array.isArray(value)) {
|
|
55
|
+
for (const entry of value)
|
|
56
|
+
if (isNode(entry))
|
|
57
|
+
found.push(entry);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (isNode(value))
|
|
61
|
+
found.push(value);
|
|
62
|
+
}
|
|
63
|
+
return found;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Walk every node in the tree. Returning `false` from `visit` skips the
|
|
67
|
+
* subtree, which is how import statements stay out of the reference count.
|
|
68
|
+
*/
|
|
69
|
+
function walk(node, visit) {
|
|
70
|
+
if (visit(node) === false)
|
|
71
|
+
return;
|
|
72
|
+
for (const child of children(node))
|
|
73
|
+
walk(child, visit);
|
|
74
|
+
}
|
|
75
|
+
function nodeName(value) {
|
|
76
|
+
if (!isNode(value))
|
|
77
|
+
return null;
|
|
78
|
+
const name = value.name;
|
|
79
|
+
return typeof name === "string" ? name : null;
|
|
80
|
+
}
|
|
81
|
+
function bodyOf(ast) {
|
|
82
|
+
const program = ast.program;
|
|
83
|
+
const source = isNode(program) ? program : ast;
|
|
84
|
+
const body = source.body;
|
|
85
|
+
return Array.isArray(body) ? body.filter(isNode) : [];
|
|
86
|
+
}
|
|
87
|
+
/** The stub body and stub initialiser, parsed rather than constructed. */
|
|
88
|
+
async function parseStubs(parser) {
|
|
89
|
+
const ast = await parser.parse({ code: STUB_SOURCE, filePath: "vf-stub.ts" });
|
|
90
|
+
const [fn, variable] = bodyOf(ast);
|
|
91
|
+
const body = fn?.body;
|
|
92
|
+
const declarations = variable?.declarations;
|
|
93
|
+
const init = Array.isArray(declarations) && isNode(declarations[0])
|
|
94
|
+
? declarations[0].init
|
|
95
|
+
: undefined;
|
|
96
|
+
if (!isNode(body) || !isNode(init))
|
|
97
|
+
return null;
|
|
98
|
+
return { body, init };
|
|
99
|
+
}
|
|
100
|
+
/** Names this module exports from its own local declarations. */
|
|
101
|
+
function exportedLocalNames(body) {
|
|
102
|
+
const names = new Set();
|
|
103
|
+
for (const statement of body) {
|
|
104
|
+
if (statement.type !== "ExportNamedDeclaration")
|
|
105
|
+
continue;
|
|
106
|
+
if (statement.exportKind === "type")
|
|
107
|
+
continue;
|
|
108
|
+
// `export { getServerData }` and `export { getServerData as data }`: the
|
|
109
|
+
// local name is what a declaration in this module is called. The reverse,
|
|
110
|
+
// `export { other as getServerData }`, exports `other` and must not touch
|
|
111
|
+
// a same-named local.
|
|
112
|
+
for (const specifier of Array.isArray(statement.specifiers) ? statement.specifiers : []) {
|
|
113
|
+
if (!isNode(specifier))
|
|
114
|
+
continue;
|
|
115
|
+
if (specifier.exportKind === "type")
|
|
116
|
+
continue;
|
|
117
|
+
// A re-export (`export { x } from "./y"`) has no local declaration to
|
|
118
|
+
// empty, so recording the name is harmless.
|
|
119
|
+
const local = nodeName(specifier.local);
|
|
120
|
+
if (local)
|
|
121
|
+
names.add(local);
|
|
122
|
+
}
|
|
123
|
+
const declaration = statement.declaration;
|
|
124
|
+
if (!isNode(declaration))
|
|
125
|
+
continue;
|
|
126
|
+
const direct = nodeName(declaration.id);
|
|
127
|
+
if (direct)
|
|
128
|
+
names.add(direct);
|
|
129
|
+
for (const declarator of Array.isArray(declaration.declarations) ? declaration.declarations : []) {
|
|
130
|
+
if (!isNode(declarator))
|
|
131
|
+
continue;
|
|
132
|
+
const name = nodeName(declarator.id);
|
|
133
|
+
if (name)
|
|
134
|
+
names.add(name);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return names;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Empty the body of every exported server-only hook. Emptying rather than
|
|
141
|
+
* deleting keeps the binding, so an export clause or re-export stays valid.
|
|
142
|
+
*/
|
|
143
|
+
function emptyServerOnlyHooks(body, exported, stubs) {
|
|
144
|
+
const targets = SERVER_ONLY_EXPORTS.filter((name) => exported.has(name));
|
|
145
|
+
if (targets.length === 0)
|
|
146
|
+
return false;
|
|
147
|
+
let changed = false;
|
|
148
|
+
const declarationsIn = (statement) => {
|
|
149
|
+
const declaration = statement.type === "ExportNamedDeclaration"
|
|
150
|
+
? statement.declaration
|
|
151
|
+
: statement;
|
|
152
|
+
return isNode(declaration) ? [declaration] : [];
|
|
153
|
+
};
|
|
154
|
+
for (const statement of body) {
|
|
155
|
+
for (const declaration of declarationsIn(statement)) {
|
|
156
|
+
if (declaration.type === "FunctionDeclaration") {
|
|
157
|
+
const name = nodeName(declaration.id);
|
|
158
|
+
if (!name || !targets.includes(name))
|
|
159
|
+
continue;
|
|
160
|
+
declaration.body = structuredClone(stubs.body);
|
|
161
|
+
changed = true;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (declaration.type !== "VariableDeclaration")
|
|
165
|
+
continue;
|
|
166
|
+
for (const declarator of Array.isArray(declaration.declarations) ? declaration.declarations : []) {
|
|
167
|
+
if (!isNode(declarator))
|
|
168
|
+
continue;
|
|
169
|
+
const name = nodeName(declarator.id);
|
|
170
|
+
if (!name || !targets.includes(name))
|
|
171
|
+
continue;
|
|
172
|
+
declarator.init = structuredClone(stubs.init);
|
|
173
|
+
changed = true;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return changed;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Identifiers the module reads, ignoring import statements and the positions
|
|
181
|
+
* where an identifier is a fixed name rather than a reference (`a.hashOf`,
|
|
182
|
+
* `{ hashOf: 1 }`). Over-counting only ever keeps an import.
|
|
183
|
+
*/
|
|
184
|
+
function referencedIdentifiers(body) {
|
|
185
|
+
const referenced = new Set();
|
|
186
|
+
// Filled in as each parent is visited, which always happens before its
|
|
187
|
+
// children.
|
|
188
|
+
const fixedNames = new WeakSet();
|
|
189
|
+
const markFixedName = (node) => {
|
|
190
|
+
const property = node.type === "MemberExpression" || node.type === "OptionalMemberExpression"
|
|
191
|
+
? node.property
|
|
192
|
+
: node.type === "ObjectProperty" || node.type === "ObjectMethod" ||
|
|
193
|
+
node.type === "ClassMethod" || node.type === "ClassProperty"
|
|
194
|
+
? node.key
|
|
195
|
+
: undefined;
|
|
196
|
+
if (node.computed === true)
|
|
197
|
+
return;
|
|
198
|
+
if (isNode(property))
|
|
199
|
+
fixedNames.add(property);
|
|
200
|
+
};
|
|
201
|
+
for (const statement of body) {
|
|
202
|
+
if (statement.type === "ImportDeclaration")
|
|
203
|
+
continue;
|
|
204
|
+
walk(statement, (node) => {
|
|
205
|
+
if (node.type === "ImportDeclaration")
|
|
206
|
+
return false;
|
|
207
|
+
markFixedName(node);
|
|
208
|
+
if (node.type === "Identifier" || node.type === "JSXIdentifier") {
|
|
209
|
+
if (fixedNames.has(node))
|
|
210
|
+
return true;
|
|
211
|
+
const name = nodeName(node);
|
|
212
|
+
if (name)
|
|
213
|
+
referenced.add(name);
|
|
214
|
+
}
|
|
215
|
+
return true;
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
return referenced;
|
|
219
|
+
}
|
|
220
|
+
/** Local binding names an import statement introduces. */
|
|
221
|
+
function importedBindings(statement) {
|
|
222
|
+
const bindings = [];
|
|
223
|
+
for (const specifier of Array.isArray(statement.specifiers) ? statement.specifiers : []) {
|
|
224
|
+
if (!isNode(specifier))
|
|
225
|
+
continue;
|
|
226
|
+
const name = nodeName(specifier.local);
|
|
227
|
+
if (name)
|
|
228
|
+
bindings.push(name);
|
|
229
|
+
}
|
|
230
|
+
return bindings;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Reduce imports nothing references any more to side-effect imports, and drop
|
|
234
|
+
* them outright when they point at a Node built-in.
|
|
235
|
+
*/
|
|
236
|
+
function dropUnusedImportBindings(body) {
|
|
237
|
+
const referenced = referencedIdentifiers(body);
|
|
238
|
+
return body.filter((statement) => {
|
|
239
|
+
if (statement.type !== "ImportDeclaration")
|
|
240
|
+
return true;
|
|
241
|
+
if (statement.importKind === "type")
|
|
242
|
+
return true;
|
|
243
|
+
const bindings = importedBindings(statement);
|
|
244
|
+
// Already a side-effect import: nothing to drop.
|
|
245
|
+
if (bindings.length === 0)
|
|
246
|
+
return true;
|
|
247
|
+
if (bindings.some((binding) => referenced.has(binding)))
|
|
248
|
+
return true;
|
|
249
|
+
const source = isNode(statement.source) ? statement.source.value : undefined;
|
|
250
|
+
if (typeof source === "string" && source.startsWith("node:"))
|
|
251
|
+
return false;
|
|
252
|
+
statement.specifiers = [];
|
|
253
|
+
return true;
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
function setBody(ast, body) {
|
|
257
|
+
const program = ast.program;
|
|
258
|
+
const target = isNode(program) ? program : ast;
|
|
259
|
+
target.body = body;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Empty the server-only hooks in `code` and drop the import bindings they were
|
|
263
|
+
* the last user of. Returns `code` unchanged when there is nothing to do, when
|
|
264
|
+
* no parser is registered, or when the module does not parse.
|
|
265
|
+
*/
|
|
266
|
+
export async function stripServerOnlyExports(code, filePath) {
|
|
267
|
+
// Cheap pre-check: no mention of a hook means no parse.
|
|
268
|
+
if (!SERVER_ONLY_EXPORTS.some((name) => code.includes(name)))
|
|
269
|
+
return code;
|
|
270
|
+
const parser = tryResolve("CodeParser");
|
|
271
|
+
if (!parser)
|
|
272
|
+
return code;
|
|
273
|
+
try {
|
|
274
|
+
const stubs = await parseStubs(parser);
|
|
275
|
+
if (!stubs)
|
|
276
|
+
return code;
|
|
277
|
+
const ast = await parser.parse({ code, filePath: filePath ?? "module.tsx" });
|
|
278
|
+
const body = bodyOf(ast);
|
|
279
|
+
if (!emptyServerOnlyHooks(body, exportedLocalNames(body), stubs))
|
|
280
|
+
return code;
|
|
281
|
+
setBody(ast, dropUnusedImportBindings(body));
|
|
282
|
+
const generated = await parser.generate(ast);
|
|
283
|
+
return generated.code;
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
logger.debug("Left the module unchanged", {
|
|
287
|
+
filePath,
|
|
288
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
289
|
+
});
|
|
290
|
+
return code;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
export const browserServerExportsStripPlugin = {
|
|
294
|
+
name: "browser-server-exports-strip",
|
|
295
|
+
// After esbuild compile and CSS strip, before any import resolution, so the
|
|
296
|
+
// dropped bindings are never rewritten or pre-fetched.
|
|
297
|
+
stage: TransformStage.COMPILE + 0.6,
|
|
298
|
+
condition: (ctx) => ctx.target === "browser",
|
|
299
|
+
transform: (ctx) => stripServerOnlyExports(ctx.code, ctx.filePath),
|
|
300
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../../../../src/src/transforms/pipeline/stages/compile.ts"],"names":[],"mappings":"AAKA,OAAO,EAAyB,KAAK,eAAe,EAAkB,MAAM,aAAa,CAAC;AAI1F,eAAO,MAAM,aAAa,EAAE,
|
|
1
|
+
{"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../../../../src/src/transforms/pipeline/stages/compile.ts"],"names":[],"mappings":"AAKA,OAAO,EAAyB,KAAK,eAAe,EAAkB,MAAM,aAAa,CAAC;AAI1F,eAAO,MAAM,aAAa,EAAE,eA6D3B,CAAC"}
|
|
@@ -2,7 +2,7 @@ import { getEsbuild } from "../../../platform/compat/esbuild.js";
|
|
|
2
2
|
import { rendererLogger } from "../../../utils/index.js";
|
|
3
3
|
import { COMPILATION_ERROR } from "../../../errors/index.js";
|
|
4
4
|
import { getErrorCollector } from "../../../observability/index.js";
|
|
5
|
-
import { getLoaderFromPath } from "../../esm/transform-utils.js";
|
|
5
|
+
import { ESBUILD_SUPPORTED_FEATURES, getLoaderFromPath } from "../../esm/transform-utils.js";
|
|
6
6
|
import { TransformStage } from "../types.js";
|
|
7
7
|
const logger = rendererLogger.component("esm-transform");
|
|
8
8
|
export const compilePlugin = {
|
|
@@ -16,6 +16,7 @@ export const compilePlugin = {
|
|
|
16
16
|
loader,
|
|
17
17
|
format: "esm",
|
|
18
18
|
target: "es2020",
|
|
19
|
+
supported: ESBUILD_SUPPORTED_FEATURES,
|
|
19
20
|
jsx: "automatic",
|
|
20
21
|
jsxImportSource: ctx.jsxImportSource,
|
|
21
22
|
minify: !ctx.dev,
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
export { parsePlugin } from "./parse.js";
|
|
7
7
|
export { compilePlugin } from "./compile.js";
|
|
8
8
|
export { cssStripPlugin } from "./ssr-css-strip.js";
|
|
9
|
+
export { browserServerExportsStripPlugin } from "./browser-server-exports-strip.js";
|
|
10
|
+
export { browserNodeBuiltinImportsPlugin } from "./browser-node-builtin-imports.js";
|
|
9
11
|
export { resolveImportsPlugin } from "./resolve-imports.js";
|
|
10
12
|
export { ssrVfModulesPlugin } from "./ssr-vf-modules.js";
|
|
11
13
|
export { ssrHttpStubPlugin } from "./ssr-http-stub.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/src/transforms/pipeline/stages/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/src/transforms/pipeline/stages/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,+BAA+B,EAAE,MAAM,mCAAmC,CAAC;AACpF,OAAO,EAAE,+BAA+B,EAAE,MAAM,mCAAmC,CAAC;AACpF,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
export { parsePlugin } from "./parse.js";
|
|
7
7
|
export { compilePlugin } from "./compile.js";
|
|
8
8
|
export { cssStripPlugin } from "./ssr-css-strip.js";
|
|
9
|
+
export { browserServerExportsStripPlugin } from "./browser-server-exports-strip.js";
|
|
10
|
+
export { browserNodeBuiltinImportsPlugin } from "./browser-node-builtin-imports.js";
|
|
9
11
|
export { resolveImportsPlugin } from "./resolve-imports.js";
|
|
10
12
|
export { ssrVfModulesPlugin } from "./ssr-vf-modules.js";
|
|
11
13
|
export { ssrHttpStubPlugin } from "./ssr-http-stub.js";
|
|
@@ -42,5 +42,16 @@ export declare function resolveAndTransformVeryfrontImport(specifier: string, ct
|
|
|
42
42
|
* Transform framework source code with React import rewriting.
|
|
43
43
|
* Entry point for top-level framework modules (e.g., Head.tsx, Router.tsx).
|
|
44
44
|
*/
|
|
45
|
+
/**
|
|
46
|
+
* Drop `with { type: "json" }` from imports that now point at a JavaScript
|
|
47
|
+
* module.
|
|
48
|
+
*
|
|
49
|
+
* A framework import of a `.json` file (`#veryfront/server/dev-ui/manifest.json`)
|
|
50
|
+
* is resolved by transforming the JSON into a cached `.mjs` that default-exports
|
|
51
|
+
* the data. The attribute on the importer describes the *original* target, so
|
|
52
|
+
* leaving it in place makes the runtime reject the rewritten import with
|
|
53
|
+
* "Expected a Json module, but identified a Mjs module".
|
|
54
|
+
*/
|
|
55
|
+
export declare function stripJsonAttributesFromModuleImports(code: string): string;
|
|
45
56
|
export declare function transformFrameworkSource(content: string, sourcePath: string, reactVersion: string, projectDir: string, fs: ReturnType<typeof createFileSystem>): Promise<string>;
|
|
46
57
|
//# sourceMappingURL=transform.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../../../../../src/src/transforms/pipeline/stages/ssr-vf-modules/transform.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../../../../../src/src/transforms/pipeline/stages/ssr-vf-modules/transform.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAmBrE,OAAO,EASL,KAAK,gBAAgB,EAGtB,MAAM,gBAAgB,CAAC;AAoBxB;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAExD;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,oBAAoB,CACxC,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,EAAE,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,GACtC,OAAO,CAAC,MAAM,CAAC,CA2BjB;AAgBD,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AA8MhE;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,gBAAgB,EACrB,oBAAoB,UAAQ,EAC5B,KAAK,SAAI,GACR,OAAO,CAAC,MAAM,CAAC,CAsCjB;AAyPD;;;GAGG;AACH,wBAAsB,kCAAkC,CACtD,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,gBAAgB,GACpB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAgDxB;AAED;;;GAGG;AACH;;;;;;;;;GASG;AACH,wBAAgB,oCAAoC,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKzE;AAED,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,GACtC,OAAO,CAAC,MAAM,CAAC,CAEjB"}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Compiles framework TypeScript/TSX files to JavaScript and recursively
|
|
5
5
|
* resolves all imports (#veryfront/, relative, React).
|
|
6
6
|
*/
|
|
7
|
+
import { ESBUILD_SUPPORTED_FEATURES } from "../../../esm/transform-utils.js";
|
|
7
8
|
import { join } from "../../../../platform/compat/path/index.js";
|
|
8
9
|
import denoConfig from "../../../../../deno.js";
|
|
9
10
|
import { rendererLogger as logger } from "../../../../utils/index.js";
|
|
@@ -122,6 +123,7 @@ async function compileFallbackSource(content, sourcePath) {
|
|
|
122
123
|
jsxImportSource: "react",
|
|
123
124
|
format: "esm",
|
|
124
125
|
target: "es2022",
|
|
126
|
+
supported: ESBUILD_SUPPORTED_FEATURES,
|
|
125
127
|
});
|
|
126
128
|
return result.code;
|
|
127
129
|
}
|
|
@@ -333,6 +335,7 @@ async function transformFrameworkCodeUncoalesced(content, sourcePath, ctx, throw
|
|
|
333
335
|
jsxImportSource: "react",
|
|
334
336
|
format: "esm",
|
|
335
337
|
target: "es2022",
|
|
338
|
+
supported: ESBUILD_SUPPORTED_FEATURES,
|
|
336
339
|
});
|
|
337
340
|
let transformed = result.code;
|
|
338
341
|
// Collect and recursively resolve all #veryfront/ imports
|
|
@@ -462,6 +465,7 @@ async function transformFrameworkCodeUncoalesced(content, sourcePath, ctx, throw
|
|
|
462
465
|
reactVersion: ctx.reactVersion,
|
|
463
466
|
reactImportMap,
|
|
464
467
|
}));
|
|
468
|
+
transformed = stripJsonAttributesFromModuleImports(transformed);
|
|
465
469
|
// Cache HTTP imports to local filesystem
|
|
466
470
|
const importMap = await loadImportMap(ctx.projectDir);
|
|
467
471
|
const cacheResult = await cacheHttpImportsToLocal(transformed, {
|
|
@@ -526,6 +530,19 @@ export async function resolveAndTransformVeryfrontImport(specifier, ctx) {
|
|
|
526
530
|
* Transform framework source code with React import rewriting.
|
|
527
531
|
* Entry point for top-level framework modules (e.g., Head.tsx, Router.tsx).
|
|
528
532
|
*/
|
|
533
|
+
/**
|
|
534
|
+
* Drop `with { type: "json" }` from imports that now point at a JavaScript
|
|
535
|
+
* module.
|
|
536
|
+
*
|
|
537
|
+
* A framework import of a `.json` file (`#veryfront/server/dev-ui/manifest.json`)
|
|
538
|
+
* is resolved by transforming the JSON into a cached `.mjs` that default-exports
|
|
539
|
+
* the data. The attribute on the importer describes the *original* target, so
|
|
540
|
+
* leaving it in place makes the runtime reject the rewritten import with
|
|
541
|
+
* "Expected a Json module, but identified a Mjs module".
|
|
542
|
+
*/
|
|
543
|
+
export function stripJsonAttributesFromModuleImports(code) {
|
|
544
|
+
return code.replace(/(from\s*["'](?:file:\/\/)?[^"']+\.mjs["'])\s*with\s*\{[^}]*\}/g, "$1");
|
|
545
|
+
}
|
|
529
546
|
export async function transformFrameworkSource(content, sourcePath, reactVersion, projectDir, fs) {
|
|
530
547
|
return transformFrameworkCode(content, sourcePath, { reactVersion, projectDir, fs }, true);
|
|
531
548
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "veryfront",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1100",
|
|
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.1100",
|
|
332
|
+
"@veryfront/ext-content-mdx": "0.1.1100",
|
|
333
|
+
"@veryfront/ext-css-tailwind": "0.1.1100",
|
|
334
|
+
"@veryfront/ext-parser-babel": "0.1.1100"
|
|
335
335
|
},
|
|
336
336
|
"devDependencies": {
|
|
337
337
|
"@types/node": "20.9.0"
|