@farm.js/plugin 0.1.0-beta.1 → 0.1.0-beta.100

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/api/index.d.ts +2 -0
  2. package/dist/api/index.d.ts.map +1 -1
  3. package/dist/api/index.js +25 -95
  4. package/dist/dev-static.d.ts +8 -0
  5. package/dist/dev-static.d.ts.map +1 -0
  6. package/dist/dev-static.js +37 -0
  7. package/dist/index.d.ts +1 -1
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +1 -1
  10. package/dist/middleware/index.d.ts.map +1 -1
  11. package/dist/middleware/index.js +28 -53
  12. package/dist/middleware/matching.d.ts +29 -0
  13. package/dist/middleware/matching.d.ts.map +1 -0
  14. package/dist/middleware/matching.js +79 -0
  15. package/dist/rsc/automatic-optimized-boundary.d.ts +11 -0
  16. package/dist/rsc/automatic-optimized-boundary.d.ts.map +1 -0
  17. package/dist/rsc/automatic-optimized-boundary.js +162 -0
  18. package/dist/rsc/compatibility.d.ts +8 -0
  19. package/dist/rsc/compatibility.d.ts.map +1 -0
  20. package/dist/rsc/compatibility.js +46 -0
  21. package/dist/rsc/dev-response.d.ts +3 -0
  22. package/dist/rsc/dev-response.d.ts.map +1 -0
  23. package/dist/rsc/dev-response.js +10 -0
  24. package/dist/rsc/entries/client.d.ts +1 -0
  25. package/dist/rsc/entries/client.d.ts.map +1 -1
  26. package/dist/rsc/entries/client.js +42 -5
  27. package/dist/rsc/entries/error-fallback.d.ts +3 -0
  28. package/dist/rsc/entries/error-fallback.d.ts.map +1 -0
  29. package/dist/rsc/entries/error-fallback.js +12 -0
  30. package/dist/rsc/entries/rsc.d.ts.map +1 -1
  31. package/dist/rsc/entries/rsc.js +187 -189
  32. package/dist/rsc/entries/ssr.d.ts.map +1 -1
  33. package/dist/rsc/entries/ssr.js +44 -74
  34. package/dist/rsc/index.d.ts +15 -2
  35. package/dist/rsc/index.d.ts.map +1 -1
  36. package/dist/rsc/index.js +167 -69
  37. package/dist/rsc/nitro-build.d.ts.map +1 -1
  38. package/dist/rsc/nitro-build.js +28 -8
  39. package/dist/rsc/optimized-boundary.d.ts +10 -1
  40. package/dist/rsc/optimized-boundary.d.ts.map +1 -1
  41. package/dist/rsc/optimized-boundary.js +229 -1
  42. package/dist/rsc/route-action-transform.d.ts +11 -0
  43. package/dist/rsc/route-action-transform.d.ts.map +1 -0
  44. package/dist/rsc/route-action-transform.js +369 -0
  45. package/dist/rsc/server-fn-transform.js +1 -1
  46. package/dist/rsc/test-fixture-dependencies.d.ts +2 -0
  47. package/dist/rsc/test-fixture-dependencies.d.ts.map +1 -0
  48. package/dist/rsc/test-fixture-dependencies.js +23 -0
  49. package/dist/rsc/types.d.ts +9 -0
  50. package/dist/rsc/types.d.ts.map +1 -1
  51. package/dist/rsc/vite-plugin-nitro.d.ts +20 -7
  52. package/dist/rsc/vite-plugin-nitro.d.ts.map +1 -1
  53. package/dist/rsc/vite-plugin-nitro.js +51 -66
  54. package/package.json +15 -8
