@farm.js/plugin 0.1.0-beta.1 → 0.1.0-beta.11
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/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +2 -76
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/middleware/index.d.ts.map +1 -1
- package/dist/rsc/automatic-optimized-boundary.d.ts +11 -0
- package/dist/rsc/automatic-optimized-boundary.d.ts.map +1 -0
- package/dist/rsc/automatic-optimized-boundary.js +162 -0
- package/dist/rsc/compatibility.d.ts +8 -0
- package/dist/rsc/compatibility.d.ts.map +1 -0
- package/dist/rsc/compatibility.js +46 -0
- package/dist/rsc/index.d.ts +11 -2
- package/dist/rsc/index.d.ts.map +1 -1
- package/dist/rsc/index.js +23 -2
- package/dist/rsc/optimized-boundary.d.ts +10 -1
- package/dist/rsc/optimized-boundary.d.ts.map +1 -1
- package/dist/rsc/optimized-boundary.js +229 -1
- package/package.json +14 -7
package/dist/api/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAiB,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAiB,MAAM,MAAM,CAAC;AAIlD,MAAM,WAAW,cAAc;IAC7B,kEAAkE;IAClE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2BAA2B;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,CAAC,OAAO,UAAU,OAAO,CAAC,OAAO,GAAE,cAAmB,GAAG,MAAM,CA2gBpE;AAED,OAAO,EAAE,OAAO,EAAE,CAAC"}
|
package/dist/api/index.js
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
* ```
|
|
23
23
|
*/
|
|
24
24
|
import { _withAfterNodeMiddleware } from "@farm.js/core/after";
|
|
25
|
+
import { invokeAPIRouteEndpoint } from "@farm.js/core/api/runtime";
|
|
25
26
|
/**
|
|
26
27
|
* Farm.js API Routes Plugin
|
|
27
28
|
*/
|
|
@@ -98,82 +99,7 @@ export default function farmApi(options = {}) {
|
|
|
98
99
|
});
|
|
99
100
|
}
|
|
100
101
|
try {
|
|
101
|
-
|
|
102
|
-
const queryObj = {};
|
|
103
|
-
url.searchParams.forEach((value, key) => {
|
|
104
|
-
queryObj[key] = value;
|
|
105
|
-
});
|
|
106
|
-
// Parse body for non-GET requests
|
|
107
|
-
let bodyObj = {};
|
|
108
|
-
if (method !== "GET" && method !== "HEAD") {
|
|
109
|
-
try {
|
|
110
|
-
const bodyText = await request.text();
|
|
111
|
-
if (bodyText) {
|
|
112
|
-
bodyObj = JSON.parse(bodyText);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
catch {
|
|
116
|
-
// Body parsing failed, use empty object
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
// Parse headers
|
|
120
|
-
const headersObj = {};
|
|
121
|
-
request.headers.forEach((value, key) => {
|
|
122
|
-
headersObj[key] = value;
|
|
123
|
-
});
|
|
124
|
-
// Validate with Zod if schemas are defined
|
|
125
|
-
const types = endpoint.__types || {};
|
|
126
|
-
let validatedQuery = queryObj;
|
|
127
|
-
let validatedBody = bodyObj;
|
|
128
|
-
if (types.query && typeof types.query.parse === "function") {
|
|
129
|
-
try {
|
|
130
|
-
validatedQuery = types.query.parse(queryObj);
|
|
131
|
-
}
|
|
132
|
-
catch (e) {
|
|
133
|
-
return new Response(JSON.stringify({
|
|
134
|
-
error: "Invalid query parameters",
|
|
135
|
-
details: e.errors || e.message,
|
|
136
|
-
}), {
|
|
137
|
-
status: 400,
|
|
138
|
-
headers: { "Content-Type": "application/json" },
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
if (types.body && typeof types.body.parse === "function") {
|
|
143
|
-
try {
|
|
144
|
-
validatedBody = types.body.parse(bodyObj);
|
|
145
|
-
}
|
|
146
|
-
catch (e) {
|
|
147
|
-
return new Response(JSON.stringify({
|
|
148
|
-
error: "Invalid request body",
|
|
149
|
-
details: e.errors || e.message,
|
|
150
|
-
}), {
|
|
151
|
-
status: 400,
|
|
152
|
-
headers: { "Content-Type": "application/json" },
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
// Build context object
|
|
157
|
-
const ctx = {
|
|
158
|
-
query: validatedQuery,
|
|
159
|
-
body: validatedBody,
|
|
160
|
-
headers: headersObj,
|
|
161
|
-
request,
|
|
162
|
-
context: {},
|
|
163
|
-
params: {},
|
|
164
|
-
};
|
|
165
|
-
// Call the handler (use __handler if available, otherwise the endpoint itself)
|
|
166
|
-
const handlerFn = endpoint.__handler || endpoint;
|
|
167
|
-
const result = await handlerFn(ctx);
|
|
168
|
-
// If result is already a Response, return it
|
|
169
|
-
if (result instanceof Response) {
|
|
170
|
-
return result;
|
|
171
|
-
}
|
|
172
|
-
// Otherwise, serialize to JSON
|
|
173
|
-
return new Response(JSON.stringify(result), {
|
|
174
|
-
status: 200,
|
|
175
|
-
headers: { "Content-Type": "application/json" },
|
|
176
|
-
});
|
|
102
|
+
return await invokeAPIRouteEndpoint(endpoint, request);
|
|
177
103
|
}
|
|
178
104
|
catch (error) {
|
|
179
105
|
return new Response(JSON.stringify({ error: error.message || "Internal Server Error" }), {
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,eAAO,MAAM,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,eAAO,MAAM,OAAO,kBAAkB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,MAAM,CAAC;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,MAAM,CAAC;AA+B5D,MAAM,WAAW,qBAAqB;IACpC,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2BAA2B;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,IAAI,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;CACtC;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IACtC,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IAChE,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAEhC,OAAO,EAAE,eAAe,CAAC;IACzB,QAAQ,EAAE,cAAc,CAAC;IACzB,GAAG,EAAE,GAAG,CAAC;IACT,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,eAAe,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;IAGf,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,KAAK,EAAE,MAAM,CAAC;IAGd,MAAM,CAAC,EAAE;QACP,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QACvB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KACjC,CAAC;IAGF,IAAI,EAAE;QACJ,KAAK,EAAE,OAAO,CAAC;QACf,GAAG,EAAE,OAAO,CAAC;QACb,MAAM,CAAC,EAAE,aAAa,CAAC;KACxB,CAAC;IAGF,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAGvB,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,OAAO,EAAE,SAAS,CAAC;IAGnB,QAAQ,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IAGrB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7C,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7C,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9C;AAED,MAAM,MAAM,iBAAiB,GAAG,CAC9B,GAAG,EAAE,iBAAiB,EACtB,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,KACtB,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAE1B,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EACF,iBAAiB,GACjB;QAAE,KAAK,EAAE,MAAM;YAAE,QAAQ,EAAE,iBAAiB,EAAE,CAAA;SAAE,CAAC;QAAC,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,CAAC;IAC7F,MAAM,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;CACjC;AA8MD;;GAEG;AACH,MAAM,CAAC,OAAO,UAAU,cAAc,CAAC,OAAO,GAAE,qBAA0B,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,MAAM,CAAC;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,MAAM,CAAC;AA+B5D,MAAM,WAAW,qBAAqB;IACpC,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2BAA2B;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,IAAI,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;CACtC;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IACtC,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IAChE,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAEhC,OAAO,EAAE,eAAe,CAAC;IACzB,QAAQ,EAAE,cAAc,CAAC;IACzB,GAAG,EAAE,GAAG,CAAC;IACT,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,eAAe,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;IAGf,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,KAAK,EAAE,MAAM,CAAC;IAGd,MAAM,CAAC,EAAE;QACP,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QACvB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KACjC,CAAC;IAGF,IAAI,EAAE;QACJ,KAAK,EAAE,OAAO,CAAC;QACf,GAAG,EAAE,OAAO,CAAC;QACb,MAAM,CAAC,EAAE,aAAa,CAAC;KACxB,CAAC;IAGF,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAGvB,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,OAAO,EAAE,SAAS,CAAC;IAGnB,QAAQ,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IAGrB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7C,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7C,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9C;AAED,MAAM,MAAM,iBAAiB,GAAG,CAC9B,GAAG,EAAE,iBAAiB,EACtB,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,KACtB,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAE1B,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EACF,iBAAiB,GACjB;QAAE,KAAK,EAAE,MAAM;YAAE,QAAQ,EAAE,iBAAiB,EAAE,CAAA;SAAE,CAAC;QAAC,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,CAAC;IAC7F,MAAM,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;CACjC;AA8MD;;GAEG;AACH,MAAM,CAAC,OAAO,UAAU,cAAc,CAAC,OAAO,GAAE,qBAA0B,GAAG,MAAM,CAwSlF;AAED,OAAO,EAAE,cAAc,EAAE,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface AutomaticOptimizedBoundaryTransform {
|
|
2
|
+
code: string;
|
|
3
|
+
boundaryCount: number;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Wrap React JSX-runtime calls for native host boundaries with Farm's
|
|
7
|
+
* server-only optimizer. Eligibility is decided against the fully evaluated
|
|
8
|
+
* React element at runtime, so unsupported trees retain normal React behavior.
|
|
9
|
+
*/
|
|
10
|
+
export declare function transformAutomaticOptimizedBoundaries(code: string, id: string): AutomaticOptimizedBoundaryTransform | null;
|
|
11
|
+
//# sourceMappingURL=automatic-optimized-boundary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"automatic-optimized-boundary.d.ts","sourceRoot":"","sources":["../../src/rsc/automatic-optimized-boundary.ts"],"names":[],"mappings":"AAwBA,MAAM,WAAW,mCAAmC;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;CACvB;AAiGD;;;;GAIG;AACH,wBAAgB,qCAAqC,CACnD,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,GACT,mCAAmC,GAAG,IAAI,CA8C5C"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { parseAst } from "vite";
|
|
2
|
+
const JSX_RUNTIME_MODULES = new Set(["react/jsx-runtime", "react/jsx-dev-runtime"]);
|
|
3
|
+
const JSX_FACTORY_EXPORTS = new Set(["jsx", "jsxs", "jsxDEV"]);
|
|
4
|
+
const AUTOMATIC_BOUNDARY_TAGS = new Set([
|
|
5
|
+
"article",
|
|
6
|
+
"aside",
|
|
7
|
+
"div",
|
|
8
|
+
"footer",
|
|
9
|
+
"header",
|
|
10
|
+
"main",
|
|
11
|
+
"nav",
|
|
12
|
+
"section",
|
|
13
|
+
"span",
|
|
14
|
+
]);
|
|
15
|
+
const AUTOMATIC_BOUNDARY_IMPORT = "@farm.js/plugin/rsc/optimized-boundary";
|
|
16
|
+
function nodeName(node) {
|
|
17
|
+
if (!node || typeof node !== "object")
|
|
18
|
+
return undefined;
|
|
19
|
+
const candidate = node;
|
|
20
|
+
if (typeof candidate.name === "string")
|
|
21
|
+
return candidate.name;
|
|
22
|
+
if (typeof candidate.value === "string")
|
|
23
|
+
return candidate.value;
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
function isClientModule(body) {
|
|
27
|
+
for (const statement of body) {
|
|
28
|
+
if (statement.type === "ImportDeclaration")
|
|
29
|
+
return false;
|
|
30
|
+
if (statement.type !== "ExpressionStatement")
|
|
31
|
+
return false;
|
|
32
|
+
const expression = statement.expression;
|
|
33
|
+
if (expression?.type !== "Literal")
|
|
34
|
+
return false;
|
|
35
|
+
if (expression.value === "use client")
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
function collectJsxFactories(body) {
|
|
41
|
+
const factories = new Set();
|
|
42
|
+
for (const statement of body) {
|
|
43
|
+
if (statement.type !== "ImportDeclaration")
|
|
44
|
+
continue;
|
|
45
|
+
const source = nodeName(statement.source);
|
|
46
|
+
if (!source || !JSX_RUNTIME_MODULES.has(source))
|
|
47
|
+
continue;
|
|
48
|
+
for (const specifier of statement.specifiers || []) {
|
|
49
|
+
if (specifier.type !== "ImportSpecifier")
|
|
50
|
+
continue;
|
|
51
|
+
const imported = nodeName(specifier.imported);
|
|
52
|
+
const local = nodeName(specifier.local);
|
|
53
|
+
if (imported && local && JSX_FACTORY_EXPORTS.has(imported))
|
|
54
|
+
factories.add(local);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return factories;
|
|
58
|
+
}
|
|
59
|
+
function collectBoundaryCalls(node, factories, calls) {
|
|
60
|
+
if (!node || typeof node !== "object")
|
|
61
|
+
return;
|
|
62
|
+
if (Array.isArray(node)) {
|
|
63
|
+
for (const child of node)
|
|
64
|
+
collectBoundaryCalls(child, factories, calls);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const candidate = node;
|
|
68
|
+
if (candidate.type === "CallExpression") {
|
|
69
|
+
const callee = candidate.callee;
|
|
70
|
+
const args = candidate.arguments || [];
|
|
71
|
+
const tag = args[0];
|
|
72
|
+
if (callee?.type === "Identifier" &&
|
|
73
|
+
factories.has(nodeName(callee) || "") &&
|
|
74
|
+
tag?.type === "Literal" &&
|
|
75
|
+
AUTOMATIC_BOUNDARY_TAGS.has(String(tag.value)) &&
|
|
76
|
+
typeof candidate.start === "number" &&
|
|
77
|
+
typeof candidate.end === "number") {
|
|
78
|
+
calls.push(candidate);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
for (const [key, value] of Object.entries(candidate)) {
|
|
82
|
+
if (key === "start" || key === "end" || key === "loc")
|
|
83
|
+
continue;
|
|
84
|
+
collectBoundaryCalls(value, factories, calls);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function createHelperName(code) {
|
|
88
|
+
const base = "__farmOptimizeBoundary";
|
|
89
|
+
let name = base;
|
|
90
|
+
let suffix = 1;
|
|
91
|
+
while (new RegExp(`\\b${name}\\b`).test(code))
|
|
92
|
+
name = `${base}${suffix++}`;
|
|
93
|
+
return name;
|
|
94
|
+
}
|
|
95
|
+
function importInsertionPoint(body) {
|
|
96
|
+
let position = 0;
|
|
97
|
+
for (const statement of body) {
|
|
98
|
+
if (statement.type === "ImportDeclaration" && typeof statement.end === "number") {
|
|
99
|
+
position = statement.end;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (statement.type === "ExpressionStatement") {
|
|
103
|
+
const expression = statement.expression;
|
|
104
|
+
if (expression?.type === "Literal" && typeof statement.end === "number") {
|
|
105
|
+
position = statement.end;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
return position;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Wrap React JSX-runtime calls for native host boundaries with Farm's
|
|
115
|
+
* server-only optimizer. Eligibility is decided against the fully evaluated
|
|
116
|
+
* React element at runtime, so unsupported trees retain normal React behavior.
|
|
117
|
+
*/
|
|
118
|
+
export function transformAutomaticOptimizedBoundaries(code, id) {
|
|
119
|
+
const cleanId = id.split("?", 1)[0].replace(/\\/g, "/");
|
|
120
|
+
if (!/\.[cm]?[jt]sx?$/.test(cleanId))
|
|
121
|
+
return null;
|
|
122
|
+
if (cleanId.includes("/node_modules/") || cleanId.includes("/.farm/"))
|
|
123
|
+
return null;
|
|
124
|
+
if (!code.includes("react/jsx-runtime") && !code.includes("react/jsx-dev-runtime"))
|
|
125
|
+
return null;
|
|
126
|
+
let ast;
|
|
127
|
+
try {
|
|
128
|
+
ast = parseAst(code);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
const body = ast.body || [];
|
|
134
|
+
if (isClientModule(body))
|
|
135
|
+
return null;
|
|
136
|
+
const factories = collectJsxFactories(body);
|
|
137
|
+
if (factories.size === 0)
|
|
138
|
+
return null;
|
|
139
|
+
const calls = [];
|
|
140
|
+
collectBoundaryCalls(ast, factories, calls);
|
|
141
|
+
if (calls.length === 0)
|
|
142
|
+
return null;
|
|
143
|
+
const helperName = createHelperName(code);
|
|
144
|
+
const insertions = [];
|
|
145
|
+
for (const call of calls) {
|
|
146
|
+
insertions.push({ position: call.start, text: `${helperName}(`, order: 1 });
|
|
147
|
+
insertions.push({ position: call.end, text: ")", order: 0 });
|
|
148
|
+
}
|
|
149
|
+
insertions.push({
|
|
150
|
+
position: importInsertionPoint(body),
|
|
151
|
+
text: `${importInsertionPoint(body) === 0 ? "" : "\n"}import { _optimizeBoundary as ${helperName} } from ${JSON.stringify(AUTOMATIC_BOUNDARY_IMPORT)};\n`,
|
|
152
|
+
order: 2,
|
|
153
|
+
});
|
|
154
|
+
let transformed = code;
|
|
155
|
+
for (const insertion of insertions.sort((left, right) => right.position - left.position || left.order - right.order)) {
|
|
156
|
+
transformed =
|
|
157
|
+
transformed.slice(0, insertion.position) +
|
|
158
|
+
insertion.text +
|
|
159
|
+
transformed.slice(insertion.position);
|
|
160
|
+
}
|
|
161
|
+
return { code: transformed, boundaryCount: calls.length };
|
|
162
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React's framework-level RSC APIs do not follow semver. Keep the complete
|
|
3
|
+
* renderer/decoder toolchain on the exact combination exercised by Farm's
|
|
4
|
+
* integration tests instead of accepting a potentially incompatible or
|
|
5
|
+
* vulnerable package resolution.
|
|
6
|
+
*/
|
|
7
|
+
export declare function assertRscPackageCompatibility(root: string): void;
|
|
8
|
+
//# sourceMappingURL=compatibility.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compatibility.d.ts","sourceRoot":"","sources":["../../src/rsc/compatibility.ts"],"names":[],"mappings":"AA0BA;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAgChE"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
const RSC_PACKAGE_VERSIONS = {
|
|
5
|
+
react: "19.2.8",
|
|
6
|
+
"react-dom": "19.2.8",
|
|
7
|
+
"react-server-dom-webpack": "19.2.8",
|
|
8
|
+
"@vitejs/plugin-rsc": "0.5.32",
|
|
9
|
+
};
|
|
10
|
+
function readPackageVersion(projectRequire, packageName) {
|
|
11
|
+
const manifestPath = projectRequire.resolve(`${packageName}/package.json`);
|
|
12
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
13
|
+
return typeof manifest.version === "string" ? manifest.version : undefined;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* React's framework-level RSC APIs do not follow semver. Keep the complete
|
|
17
|
+
* renderer/decoder toolchain on the exact combination exercised by Farm's
|
|
18
|
+
* integration tests instead of accepting a potentially incompatible or
|
|
19
|
+
* vulnerable package resolution.
|
|
20
|
+
*/
|
|
21
|
+
export function assertRscPackageCompatibility(root) {
|
|
22
|
+
const projectRequire = createRequire(path.join(path.resolve(root), "__farm_rsc_check__.cjs"));
|
|
23
|
+
const problems = [];
|
|
24
|
+
for (const [packageName, expectedVersion] of Object.entries(RSC_PACKAGE_VERSIONS)) {
|
|
25
|
+
try {
|
|
26
|
+
const actualVersion = readPackageVersion(projectRequire, packageName);
|
|
27
|
+
if (actualVersion !== expectedVersion) {
|
|
28
|
+
problems.push(`${packageName}: expected ${expectedVersion}, found ${actualVersion ?? "unknown"}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
problems.push(`${packageName}: expected ${expectedVersion}, package is not installed`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (problems.length === 0)
|
|
36
|
+
return;
|
|
37
|
+
throw new Error([
|
|
38
|
+
"[Farm.js] Unsupported React Server Components package combination.",
|
|
39
|
+
...problems.map((problem) => `- ${problem}`),
|
|
40
|
+
"",
|
|
41
|
+
"Farm pins the framework-level RSC APIs to one supported, integration-tested combination.",
|
|
42
|
+
"Install the supported versions with:",
|
|
43
|
+
"pnpm add react@19.2.8 react-dom@19.2.8 react-server-dom-webpack@19.2.8",
|
|
44
|
+
"pnpm add -D @vitejs/plugin-rsc@0.5.32",
|
|
45
|
+
].join("\n"));
|
|
46
|
+
}
|
package/dist/rsc/index.d.ts
CHANGED
|
@@ -38,11 +38,20 @@ export interface FarmRscConfig {
|
|
|
38
38
|
basePath?: string;
|
|
39
39
|
port?: number;
|
|
40
40
|
experimental?: {
|
|
41
|
+
/**
|
|
42
|
+
* Enable React Server Components rendering.
|
|
43
|
+
* @default true
|
|
44
|
+
*/
|
|
41
45
|
serverComponents?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Enable React Server Actions and their public POST decoder.
|
|
48
|
+
* @default false
|
|
49
|
+
*/
|
|
42
50
|
serverActions?: boolean;
|
|
43
51
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
52
|
+
* Automatically optimize eligible server-only host-element subtrees with
|
|
53
|
+
* the native Strata renderer. Unsupported trees keep normal React
|
|
54
|
+
* rendering; application components do not need a boundary import.
|
|
46
55
|
*
|
|
47
56
|
* @experimental
|
|
48
57
|
* @default false
|
package/dist/rsc/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rsc/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAA4B,KAAK,MAAM,EAAE,KAAK,UAAU,EAAE,MAAM,MAAM,CAAC;AAE9E,OAAO,KAAK,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACrE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,sCAAsC,CAAC;AACpF,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rsc/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAA4B,KAAK,MAAM,EAAE,KAAK,UAAU,EAAE,MAAM,MAAM,CAAC;AAE9E,OAAO,KAAK,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACrE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,sCAAsC,CAAC;AACpF,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AA4B9E,YAAY,EAAE,oBAAoB,EAAE,YAAY,EAAE,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAExF,YAAY,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAC1D,YAAY,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAGjE,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IACpC,MAAM,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE;QACb;;;WAGG;QACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;QAC3B;;;WAGG;QACH,aAAa,CAAC,EAAE,OAAO,CAAC;QACxB;;;;;;;WAOG;QACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;KAC7B,CAAC;IACF,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,aAAa,CAAC,EAAE,uBAAuB,CAAC;IACxC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACjD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE;QACR,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,gHAAgH;IAChH,OAAO,CAAC,EAAE,GAAG,EAAE,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,GAAE,aAAkB,GAAG,UAAU,CAiDnE;AA8TD;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAU,OAAO,CAAC,OAAO,GAAE,oBAAyB,GAAG,MAAM,EAAE,CA0oC5E"}
|
package/dist/rsc/index.js
CHANGED
|
@@ -28,7 +28,9 @@ import { generateRscEntry } from "./entries/rsc.js";
|
|
|
28
28
|
import { generateSsrEntry } from "./entries/ssr.js";
|
|
29
29
|
import { generateClientEntry } from "./entries/client.js";
|
|
30
30
|
import { transformFarmServerFns } from "./server-fn-transform.js";
|
|
31
|
+
import { transformAutomaticOptimizedBoundaries } from "./automatic-optimized-boundary.js";
|
|
31
32
|
import { resolveRscBuildOutputPath } from "./build-paths.js";
|
|
33
|
+
import { assertRscPackageCompatibility } from "./compatibility.js";
|
|
32
34
|
import fs from "fs/promises";
|
|
33
35
|
import path from "path";
|
|
34
36
|
import { pathToFileURL } from "node:url";
|
|
@@ -53,7 +55,7 @@ export function defineConfig(config = {}) {
|
|
|
53
55
|
// Core Farm RSC settings
|
|
54
56
|
experimental: {
|
|
55
57
|
serverComponents: config.experimental?.serverComponents ?? true,
|
|
56
|
-
serverActions: config.experimental?.serverActions ??
|
|
58
|
+
serverActions: config.experimental?.serverActions ?? false,
|
|
57
59
|
optimizedBoundary: config.experimental?.optimizedBoundary ?? false,
|
|
58
60
|
},
|
|
59
61
|
srcDir: config.srcDir ?? "src",
|
|
@@ -481,6 +483,7 @@ export default function farmRsc(options = {}) {
|
|
|
481
483
|
return;
|
|
482
484
|
}
|
|
483
485
|
const root = c.root ?? process.cwd();
|
|
486
|
+
assertRscPackageCompatibility(root);
|
|
484
487
|
if (optimizedBoundaryEnabled) {
|
|
485
488
|
registerRscNitroRuntimePackage(root, STRATA_PACKAGE, resolveOptimizedBoundaryRuntimeRoot());
|
|
486
489
|
}
|
|
@@ -599,7 +602,7 @@ export default function farmRsc(options = {}) {
|
|
|
599
602
|
}
|
|
600
603
|
: {}),
|
|
601
604
|
resolve: {
|
|
602
|
-
dedupe: ["react", "react-dom"],
|
|
605
|
+
dedupe: ["react", "react-dom", "react-server-dom-webpack"],
|
|
603
606
|
alias: [
|
|
604
607
|
...Object.entries(layerAliases).map(([find, replacement]) => ({
|
|
605
608
|
find,
|
|
@@ -708,6 +711,24 @@ export default function farmRsc(options = {}) {
|
|
|
708
711
|
return null;
|
|
709
712
|
},
|
|
710
713
|
},
|
|
714
|
+
{
|
|
715
|
+
name: "@farm.js/plugin/rsc:automatic-optimized-boundary",
|
|
716
|
+
enforce: "post",
|
|
717
|
+
transform(code, id) {
|
|
718
|
+
if (!rscEnabled || !optimizedBoundaryEnabled)
|
|
719
|
+
return null;
|
|
720
|
+
const environmentName = this.environment?.name;
|
|
721
|
+
if (environmentName !== "rsc")
|
|
722
|
+
return null;
|
|
723
|
+
const result = transformAutomaticOptimizedBoundaries(code, id);
|
|
724
|
+
if (!result)
|
|
725
|
+
return null;
|
|
726
|
+
if (debug) {
|
|
727
|
+
console.log(`[Farm.js] Added ${result.boundaryCount} automatic optimized-boundary candidate${result.boundaryCount === 1 ? "" : "s"} in ${id}`);
|
|
728
|
+
}
|
|
729
|
+
return { code: result.code, map: null };
|
|
730
|
+
},
|
|
731
|
+
},
|
|
711
732
|
{
|
|
712
733
|
name: "@farm.js/plugin/rsc:server-fn-actions",
|
|
713
734
|
enforce: "pre",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type RenderOptions, type StrataDocument } from "@farming-labs/strata";
|
|
2
2
|
import { type StaticFragmentBoundary, type StaticFragmentProps } from "@farming-labs/strata/react-server";
|
|
3
|
+
import { type ReactElement } from "react";
|
|
3
4
|
export type { RenderOptions, StrataDocument, StrataElement, StrataNode, StrataTag, StrataText, } from "@farming-labs/strata";
|
|
4
5
|
export interface OptimizedBoundaryProps extends Omit<StaticFragmentProps, "content"> {
|
|
5
6
|
/**
|
|
@@ -9,12 +10,20 @@ export interface OptimizedBoundaryProps extends Omit<StaticFragmentProps, "conte
|
|
|
9
10
|
document: StrataDocument;
|
|
10
11
|
renderOptions?: RenderOptions;
|
|
11
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Internal compiler target for automatic optimized boundaries. This helper is
|
|
15
|
+
* deliberately fail-open: any unsupported or unsafe tree returns the original
|
|
16
|
+
* React element unchanged.
|
|
17
|
+
*
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
20
|
+
export declare function _optimizeBoundary(element: ReactElement): ReactElement;
|
|
12
21
|
/**
|
|
13
22
|
* Render a typed host-only document with the optimized native runtime and
|
|
14
23
|
* place it inside an opaque React-owned boundary.
|
|
15
24
|
*
|
|
16
25
|
* Enable `experimental.optimizedBoundary` before importing this component.
|
|
17
26
|
*/
|
|
18
|
-
export declare function OptimizedBoundary({ document, renderOptions, ...props }: OptimizedBoundaryProps):
|
|
27
|
+
export declare function OptimizedBoundary({ document, renderOptions, ...props }: OptimizedBoundaryProps): ReactElement;
|
|
19
28
|
export type OptimizedBoundaryTag = StaticFragmentBoundary;
|
|
20
29
|
//# sourceMappingURL=optimized-boundary.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"optimized-boundary.d.ts","sourceRoot":"","sources":["../../src/rsc/optimized-boundary.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"optimized-boundary.d.ts","sourceRoot":"","sources":["../../src/rsc/optimized-boundary.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,cAAc,EAIpB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAEL,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACzB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAA2C,KAAK,YAAY,EAAE,MAAM,OAAO,CAAC;AAEnF,YAAY,EACV,aAAa,EACb,cAAc,EACd,aAAa,EACb,UAAU,EACV,SAAS,EACT,UAAU,GACX,MAAM,sBAAsB,CAAC;AAE9B,MAAM,WAAW,sBAAuB,SAAQ,IAAI,CAAC,mBAAmB,EAAE,SAAS,CAAC;IAClF;;;OAGG;IACH,QAAQ,EAAE,cAAc,CAAC;IACzB,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAiND;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,YAAY,GAAG,YAAY,CAsBrE;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,QAAQ,EACR,aAAa,EACb,GAAG,KAAK,EACT,EAAE,sBAAsB,GAAG,YAAY,CAMvC;AAED,MAAM,MAAM,oBAAoB,GAAG,sBAAsB,CAAC"}
|
|
@@ -1,5 +1,233 @@
|
|
|
1
|
-
import { render } from "@farming-labs/strata";
|
|
1
|
+
import { render, } from "@farming-labs/strata";
|
|
2
2
|
import { StaticFragment, } from "@farming-labs/strata/react-server";
|
|
3
|
+
import { createElement, Fragment, isValidElement } from "react";
|
|
4
|
+
const AUTOMATIC_MIN_NODES = 8;
|
|
5
|
+
const AUTOMATIC_MIN_BYTES = 256;
|
|
6
|
+
const AUTOMATIC_BOUNDARIES = new Set([
|
|
7
|
+
"article",
|
|
8
|
+
"aside",
|
|
9
|
+
"div",
|
|
10
|
+
"footer",
|
|
11
|
+
"header",
|
|
12
|
+
"main",
|
|
13
|
+
"nav",
|
|
14
|
+
"section",
|
|
15
|
+
"span",
|
|
16
|
+
]);
|
|
17
|
+
const STRATA_TAGS = new Set([
|
|
18
|
+
"a",
|
|
19
|
+
"abbr",
|
|
20
|
+
"address",
|
|
21
|
+
"article",
|
|
22
|
+
"aside",
|
|
23
|
+
"b",
|
|
24
|
+
"blockquote",
|
|
25
|
+
"br",
|
|
26
|
+
"caption",
|
|
27
|
+
"cite",
|
|
28
|
+
"code",
|
|
29
|
+
"col",
|
|
30
|
+
"colgroup",
|
|
31
|
+
"dd",
|
|
32
|
+
"del",
|
|
33
|
+
"details",
|
|
34
|
+
"dfn",
|
|
35
|
+
"div",
|
|
36
|
+
"dl",
|
|
37
|
+
"dt",
|
|
38
|
+
"em",
|
|
39
|
+
"figcaption",
|
|
40
|
+
"figure",
|
|
41
|
+
"footer",
|
|
42
|
+
"h1",
|
|
43
|
+
"h2",
|
|
44
|
+
"h3",
|
|
45
|
+
"h4",
|
|
46
|
+
"h5",
|
|
47
|
+
"h6",
|
|
48
|
+
"header",
|
|
49
|
+
"hr",
|
|
50
|
+
"i",
|
|
51
|
+
"img",
|
|
52
|
+
"ins",
|
|
53
|
+
"kbd",
|
|
54
|
+
"li",
|
|
55
|
+
"main",
|
|
56
|
+
"mark",
|
|
57
|
+
"nav",
|
|
58
|
+
"ol",
|
|
59
|
+
"p",
|
|
60
|
+
"pre",
|
|
61
|
+
"q",
|
|
62
|
+
"s",
|
|
63
|
+
"samp",
|
|
64
|
+
"section",
|
|
65
|
+
"small",
|
|
66
|
+
"span",
|
|
67
|
+
"strong",
|
|
68
|
+
"sub",
|
|
69
|
+
"summary",
|
|
70
|
+
"sup",
|
|
71
|
+
"table",
|
|
72
|
+
"tbody",
|
|
73
|
+
"td",
|
|
74
|
+
"tfoot",
|
|
75
|
+
"th",
|
|
76
|
+
"thead",
|
|
77
|
+
"time",
|
|
78
|
+
"tr",
|
|
79
|
+
"u",
|
|
80
|
+
"ul",
|
|
81
|
+
"var",
|
|
82
|
+
"wbr",
|
|
83
|
+
]);
|
|
84
|
+
const BOOLEAN_ATTRIBUTES = new Set(["hidden", "reversed"]);
|
|
85
|
+
const ATTRIBUTE_NAMES = {
|
|
86
|
+
className: "class",
|
|
87
|
+
colSpan: "colspan",
|
|
88
|
+
dateTime: "datetime",
|
|
89
|
+
rowSpan: "rowspan",
|
|
90
|
+
};
|
|
91
|
+
function AutomaticOptimizedBoundary({ document, fallback, ...props }) {
|
|
92
|
+
let content;
|
|
93
|
+
try {
|
|
94
|
+
content = render(document);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return fallback;
|
|
98
|
+
}
|
|
99
|
+
if (content.nodeCount < AUTOMATIC_MIN_NODES && content.bytes < AUTOMATIC_MIN_BYTES) {
|
|
100
|
+
return fallback;
|
|
101
|
+
}
|
|
102
|
+
return StaticFragment({ ...props, content });
|
|
103
|
+
}
|
|
104
|
+
function attributeValue(name, value) {
|
|
105
|
+
if (value === null || value === undefined)
|
|
106
|
+
return undefined;
|
|
107
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "bigint") {
|
|
108
|
+
return String(value);
|
|
109
|
+
}
|
|
110
|
+
if (typeof value !== "boolean")
|
|
111
|
+
return null;
|
|
112
|
+
if (name.startsWith("aria-") || name.startsWith("data-"))
|
|
113
|
+
return String(value);
|
|
114
|
+
if (BOOLEAN_ATTRIBUTES.has(name))
|
|
115
|
+
return value ? "" : undefined;
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
function convertAttributes(tag, props) {
|
|
119
|
+
const attributes = {};
|
|
120
|
+
for (const [reactName, rawValue] of Object.entries(props)) {
|
|
121
|
+
if (reactName === "children")
|
|
122
|
+
continue;
|
|
123
|
+
if (reactName === "dangerouslySetInnerHTML" ||
|
|
124
|
+
reactName === "ref" ||
|
|
125
|
+
reactName === "style" ||
|
|
126
|
+
reactName === "suppressHydrationWarning" ||
|
|
127
|
+
/^on[A-Z]/.test(reactName)) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
const name = ATTRIBUTE_NAMES[reactName] || reactName;
|
|
131
|
+
const value = attributeValue(name, rawValue);
|
|
132
|
+
if (value === null)
|
|
133
|
+
return null;
|
|
134
|
+
if (value !== undefined)
|
|
135
|
+
attributes[name] = value;
|
|
136
|
+
}
|
|
137
|
+
return Object.keys(attributes).length > 0 ? attributes : {};
|
|
138
|
+
}
|
|
139
|
+
function automaticDocumentFromElement(element) {
|
|
140
|
+
const rootProps = element.props;
|
|
141
|
+
const children = [];
|
|
142
|
+
if (!appendStrataNodes(rootProps.children, children))
|
|
143
|
+
return null;
|
|
144
|
+
return { type: "document", ...(children.length > 0 ? { children } : {}) };
|
|
145
|
+
}
|
|
146
|
+
function appendStrataNodes(value, output) {
|
|
147
|
+
if (value === null || value === undefined || typeof value === "boolean")
|
|
148
|
+
return true;
|
|
149
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "bigint") {
|
|
150
|
+
output.push({ type: "text", value: String(value) });
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
if (Array.isArray(value)) {
|
|
154
|
+
for (const child of value) {
|
|
155
|
+
if (!appendStrataNodes(child, output))
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
if (!isValidElement(value))
|
|
161
|
+
return false;
|
|
162
|
+
const element = value;
|
|
163
|
+
const props = element.props;
|
|
164
|
+
if (element.type === Fragment)
|
|
165
|
+
return appendStrataNodes(props.children, output);
|
|
166
|
+
if (element.type === OptimizedBoundary || element.type === AutomaticOptimizedBoundary) {
|
|
167
|
+
const boundary = props.as ?? "div";
|
|
168
|
+
if (typeof boundary !== "string" || !STRATA_TAGS.has(boundary))
|
|
169
|
+
return false;
|
|
170
|
+
const { as: _as, document: _document, fallback: _fallback, ...boundaryProps } = props;
|
|
171
|
+
const attributes = convertAttributes(boundary, boundaryProps);
|
|
172
|
+
if (!attributes)
|
|
173
|
+
return false;
|
|
174
|
+
const document = props.document;
|
|
175
|
+
if (!document || document.type !== "document")
|
|
176
|
+
return false;
|
|
177
|
+
output.push({
|
|
178
|
+
type: "element",
|
|
179
|
+
tag: boundary,
|
|
180
|
+
...(Object.keys(attributes).length > 0 ? { attributes } : {}),
|
|
181
|
+
...(document.children?.length ? { children: document.children } : {}),
|
|
182
|
+
});
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
if (typeof element.type !== "string" || !STRATA_TAGS.has(element.type)) {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
const tag = element.type;
|
|
189
|
+
const attributes = convertAttributes(tag, props);
|
|
190
|
+
if (!attributes)
|
|
191
|
+
return false;
|
|
192
|
+
const children = [];
|
|
193
|
+
if (!appendStrataNodes(props.children, children))
|
|
194
|
+
return false;
|
|
195
|
+
output.push({
|
|
196
|
+
type: "element",
|
|
197
|
+
tag,
|
|
198
|
+
...(Object.keys(attributes).length > 0 ? { attributes } : {}),
|
|
199
|
+
...(children.length > 0 ? { children } : {}),
|
|
200
|
+
});
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Internal compiler target for automatic optimized boundaries. This helper is
|
|
205
|
+
* deliberately fail-open: any unsupported or unsafe tree returns the original
|
|
206
|
+
* React element unchanged.
|
|
207
|
+
*
|
|
208
|
+
* @internal
|
|
209
|
+
*/
|
|
210
|
+
export function _optimizeBoundary(element) {
|
|
211
|
+
if (!isValidElement(element) ||
|
|
212
|
+
typeof element.type !== "string" ||
|
|
213
|
+
!AUTOMATIC_BOUNDARIES.has(element.type)) {
|
|
214
|
+
return element;
|
|
215
|
+
}
|
|
216
|
+
const props = element.props;
|
|
217
|
+
if (props.dangerouslySetInnerHTML !== undefined || props.ref !== undefined)
|
|
218
|
+
return element;
|
|
219
|
+
const document = automaticDocumentFromElement(element);
|
|
220
|
+
if (!document)
|
|
221
|
+
return element;
|
|
222
|
+
const { children: _children, ...boundaryProps } = props;
|
|
223
|
+
return createElement(AutomaticOptimizedBoundary, {
|
|
224
|
+
...boundaryProps,
|
|
225
|
+
...(element.key !== null ? { key: element.key } : {}),
|
|
226
|
+
as: element.type,
|
|
227
|
+
document,
|
|
228
|
+
fallback: element,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
3
231
|
/**
|
|
4
232
|
* Render a typed host-only document with the optimized native runtime and
|
|
5
233
|
* place it inside an opaque React-owned boundary.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@farm.js/plugin",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.11",
|
|
4
4
|
"description": "Official plugins for Farm.js framework",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Farm.js Team",
|
|
@@ -61,28 +61,35 @@
|
|
|
61
61
|
"nitro": "3.0.1-alpha.0",
|
|
62
62
|
"picocolors": "^1.0.0",
|
|
63
63
|
"rsc-html-stream": "^0.0.4",
|
|
64
|
-
"@farm.js/core": "0.1.0-beta.
|
|
64
|
+
"@farm.js/core": "0.1.0-beta.11"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
67
|
"@types/node": "^20.19.21",
|
|
68
68
|
"@types/react": "^19.0.0",
|
|
69
69
|
"@types/react-dom": "^19.0.0",
|
|
70
|
-
"@vitejs/plugin-rsc": "
|
|
70
|
+
"@vitejs/plugin-rsc": "0.5.32",
|
|
71
|
+
"react": "19.2.8",
|
|
72
|
+
"react-dom": "19.2.8",
|
|
73
|
+
"react-server-dom-webpack": "19.2.8",
|
|
71
74
|
"tsdown": "^0.15.7",
|
|
72
75
|
"typescript": "^5.5.3",
|
|
73
76
|
"vite": "^6.0.0",
|
|
74
77
|
"vitest": "^1.1.0"
|
|
75
78
|
},
|
|
76
79
|
"peerDependencies": {
|
|
77
|
-
"@vitejs/plugin-rsc": "
|
|
78
|
-
"react": "
|
|
79
|
-
"react-dom": "
|
|
80
|
+
"@vitejs/plugin-rsc": "0.5.32",
|
|
81
|
+
"react": "19.2.8",
|
|
82
|
+
"react-dom": "19.2.8",
|
|
83
|
+
"react-server-dom-webpack": "19.2.8",
|
|
80
84
|
"vite": ">=6.0.0",
|
|
81
|
-
"@farm.js/core": "0.1.0-beta.
|
|
85
|
+
"@farm.js/core": "0.1.0-beta.11"
|
|
82
86
|
},
|
|
83
87
|
"peerDependenciesMeta": {
|
|
84
88
|
"@vitejs/plugin-rsc": {
|
|
85
89
|
"optional": true
|
|
90
|
+
},
|
|
91
|
+
"react-server-dom-webpack": {
|
|
92
|
+
"optional": true
|
|
86
93
|
}
|
|
87
94
|
},
|
|
88
95
|
"scripts": {
|