@@ -0,0 +1,369 @@
1
+ const ROUTE_FACTORY_IMPORT_RE = /\bimport\s+(?:type\s+)?(?:[A-Za-z_$][\w$]*\s*,\s*)?(\{[^}]*\})\s+from\s*["']@farm.js\/core(?:\/routes)?["']\s*;?/g;
2
+ const EXPORTED_ROUTE_RE = /\bexport\s+(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*([A-Za-z_$][\w$]*)\s*\(/g;
3
+ const IMPORT_RE = /\bimport\s+(?!type\b)([\s\S]*?)\s+from\s*(["'][^"']+["'])\s*;?/g;
4
+ const IDENTIFIER_RE = /^[A-Za-z_$][\w$]*$/;
5
+ const MODULE_EXT_RE = /\.[cm]?[jt]sx?$/;
6
+ /**
7
+ * Replace a programmatic route imported by the browser with a small action-only
8
+ * proxy. Route loaders, components, database imports, and other server code do
9
+ * not enter the client graph.
10
+ */
11
+ export function transformFarmRouteActionClients(code, id) {
12
+ if (!isTransformableModule(id) || !code.includes("createRoute") || !code.includes("actions")) {
13
+ return null;
14
+ }
15
+ const routeFactories = findRouteFactoryImportNames(code);
16
+ if (routeFactories.size === 0)
17
+ return null;
18
+ const routes = findActionRoutes(code, routeFactories, id);
19
+ if (routes.length === 0)
20
+ return null;
21
+ const importStatements = findRuntimeImports(code);
22
+ const requiredImports = new Set();
23
+ for (const route of routes) {
24
+ for (const action of route.actions) {
25
+ const statement = importStatements.get(action.local);
26
+ if (!statement) {
27
+ throw new Error(`[Farm.js] Route "${route.exportName}" action "${action.name}" in ${id} must reference ` +
28
+ "a server function imported from another module. Define and export createServerFn in an actions module, then import it into the route.");
29
+ }
30
+ requiredImports.add(statement);
31
+ }
32
+ }
33
+ const output = [
34
+ ...requiredImports,
35
+ ...routes.map((route) => generateRouteActionProxy(route)),
36
+ ...findRouteDefaultExports(code, routes),
37
+ ].join("\n");
38
+ return {
39
+ code: `${output}\n`,
40
+ routes: routes.map((route) => route.exportName),
41
+ };
42
+ }
43
+ function findRouteFactoryImportNames(code) {
44
+ const names = new Set();
45
+ for (const match of code.matchAll(ROUTE_FACTORY_IMPORT_RE)) {
46
+ const namedImports = (match[1] ?? "").slice(1, -1).split(",");
47
+ for (const specifier of namedImports) {
48
+ const normalized = specifier.trim().replace(/^type\s+/, "");
49
+ const specifierMatch = normalized.match(/^createRoute(?:\s+as\s+([A-Za-z_$][\w$]*))?$/);
50
+ if (specifierMatch)
51
+ names.add(specifierMatch[1] ?? "createRoute");
52
+ }
53
+ }
54
+ return names;
55
+ }
56
+ function findActionRoutes(code, factories, id) {
57
+ const routes = [];
58
+ for (const match of code.matchAll(EXPORTED_ROUTE_RE)) {
59
+ const exportName = match[1];
60
+ const calleeName = match[2];
61
+ if (!exportName || !calleeName || !factories.has(calleeName))
62
+ continue;
63
+ const openParen = (match.index ?? 0) + match[0].length - 1;
64
+ const closeParen = findMatchingDelimiter(code, openParen, "(", ")");
65
+ if (closeParen === -1)
66
+ continue;
67
+ const args = splitTopLevel(code, openParen + 1, closeParen);
68
+ if (args.length < 2)
69
+ continue;
70
+ const pathSource = readSegment(code, args[0]);
71
+ if (!isQuotedString(pathSource)) {
72
+ throw new Error(`[Farm.js] Route "${exportName}" in ${id} must use a static string path before its actions can be imported by a Client Component.`);
73
+ }
74
+ const options = readSegment(code, args[1]);
75
+ if (!options.startsWith("{") || !options.endsWith("}"))
76
+ continue;
77
+ const properties = parseObjectProperties(options.slice(1, -1));
78
+ const actionsSource = properties.get("actions");
79
+ if (!actionsSource)
80
+ continue;
81
+ if (!actionsSource.startsWith("{") || !actionsSource.endsWith("}")) {
82
+ throw new Error(`[Farm.js] Route "${exportName}" actions in ${id} must be an object literal so Farm can generate its client proxy.`);
83
+ }
84
+ const actions = parseActionProperties(actionsSource.slice(1, -1), exportName, id);
85
+ if (actions.length === 0)
86
+ continue;
87
+ const defaultSource = properties.get("defaultAction");
88
+ const defaultAction = defaultSource
89
+ ? parseStringValue(defaultSource, exportName, id)
90
+ : actions[0].name;
91
+ if (!actions.some((action) => action.name === defaultAction)) {
92
+ throw new Error(`[Farm.js] Route "${exportName}" defaultAction "${defaultAction}" in ${id} does not match a declared action.`);
93
+ }
94
+ routes.push({ exportName, pathSource, actions, defaultAction });
95
+ }
96
+ return routes;
97
+ }
98
+ function parseActionProperties(source, routeName, id) {
99
+ const properties = parseObjectProperties(source, true);
100
+ const actions = [];
101
+ for (const [name, value] of properties) {
102
+ const local = value || name;
103
+ if (!IDENTIFIER_RE.test(name) || !IDENTIFIER_RE.test(local)) {
104
+ throw new Error(`[Farm.js] Route "${routeName}" actions in ${id} must use identifier entries such as { update } or { update: updateProduct }.`);
105
+ }
106
+ actions.push({ name, local });
107
+ }
108
+ return actions;
109
+ }
110
+ function parseObjectProperties(source, allowShorthand = false) {
111
+ const properties = new Map();
112
+ for (const segment of splitTopLevel(source, 0, source.length)) {
113
+ const entry = readSegment(source, segment);
114
+ if (!entry)
115
+ continue;
116
+ if (entry.startsWith("...")) {
117
+ properties.set(entry, "");
118
+ continue;
119
+ }
120
+ const colon = findTopLevelColon(entry);
121
+ if (colon === -1) {
122
+ if (allowShorthand)
123
+ properties.set(entry, "");
124
+ continue;
125
+ }
126
+ const keySource = entry.slice(0, colon).trim();
127
+ const value = entry.slice(colon + 1).trim();
128
+ const key = isQuotedString(keySource) ? unquote(keySource) : keySource;
129
+ properties.set(key, value);
130
+ }
131
+ return properties;
132
+ }
133
+ function findRuntimeImports(code) {
134
+ const imports = new Map();
135
+ for (const match of code.matchAll(IMPORT_RE)) {
136
+ const clause = (match[1] ?? "").trim();
137
+ const statement = match[0];
138
+ const namedStart = clause.indexOf("{");
139
+ const namedEnd = clause.lastIndexOf("}");
140
+ if (namedStart > -1 && namedEnd > namedStart) {
141
+ for (const specifier of clause.slice(namedStart + 1, namedEnd).split(",")) {
142
+ const normalized = specifier.trim().replace(/^type\s+/, "");
143
+ if (!normalized || specifier.trim().startsWith("type "))
144
+ continue;
145
+ const parts = normalized.split(/\s+as\s+/);
146
+ const local = parts[1] ?? parts[0];
147
+ if (local && IDENTIFIER_RE.test(local))
148
+ imports.set(local, statement);
149
+ }
150
+ }
151
+ const defaultImport = clause
152
+ .slice(0, namedStart === -1 ? clause.length : namedStart)
153
+ .replace(/,$/, "")
154
+ .trim();
155
+ if (IDENTIFIER_RE.test(defaultImport))
156
+ imports.set(defaultImport, statement);
157
+ }
158
+ return imports;
159
+ }
160
+ function generateRouteActionProxy(route) {
161
+ const actions = route.actions
162
+ .map((action) => `${JSON.stringify(action.name)}: ${action.local}`)
163
+ .join(", ");
164
+ const defaultAction = route.actions.find((action) => action.name === route.defaultAction);
165
+ return `export const ${route.exportName} = Object.freeze({
166
+ __farmRouteActionTarget: true,
167
+ path: ${route.pathSource},
168
+ actions: Object.freeze({ ${actions} }),
169
+ defaultAction: ${JSON.stringify(route.defaultAction)},
170
+ action: ${defaultAction.local},
171
+ });`;
172
+ }
173
+ function findRouteDefaultExports(code, routes) {
174
+ const exports = [];
175
+ for (const route of routes) {
176
+ const defaultExport = new RegExp(`\\bexport\\s+default\\s+${escapeRegExp(route.exportName)}\\s*;?`);
177
+ if (defaultExport.test(code))
178
+ exports.push(`export default ${route.exportName};`);
179
+ }
180
+ return exports;
181
+ }
182
+ function splitTopLevel(source, start, end) {
183
+ const segments = [];
184
+ let segmentStart = start;
185
+ let parens = 0;
186
+ let braces = 0;
187
+ let brackets = 0;
188
+ let quote = null;
189
+ let escaped = false;
190
+ let lineComment = false;
191
+ let blockComment = false;
192
+ for (let index = start; index < end; index++) {
193
+ const char = source[index];
194
+ const next = source[index + 1];
195
+ if (lineComment) {
196
+ if (char === "\n" || char === "\r")
197
+ lineComment = false;
198
+ continue;
199
+ }
200
+ if (blockComment) {
201
+ if (char === "*" && next === "/") {
202
+ blockComment = false;
203
+ index++;
204
+ }
205
+ continue;
206
+ }
207
+ if (quote) {
208
+ if (escaped)
209
+ escaped = false;
210
+ else if (char === "\\")
211
+ escaped = true;
212
+ else if (char === quote)
213
+ quote = null;
214
+ continue;
215
+ }
216
+ if (char === "/" && next === "/") {
217
+ lineComment = true;
218
+ index++;
219
+ continue;
220
+ }
221
+ if (char === "/" && next === "*") {
222
+ blockComment = true;
223
+ index++;
224
+ continue;
225
+ }
226
+ if (char === "'" || char === '"' || char === "`") {
227
+ quote = char;
228
+ continue;
229
+ }
230
+ if (char === "(")
231
+ parens++;
232
+ else if (char === ")")
233
+ parens--;
234
+ else if (char === "{")
235
+ braces++;
236
+ else if (char === "}")
237
+ braces--;
238
+ else if (char === "[")
239
+ brackets++;
240
+ else if (char === "]")
241
+ brackets--;
242
+ else if (char === "," && parens === 0 && braces === 0 && brackets === 0) {
243
+ segments.push([segmentStart, index]);
244
+ segmentStart = index + 1;
245
+ }
246
+ }
247
+ segments.push([segmentStart, end]);
248
+ return segments;
249
+ }
250
+ function findTopLevelColon(source) {
251
+ const segments = splitTopLevel(source, 0, source.length);
252
+ if (segments.length !== 1)
253
+ return -1;
254
+ let parens = 0;
255
+ let braces = 0;
256
+ let brackets = 0;
257
+ let quote = null;
258
+ let escaped = false;
259
+ for (let index = 0; index < source.length; index++) {
260
+ const char = source[index];
261
+ if (quote) {
262
+ if (escaped)
263
+ escaped = false;
264
+ else if (char === "\\")
265
+ escaped = true;
266
+ else if (char === quote)
267
+ quote = null;
268
+ continue;
269
+ }
270
+ if (char === "'" || char === '"' || char === "`") {
271
+ quote = char;
272
+ continue;
273
+ }
274
+ if (char === "(")
275
+ parens++;
276
+ else if (char === ")")
277
+ parens--;
278
+ else if (char === "{")
279
+ braces++;
280
+ else if (char === "}")
281
+ braces--;
282
+ else if (char === "[")
283
+ brackets++;
284
+ else if (char === "]")
285
+ brackets--;
286
+ else if (char === ":" && parens === 0 && braces === 0 && brackets === 0)
287
+ return index;
288
+ }
289
+ return -1;
290
+ }
291
+ function findMatchingDelimiter(source, openIndex, open, close) {
292
+ let depth = 0;
293
+ let quote = null;
294
+ let lineComment = false;
295
+ let blockComment = false;
296
+ let escaped = false;
297
+ for (let index = openIndex; index < source.length; index++) {
298
+ const char = source[index];
299
+ const next = source[index + 1];
300
+ if (lineComment) {
301
+ if (char === "\n" || char === "\r")
302
+ lineComment = false;
303
+ continue;
304
+ }
305
+ if (blockComment) {
306
+ if (char === "*" && next === "/") {
307
+ blockComment = false;
308
+ index++;
309
+ }
310
+ continue;
311
+ }
312
+ if (quote) {
313
+ if (escaped)
314
+ escaped = false;
315
+ else if (char === "\\")
316
+ escaped = true;
317
+ else if (char === quote)
318
+ quote = null;
319
+ continue;
320
+ }
321
+ if (char === "/" && next === "/") {
322
+ lineComment = true;
323
+ index++;
324
+ continue;
325
+ }
326
+ if (char === "/" && next === "*") {
327
+ blockComment = true;
328
+ index++;
329
+ continue;
330
+ }
331
+ if (char === "'" || char === '"' || char === "`") {
332
+ quote = char;
333
+ continue;
334
+ }
335
+ if (char === open)
336
+ depth++;
337
+ else if (char === close && --depth === 0)
338
+ return index;
339
+ }
340
+ return -1;
341
+ }
342
+ function readSegment(source, segment) {
343
+ return source.slice(segment[0], segment[1]).trim();
344
+ }
345
+ function parseStringValue(source, routeName, id) {
346
+ if (!isQuotedString(source)) {
347
+ throw new Error(`[Farm.js] Route "${routeName}" defaultAction in ${id} must be a static string.`);
348
+ }
349
+ return unquote(source);
350
+ }
351
+ function isQuotedString(value) {
352
+ return (value.length >= 2 &&
353
+ ((value.startsWith('"') && value.endsWith('"')) ||
354
+ (value.startsWith("'") && value.endsWith("'"))));
355
+ }
356
+ function unquote(value) {
357
+ const quote = value[0];
358
+ const body = value.slice(1, -1);
359
+ if (quote === '"')
360
+ return JSON.parse(value);
361
+ return body.replace(/\\'/g, "'").replace(/\\\\/g, "\\");
362
+ }
363
+ function isTransformableModule(id) {
364
+ const cleanId = id.split("?", 1)[0];
365
+ return MODULE_EXT_RE.test(cleanId) && !/\.d\.[cm]?ts$/.test(cleanId);
366
+ }
367
+ function escapeRegExp(value) {
368
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
369
+ }
@@ -1,5 +1,5 @@
1
1
  const SERVER_FUNCTION_FACTORY_IMPORT_RE = /\bimport\s+(?:type\s+)?(?:[A-Za-z_$][\w$]*\s*,\s*)?(\{[^}]*\})\s+from\s*["']@farm.js\/core(?:\/(?:server-fn|server-query))?["']\s*;?/g;
2
- const EXPORT_SERVER_FUNCTION_FACTORY_RE = /\bexport\s+(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*([A-Za-z_$][\w$]*)\s*\(/g;
2
+ const EXPORT_SERVER_FUNCTION_FACTORY_RE = /\bexport\s+(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*([A-Za-z_$][\w$]*)\s*(?:<[^<>]*(?:<[^<>]*>[^<>]*)*>)?\s*\(/g;
3
3
  const MODULE_EXT_RE = /\.[cm]?[jt]sx?$/;
4
4
  const DECLARATION_RE = /(^|\n)\s*["']([^"']+)["']\s*;?/g;
5
5
  export function transformFarmServerFns(code, id) {
@@ -0,0 +1,2 @@
1
+ export declare function linkRscFixtureDependencies(root: string): void;
2
+ //# sourceMappingURL=test-fixture-dependencies.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-fixture-dependencies.d.ts","sourceRoot":"","sources":["../../src/rsc/test-fixture-dependencies.ts"],"names":[],"mappings":"AAaA,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAY7D"}
@@ -0,0 +1,23 @@
1
+ import { existsSync, mkdirSync, realpathSync, symlinkSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ const RSC_FIXTURE_PACKAGES = [
5
+ "react",
6
+ "react-dom",
7
+ "react-server-dom-webpack",
8
+ "@vitejs/plugin-rsc",
9
+ ];
10
+ const repositoryRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../../..");
11
+ export function linkRscFixtureDependencies(root) {
12
+ for (const packageName of RSC_FIXTURE_PACKAGES) {
13
+ const source = [
14
+ path.join(repositoryRoot, "node_modules", packageName),
15
+ path.join(repositoryRoot, "examples/rsc-demo/node_modules", packageName),
16
+ ].find((candidate) => existsSync(candidate));
17
+ if (!source)
18
+ throw new Error(`Missing RSC fixture dependency: ${packageName}`);
19
+ const target = path.join(root, "node_modules", packageName);
20
+ mkdirSync(path.dirname(target), { recursive: true });
21
+ symlinkSync(realpathSync(source), target, process.platform === "win32" ? "junction" : "dir");
22
+ }
23
+ }
@@ -54,6 +54,8 @@ export interface EntryContext {
54
54
  outDir: string;
55
55
  /** Base URL path, e.g. '/' */
56
56
  basePath: string;
57
+ /** Resolved same-origin mount for canonical /api routes. */
58
+ apiBasePath?: string;
57
59
  /** Route files subdirectory within srcDir */
58
60
  routesDir?: string;
59
61
  /** Root-relative Vite URL for an existing global stylesheet. */
@@ -72,6 +74,13 @@ export interface EntryContext {
72
74
  deploymentId: string;
73
75
  /** Whether debug mode is enabled */
74
76
  debug: boolean;
77
+ /** True only for the Vite serve command, never for a production build. */
78
+ development?: boolean;
79
+ /** Generated import/init fragments for `cache.client.adapter` persistence. */
80
+ clientCachePersistence?: {
81
+ imports: string;
82
+ init: string;
83
+ };
75
84
  }
76
85
  /**
77
86
  * Matched route information from the file-based router.
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/rsc/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,uBAAuB,EACvB,+BAA+B,EAChC,MAAM,sCAAsC,CAAC;AAE9C;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,yDAAyD;IACzD,aAAa,CAAC,EAAE,uBAAuB,CAAC;IAExC,+EAA+E;IAC/E,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;OAIG;IACH,OAAO,CAAC,EAAE;QACR,wCAAwC;QACxC,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,wCAAwC;QACxC,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,2CAA2C;QAC3C,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IAEF;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,0CAA0C;IAC1C,MAAM,EAAE,MAAM,CAAC;IAEf,0CAA0C;IAC1C,MAAM,EAAE,MAAM,CAAC;IAEf,8BAA8B;IAC9B,QAAQ,EAAE,MAAM,CAAC;IAEjB,6CAA6C;IAC7C,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,gEAAgE;IAChE,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,uFAAuF;IACvF,UAAU,CAAC,EAAE,KAAK,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;KACd,CAAC,CAAC;IAEH,yCAAyC;IACzC,cAAc,EAAE,OAAO,CAAC;IAExB,wDAAwD;IACxD,aAAa,EAAE,+BAA+B,CAAC;IAE/C,8EAA8E;IAC9E,YAAY,EAAE,MAAM,CAAC;IAErB,oCAAoC;IACpC,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,0CAA0C;IAC1C,IAAI,EAAE,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;IAE/B,gEAAgE;IAChE,OAAO,EAAE,MAAM,CAAC;IAEhB,yDAAyD;IACzD,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,0DAA0D;IAC1D,IAAI,EAAE,KAAK,CAAC,YAAY,CAAC;IAEzB,wFAAwF;IACxF,WAAW,CAAC,EAAE,KAAK,CAAC,YAAY,CAAC;IAEjC,yEAAyE;IACzE,QAAQ,CAAC,EAAE;QACT,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;IAEF,0DAA0D;IAC1D,WAAW,CAAC,EAAE;QACZ,EAAE,EAAE,OAAO,CAAC;QACZ,IAAI,EAAE,OAAO,CAAC;KACf,CAAC;IAEF,6CAA6C;IAC7C,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;GAEG;AACH,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,UAAU;QAClB,QAAQ,CAAC,OAAO,EAAE;YAChB,qCAAqC;YACrC,OAAO,IAAI,KAAK,CAAC,YAAY,EAAE,CAAC;YAEhC,6CAA6C;YAC7C,UAAU,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;YAEhE,kDAAkD;YAClD,0BAA0B,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;SAC5D,CAAC;KACH;CACF"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/rsc/types.ts"],"names":[],"mappings":"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"}
@@ -1,15 +1,28 @@
1
1
  /**
2
- * Vite plugin to run Nitro after RSC build (single "vite build", no post script).
3
- * Implements the Nitro RSC deployment pipeline:
4
- * - Step 1: Force per-environment output dirs to .nitro/vite/dist/${name} so Nitro can locate artifacts.
5
- * - Step 2: Capture the server (RSC) Rollup bundle in writeBundle.
6
- * - Step 3: Find the SSR entry chunk from the bundle.
7
- * - Step 4: Compute renderer path and pass to Nitro with client publicDir.
2
+ * Vite plugin for the Nitro RSC deployment pipeline. It forces per-environment
3
+ * output dirs (`Step 1`), captures the server (RSC) Rollup bundle in `writeBundle`
4
+ * (`Step 2`), and exposes `runNitroFromBuildApp()`, which builds Nitro from the
5
+ * captured outputs once all environments have finished writing.
6
+ *
7
+ * Farm's RSC builder invokes `runNitroFromBuildApp()` after every environment
8
+ * has written its output, so a single `vite build` still produces the
9
+ * deployment bundle.
10
+ *
11
+ * The plugin no longer triggers Nitro itself from a Rollup hook: the previous
12
+ * `buildEnd` trigger fired before its own environment's `prepareOutDir`/`writeBundle`,
13
+ * so on a rebuild it observed the previous build's stale outputs and built Nitro
14
+ * against them (and set `__FARM_NITRO_PLUGIN_RAN`, suppressing the proper run).
15
+ * Removing it keeps Nitro from running against stale artifacts while the post-app
16
+ * hook preserves the one-command build contract.
17
+ *
18
+ * - Step 1: Force per-environment output dirs to `.nitro/vite/dist/${name}` so Nitro can locate artifacts.
19
+ * - Step 2: Capture the server (RSC) Rollup bundle in `writeBundle`.
8
20
  *
9
21
  * @example
10
22
  * ```ts
11
- * import nitro from '@farm.js/plugin/rsc/vite-plugin-nitro'
23
+ * import { defineConfig, nitro } from '@farm.js/plugin/rsc'
12
24
  * export default defineConfig({
25
+ * srcDir: 'src',
13
26
  * plugins: [rsc(), react(), nitro({ server: { environmentName: 'rsc' }, config: { preset: 'vercel' } })],
14
27
  * })
15
28
  * ```
@@ -1 +1 @@
1
- {"version":3,"file":"vite-plugin-nitro.d.ts","sourceRoot":"","sources":["../../src/rsc/vite-plugin-nitro.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAenC,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,EAAE;QACP,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,4DAA4D;QAC5D,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,MAAM,CAAC,EAAE;QACP,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,kEAAkE;IAClE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAWD,MAAM,CAAC,OAAO,UAAU,KAAK,CAAC,OAAO,GAAE,kBAAuB,GAAG,MAAM,CAoHtE;AAED,kFAAkF;AAClF,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC,CA+C1D"}
1
+ {"version":3,"file":"vite-plugin-nitro.d.ts","sourceRoot":"","sources":["../../src/rsc/vite-plugin-nitro.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAenC,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,EAAE;QACP,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,4DAA4D;QAC5D,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,MAAM,CAAC,EAAE;QACP,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,kEAAkE;IAClE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAID,MAAM,CAAC,OAAO,UAAU,KAAK,CAAC,OAAO,GAAE,kBAAuB,GAAG,MAAM,CAqEtE;AAED,kFAAkF;AAClF,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC,CAkC1D"}
@@ -1,15 +1,28 @@
1
1
  /**
2
- * Vite plugin to run Nitro after RSC build (single "vite build", no post script).
3
- * Implements the Nitro RSC deployment pipeline:
4
- * - Step 1: Force per-environment output dirs to .nitro/vite/dist/${name} so Nitro can locate artifacts.
5
- * - Step 2: Capture the server (RSC) Rollup bundle in writeBundle.
6
- * - Step 3: Find the SSR entry chunk from the bundle.
7
- * - Step 4: Compute renderer path and pass to Nitro with client publicDir.
2
+ * Vite plugin for the Nitro RSC deployment pipeline. It forces per-environment
3
+ * output dirs (`Step 1`), captures the server (RSC) Rollup bundle in `writeBundle`
4
+ * (`Step 2`), and exposes `runNitroFromBuildApp()`, which builds Nitro from the
5
+ * captured outputs once all environments have finished writing.
6
+ *
7
+ * Farm's RSC builder invokes `runNitroFromBuildApp()` after every environment
8
+ * has written its output, so a single `vite build` still produces the
9
+ * deployment bundle.
10
+ *
11
+ * The plugin no longer triggers Nitro itself from a Rollup hook: the previous
12
+ * `buildEnd` trigger fired before its own environment's `prepareOutDir`/`writeBundle`,
13
+ * so on a rebuild it observed the previous build's stale outputs and built Nitro
14
+ * against them (and set `__FARM_NITRO_PLUGIN_RAN`, suppressing the proper run).
15
+ * Removing it keeps Nitro from running against stale artifacts while the post-app
16
+ * hook preserves the one-command build contract.
17
+ *
18
+ * - Step 1: Force per-environment output dirs to `.nitro/vite/dist/${name}` so Nitro can locate artifacts.
19
+ * - Step 2: Capture the server (RSC) Rollup bundle in `writeBundle`.
8
20
  *
9
21
  * @example
10
22
  * ```ts
11
- * import nitro from '@farm.js/plugin/rsc/vite-plugin-nitro'
23
+ * import { defineConfig, nitro } from '@farm.js/plugin/rsc'
12
24
  * export default defineConfig({
25
+ * srcDir: 'src',
13
26
  * plugins: [rsc(), react(), nitro({ server: { environmentName: 'rsc' }, config: { preset: 'vercel' } })],
14
27
  * })
15
28
  * ```
@@ -18,9 +31,6 @@ import path from "path";
18
31
  import { buildRscNitro } from "./nitro-build.js";
19
32
  import { waitForRscOutputs } from "./nitro-build.js";
20
33
  import { resolveRscBuildOutputPath } from "./build-paths.js";
21
- let nitroRunScheduled = false;
22
- /** Stored RSC (server) bundle from writeBundle so we can find the entry chunk. */
23
- let serverBundle = null;
24
34
  const NITRO_VITE_DIST = ".nitro/vite/dist";
25
35
  export default function nitro(options = {}) {
26
36
  const serverEnvName = options.server?.environmentName ?? "rsc";
@@ -29,12 +39,15 @@ export default function nitro(options = {}) {
29
39
  let rscOutDir = path.join(NITRO_VITE_DIST, "rsc");
30
40
  let ssrOutDir = path.join(NITRO_VITE_DIST, "ssr");
31
41
  let clientOutDir = path.join(NITRO_VITE_DIST, "client");
32
- let baseOutDir = NITRO_VITE_DIST;
42
+ let clientAssetsDir = "assets";
33
43
  return {
34
44
  name: "vite-plugin-nitro",
35
45
  apply: "build",
36
46
  // Step 1: Force per-environment output dirs so Nitro can locate artifacts.
37
47
  config() {
48
+ delete globalThis.__FARM_NITRO_SERVER_BUNDLE;
49
+ delete globalThis.__FARM_NITRO_PATHS;
50
+ delete globalThis.__FARM_NITRO_BUILD_PROMISE;
38
51
  return {
39
52
  environments: {
40
53
  rsc: { build: { outDir: path.join(NITRO_VITE_DIST, "rsc") } },
@@ -50,20 +63,23 @@ export default function nitro(options = {}) {
50
63
  rscOutDir = envs.rsc.build.outDir;
51
64
  ssrOutDir = envs.ssr.build.outDir;
52
65
  clientOutDir = envs.client.build.outDir;
53
- const dir = path.dirname(rscOutDir);
54
- baseOutDir = path.isAbsolute(dir) ? path.relative(resolvedRoot, dir) || "." : dir;
55
66
  }
56
67
  else {
57
- baseOutDir = options.outDir ?? "dist";
68
+ const baseOutDir = options.outDir ?? "dist";
58
69
  rscOutDir = path.join(baseOutDir, "rsc");
59
70
  ssrOutDir = path.join(baseOutDir, "ssr");
60
71
  clientOutDir = path.join(baseOutDir, "client");
61
72
  }
73
+ const clientBuildAssetsDir = envs?.client?.build?.assetsDir;
74
+ clientAssetsDir =
75
+ typeof clientBuildAssetsDir === "string" && clientBuildAssetsDir
76
+ ? clientBuildAssetsDir
77
+ : "assets";
62
78
  },
63
79
  // Step 2: Capture the server (RSC) Rollup bundle when that environment writes.
64
80
  writeBundle(_options, bundle) {
65
81
  if (this.environment?.name === serverEnvName) {
66
- serverBundle = bundle;
82
+ delete globalThis.__FARM_NITRO_BUILD_PROMISE;
67
83
  globalThis.__FARM_NITRO_SERVER_BUNDLE = bundle;
68
84
  globalThis.__FARM_NITRO_PATHS = {
69
85
  root: resolvedRoot,
@@ -72,59 +88,10 @@ export default function nitro(options = {}) {
72
88
  clientOutDir,
73
89
  serverEntryName,
74
90
  preset: options.config?.preset ?? process.env.NITRO_PRESET ?? "vercel",
91
+ clientAssetsDir,
75
92
  };
76
93
  }
77
94
  },
78
- // Run Nitro once all env outputs exist (after step 5). buildEnd runs after each env; only run when all exist.
79
- async buildEnd() {
80
- if (nitroRunScheduled)
81
- return;
82
- const root = path.resolve(resolvedRoot);
83
- const { existsSync } = await import("fs");
84
- await new Promise((r) => setTimeout(r, 800));
85
- const baseOutDir = rscOutDir.includes(".nitro/vite/dist")
86
- ? NITRO_VITE_DIST
87
- : path.isAbsolute(path.dirname(rscOutDir))
88
- ? path.relative(root, path.dirname(rscOutDir)) || "."
89
- : path.dirname(rscOutDir);
90
- const rscOk = existsSync(resolveRscBuildOutputPath(root, baseOutDir, "rsc", "index.js"));
91
- const ssrOk = existsSync(resolveRscBuildOutputPath(root, baseOutDir, "ssr", "index.js"));
92
- const clientOk = existsSync(resolveRscBuildOutputPath(root, baseOutDir, "client", "assets"));
93
- if (!rscOk || !ssrOk || !clientOk)
94
- return;
95
- nitroRunScheduled = true;
96
- const preset = options.config?.preset ?? process.env.NITRO_PRESET ?? "vercel";
97
- const run = async () => {
98
- await waitForRscOutputs(root, baseOutDir, { timeoutMs: 20_000 }).catch(() => {
99
- // Outputs already verified; proceed (nitro-build stubs manifest if missing)
100
- });
101
- let rendererPath = resolveRscBuildOutputPath(root, rscOutDir, "index.js");
102
- if (serverBundle) {
103
- const serverEntryChunks = [];
104
- for (const chunk of Object.values(serverBundle)) {
105
- if (chunk.type === "chunk" && chunk.isEntry && chunk.fileName) {
106
- serverEntryChunks.push(chunk);
107
- }
108
- }
109
- const selected = serverEntryName
110
- ? serverEntryChunks.find((c) => (c.name ?? c.fileName) === serverEntryName)
111
- : serverEntryChunks[0];
112
- if (selected?.fileName) {
113
- rendererPath = resolveRscBuildOutputPath(root, rscOutDir, selected.fileName);
114
- }
115
- }
116
- await buildRscNitro({
117
- root,
118
- rendererPath,
119
- publicDir: resolveRscBuildOutputPath(root, clientOutDir),
120
- ssrPath: resolveRscBuildOutputPath(root, ssrOutDir, "index.js"),
121
- assetsDir: undefined,
122
- preset,
123
- });
124
- globalThis.__FARM_NITRO_PLUGIN_RAN = true;
125
- };
126
- await run();
127
- },
128
95
  };
129
96
  }
130
97
  /** Called by RSC plugin buildApp (post) to run Nitro with the captured bundle. */
@@ -133,6 +100,24 @@ export async function runNitroFromBuildApp() {
133
100
  const bundle = globalThis.__FARM_NITRO_SERVER_BUNDLE;
134
101
  if (!paths)
135
102
  return;
103
+ const existing = globalThis.__FARM_NITRO_BUILD_PROMISE;
104
+ if (existing) {
105
+ await existing;
106
+ return;
107
+ }
108
+ const run = runCapturedNitroBuild(paths, bundle);
109
+ globalThis.__FARM_NITRO_BUILD_PROMISE = run;
110
+ try {
111
+ await run;
112
+ }
113
+ catch (error) {
114
+ if (globalThis.__FARM_NITRO_BUILD_PROMISE === run) {
115
+ delete globalThis.__FARM_NITRO_BUILD_PROMISE;
116
+ }
117
+ throw error;
118
+ }
119
+ }
120
+ async function runCapturedNitroBuild(paths, bundle) {
136
121
  const root = paths.root;
137
122
  const preset = paths.preset;
138
123
  const baseOutDir = path.dirname(paths.rscOutDir);
@@ -157,7 +142,7 @@ export async function runNitroFromBuildApp() {
157
142
  rendererPath,
158
143
  publicDir: resolveRscBuildOutputPath(root, paths.clientOutDir),
159
144
  ssrPath: resolveRscBuildOutputPath(root, paths.ssrOutDir, "index.js"),
160
- assetsDir: undefined,
145
+ assetsDir: paths.clientAssetsDir ?? "assets",
161
146
  preset,
162
147
  });
163
148
  }