@tanstack/start-plugin-core 1.142.11 → 1.142.12
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/esm/create-server-fn-plugin/compiler.d.ts +10 -4
- package/dist/esm/create-server-fn-plugin/compiler.js +126 -72
- package/dist/esm/create-server-fn-plugin/compiler.js.map +1 -1
- package/dist/esm/create-server-fn-plugin/plugin.js +22 -24
- package/dist/esm/create-server-fn-plugin/plugin.js.map +1 -1
- package/package.json +2 -2
- package/src/create-server-fn-plugin/compiler.ts +224 -105
- package/src/create-server-fn-plugin/plugin.ts +33 -32
|
@@ -22,6 +22,13 @@ type ExportEntry = {
|
|
|
22
22
|
};
|
|
23
23
|
type Kind = 'None' | `Root` | `Builder` | LookupKind;
|
|
24
24
|
export type LookupKind = 'ServerFn' | 'Middleware' | 'IsomorphicFn' | 'ServerOnlyFn' | 'ClientOnlyFn';
|
|
25
|
+
export declare const KindDetectionPatterns: Record<LookupKind, RegExp>;
|
|
26
|
+
export declare const LookupKindsPerEnv: Record<'client' | 'server', Set<LookupKind>>;
|
|
27
|
+
/**
|
|
28
|
+
* Detects which LookupKinds are present in the code using string matching.
|
|
29
|
+
* This is a fast pre-scan before AST parsing to limit the work done during compilation.
|
|
30
|
+
*/
|
|
31
|
+
export declare function detectKindsInCode(code: string, env: 'client' | 'server'): Set<LookupKind>;
|
|
25
32
|
export type LookupConfig = {
|
|
26
33
|
libName: string;
|
|
27
34
|
rootExport: string;
|
|
@@ -38,8 +45,6 @@ export declare class ServerFnCompiler {
|
|
|
38
45
|
private moduleCache;
|
|
39
46
|
private initialized;
|
|
40
47
|
private validLookupKinds;
|
|
41
|
-
private hasDirectCallKinds;
|
|
42
|
-
private hasRootAsCandidateKinds;
|
|
43
48
|
private knownRootImports;
|
|
44
49
|
constructor(options: {
|
|
45
50
|
env: 'client' | 'server';
|
|
@@ -58,12 +63,13 @@ export declare class ServerFnCompiler {
|
|
|
58
63
|
ast: import('@tanstack/router-utils').ParseAstResult;
|
|
59
64
|
};
|
|
60
65
|
invalidateModule(id: string): boolean;
|
|
61
|
-
compile({ code, id, isProviderFile, }: {
|
|
66
|
+
compile({ code, id, isProviderFile, detectedKinds, }: {
|
|
62
67
|
code: string;
|
|
63
68
|
id: string;
|
|
64
69
|
isProviderFile: boolean;
|
|
70
|
+
/** Pre-detected kinds present in this file. If not provided, all valid kinds are checked. */
|
|
71
|
+
detectedKinds?: Set<LookupKind>;
|
|
65
72
|
}): Promise<import('@tanstack/router-utils').GeneratorResult | null>;
|
|
66
|
-
private collectCandidates;
|
|
67
73
|
private resolveIdentifierKind;
|
|
68
74
|
/**
|
|
69
75
|
* Recursively find an export in a module, following `export * from` chains.
|
|
@@ -24,6 +24,38 @@ const LookupSetup = {
|
|
|
24
24
|
ServerOnlyFn: { type: "directCall" },
|
|
25
25
|
ClientOnlyFn: { type: "directCall" }
|
|
26
26
|
};
|
|
27
|
+
const KindDetectionPatterns = {
|
|
28
|
+
ServerFn: /\.handler\s*\(/,
|
|
29
|
+
Middleware: /createMiddleware/,
|
|
30
|
+
IsomorphicFn: /createIsomorphicFn/,
|
|
31
|
+
ServerOnlyFn: /createServerOnlyFn/,
|
|
32
|
+
ClientOnlyFn: /createClientOnlyFn/
|
|
33
|
+
};
|
|
34
|
+
const LookupKindsPerEnv = {
|
|
35
|
+
client: /* @__PURE__ */ new Set([
|
|
36
|
+
"Middleware",
|
|
37
|
+
"ServerFn",
|
|
38
|
+
"IsomorphicFn",
|
|
39
|
+
"ServerOnlyFn",
|
|
40
|
+
"ClientOnlyFn"
|
|
41
|
+
]),
|
|
42
|
+
server: /* @__PURE__ */ new Set([
|
|
43
|
+
"ServerFn",
|
|
44
|
+
"IsomorphicFn",
|
|
45
|
+
"ServerOnlyFn",
|
|
46
|
+
"ClientOnlyFn"
|
|
47
|
+
])
|
|
48
|
+
};
|
|
49
|
+
function detectKindsInCode(code, env) {
|
|
50
|
+
const detected = /* @__PURE__ */ new Set();
|
|
51
|
+
const validForEnv = LookupKindsPerEnv[env];
|
|
52
|
+
for (const [kind, pattern] of Object.entries(KindDetectionPatterns)) {
|
|
53
|
+
if (validForEnv.has(kind) && pattern.test(code)) {
|
|
54
|
+
detected.add(kind);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return detected;
|
|
58
|
+
}
|
|
27
59
|
const IdentifierToKinds = /* @__PURE__ */ new Map();
|
|
28
60
|
for (const [kind, setup] of Object.entries(LookupSetup)) {
|
|
29
61
|
if (setup.type === "methodChain") {
|
|
@@ -37,27 +69,53 @@ for (const [kind, setup] of Object.entries(LookupSetup)) {
|
|
|
37
69
|
}
|
|
38
70
|
}
|
|
39
71
|
}
|
|
72
|
+
const DirectCallFactoryNames = /* @__PURE__ */ new Set([
|
|
73
|
+
"createServerOnlyFn",
|
|
74
|
+
"createClientOnlyFn",
|
|
75
|
+
"createIsomorphicFn"
|
|
76
|
+
]);
|
|
77
|
+
function needsDirectCallDetection(kinds) {
|
|
78
|
+
for (const kind of kinds) {
|
|
79
|
+
const setup = LookupSetup[kind];
|
|
80
|
+
if (setup.type === "directCall" || setup.allowRootAsCandidate) {
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
function isNestedDirectCallCandidate(node) {
|
|
87
|
+
let calleeName;
|
|
88
|
+
if (t.isIdentifier(node.callee)) {
|
|
89
|
+
calleeName = node.callee.name;
|
|
90
|
+
} else if (t.isMemberExpression(node.callee) && t.isIdentifier(node.callee.property)) {
|
|
91
|
+
calleeName = node.callee.property.name;
|
|
92
|
+
}
|
|
93
|
+
return calleeName !== void 0 && DirectCallFactoryNames.has(calleeName);
|
|
94
|
+
}
|
|
95
|
+
function isTopLevelDirectCallCandidate(path) {
|
|
96
|
+
const node = path.node;
|
|
97
|
+
const isSimpleCall = t.isIdentifier(node.callee) || t.isMemberExpression(node.callee) && t.isIdentifier(node.callee.object) && t.isIdentifier(node.callee.property);
|
|
98
|
+
if (!isSimpleCall) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
const parent = path.parent;
|
|
102
|
+
if (!t.isVariableDeclarator(parent) || parent.init !== node) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
const grandParent = path.parentPath.parent;
|
|
106
|
+
if (!t.isVariableDeclaration(grandParent)) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
return t.isProgram(path.parentPath.parentPath?.parent);
|
|
110
|
+
}
|
|
40
111
|
class ServerFnCompiler {
|
|
41
112
|
constructor(options) {
|
|
42
113
|
this.options = options;
|
|
43
114
|
this.validLookupKinds = options.lookupKinds;
|
|
44
|
-
this.hasDirectCallKinds = false;
|
|
45
|
-
this.hasRootAsCandidateKinds = false;
|
|
46
|
-
for (const kind of options.lookupKinds) {
|
|
47
|
-
const setup = LookupSetup[kind];
|
|
48
|
-
if (setup.type === "directCall") {
|
|
49
|
-
this.hasDirectCallKinds = true;
|
|
50
|
-
} else if (setup.allowRootAsCandidate) {
|
|
51
|
-
this.hasRootAsCandidateKinds = true;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
115
|
}
|
|
55
116
|
moduleCache = /* @__PURE__ */ new Map();
|
|
56
117
|
initialized = false;
|
|
57
118
|
validLookupKinds;
|
|
58
|
-
// Precomputed flags for candidate detection (avoid recomputing on each collectCandidates call)
|
|
59
|
-
hasDirectCallKinds;
|
|
60
|
-
hasRootAsCandidateKinds;
|
|
61
119
|
// Fast lookup for direct imports from known libraries (e.g., '@tanstack/react-start')
|
|
62
120
|
// Maps: libName → (exportName → Kind)
|
|
63
121
|
// This allows O(1) resolution for the common case without async resolveId calls
|
|
@@ -207,43 +265,59 @@ class ServerFnCompiler {
|
|
|
207
265
|
async compile({
|
|
208
266
|
code,
|
|
209
267
|
id,
|
|
210
|
-
isProviderFile
|
|
268
|
+
isProviderFile,
|
|
269
|
+
detectedKinds
|
|
211
270
|
}) {
|
|
212
271
|
if (!this.initialized) {
|
|
213
272
|
await this.init();
|
|
214
273
|
}
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
274
|
+
const fileKinds = detectedKinds ? new Set([...detectedKinds].filter((k) => this.validLookupKinds.has(k))) : this.validLookupKinds;
|
|
275
|
+
if (fileKinds.size === 0) {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
const checkDirectCalls = needsDirectCallDetection(fileKinds);
|
|
279
|
+
const { ast } = this.ingestModule({ code, id });
|
|
280
|
+
const candidatePaths = [];
|
|
281
|
+
const chainCallPaths = /* @__PURE__ */ new Map();
|
|
282
|
+
babel.traverse(ast, {
|
|
283
|
+
CallExpression: (path) => {
|
|
284
|
+
const node = path.node;
|
|
285
|
+
const parent = path.parent;
|
|
286
|
+
if (t.isMemberExpression(parent) && t.isCallExpression(path.parentPath.parent)) {
|
|
287
|
+
chainCallPaths.set(node, path);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
if (isMethodChainCandidate(node, fileKinds)) {
|
|
291
|
+
candidatePaths.push(path);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
if (checkDirectCalls) {
|
|
295
|
+
if (isTopLevelDirectCallCandidate(path)) {
|
|
296
|
+
candidatePaths.push(path);
|
|
297
|
+
} else if (isNestedDirectCallCandidate(node)) {
|
|
298
|
+
candidatePaths.push(path);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
if (candidatePaths.length === 0) {
|
|
218
304
|
return null;
|
|
219
305
|
}
|
|
220
306
|
const resolvedCandidates = await Promise.all(
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
kind: await this.resolveExprKind(
|
|
307
|
+
candidatePaths.map(async (path) => ({
|
|
308
|
+
path,
|
|
309
|
+
kind: await this.resolveExprKind(path.node, id)
|
|
224
310
|
}))
|
|
225
311
|
);
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
if (toRewriteMap.size === 0) {
|
|
312
|
+
const validCandidates = resolvedCandidates.filter(
|
|
313
|
+
({ kind }) => this.validLookupKinds.has(kind)
|
|
314
|
+
);
|
|
315
|
+
if (validCandidates.length === 0) {
|
|
233
316
|
return null;
|
|
234
317
|
}
|
|
235
318
|
const pathsToRewrite = [];
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
CallExpression(path) {
|
|
239
|
-
callExprPaths.set(path.node, path);
|
|
240
|
-
}
|
|
241
|
-
});
|
|
242
|
-
for (const [node, kind] of toRewriteMap) {
|
|
243
|
-
const path = callExprPaths.get(node);
|
|
244
|
-
if (!path) {
|
|
245
|
-
continue;
|
|
246
|
-
}
|
|
319
|
+
for (const { path, kind } of validCandidates) {
|
|
320
|
+
const node = path.node;
|
|
247
321
|
const methodChain = {
|
|
248
322
|
middleware: null,
|
|
249
323
|
inputValidator: null,
|
|
@@ -252,6 +326,7 @@ class ServerFnCompiler {
|
|
|
252
326
|
client: null
|
|
253
327
|
};
|
|
254
328
|
let currentNode = node;
|
|
329
|
+
let currentPath = path;
|
|
255
330
|
while (true) {
|
|
256
331
|
const callee = currentNode.callee;
|
|
257
332
|
if (!t.isMemberExpression(callee)) {
|
|
@@ -260,7 +335,6 @@ class ServerFnCompiler {
|
|
|
260
335
|
if (t.isIdentifier(callee.property)) {
|
|
261
336
|
const name = callee.property.name;
|
|
262
337
|
if (name in methodChain) {
|
|
263
|
-
const currentPath = callExprPaths.get(currentNode);
|
|
264
338
|
const args = currentPath.get("arguments");
|
|
265
339
|
const firstArgPath = Array.isArray(args) && args.length > 0 ? args[0] ?? null : null;
|
|
266
340
|
methodChain[name] = {
|
|
@@ -273,14 +347,14 @@ class ServerFnCompiler {
|
|
|
273
347
|
break;
|
|
274
348
|
}
|
|
275
349
|
currentNode = callee.object;
|
|
350
|
+
const nextPath = chainCallPaths.get(currentNode);
|
|
351
|
+
if (!nextPath) {
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
currentPath = nextPath;
|
|
276
355
|
}
|
|
277
356
|
pathsToRewrite.push({ path, kind, methodChain });
|
|
278
357
|
}
|
|
279
|
-
if (pathsToRewrite.length !== toRewriteMap.size) {
|
|
280
|
-
throw new Error(
|
|
281
|
-
`Internal error: could not find all paths to rewrite. please file an issue`
|
|
282
|
-
);
|
|
283
|
-
}
|
|
284
358
|
const refIdents = findReferencedIdentifiers(ast);
|
|
285
359
|
for (const { path, kind, methodChain } of pathsToRewrite) {
|
|
286
360
|
const candidate = { path, methodChain };
|
|
@@ -313,28 +387,6 @@ class ServerFnCompiler {
|
|
|
313
387
|
filename: id
|
|
314
388
|
});
|
|
315
389
|
}
|
|
316
|
-
// collects all candidate CallExpressions at top-level
|
|
317
|
-
collectCandidates(bindings) {
|
|
318
|
-
const candidates = [];
|
|
319
|
-
for (const binding of bindings.values()) {
|
|
320
|
-
if (binding.type === "var" && t.isCallExpression(binding.init)) {
|
|
321
|
-
const methodChainCandidate = isCandidateCallExpression(
|
|
322
|
-
binding.init,
|
|
323
|
-
this.validLookupKinds
|
|
324
|
-
);
|
|
325
|
-
if (methodChainCandidate) {
|
|
326
|
-
candidates.push(methodChainCandidate);
|
|
327
|
-
continue;
|
|
328
|
-
}
|
|
329
|
-
if (this.hasDirectCallKinds || this.hasRootAsCandidateKinds) {
|
|
330
|
-
if (t.isIdentifier(binding.init.callee) || t.isMemberExpression(binding.init.callee) && t.isIdentifier(binding.init.callee.property)) {
|
|
331
|
-
candidates.push(binding.init);
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
return candidates;
|
|
337
|
-
}
|
|
338
390
|
async resolveIdentifierKind(ident, id, visited = /* @__PURE__ */ new Set()) {
|
|
339
391
|
const info = await this.getModuleInfo(id);
|
|
340
392
|
const binding = info.bindings.get(ident);
|
|
@@ -542,23 +594,25 @@ class ServerFnCompiler {
|
|
|
542
594
|
return cached;
|
|
543
595
|
}
|
|
544
596
|
}
|
|
545
|
-
function
|
|
546
|
-
if (!t.isCallExpression(node)) return void 0;
|
|
597
|
+
function isMethodChainCandidate(node, lookupKinds) {
|
|
547
598
|
const callee = node.callee;
|
|
548
599
|
if (!t.isMemberExpression(callee) || !t.isIdentifier(callee.property)) {
|
|
549
|
-
return
|
|
600
|
+
return false;
|
|
550
601
|
}
|
|
551
602
|
const possibleKinds = IdentifierToKinds.get(callee.property.name);
|
|
552
603
|
if (possibleKinds) {
|
|
553
604
|
for (const kind of possibleKinds) {
|
|
554
605
|
if (lookupKinds.has(kind)) {
|
|
555
|
-
return
|
|
606
|
+
return true;
|
|
556
607
|
}
|
|
557
608
|
}
|
|
558
609
|
}
|
|
559
|
-
return
|
|
610
|
+
return false;
|
|
560
611
|
}
|
|
561
612
|
export {
|
|
562
|
-
|
|
613
|
+
KindDetectionPatterns,
|
|
614
|
+
LookupKindsPerEnv,
|
|
615
|
+
ServerFnCompiler,
|
|
616
|
+
detectKindsInCode
|
|
563
617
|
};
|
|
564
618
|
//# sourceMappingURL=compiler.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compiler.js","sources":["../../../src/create-server-fn-plugin/compiler.ts"],"sourcesContent":["/* eslint-disable import/no-commonjs */\nimport * as t from '@babel/types'\nimport { generateFromAst, parseAst } from '@tanstack/router-utils'\nimport babel from '@babel/core'\nimport {\n deadCodeElimination,\n findReferencedIdentifiers,\n} from 'babel-dead-code-elimination'\nimport { handleCreateServerFn } from './handleCreateServerFn'\nimport { handleCreateMiddleware } from './handleCreateMiddleware'\nimport { handleCreateIsomorphicFn } from './handleCreateIsomorphicFn'\nimport { handleEnvOnlyFn } from './handleEnvOnly'\nimport type { MethodChainPaths, RewriteCandidate } from './types'\n\ntype Binding =\n | {\n type: 'import'\n source: string\n importedName: string\n resolvedKind?: Kind\n }\n | {\n type: 'var'\n init: t.Expression | null\n resolvedKind?: Kind\n }\n\ntype ExportEntry =\n | { tag: 'Normal'; name: string }\n | { tag: 'Default'; name: string }\n | { tag: 'Namespace'; name: string; targetId: string } // for `export * as ns from './x'`\n\ntype Kind = 'None' | `Root` | `Builder` | LookupKind\n\nexport type LookupKind =\n | 'ServerFn'\n | 'Middleware'\n | 'IsomorphicFn'\n | 'ServerOnlyFn'\n | 'ClientOnlyFn'\n\n// Detection strategy for each kind\ntype MethodChainSetup = {\n type: 'methodChain'\n candidateCallIdentifier: Set<string>\n // If true, a call to the root function (e.g., createIsomorphicFn()) is also a candidate\n // even without chained method calls. This is used for IsomorphicFn which can be\n // called without .client() or .server() (resulting in a no-op function).\n allowRootAsCandidate?: boolean\n}\ntype DirectCallSetup = { type: 'directCall' }\n\nconst LookupSetup: Record<LookupKind, MethodChainSetup | DirectCallSetup> = {\n ServerFn: {\n type: 'methodChain',\n candidateCallIdentifier: new Set(['handler']),\n },\n Middleware: {\n type: 'methodChain',\n candidateCallIdentifier: new Set(['server', 'client', 'createMiddlewares']),\n },\n IsomorphicFn: {\n type: 'methodChain',\n candidateCallIdentifier: new Set(['server', 'client']),\n allowRootAsCandidate: true, // createIsomorphicFn() alone is valid (returns no-op)\n },\n ServerOnlyFn: { type: 'directCall' },\n ClientOnlyFn: { type: 'directCall' },\n}\n\n// Pre-computed map: identifier name -> Set<LookupKind> for fast candidate detection (method chain only)\n// Multiple kinds can share the same identifier (e.g., 'server' and 'client' are used by both Middleware and IsomorphicFn)\nconst IdentifierToKinds = new Map<string, Set<LookupKind>>()\nfor (const [kind, setup] of Object.entries(LookupSetup) as Array<\n [LookupKind, MethodChainSetup | DirectCallSetup]\n>) {\n if (setup.type === 'methodChain') {\n for (const id of setup.candidateCallIdentifier) {\n let kinds = IdentifierToKinds.get(id)\n if (!kinds) {\n kinds = new Set()\n IdentifierToKinds.set(id, kinds)\n }\n kinds.add(kind)\n }\n }\n}\n\nexport type LookupConfig = {\n libName: string\n rootExport: string\n kind: LookupKind | 'Root' // 'Root' for builder pattern, LookupKind for direct call\n}\n\ninterface ModuleInfo {\n id: string\n bindings: Map<string, Binding>\n exports: Map<string, ExportEntry>\n // Track `export * from './module'` declarations for re-export resolution\n reExportAllSources: Array<string>\n}\n\nexport class ServerFnCompiler {\n private moduleCache = new Map<string, ModuleInfo>()\n private initialized = false\n private validLookupKinds: Set<LookupKind>\n // Precomputed flags for candidate detection (avoid recomputing on each collectCandidates call)\n private hasDirectCallKinds: boolean\n private hasRootAsCandidateKinds: boolean\n // Fast lookup for direct imports from known libraries (e.g., '@tanstack/react-start')\n // Maps: libName → (exportName → Kind)\n // This allows O(1) resolution for the common case without async resolveId calls\n private knownRootImports = new Map<string, Map<string, Kind>>()\n constructor(\n private options: {\n env: 'client' | 'server'\n directive: string\n lookupConfigurations: Array<LookupConfig>\n lookupKinds: Set<LookupKind>\n loadModule: (id: string) => Promise<void>\n resolveId: (id: string, importer?: string) => Promise<string | null>\n },\n ) {\n this.validLookupKinds = options.lookupKinds\n\n // Precompute flags for candidate detection\n this.hasDirectCallKinds = false\n this.hasRootAsCandidateKinds = false\n for (const kind of options.lookupKinds) {\n const setup = LookupSetup[kind]\n if (setup.type === 'directCall') {\n this.hasDirectCallKinds = true\n } else if (setup.allowRootAsCandidate) {\n this.hasRootAsCandidateKinds = true\n }\n }\n }\n\n private async init() {\n // Register internal stub package exports for recognition.\n // These don't need module resolution - only the knownRootImports fast path.\n this.knownRootImports.set(\n '@tanstack/start-fn-stubs',\n new Map<string, Kind>([\n ['createIsomorphicFn', 'IsomorphicFn'],\n ['createServerOnlyFn', 'ServerOnlyFn'],\n ['createClientOnlyFn', 'ClientOnlyFn'],\n ]),\n )\n\n await Promise.all(\n this.options.lookupConfigurations.map(async (config) => {\n // Populate the fast lookup map for direct imports (by package name)\n // This allows O(1) recognition of imports from known packages.\n let libExports = this.knownRootImports.get(config.libName)\n if (!libExports) {\n libExports = new Map()\n this.knownRootImports.set(config.libName, libExports)\n }\n libExports.set(config.rootExport, config.kind)\n\n const libId = await this.options.resolveId(config.libName)\n if (!libId) {\n throw new Error(`could not resolve \"${config.libName}\"`)\n }\n let rootModule = this.moduleCache.get(libId)\n if (!rootModule) {\n // insert root binding\n rootModule = {\n bindings: new Map(),\n exports: new Map(),\n id: libId,\n reExportAllSources: [],\n }\n this.moduleCache.set(libId, rootModule)\n }\n\n rootModule.exports.set(config.rootExport, {\n tag: 'Normal',\n name: config.rootExport,\n })\n rootModule.exports.set('*', {\n tag: 'Namespace',\n name: config.rootExport,\n targetId: libId,\n })\n rootModule.bindings.set(config.rootExport, {\n type: 'var',\n init: null, // Not needed since resolvedKind is set\n resolvedKind: config.kind satisfies Kind,\n })\n this.moduleCache.set(libId, rootModule)\n }),\n )\n\n this.initialized = true\n }\n\n public ingestModule({ code, id }: { code: string; id: string }) {\n const ast = parseAst({ code })\n\n const bindings = new Map<string, Binding>()\n const exports = new Map<string, ExportEntry>()\n const reExportAllSources: Array<string> = []\n\n // we are only interested in top-level bindings, hence we don't traverse the AST\n // instead we only iterate over the program body\n for (const node of ast.program.body) {\n if (t.isImportDeclaration(node)) {\n const source = node.source.value\n for (const s of node.specifiers) {\n if (t.isImportSpecifier(s)) {\n const importedName = t.isIdentifier(s.imported)\n ? s.imported.name\n : s.imported.value\n bindings.set(s.local.name, { type: 'import', source, importedName })\n } else if (t.isImportDefaultSpecifier(s)) {\n bindings.set(s.local.name, {\n type: 'import',\n source,\n importedName: 'default',\n })\n } else if (t.isImportNamespaceSpecifier(s)) {\n bindings.set(s.local.name, {\n type: 'import',\n source,\n importedName: '*',\n })\n }\n }\n } else if (t.isVariableDeclaration(node)) {\n for (const decl of node.declarations) {\n if (t.isIdentifier(decl.id)) {\n bindings.set(decl.id.name, {\n type: 'var',\n init: decl.init ?? null,\n })\n }\n }\n } else if (t.isExportNamedDeclaration(node)) {\n // export const foo = ...\n if (node.declaration) {\n if (t.isVariableDeclaration(node.declaration)) {\n for (const d of node.declaration.declarations) {\n if (t.isIdentifier(d.id)) {\n exports.set(d.id.name, { tag: 'Normal', name: d.id.name })\n bindings.set(d.id.name, { type: 'var', init: d.init ?? null })\n }\n }\n }\n }\n for (const sp of node.specifiers) {\n if (t.isExportNamespaceSpecifier(sp)) {\n exports.set(sp.exported.name, {\n tag: 'Namespace',\n name: sp.exported.name,\n targetId: node.source?.value || '',\n })\n }\n // export { local as exported }\n else if (t.isExportSpecifier(sp)) {\n const local = sp.local.name\n const exported = t.isIdentifier(sp.exported)\n ? sp.exported.name\n : sp.exported.value\n exports.set(exported, { tag: 'Normal', name: local })\n\n // When re-exporting from another module (export { foo } from './module'),\n // create an import binding so the server function can be resolved\n if (node.source) {\n bindings.set(local, {\n type: 'import',\n source: node.source.value,\n importedName: local,\n })\n }\n }\n }\n } else if (t.isExportDefaultDeclaration(node)) {\n const d = node.declaration\n if (t.isIdentifier(d)) {\n exports.set('default', { tag: 'Default', name: d.name })\n } else {\n const synth = '__default_export__'\n bindings.set(synth, { type: 'var', init: d as t.Expression })\n exports.set('default', { tag: 'Default', name: synth })\n }\n } else if (t.isExportAllDeclaration(node)) {\n // Handle `export * from './module'` syntax\n // Track the source so we can look up exports from it when needed\n reExportAllSources.push(node.source.value)\n }\n }\n\n const info: ModuleInfo = {\n id,\n bindings,\n exports,\n reExportAllSources,\n }\n this.moduleCache.set(id, info)\n return { info, ast }\n }\n\n public invalidateModule(id: string) {\n return this.moduleCache.delete(id)\n }\n\n public async compile({\n code,\n id,\n isProviderFile,\n }: {\n code: string\n id: string\n isProviderFile: boolean\n }) {\n if (!this.initialized) {\n await this.init()\n }\n const { info, ast } = this.ingestModule({ code, id })\n const candidates = this.collectCandidates(info.bindings)\n if (candidates.length === 0) {\n // this hook will only be invoked if there is `.handler(` | `.server(` | `.client(` in the code,\n // so not discovering a handler candidate is rather unlikely, but maybe possible?\n return null\n }\n\n // let's find out which of the candidates are actually server functions\n // Resolve all candidates in parallel for better performance\n const resolvedCandidates = await Promise.all(\n candidates.map(async (candidate) => ({\n candidate,\n kind: await this.resolveExprKind(candidate, id),\n })),\n )\n\n // Map from candidate/root node -> kind\n // Note: For top-level variable declarations, candidate === root (the outermost CallExpression)\n const toRewriteMap = new Map<t.CallExpression, LookupKind>()\n for (const { candidate, kind } of resolvedCandidates) {\n if (this.validLookupKinds.has(kind as LookupKind)) {\n toRewriteMap.set(candidate, kind as LookupKind)\n }\n }\n if (toRewriteMap.size === 0) {\n return null\n }\n\n // Single-pass traversal to find NodePaths and collect method chains\n const pathsToRewrite: Array<{\n path: babel.NodePath<t.CallExpression>\n kind: LookupKind\n methodChain: MethodChainPaths\n }> = []\n\n // First, collect all CallExpression paths in the AST for O(1) lookup\n const callExprPaths = new Map<\n t.CallExpression,\n babel.NodePath<t.CallExpression>\n >()\n\n babel.traverse(ast, {\n CallExpression(path) {\n callExprPaths.set(path.node, path)\n },\n })\n\n // Now process candidates - we can look up any CallExpression path in O(1)\n for (const [node, kind] of toRewriteMap) {\n const path = callExprPaths.get(node)\n if (!path) {\n continue\n }\n\n // Collect method chain paths by walking DOWN from root through the chain\n const methodChain: MethodChainPaths = {\n middleware: null,\n inputValidator: null,\n handler: null,\n server: null,\n client: null,\n }\n\n // Walk down the call chain using nodes, look up paths from map\n let currentNode: t.CallExpression = node\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n while (true) {\n const callee = currentNode.callee\n if (!t.isMemberExpression(callee)) {\n break\n }\n\n // Record method chain path if it's a known method\n if (t.isIdentifier(callee.property)) {\n const name = callee.property.name as keyof MethodChainPaths\n if (name in methodChain) {\n const currentPath = callExprPaths.get(currentNode)!\n // Get first argument path\n const args = currentPath.get('arguments')\n const firstArgPath =\n Array.isArray(args) && args.length > 0 ? (args[0] ?? null) : null\n methodChain[name] = {\n callPath: currentPath,\n firstArgPath,\n }\n }\n }\n\n // Move to the inner call (the object of the member expression)\n if (!t.isCallExpression(callee.object)) {\n break\n }\n currentNode = callee.object\n }\n\n pathsToRewrite.push({ path, kind, methodChain })\n }\n\n // Verify we found all candidates (pathsToRewrite should have same size as toRewriteMap had)\n if (pathsToRewrite.length !== toRewriteMap.size) {\n throw new Error(\n `Internal error: could not find all paths to rewrite. please file an issue`,\n )\n }\n\n const refIdents = findReferencedIdentifiers(ast)\n\n for (const { path, kind, methodChain } of pathsToRewrite) {\n const candidate: RewriteCandidate = { path, methodChain }\n if (kind === 'ServerFn') {\n handleCreateServerFn(candidate, {\n env: this.options.env,\n code,\n directive: this.options.directive,\n isProviderFile,\n })\n } else if (kind === 'Middleware') {\n handleCreateMiddleware(candidate, {\n env: this.options.env,\n })\n } else if (kind === 'IsomorphicFn') {\n handleCreateIsomorphicFn(candidate, {\n env: this.options.env,\n })\n } else {\n // ServerOnlyFn or ClientOnlyFn\n handleEnvOnlyFn(candidate, {\n env: this.options.env,\n kind,\n })\n }\n }\n\n deadCodeElimination(ast, refIdents)\n\n return generateFromAst(ast, {\n sourceMaps: true,\n sourceFileName: id,\n filename: id,\n })\n }\n\n // collects all candidate CallExpressions at top-level\n private collectCandidates(bindings: Map<string, Binding>) {\n const candidates: Array<t.CallExpression> = []\n\n for (const binding of bindings.values()) {\n if (binding.type === 'var' && t.isCallExpression(binding.init)) {\n // Pattern 1: Method chain pattern (.handler(), .server(), etc.)\n const methodChainCandidate = isCandidateCallExpression(\n binding.init,\n this.validLookupKinds,\n )\n if (methodChainCandidate) {\n candidates.push(methodChainCandidate)\n continue\n }\n\n // Pattern 2: Direct call pattern\n // Handles:\n // - createServerOnlyFn(), createClientOnlyFn() (direct call kinds)\n // - createIsomorphicFn() (root-as-candidate kinds)\n // - TanStackStart.createServerOnlyFn() (namespace calls)\n if (this.hasDirectCallKinds || this.hasRootAsCandidateKinds) {\n if (\n t.isIdentifier(binding.init.callee) ||\n (t.isMemberExpression(binding.init.callee) &&\n t.isIdentifier(binding.init.callee.property))\n ) {\n // Include as candidate - kind resolution will verify it's actually a known export\n candidates.push(binding.init)\n }\n }\n }\n }\n return candidates\n }\n\n private async resolveIdentifierKind(\n ident: string,\n id: string,\n visited = new Set<string>(),\n ): Promise<Kind> {\n const info = await this.getModuleInfo(id)\n\n const binding = info.bindings.get(ident)\n if (!binding) {\n return 'None'\n }\n if (binding.resolvedKind) {\n return binding.resolvedKind\n }\n\n // TODO improve cycle detection? should we throw here instead of returning 'None'?\n // prevent cycles\n const vKey = `${id}:${ident}`\n if (visited.has(vKey)) {\n return 'None'\n }\n visited.add(vKey)\n\n const resolvedKind = await this.resolveBindingKind(binding, id, visited)\n binding.resolvedKind = resolvedKind\n return resolvedKind\n }\n\n /**\n * Recursively find an export in a module, following `export * from` chains.\n * Returns the module info and binding if found, or undefined if not found.\n */\n private async findExportInModule(\n moduleInfo: ModuleInfo,\n exportName: string,\n visitedModules = new Set<string>(),\n ): Promise<{ moduleInfo: ModuleInfo; binding: Binding } | undefined> {\n // Prevent infinite loops in circular re-exports\n if (visitedModules.has(moduleInfo.id)) {\n return undefined\n }\n visitedModules.add(moduleInfo.id)\n\n // First check direct exports\n const directExport = moduleInfo.exports.get(exportName)\n if (directExport) {\n const binding = moduleInfo.bindings.get(directExport.name)\n if (binding) {\n return { moduleInfo, binding }\n }\n }\n\n // If not found, recursively check re-export-all sources in parallel\n // Valid code won't have duplicate exports across chains, so first match wins\n if (moduleInfo.reExportAllSources.length > 0) {\n const results = await Promise.all(\n moduleInfo.reExportAllSources.map(async (reExportSource) => {\n const reExportTarget = await this.options.resolveId(\n reExportSource,\n moduleInfo.id,\n )\n if (reExportTarget) {\n const reExportModule = await this.getModuleInfo(reExportTarget)\n return this.findExportInModule(\n reExportModule,\n exportName,\n visitedModules,\n )\n }\n return undefined\n }),\n )\n // Return the first valid result\n for (const result of results) {\n if (result) {\n return result\n }\n }\n }\n\n return undefined\n }\n\n private async resolveBindingKind(\n binding: Binding,\n fileId: string,\n visited = new Set<string>(),\n ): Promise<Kind> {\n if (binding.resolvedKind) {\n return binding.resolvedKind\n }\n if (binding.type === 'import') {\n // Fast path: check if this is a direct import from a known library\n // (e.g., import { createServerFn } from '@tanstack/react-start')\n // This avoids async resolveId calls for the common case\n const knownExports = this.knownRootImports.get(binding.source)\n if (knownExports) {\n const kind = knownExports.get(binding.importedName)\n if (kind) {\n binding.resolvedKind = kind\n return kind\n }\n }\n\n // Slow path: resolve through the module graph\n const target = await this.options.resolveId(binding.source, fileId)\n if (!target) {\n return 'None'\n }\n\n const importedModule = await this.getModuleInfo(target)\n\n // Find the export, recursively searching through export * from chains\n const found = await this.findExportInModule(\n importedModule,\n binding.importedName,\n )\n\n if (!found) {\n return 'None'\n }\n\n const { moduleInfo: foundModule, binding: foundBinding } = found\n\n if (foundBinding.resolvedKind) {\n return foundBinding.resolvedKind\n }\n\n const resolvedKind = await this.resolveBindingKind(\n foundBinding,\n foundModule.id,\n visited,\n )\n foundBinding.resolvedKind = resolvedKind\n return resolvedKind\n }\n\n const resolvedKind = await this.resolveExprKind(\n binding.init,\n fileId,\n visited,\n )\n binding.resolvedKind = resolvedKind\n return resolvedKind\n }\n\n private async resolveExprKind(\n expr: t.Expression | null,\n fileId: string,\n visited = new Set<string>(),\n ): Promise<Kind> {\n if (!expr) {\n return 'None'\n }\n\n // Unwrap common TypeScript/parenthesized wrappers first for efficiency\n while (\n t.isTSAsExpression(expr) ||\n t.isTSNonNullExpression(expr) ||\n t.isParenthesizedExpression(expr)\n ) {\n expr = expr.expression\n }\n\n let result: Kind = 'None'\n\n if (t.isCallExpression(expr)) {\n if (!t.isExpression(expr.callee)) {\n return 'None'\n }\n const calleeKind = await this.resolveCalleeKind(\n expr.callee,\n fileId,\n visited,\n )\n if (calleeKind === 'Root' || calleeKind === 'Builder') {\n return 'Builder'\n }\n // Use direct Set.has() instead of iterating\n if (this.validLookupKinds.has(calleeKind as LookupKind)) {\n return calleeKind\n }\n } else if (t.isMemberExpression(expr) && t.isIdentifier(expr.property)) {\n result = await this.resolveCalleeKind(expr.object, fileId, visited)\n }\n\n if (result === 'None' && t.isIdentifier(expr)) {\n result = await this.resolveIdentifierKind(expr.name, fileId, visited)\n }\n\n return result\n }\n\n private async resolveCalleeKind(\n callee: t.Expression,\n fileId: string,\n visited = new Set<string>(),\n ): Promise<Kind> {\n if (t.isIdentifier(callee)) {\n return this.resolveIdentifierKind(callee.name, fileId, visited)\n }\n\n if (t.isMemberExpression(callee) && t.isIdentifier(callee.property)) {\n const prop = callee.property.name\n\n // Check if this property matches any method chain pattern\n const possibleKinds = IdentifierToKinds.get(prop)\n if (possibleKinds) {\n // Resolve base expression ONCE and reuse for all pattern checks\n const base = await this.resolveExprKind(callee.object, fileId, visited)\n\n // Check each possible kind that uses this identifier\n for (const kind of possibleKinds) {\n if (!this.validLookupKinds.has(kind)) continue\n\n if (kind === 'ServerFn') {\n if (base === 'Root' || base === 'Builder') {\n return 'ServerFn'\n }\n } else if (kind === 'Middleware') {\n if (\n base === 'Root' ||\n base === 'Builder' ||\n base === 'Middleware'\n ) {\n return 'Middleware'\n }\n } else if (kind === 'IsomorphicFn') {\n if (\n base === 'Root' ||\n base === 'Builder' ||\n base === 'IsomorphicFn'\n ) {\n return 'IsomorphicFn'\n }\n }\n }\n }\n\n // Check if the object is a namespace import\n if (t.isIdentifier(callee.object)) {\n const info = await this.getModuleInfo(fileId)\n const binding = info.bindings.get(callee.object.name)\n if (\n binding &&\n binding.type === 'import' &&\n binding.importedName === '*'\n ) {\n // resolve the property from the target module\n const targetModuleId = await this.options.resolveId(\n binding.source,\n fileId,\n )\n if (targetModuleId) {\n const targetModule = await this.getModuleInfo(targetModuleId)\n const exportEntry = targetModule.exports.get(callee.property.name)\n if (exportEntry) {\n const exportedBinding = targetModule.bindings.get(\n exportEntry.name,\n )\n if (exportedBinding) {\n return await this.resolveBindingKind(\n exportedBinding,\n targetModule.id,\n visited,\n )\n }\n }\n } else {\n return 'None'\n }\n }\n }\n return this.resolveExprKind(callee.object, fileId, visited)\n }\n\n // handle nested expressions\n return this.resolveExprKind(callee, fileId, visited)\n }\n\n private async getModuleInfo(id: string) {\n let cached = this.moduleCache.get(id)\n if (cached) {\n return cached\n }\n\n await this.options.loadModule(id)\n\n cached = this.moduleCache.get(id)\n if (!cached) {\n throw new Error(`could not load module info for ${id}`)\n }\n return cached\n }\n}\n\nfunction isCandidateCallExpression(\n node: t.Node | null | undefined,\n lookupKinds: Set<LookupKind>,\n): t.CallExpression | undefined {\n if (!t.isCallExpression(node)) return undefined\n\n const callee = node.callee\n if (!t.isMemberExpression(callee) || !t.isIdentifier(callee.property)) {\n return undefined\n }\n\n // Use pre-computed map for O(1) lookup\n // IdentifierToKinds maps identifier -> Set<LookupKind> to handle shared identifiers\n const possibleKinds = IdentifierToKinds.get(callee.property.name)\n if (possibleKinds) {\n // Check if any of the possible kinds are in the valid lookup kinds\n for (const kind of possibleKinds) {\n if (lookupKinds.has(kind)) {\n return node\n }\n }\n }\n\n return undefined\n}\n"],"names":["resolvedKind"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"compiler.js","sources":["../../../src/create-server-fn-plugin/compiler.ts"],"sourcesContent":["/* eslint-disable import/no-commonjs */\nimport * as t from '@babel/types'\nimport { generateFromAst, parseAst } from '@tanstack/router-utils'\nimport babel from '@babel/core'\nimport {\n deadCodeElimination,\n findReferencedIdentifiers,\n} from 'babel-dead-code-elimination'\nimport { handleCreateServerFn } from './handleCreateServerFn'\nimport { handleCreateMiddleware } from './handleCreateMiddleware'\nimport { handleCreateIsomorphicFn } from './handleCreateIsomorphicFn'\nimport { handleEnvOnlyFn } from './handleEnvOnly'\nimport type { MethodChainPaths, RewriteCandidate } from './types'\n\ntype Binding =\n | {\n type: 'import'\n source: string\n importedName: string\n resolvedKind?: Kind\n }\n | {\n type: 'var'\n init: t.Expression | null\n resolvedKind?: Kind\n }\n\ntype ExportEntry =\n | { tag: 'Normal'; name: string }\n | { tag: 'Default'; name: string }\n | { tag: 'Namespace'; name: string; targetId: string } // for `export * as ns from './x'`\n\ntype Kind = 'None' | `Root` | `Builder` | LookupKind\n\nexport type LookupKind =\n | 'ServerFn'\n | 'Middleware'\n | 'IsomorphicFn'\n | 'ServerOnlyFn'\n | 'ClientOnlyFn'\n\n// Detection strategy for each kind\ntype MethodChainSetup = {\n type: 'methodChain'\n candidateCallIdentifier: Set<string>\n // If true, a call to the root function (e.g., createIsomorphicFn()) is also a candidate\n // even without chained method calls. This is used for IsomorphicFn which can be\n // called without .client() or .server() (resulting in a no-op function).\n allowRootAsCandidate?: boolean\n}\ntype DirectCallSetup = { type: 'directCall' }\n\nconst LookupSetup: Record<LookupKind, MethodChainSetup | DirectCallSetup> = {\n ServerFn: {\n type: 'methodChain',\n candidateCallIdentifier: new Set(['handler']),\n },\n Middleware: {\n type: 'methodChain',\n candidateCallIdentifier: new Set(['server', 'client', 'createMiddlewares']),\n },\n IsomorphicFn: {\n type: 'methodChain',\n candidateCallIdentifier: new Set(['server', 'client']),\n allowRootAsCandidate: true, // createIsomorphicFn() alone is valid (returns no-op)\n },\n ServerOnlyFn: { type: 'directCall' },\n ClientOnlyFn: { type: 'directCall' },\n}\n\n// Single source of truth for detecting which kinds are present in code\n// These patterns are used for:\n// 1. Pre-scanning code to determine which kinds to look for (before AST parsing)\n// 2. Deriving the plugin's transform code filter\nexport const KindDetectionPatterns: Record<LookupKind, RegExp> = {\n ServerFn: /\\.handler\\s*\\(/,\n Middleware: /createMiddleware/,\n IsomorphicFn: /createIsomorphicFn/,\n ServerOnlyFn: /createServerOnlyFn/,\n ClientOnlyFn: /createClientOnlyFn/,\n}\n\n// Which kinds are valid for each environment\nexport const LookupKindsPerEnv: Record<'client' | 'server', Set<LookupKind>> = {\n client: new Set([\n 'Middleware',\n 'ServerFn',\n 'IsomorphicFn',\n 'ServerOnlyFn',\n 'ClientOnlyFn',\n ] as const),\n server: new Set([\n 'ServerFn',\n 'IsomorphicFn',\n 'ServerOnlyFn',\n 'ClientOnlyFn',\n ] as const),\n}\n\n/**\n * Detects which LookupKinds are present in the code using string matching.\n * This is a fast pre-scan before AST parsing to limit the work done during compilation.\n */\nexport function detectKindsInCode(\n code: string,\n env: 'client' | 'server',\n): Set<LookupKind> {\n const detected = new Set<LookupKind>()\n const validForEnv = LookupKindsPerEnv[env]\n\n for (const [kind, pattern] of Object.entries(KindDetectionPatterns) as Array<\n [LookupKind, RegExp]\n >) {\n if (validForEnv.has(kind) && pattern.test(code)) {\n detected.add(kind)\n }\n }\n\n return detected\n}\n\n// Pre-computed map: identifier name -> Set<LookupKind> for fast candidate detection (method chain only)\n// Multiple kinds can share the same identifier (e.g., 'server' and 'client' are used by both Middleware and IsomorphicFn)\nconst IdentifierToKinds = new Map<string, Set<LookupKind>>()\nfor (const [kind, setup] of Object.entries(LookupSetup) as Array<\n [LookupKind, MethodChainSetup | DirectCallSetup]\n>) {\n if (setup.type === 'methodChain') {\n for (const id of setup.candidateCallIdentifier) {\n let kinds = IdentifierToKinds.get(id)\n if (!kinds) {\n kinds = new Set()\n IdentifierToKinds.set(id, kinds)\n }\n kinds.add(kind)\n }\n }\n}\n\n// Known factory function names for direct call and root-as-candidate patterns\n// These are the names that, when called directly, create a new function.\n// Used to filter nested candidates - we only want to include actual factory calls,\n// not invocations of already-created functions (e.g., `myServerFn()` should NOT be a candidate)\nconst DirectCallFactoryNames = new Set([\n 'createServerOnlyFn',\n 'createClientOnlyFn',\n 'createIsomorphicFn',\n])\n\nexport type LookupConfig = {\n libName: string\n rootExport: string\n kind: LookupKind | 'Root' // 'Root' for builder pattern, LookupKind for direct call\n}\n\ninterface ModuleInfo {\n id: string\n bindings: Map<string, Binding>\n exports: Map<string, ExportEntry>\n // Track `export * from './module'` declarations for re-export resolution\n reExportAllSources: Array<string>\n}\n\n/**\n * Computes whether any file kinds need direct-call candidate detection.\n * This includes both directCall types (ServerOnlyFn, ClientOnlyFn) and\n * allowRootAsCandidate types (IsomorphicFn).\n */\nfunction needsDirectCallDetection(kinds: Set<LookupKind>): boolean {\n for (const kind of kinds) {\n const setup = LookupSetup[kind]\n if (setup.type === 'directCall' || setup.allowRootAsCandidate) {\n return true\n }\n }\n return false\n}\n\n/**\n * Checks if a CallExpression is a direct-call candidate for NESTED detection.\n * Returns true if the callee is a known factory function name.\n * This is stricter than top-level detection because we need to filter out\n * invocations of existing server functions (e.g., `myServerFn()`).\n */\nfunction isNestedDirectCallCandidate(node: t.CallExpression): boolean {\n let calleeName: string | undefined\n if (t.isIdentifier(node.callee)) {\n calleeName = node.callee.name\n } else if (\n t.isMemberExpression(node.callee) &&\n t.isIdentifier(node.callee.property)\n ) {\n calleeName = node.callee.property.name\n }\n return calleeName !== undefined && DirectCallFactoryNames.has(calleeName)\n}\n\n/**\n * Checks if a CallExpression path is a top-level direct-call candidate.\n * Top-level means the call is the init of a VariableDeclarator at program level.\n * We accept any simple identifier call or namespace call at top level\n * (e.g., `isomorphicFn()`, `TanStackStart.createServerOnlyFn()`) and let\n * resolution verify it. This handles renamed imports.\n */\nfunction isTopLevelDirectCallCandidate(\n path: babel.NodePath<t.CallExpression>,\n): boolean {\n const node = path.node\n\n // Must be a simple identifier call or namespace call\n const isSimpleCall =\n t.isIdentifier(node.callee) ||\n (t.isMemberExpression(node.callee) &&\n t.isIdentifier(node.callee.object) &&\n t.isIdentifier(node.callee.property))\n\n if (!isSimpleCall) {\n return false\n }\n\n // Must be top-level: VariableDeclarator -> VariableDeclaration -> Program\n const parent = path.parent\n if (!t.isVariableDeclarator(parent) || parent.init !== node) {\n return false\n }\n const grandParent = path.parentPath.parent\n if (!t.isVariableDeclaration(grandParent)) {\n return false\n }\n return t.isProgram(path.parentPath.parentPath?.parent)\n}\n\nexport class ServerFnCompiler {\n private moduleCache = new Map<string, ModuleInfo>()\n private initialized = false\n private validLookupKinds: Set<LookupKind>\n // Fast lookup for direct imports from known libraries (e.g., '@tanstack/react-start')\n // Maps: libName → (exportName → Kind)\n // This allows O(1) resolution for the common case without async resolveId calls\n private knownRootImports = new Map<string, Map<string, Kind>>()\n constructor(\n private options: {\n env: 'client' | 'server'\n directive: string\n lookupConfigurations: Array<LookupConfig>\n lookupKinds: Set<LookupKind>\n loadModule: (id: string) => Promise<void>\n resolveId: (id: string, importer?: string) => Promise<string | null>\n },\n ) {\n this.validLookupKinds = options.lookupKinds\n }\n\n private async init() {\n // Register internal stub package exports for recognition.\n // These don't need module resolution - only the knownRootImports fast path.\n this.knownRootImports.set(\n '@tanstack/start-fn-stubs',\n new Map<string, Kind>([\n ['createIsomorphicFn', 'IsomorphicFn'],\n ['createServerOnlyFn', 'ServerOnlyFn'],\n ['createClientOnlyFn', 'ClientOnlyFn'],\n ]),\n )\n\n await Promise.all(\n this.options.lookupConfigurations.map(async (config) => {\n // Populate the fast lookup map for direct imports (by package name)\n // This allows O(1) recognition of imports from known packages.\n let libExports = this.knownRootImports.get(config.libName)\n if (!libExports) {\n libExports = new Map()\n this.knownRootImports.set(config.libName, libExports)\n }\n libExports.set(config.rootExport, config.kind)\n\n const libId = await this.options.resolveId(config.libName)\n if (!libId) {\n throw new Error(`could not resolve \"${config.libName}\"`)\n }\n let rootModule = this.moduleCache.get(libId)\n if (!rootModule) {\n // insert root binding\n rootModule = {\n bindings: new Map(),\n exports: new Map(),\n id: libId,\n reExportAllSources: [],\n }\n this.moduleCache.set(libId, rootModule)\n }\n\n rootModule.exports.set(config.rootExport, {\n tag: 'Normal',\n name: config.rootExport,\n })\n rootModule.exports.set('*', {\n tag: 'Namespace',\n name: config.rootExport,\n targetId: libId,\n })\n rootModule.bindings.set(config.rootExport, {\n type: 'var',\n init: null, // Not needed since resolvedKind is set\n resolvedKind: config.kind satisfies Kind,\n })\n this.moduleCache.set(libId, rootModule)\n }),\n )\n\n this.initialized = true\n }\n\n public ingestModule({ code, id }: { code: string; id: string }) {\n const ast = parseAst({ code })\n\n const bindings = new Map<string, Binding>()\n const exports = new Map<string, ExportEntry>()\n const reExportAllSources: Array<string> = []\n\n // we are only interested in top-level bindings, hence we don't traverse the AST\n // instead we only iterate over the program body\n for (const node of ast.program.body) {\n if (t.isImportDeclaration(node)) {\n const source = node.source.value\n for (const s of node.specifiers) {\n if (t.isImportSpecifier(s)) {\n const importedName = t.isIdentifier(s.imported)\n ? s.imported.name\n : s.imported.value\n bindings.set(s.local.name, { type: 'import', source, importedName })\n } else if (t.isImportDefaultSpecifier(s)) {\n bindings.set(s.local.name, {\n type: 'import',\n source,\n importedName: 'default',\n })\n } else if (t.isImportNamespaceSpecifier(s)) {\n bindings.set(s.local.name, {\n type: 'import',\n source,\n importedName: '*',\n })\n }\n }\n } else if (t.isVariableDeclaration(node)) {\n for (const decl of node.declarations) {\n if (t.isIdentifier(decl.id)) {\n bindings.set(decl.id.name, {\n type: 'var',\n init: decl.init ?? null,\n })\n }\n }\n } else if (t.isExportNamedDeclaration(node)) {\n // export const foo = ...\n if (node.declaration) {\n if (t.isVariableDeclaration(node.declaration)) {\n for (const d of node.declaration.declarations) {\n if (t.isIdentifier(d.id)) {\n exports.set(d.id.name, { tag: 'Normal', name: d.id.name })\n bindings.set(d.id.name, { type: 'var', init: d.init ?? null })\n }\n }\n }\n }\n for (const sp of node.specifiers) {\n if (t.isExportNamespaceSpecifier(sp)) {\n exports.set(sp.exported.name, {\n tag: 'Namespace',\n name: sp.exported.name,\n targetId: node.source?.value || '',\n })\n }\n // export { local as exported }\n else if (t.isExportSpecifier(sp)) {\n const local = sp.local.name\n const exported = t.isIdentifier(sp.exported)\n ? sp.exported.name\n : sp.exported.value\n exports.set(exported, { tag: 'Normal', name: local })\n\n // When re-exporting from another module (export { foo } from './module'),\n // create an import binding so the server function can be resolved\n if (node.source) {\n bindings.set(local, {\n type: 'import',\n source: node.source.value,\n importedName: local,\n })\n }\n }\n }\n } else if (t.isExportDefaultDeclaration(node)) {\n const d = node.declaration\n if (t.isIdentifier(d)) {\n exports.set('default', { tag: 'Default', name: d.name })\n } else {\n const synth = '__default_export__'\n bindings.set(synth, { type: 'var', init: d as t.Expression })\n exports.set('default', { tag: 'Default', name: synth })\n }\n } else if (t.isExportAllDeclaration(node)) {\n // Handle `export * from './module'` syntax\n // Track the source so we can look up exports from it when needed\n reExportAllSources.push(node.source.value)\n }\n }\n\n const info: ModuleInfo = {\n id,\n bindings,\n exports,\n reExportAllSources,\n }\n this.moduleCache.set(id, info)\n return { info, ast }\n }\n\n public invalidateModule(id: string) {\n return this.moduleCache.delete(id)\n }\n\n public async compile({\n code,\n id,\n isProviderFile,\n detectedKinds,\n }: {\n code: string\n id: string\n isProviderFile: boolean\n /** Pre-detected kinds present in this file. If not provided, all valid kinds are checked. */\n detectedKinds?: Set<LookupKind>\n }) {\n if (!this.initialized) {\n await this.init()\n }\n\n // Use detected kinds if provided, otherwise fall back to all valid kinds for this env\n const fileKinds = detectedKinds\n ? new Set([...detectedKinds].filter((k) => this.validLookupKinds.has(k)))\n : this.validLookupKinds\n\n // Early exit if no kinds to process\n if (fileKinds.size === 0) {\n return null\n }\n\n const checkDirectCalls = needsDirectCallDetection(fileKinds)\n\n const { ast } = this.ingestModule({ code, id })\n\n // Single-pass traversal to:\n // 1. Collect candidate paths (only candidates, not all CallExpressions)\n // 2. Build a map for looking up paths of nested calls in method chains\n const candidatePaths: Array<babel.NodePath<t.CallExpression>> = []\n // Map for nested chain lookup - only populated for CallExpressions that are\n // part of a method chain (callee.object is a CallExpression)\n const chainCallPaths = new Map<\n t.CallExpression,\n babel.NodePath<t.CallExpression>\n >()\n\n babel.traverse(ast, {\n CallExpression: (path) => {\n const node = path.node\n const parent = path.parent\n\n // Check if this call is part of a larger chain (inner call)\n // If so, store it for method chain lookup but don't treat as candidate\n if (\n t.isMemberExpression(parent) &&\n t.isCallExpression(path.parentPath.parent)\n ) {\n // This is an inner call in a chain - store for later lookup\n chainCallPaths.set(node, path)\n return\n }\n\n // Pattern 1: Method chain pattern (.handler(), .server(), .client(), etc.)\n if (isMethodChainCandidate(node, fileKinds)) {\n candidatePaths.push(path)\n return\n }\n\n // Pattern 2: Direct call pattern\n if (checkDirectCalls) {\n if (isTopLevelDirectCallCandidate(path)) {\n candidatePaths.push(path)\n } else if (isNestedDirectCallCandidate(node)) {\n candidatePaths.push(path)\n }\n }\n },\n })\n\n if (candidatePaths.length === 0) {\n return null\n }\n\n // Resolve all candidates in parallel to determine their kinds\n const resolvedCandidates = await Promise.all(\n candidatePaths.map(async (path) => ({\n path,\n kind: await this.resolveExprKind(path.node, id),\n })),\n )\n\n // Filter to valid candidates\n const validCandidates = resolvedCandidates.filter(({ kind }) =>\n this.validLookupKinds.has(kind as LookupKind),\n ) as Array<{ path: babel.NodePath<t.CallExpression>; kind: LookupKind }>\n\n if (validCandidates.length === 0) {\n return null\n }\n\n // Process valid candidates to collect method chains\n const pathsToRewrite: Array<{\n path: babel.NodePath<t.CallExpression>\n kind: LookupKind\n methodChain: MethodChainPaths\n }> = []\n\n for (const { path, kind } of validCandidates) {\n const node = path.node\n\n // Collect method chain paths by walking DOWN from root through the chain\n const methodChain: MethodChainPaths = {\n middleware: null,\n inputValidator: null,\n handler: null,\n server: null,\n client: null,\n }\n\n // Walk down the call chain using nodes, look up paths from map\n let currentNode: t.CallExpression = node\n let currentPath: babel.NodePath<t.CallExpression> = path\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n while (true) {\n const callee = currentNode.callee\n if (!t.isMemberExpression(callee)) {\n break\n }\n\n // Record method chain path if it's a known method\n if (t.isIdentifier(callee.property)) {\n const name = callee.property.name as keyof MethodChainPaths\n if (name in methodChain) {\n // Get first argument path\n const args = currentPath.get('arguments')\n const firstArgPath =\n Array.isArray(args) && args.length > 0 ? (args[0] ?? null) : null\n methodChain[name] = {\n callPath: currentPath,\n firstArgPath,\n }\n }\n }\n\n // Move to the inner call (the object of the member expression)\n if (!t.isCallExpression(callee.object)) {\n break\n }\n currentNode = callee.object\n // Look up path from chain map, or use candidate path if not found\n const nextPath = chainCallPaths.get(currentNode)\n if (!nextPath) {\n break\n }\n currentPath = nextPath\n }\n\n pathsToRewrite.push({ path, kind, methodChain })\n }\n\n const refIdents = findReferencedIdentifiers(ast)\n\n for (const { path, kind, methodChain } of pathsToRewrite) {\n const candidate: RewriteCandidate = { path, methodChain }\n if (kind === 'ServerFn') {\n handleCreateServerFn(candidate, {\n env: this.options.env,\n code,\n directive: this.options.directive,\n isProviderFile,\n })\n } else if (kind === 'Middleware') {\n handleCreateMiddleware(candidate, {\n env: this.options.env,\n })\n } else if (kind === 'IsomorphicFn') {\n handleCreateIsomorphicFn(candidate, {\n env: this.options.env,\n })\n } else {\n // ServerOnlyFn or ClientOnlyFn\n handleEnvOnlyFn(candidate, {\n env: this.options.env,\n kind,\n })\n }\n }\n\n deadCodeElimination(ast, refIdents)\n\n return generateFromAst(ast, {\n sourceMaps: true,\n sourceFileName: id,\n filename: id,\n })\n }\n\n private async resolveIdentifierKind(\n ident: string,\n id: string,\n visited = new Set<string>(),\n ): Promise<Kind> {\n const info = await this.getModuleInfo(id)\n\n const binding = info.bindings.get(ident)\n if (!binding) {\n return 'None'\n }\n if (binding.resolvedKind) {\n return binding.resolvedKind\n }\n\n // TODO improve cycle detection? should we throw here instead of returning 'None'?\n // prevent cycles\n const vKey = `${id}:${ident}`\n if (visited.has(vKey)) {\n return 'None'\n }\n visited.add(vKey)\n\n const resolvedKind = await this.resolveBindingKind(binding, id, visited)\n binding.resolvedKind = resolvedKind\n return resolvedKind\n }\n\n /**\n * Recursively find an export in a module, following `export * from` chains.\n * Returns the module info and binding if found, or undefined if not found.\n */\n private async findExportInModule(\n moduleInfo: ModuleInfo,\n exportName: string,\n visitedModules = new Set<string>(),\n ): Promise<{ moduleInfo: ModuleInfo; binding: Binding } | undefined> {\n // Prevent infinite loops in circular re-exports\n if (visitedModules.has(moduleInfo.id)) {\n return undefined\n }\n visitedModules.add(moduleInfo.id)\n\n // First check direct exports\n const directExport = moduleInfo.exports.get(exportName)\n if (directExport) {\n const binding = moduleInfo.bindings.get(directExport.name)\n if (binding) {\n return { moduleInfo, binding }\n }\n }\n\n // If not found, recursively check re-export-all sources in parallel\n // Valid code won't have duplicate exports across chains, so first match wins\n if (moduleInfo.reExportAllSources.length > 0) {\n const results = await Promise.all(\n moduleInfo.reExportAllSources.map(async (reExportSource) => {\n const reExportTarget = await this.options.resolveId(\n reExportSource,\n moduleInfo.id,\n )\n if (reExportTarget) {\n const reExportModule = await this.getModuleInfo(reExportTarget)\n return this.findExportInModule(\n reExportModule,\n exportName,\n visitedModules,\n )\n }\n return undefined\n }),\n )\n // Return the first valid result\n for (const result of results) {\n if (result) {\n return result\n }\n }\n }\n\n return undefined\n }\n\n private async resolveBindingKind(\n binding: Binding,\n fileId: string,\n visited = new Set<string>(),\n ): Promise<Kind> {\n if (binding.resolvedKind) {\n return binding.resolvedKind\n }\n if (binding.type === 'import') {\n // Fast path: check if this is a direct import from a known library\n // (e.g., import { createServerFn } from '@tanstack/react-start')\n // This avoids async resolveId calls for the common case\n const knownExports = this.knownRootImports.get(binding.source)\n if (knownExports) {\n const kind = knownExports.get(binding.importedName)\n if (kind) {\n binding.resolvedKind = kind\n return kind\n }\n }\n\n // Slow path: resolve through the module graph\n const target = await this.options.resolveId(binding.source, fileId)\n if (!target) {\n return 'None'\n }\n\n const importedModule = await this.getModuleInfo(target)\n\n // Find the export, recursively searching through export * from chains\n const found = await this.findExportInModule(\n importedModule,\n binding.importedName,\n )\n\n if (!found) {\n return 'None'\n }\n\n const { moduleInfo: foundModule, binding: foundBinding } = found\n\n if (foundBinding.resolvedKind) {\n return foundBinding.resolvedKind\n }\n\n const resolvedKind = await this.resolveBindingKind(\n foundBinding,\n foundModule.id,\n visited,\n )\n foundBinding.resolvedKind = resolvedKind\n return resolvedKind\n }\n\n const resolvedKind = await this.resolveExprKind(\n binding.init,\n fileId,\n visited,\n )\n binding.resolvedKind = resolvedKind\n return resolvedKind\n }\n\n private async resolveExprKind(\n expr: t.Expression | null,\n fileId: string,\n visited = new Set<string>(),\n ): Promise<Kind> {\n if (!expr) {\n return 'None'\n }\n\n // Unwrap common TypeScript/parenthesized wrappers first for efficiency\n while (\n t.isTSAsExpression(expr) ||\n t.isTSNonNullExpression(expr) ||\n t.isParenthesizedExpression(expr)\n ) {\n expr = expr.expression\n }\n\n let result: Kind = 'None'\n\n if (t.isCallExpression(expr)) {\n if (!t.isExpression(expr.callee)) {\n return 'None'\n }\n const calleeKind = await this.resolveCalleeKind(\n expr.callee,\n fileId,\n visited,\n )\n if (calleeKind === 'Root' || calleeKind === 'Builder') {\n return 'Builder'\n }\n // Use direct Set.has() instead of iterating\n if (this.validLookupKinds.has(calleeKind as LookupKind)) {\n return calleeKind\n }\n } else if (t.isMemberExpression(expr) && t.isIdentifier(expr.property)) {\n result = await this.resolveCalleeKind(expr.object, fileId, visited)\n }\n\n if (result === 'None' && t.isIdentifier(expr)) {\n result = await this.resolveIdentifierKind(expr.name, fileId, visited)\n }\n\n return result\n }\n\n private async resolveCalleeKind(\n callee: t.Expression,\n fileId: string,\n visited = new Set<string>(),\n ): Promise<Kind> {\n if (t.isIdentifier(callee)) {\n return this.resolveIdentifierKind(callee.name, fileId, visited)\n }\n\n if (t.isMemberExpression(callee) && t.isIdentifier(callee.property)) {\n const prop = callee.property.name\n\n // Check if this property matches any method chain pattern\n const possibleKinds = IdentifierToKinds.get(prop)\n if (possibleKinds) {\n // Resolve base expression ONCE and reuse for all pattern checks\n const base = await this.resolveExprKind(callee.object, fileId, visited)\n\n // Check each possible kind that uses this identifier\n for (const kind of possibleKinds) {\n if (!this.validLookupKinds.has(kind)) continue\n\n if (kind === 'ServerFn') {\n if (base === 'Root' || base === 'Builder') {\n return 'ServerFn'\n }\n } else if (kind === 'Middleware') {\n if (\n base === 'Root' ||\n base === 'Builder' ||\n base === 'Middleware'\n ) {\n return 'Middleware'\n }\n } else if (kind === 'IsomorphicFn') {\n if (\n base === 'Root' ||\n base === 'Builder' ||\n base === 'IsomorphicFn'\n ) {\n return 'IsomorphicFn'\n }\n }\n }\n }\n\n // Check if the object is a namespace import\n if (t.isIdentifier(callee.object)) {\n const info = await this.getModuleInfo(fileId)\n const binding = info.bindings.get(callee.object.name)\n if (\n binding &&\n binding.type === 'import' &&\n binding.importedName === '*'\n ) {\n // resolve the property from the target module\n const targetModuleId = await this.options.resolveId(\n binding.source,\n fileId,\n )\n if (targetModuleId) {\n const targetModule = await this.getModuleInfo(targetModuleId)\n const exportEntry = targetModule.exports.get(callee.property.name)\n if (exportEntry) {\n const exportedBinding = targetModule.bindings.get(\n exportEntry.name,\n )\n if (exportedBinding) {\n return await this.resolveBindingKind(\n exportedBinding,\n targetModule.id,\n visited,\n )\n }\n }\n } else {\n return 'None'\n }\n }\n }\n return this.resolveExprKind(callee.object, fileId, visited)\n }\n\n // handle nested expressions\n return this.resolveExprKind(callee, fileId, visited)\n }\n\n private async getModuleInfo(id: string) {\n let cached = this.moduleCache.get(id)\n if (cached) {\n return cached\n }\n\n await this.options.loadModule(id)\n\n cached = this.moduleCache.get(id)\n if (!cached) {\n throw new Error(`could not load module info for ${id}`)\n }\n return cached\n }\n}\n\n/**\n * Checks if a CallExpression has a method chain pattern that matches any of the lookup kinds.\n * E.g., `.handler()`, `.server()`, `.client()`, `.createMiddlewares()`\n */\nfunction isMethodChainCandidate(\n node: t.CallExpression,\n lookupKinds: Set<LookupKind>,\n): boolean {\n const callee = node.callee\n if (!t.isMemberExpression(callee) || !t.isIdentifier(callee.property)) {\n return false\n }\n\n // Use pre-computed map for O(1) lookup\n // IdentifierToKinds maps identifier -> Set<LookupKind> to handle shared identifiers\n const possibleKinds = IdentifierToKinds.get(callee.property.name)\n if (possibleKinds) {\n // Check if any of the possible kinds are in the valid lookup kinds\n for (const kind of possibleKinds) {\n if (lookupKinds.has(kind)) {\n return true\n }\n }\n }\n\n return false\n}\n"],"names":["resolvedKind"],"mappings":"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"}
|
|
@@ -1,24 +1,22 @@
|
|
|
1
1
|
import { TRANSFORM_ID_REGEX } from "../constants.js";
|
|
2
|
-
import { ServerFnCompiler } from "./compiler.js";
|
|
2
|
+
import { ServerFnCompiler, LookupKindsPerEnv, detectKindsInCode, KindDetectionPatterns } from "./compiler.js";
|
|
3
3
|
function cleanId(id) {
|
|
4
|
+
if (id.startsWith("\0")) {
|
|
5
|
+
id = id.slice(1);
|
|
6
|
+
}
|
|
4
7
|
const queryIndex = id.indexOf("?");
|
|
5
8
|
return queryIndex === -1 ? id : id.substring(0, queryIndex);
|
|
6
9
|
}
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"IsomorphicFn",
|
|
18
|
-
"ServerOnlyFn",
|
|
19
|
-
"ClientOnlyFn"
|
|
20
|
-
])
|
|
21
|
-
};
|
|
10
|
+
function getTransformCodeFilterForEnv(env) {
|
|
11
|
+
const validKinds = LookupKindsPerEnv[env];
|
|
12
|
+
const patterns = [];
|
|
13
|
+
for (const [kind, pattern] of Object.entries(KindDetectionPatterns)) {
|
|
14
|
+
if (validKinds.has(kind)) {
|
|
15
|
+
patterns.push(pattern);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return patterns;
|
|
19
|
+
}
|
|
22
20
|
const getLookupConfigurationsForEnv = (env, framework) => {
|
|
23
21
|
const commonConfigs = [
|
|
24
22
|
{
|
|
@@ -64,17 +62,11 @@ const SERVER_FN_LOOKUP = "server-fn-module-lookup";
|
|
|
64
62
|
function buildDirectiveSplitParam(directive) {
|
|
65
63
|
return `tsr-directive-${directive.replace(/[^a-zA-Z0-9]/g, "-")}`;
|
|
66
64
|
}
|
|
67
|
-
const commonTransformCodeFilter = [
|
|
68
|
-
/\.\s*handler\(/,
|
|
69
|
-
/createIsomorphicFn/,
|
|
70
|
-
/createServerOnlyFn/,
|
|
71
|
-
/createClientOnlyFn/
|
|
72
|
-
];
|
|
73
65
|
function createServerFnPlugin(opts) {
|
|
74
66
|
const compilers = {};
|
|
75
67
|
const directiveSplitParam = buildDirectiveSplitParam(opts.directive);
|
|
76
68
|
function perEnvServerFnPlugin(environment) {
|
|
77
|
-
const transformCodeFilter = environment.type
|
|
69
|
+
const transformCodeFilter = getTransformCodeFilterForEnv(environment.type);
|
|
78
70
|
return {
|
|
79
71
|
name: `tanstack-start-core::server-fn:${environment.name}`,
|
|
80
72
|
enforce: "pre",
|
|
@@ -132,8 +124,14 @@ function createServerFnPlugin(opts) {
|
|
|
132
124
|
compilers[this.environment.name] = compiler;
|
|
133
125
|
}
|
|
134
126
|
const isProviderFile = id.includes(directiveSplitParam);
|
|
127
|
+
const detectedKinds = detectKindsInCode(code, environment.type);
|
|
135
128
|
id = cleanId(id);
|
|
136
|
-
const result = await compiler.compile({
|
|
129
|
+
const result = await compiler.compile({
|
|
130
|
+
id,
|
|
131
|
+
code,
|
|
132
|
+
isProviderFile,
|
|
133
|
+
detectedKinds
|
|
134
|
+
});
|
|
137
135
|
return result;
|
|
138
136
|
}
|
|
139
137
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","sources":["../../../src/create-server-fn-plugin/plugin.ts"],"sourcesContent":["import { TRANSFORM_ID_REGEX } from '../constants'\nimport { ServerFnCompiler } from './compiler'\nimport type { CompileStartFrameworkOptions } from '../types'\nimport type { LookupConfig, LookupKind } from './compiler'\nimport type { PluginOption } from 'vite'\n\nfunction cleanId(id: string): string {\n const queryIndex = id.indexOf('?')\n return queryIndex === -1 ? id : id.substring(0, queryIndex)\n}\n\nconst LookupKindsPerEnv: Record<'client' | 'server', Set<LookupKind>> = {\n client: new Set([\n 'Middleware',\n 'ServerFn',\n 'IsomorphicFn',\n 'ServerOnlyFn',\n 'ClientOnlyFn',\n ] as const),\n server: new Set([\n 'ServerFn',\n 'IsomorphicFn',\n 'ServerOnlyFn',\n 'ClientOnlyFn',\n ] as const),\n}\n\nconst getLookupConfigurationsForEnv = (\n env: 'client' | 'server',\n framework: CompileStartFrameworkOptions,\n): Array<LookupConfig> => {\n // Common configs for all environments\n const commonConfigs: Array<LookupConfig> = [\n {\n libName: `@tanstack/${framework}-start`,\n rootExport: 'createServerFn',\n kind: 'Root',\n },\n {\n libName: `@tanstack/${framework}-start`,\n rootExport: 'createIsomorphicFn',\n kind: 'IsomorphicFn',\n },\n {\n libName: `@tanstack/${framework}-start`,\n rootExport: 'createServerOnlyFn',\n kind: 'ServerOnlyFn',\n },\n {\n libName: `@tanstack/${framework}-start`,\n rootExport: 'createClientOnlyFn',\n kind: 'ClientOnlyFn',\n },\n ]\n\n if (env === 'client') {\n return [\n {\n libName: `@tanstack/${framework}-start`,\n rootExport: 'createMiddleware',\n kind: 'Root',\n },\n {\n libName: `@tanstack/${framework}-start`,\n rootExport: 'createStart',\n kind: 'Root',\n },\n ...commonConfigs,\n ]\n } else {\n return commonConfigs\n }\n}\nconst SERVER_FN_LOOKUP = 'server-fn-module-lookup'\n\nfunction buildDirectiveSplitParam(directive: string) {\n return `tsr-directive-${directive.replace(/[^a-zA-Z0-9]/g, '-')}`\n}\n\nconst commonTransformCodeFilter = [\n /\\.\\s*handler\\(/,\n /createIsomorphicFn/,\n /createServerOnlyFn/,\n /createClientOnlyFn/,\n]\nexport function createServerFnPlugin(opts: {\n framework: CompileStartFrameworkOptions\n directive: string\n environments: Array<{ name: string; type: 'client' | 'server' }>\n}): PluginOption {\n const compilers: Record<string /* envName */, ServerFnCompiler> = {}\n const directiveSplitParam = buildDirectiveSplitParam(opts.directive)\n\n function perEnvServerFnPlugin(environment: {\n name: string\n type: 'client' | 'server'\n }): PluginOption {\n // Code filter patterns for transform functions:\n // - `.handler(` for createServerFn\n // - `createMiddleware(` for middleware (client only)\n // - `createIsomorphicFn` for isomorphic functions\n // - `createServerOnlyFn` for server-only functions\n // - `createClientOnlyFn` for client-only functions\n const transformCodeFilter =\n environment.type === 'client'\n ? [...commonTransformCodeFilter, /createMiddleware\\s*\\(/]\n : commonTransformCodeFilter\n\n return {\n name: `tanstack-start-core::server-fn:${environment.name}`,\n enforce: 'pre',\n applyToEnvironment(env) {\n return env.name === environment.name\n },\n transform: {\n filter: {\n id: {\n exclude: new RegExp(`${SERVER_FN_LOOKUP}$`),\n include: TRANSFORM_ID_REGEX,\n },\n code: {\n include: transformCodeFilter,\n },\n },\n async handler(code, id) {\n let compiler = compilers[this.environment.name]\n if (!compiler) {\n compiler = new ServerFnCompiler({\n env: environment.type,\n directive: opts.directive,\n lookupKinds: LookupKindsPerEnv[environment.type],\n lookupConfigurations: getLookupConfigurationsForEnv(\n environment.type,\n opts.framework,\n ),\n loadModule: async (id: string) => {\n if (this.environment.mode === 'build') {\n const loaded = await this.load({ id })\n if (!loaded.code) {\n throw new Error(`could not load module ${id}`)\n }\n compiler!.ingestModule({ code: loaded.code, id })\n } else if (this.environment.mode === 'dev') {\n /**\n * in dev, vite does not return code from `ctx.load()`\n * so instead, we need to take a different approach\n * we must force vite to load the module and run it through the vite plugin pipeline\n * we can do this by using the `fetchModule` method\n * the `captureServerFnModuleLookupPlugin` captures the module code via its transform hook and invokes analyzeModuleAST\n */\n await this.environment.fetchModule(\n id + '?' + SERVER_FN_LOOKUP,\n )\n } else {\n throw new Error(\n `could not load module ${id}: unknown environment mode ${this.environment.mode}`,\n )\n }\n },\n resolveId: async (source: string, importer?: string) => {\n const r = await this.resolve(source, importer)\n if (r) {\n if (!r.external) {\n return cleanId(r.id)\n }\n }\n return null\n },\n })\n compilers[this.environment.name] = compiler\n }\n\n const isProviderFile = id.includes(directiveSplitParam)\n\n id = cleanId(id)\n const result = await compiler.compile({ id, code, isProviderFile })\n return result\n },\n },\n\n hotUpdate(ctx) {\n const compiler = compilers[this.environment.name]\n\n ctx.modules.forEach((m) => {\n if (m.id) {\n const deleted = compiler?.invalidateModule(m.id)\n if (deleted) {\n m.importers.forEach((importer) => {\n if (importer.id) {\n compiler?.invalidateModule(importer.id)\n }\n })\n }\n }\n })\n },\n }\n }\n\n return [\n ...opts.environments.map(perEnvServerFnPlugin),\n {\n name: 'tanstack-start-core:capture-server-fn-module-lookup',\n // we only need this plugin in dev mode\n apply: 'serve',\n applyToEnvironment(env) {\n return !!opts.environments.find((e) => e.name === env.name)\n },\n transform: {\n filter: {\n id: new RegExp(`${SERVER_FN_LOOKUP}$`),\n },\n handler(code, id) {\n const compiler = compilers[this.environment.name]\n compiler?.ingestModule({ code, id: cleanId(id) })\n },\n },\n },\n ]\n}\n"],"names":["id"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"plugin.js","sources":["../../../src/create-server-fn-plugin/plugin.ts"],"sourcesContent":["import { TRANSFORM_ID_REGEX } from '../constants'\nimport {\n KindDetectionPatterns,\n LookupKindsPerEnv,\n ServerFnCompiler,\n detectKindsInCode,\n} from './compiler'\nimport type { CompileStartFrameworkOptions } from '../types'\nimport type { LookupConfig, LookupKind } from './compiler'\nimport type { PluginOption } from 'vite'\n\nfunction cleanId(id: string): string {\n // Remove null byte prefix used by Vite/Rollup for virtual modules\n if (id.startsWith('\\0')) {\n id = id.slice(1)\n }\n const queryIndex = id.indexOf('?')\n return queryIndex === -1 ? id : id.substring(0, queryIndex)\n}\n\n// Derive transform code filter from KindDetectionPatterns (single source of truth)\nfunction getTransformCodeFilterForEnv(env: 'client' | 'server'): Array<RegExp> {\n const validKinds = LookupKindsPerEnv[env]\n const patterns: Array<RegExp> = []\n for (const [kind, pattern] of Object.entries(KindDetectionPatterns) as Array<\n [LookupKind, RegExp]\n >) {\n if (validKinds.has(kind)) {\n patterns.push(pattern)\n }\n }\n return patterns\n}\n\nconst getLookupConfigurationsForEnv = (\n env: 'client' | 'server',\n framework: CompileStartFrameworkOptions,\n): Array<LookupConfig> => {\n // Common configs for all environments\n const commonConfigs: Array<LookupConfig> = [\n {\n libName: `@tanstack/${framework}-start`,\n rootExport: 'createServerFn',\n kind: 'Root',\n },\n {\n libName: `@tanstack/${framework}-start`,\n rootExport: 'createIsomorphicFn',\n kind: 'IsomorphicFn',\n },\n {\n libName: `@tanstack/${framework}-start`,\n rootExport: 'createServerOnlyFn',\n kind: 'ServerOnlyFn',\n },\n {\n libName: `@tanstack/${framework}-start`,\n rootExport: 'createClientOnlyFn',\n kind: 'ClientOnlyFn',\n },\n ]\n\n if (env === 'client') {\n return [\n {\n libName: `@tanstack/${framework}-start`,\n rootExport: 'createMiddleware',\n kind: 'Root',\n },\n {\n libName: `@tanstack/${framework}-start`,\n rootExport: 'createStart',\n kind: 'Root',\n },\n ...commonConfigs,\n ]\n } else {\n return commonConfigs\n }\n}\nconst SERVER_FN_LOOKUP = 'server-fn-module-lookup'\n\nfunction buildDirectiveSplitParam(directive: string) {\n return `tsr-directive-${directive.replace(/[^a-zA-Z0-9]/g, '-')}`\n}\n\nexport function createServerFnPlugin(opts: {\n framework: CompileStartFrameworkOptions\n directive: string\n environments: Array<{ name: string; type: 'client' | 'server' }>\n}): PluginOption {\n const compilers: Record<string /* envName */, ServerFnCompiler> = {}\n const directiveSplitParam = buildDirectiveSplitParam(opts.directive)\n\n function perEnvServerFnPlugin(environment: {\n name: string\n type: 'client' | 'server'\n }): PluginOption {\n // Derive transform code filter from KindDetectionPatterns (single source of truth)\n const transformCodeFilter = getTransformCodeFilterForEnv(environment.type)\n\n return {\n name: `tanstack-start-core::server-fn:${environment.name}`,\n enforce: 'pre',\n applyToEnvironment(env) {\n return env.name === environment.name\n },\n transform: {\n filter: {\n id: {\n exclude: new RegExp(`${SERVER_FN_LOOKUP}$`),\n include: TRANSFORM_ID_REGEX,\n },\n code: {\n include: transformCodeFilter,\n },\n },\n async handler(code, id) {\n let compiler = compilers[this.environment.name]\n if (!compiler) {\n compiler = new ServerFnCompiler({\n env: environment.type,\n directive: opts.directive,\n lookupKinds: LookupKindsPerEnv[environment.type],\n lookupConfigurations: getLookupConfigurationsForEnv(\n environment.type,\n opts.framework,\n ),\n loadModule: async (id: string) => {\n if (this.environment.mode === 'build') {\n const loaded = await this.load({ id })\n if (!loaded.code) {\n throw new Error(`could not load module ${id}`)\n }\n compiler!.ingestModule({ code: loaded.code, id })\n } else if (this.environment.mode === 'dev') {\n /**\n * in dev, vite does not return code from `ctx.load()`\n * so instead, we need to take a different approach\n * we must force vite to load the module and run it through the vite plugin pipeline\n * we can do this by using the `fetchModule` method\n * the `captureServerFnModuleLookupPlugin` captures the module code via its transform hook and invokes analyzeModuleAST\n */\n await this.environment.fetchModule(\n id + '?' + SERVER_FN_LOOKUP,\n )\n } else {\n throw new Error(\n `could not load module ${id}: unknown environment mode ${this.environment.mode}`,\n )\n }\n },\n resolveId: async (source: string, importer?: string) => {\n const r = await this.resolve(source, importer)\n if (r) {\n if (!r.external) {\n return cleanId(r.id)\n }\n }\n return null\n },\n })\n compilers[this.environment.name] = compiler\n }\n\n const isProviderFile = id.includes(directiveSplitParam)\n\n // Detect which kinds are present in this file before parsing\n const detectedKinds = detectKindsInCode(code, environment.type)\n\n id = cleanId(id)\n const result = await compiler.compile({\n id,\n code,\n isProviderFile,\n detectedKinds,\n })\n return result\n },\n },\n\n hotUpdate(ctx) {\n const compiler = compilers[this.environment.name]\n\n ctx.modules.forEach((m) => {\n if (m.id) {\n const deleted = compiler?.invalidateModule(m.id)\n if (deleted) {\n m.importers.forEach((importer) => {\n if (importer.id) {\n compiler?.invalidateModule(importer.id)\n }\n })\n }\n }\n })\n },\n }\n }\n\n return [\n ...opts.environments.map(perEnvServerFnPlugin),\n {\n name: 'tanstack-start-core:capture-server-fn-module-lookup',\n // we only need this plugin in dev mode\n apply: 'serve',\n applyToEnvironment(env) {\n return !!opts.environments.find((e) => e.name === env.name)\n },\n transform: {\n filter: {\n id: new RegExp(`${SERVER_FN_LOOKUP}$`),\n },\n handler(code, id) {\n const compiler = compilers[this.environment.name]\n compiler?.ingestModule({ code, id: cleanId(id) })\n },\n },\n },\n ]\n}\n"],"names":["id"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/start-plugin-core",
|
|
3
|
-
"version": "1.142.
|
|
3
|
+
"version": "1.142.12",
|
|
4
4
|
"description": "Modern and scalable routing for React applications",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -60,9 +60,9 @@
|
|
|
60
60
|
"xmlbuilder2": "^4.0.0",
|
|
61
61
|
"zod": "^3.24.2",
|
|
62
62
|
"@tanstack/router-core": "1.142.11",
|
|
63
|
+
"@tanstack/router-generator": "1.142.11",
|
|
63
64
|
"@tanstack/router-utils": "1.141.0",
|
|
64
65
|
"@tanstack/server-functions-plugin": "1.142.1",
|
|
65
|
-
"@tanstack/router-generator": "1.142.11",
|
|
66
66
|
"@tanstack/router-plugin": "1.142.11",
|
|
67
67
|
"@tanstack/start-client-core": "1.142.11",
|
|
68
68
|
"@tanstack/start-server-core": "1.142.11"
|
|
@@ -68,6 +68,57 @@ const LookupSetup: Record<LookupKind, MethodChainSetup | DirectCallSetup> = {
|
|
|
68
68
|
ClientOnlyFn: { type: 'directCall' },
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
// Single source of truth for detecting which kinds are present in code
|
|
72
|
+
// These patterns are used for:
|
|
73
|
+
// 1. Pre-scanning code to determine which kinds to look for (before AST parsing)
|
|
74
|
+
// 2. Deriving the plugin's transform code filter
|
|
75
|
+
export const KindDetectionPatterns: Record<LookupKind, RegExp> = {
|
|
76
|
+
ServerFn: /\.handler\s*\(/,
|
|
77
|
+
Middleware: /createMiddleware/,
|
|
78
|
+
IsomorphicFn: /createIsomorphicFn/,
|
|
79
|
+
ServerOnlyFn: /createServerOnlyFn/,
|
|
80
|
+
ClientOnlyFn: /createClientOnlyFn/,
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Which kinds are valid for each environment
|
|
84
|
+
export const LookupKindsPerEnv: Record<'client' | 'server', Set<LookupKind>> = {
|
|
85
|
+
client: new Set([
|
|
86
|
+
'Middleware',
|
|
87
|
+
'ServerFn',
|
|
88
|
+
'IsomorphicFn',
|
|
89
|
+
'ServerOnlyFn',
|
|
90
|
+
'ClientOnlyFn',
|
|
91
|
+
] as const),
|
|
92
|
+
server: new Set([
|
|
93
|
+
'ServerFn',
|
|
94
|
+
'IsomorphicFn',
|
|
95
|
+
'ServerOnlyFn',
|
|
96
|
+
'ClientOnlyFn',
|
|
97
|
+
] as const),
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Detects which LookupKinds are present in the code using string matching.
|
|
102
|
+
* This is a fast pre-scan before AST parsing to limit the work done during compilation.
|
|
103
|
+
*/
|
|
104
|
+
export function detectKindsInCode(
|
|
105
|
+
code: string,
|
|
106
|
+
env: 'client' | 'server',
|
|
107
|
+
): Set<LookupKind> {
|
|
108
|
+
const detected = new Set<LookupKind>()
|
|
109
|
+
const validForEnv = LookupKindsPerEnv[env]
|
|
110
|
+
|
|
111
|
+
for (const [kind, pattern] of Object.entries(KindDetectionPatterns) as Array<
|
|
112
|
+
[LookupKind, RegExp]
|
|
113
|
+
>) {
|
|
114
|
+
if (validForEnv.has(kind) && pattern.test(code)) {
|
|
115
|
+
detected.add(kind)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return detected
|
|
120
|
+
}
|
|
121
|
+
|
|
71
122
|
// Pre-computed map: identifier name -> Set<LookupKind> for fast candidate detection (method chain only)
|
|
72
123
|
// Multiple kinds can share the same identifier (e.g., 'server' and 'client' are used by both Middleware and IsomorphicFn)
|
|
73
124
|
const IdentifierToKinds = new Map<string, Set<LookupKind>>()
|
|
@@ -86,6 +137,16 @@ for (const [kind, setup] of Object.entries(LookupSetup) as Array<
|
|
|
86
137
|
}
|
|
87
138
|
}
|
|
88
139
|
|
|
140
|
+
// Known factory function names for direct call and root-as-candidate patterns
|
|
141
|
+
// These are the names that, when called directly, create a new function.
|
|
142
|
+
// Used to filter nested candidates - we only want to include actual factory calls,
|
|
143
|
+
// not invocations of already-created functions (e.g., `myServerFn()` should NOT be a candidate)
|
|
144
|
+
const DirectCallFactoryNames = new Set([
|
|
145
|
+
'createServerOnlyFn',
|
|
146
|
+
'createClientOnlyFn',
|
|
147
|
+
'createIsomorphicFn',
|
|
148
|
+
])
|
|
149
|
+
|
|
89
150
|
export type LookupConfig = {
|
|
90
151
|
libName: string
|
|
91
152
|
rootExport: string
|
|
@@ -100,13 +161,79 @@ interface ModuleInfo {
|
|
|
100
161
|
reExportAllSources: Array<string>
|
|
101
162
|
}
|
|
102
163
|
|
|
164
|
+
/**
|
|
165
|
+
* Computes whether any file kinds need direct-call candidate detection.
|
|
166
|
+
* This includes both directCall types (ServerOnlyFn, ClientOnlyFn) and
|
|
167
|
+
* allowRootAsCandidate types (IsomorphicFn).
|
|
168
|
+
*/
|
|
169
|
+
function needsDirectCallDetection(kinds: Set<LookupKind>): boolean {
|
|
170
|
+
for (const kind of kinds) {
|
|
171
|
+
const setup = LookupSetup[kind]
|
|
172
|
+
if (setup.type === 'directCall' || setup.allowRootAsCandidate) {
|
|
173
|
+
return true
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return false
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Checks if a CallExpression is a direct-call candidate for NESTED detection.
|
|
181
|
+
* Returns true if the callee is a known factory function name.
|
|
182
|
+
* This is stricter than top-level detection because we need to filter out
|
|
183
|
+
* invocations of existing server functions (e.g., `myServerFn()`).
|
|
184
|
+
*/
|
|
185
|
+
function isNestedDirectCallCandidate(node: t.CallExpression): boolean {
|
|
186
|
+
let calleeName: string | undefined
|
|
187
|
+
if (t.isIdentifier(node.callee)) {
|
|
188
|
+
calleeName = node.callee.name
|
|
189
|
+
} else if (
|
|
190
|
+
t.isMemberExpression(node.callee) &&
|
|
191
|
+
t.isIdentifier(node.callee.property)
|
|
192
|
+
) {
|
|
193
|
+
calleeName = node.callee.property.name
|
|
194
|
+
}
|
|
195
|
+
return calleeName !== undefined && DirectCallFactoryNames.has(calleeName)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Checks if a CallExpression path is a top-level direct-call candidate.
|
|
200
|
+
* Top-level means the call is the init of a VariableDeclarator at program level.
|
|
201
|
+
* We accept any simple identifier call or namespace call at top level
|
|
202
|
+
* (e.g., `isomorphicFn()`, `TanStackStart.createServerOnlyFn()`) and let
|
|
203
|
+
* resolution verify it. This handles renamed imports.
|
|
204
|
+
*/
|
|
205
|
+
function isTopLevelDirectCallCandidate(
|
|
206
|
+
path: babel.NodePath<t.CallExpression>,
|
|
207
|
+
): boolean {
|
|
208
|
+
const node = path.node
|
|
209
|
+
|
|
210
|
+
// Must be a simple identifier call or namespace call
|
|
211
|
+
const isSimpleCall =
|
|
212
|
+
t.isIdentifier(node.callee) ||
|
|
213
|
+
(t.isMemberExpression(node.callee) &&
|
|
214
|
+
t.isIdentifier(node.callee.object) &&
|
|
215
|
+
t.isIdentifier(node.callee.property))
|
|
216
|
+
|
|
217
|
+
if (!isSimpleCall) {
|
|
218
|
+
return false
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Must be top-level: VariableDeclarator -> VariableDeclaration -> Program
|
|
222
|
+
const parent = path.parent
|
|
223
|
+
if (!t.isVariableDeclarator(parent) || parent.init !== node) {
|
|
224
|
+
return false
|
|
225
|
+
}
|
|
226
|
+
const grandParent = path.parentPath.parent
|
|
227
|
+
if (!t.isVariableDeclaration(grandParent)) {
|
|
228
|
+
return false
|
|
229
|
+
}
|
|
230
|
+
return t.isProgram(path.parentPath.parentPath?.parent)
|
|
231
|
+
}
|
|
232
|
+
|
|
103
233
|
export class ServerFnCompiler {
|
|
104
234
|
private moduleCache = new Map<string, ModuleInfo>()
|
|
105
235
|
private initialized = false
|
|
106
236
|
private validLookupKinds: Set<LookupKind>
|
|
107
|
-
// Precomputed flags for candidate detection (avoid recomputing on each collectCandidates call)
|
|
108
|
-
private hasDirectCallKinds: boolean
|
|
109
|
-
private hasRootAsCandidateKinds: boolean
|
|
110
237
|
// Fast lookup for direct imports from known libraries (e.g., '@tanstack/react-start')
|
|
111
238
|
// Maps: libName → (exportName → Kind)
|
|
112
239
|
// This allows O(1) resolution for the common case without async resolveId calls
|
|
@@ -122,18 +249,6 @@ export class ServerFnCompiler {
|
|
|
122
249
|
},
|
|
123
250
|
) {
|
|
124
251
|
this.validLookupKinds = options.lookupKinds
|
|
125
|
-
|
|
126
|
-
// Precompute flags for candidate detection
|
|
127
|
-
this.hasDirectCallKinds = false
|
|
128
|
-
this.hasRootAsCandidateKinds = false
|
|
129
|
-
for (const kind of options.lookupKinds) {
|
|
130
|
-
const setup = LookupSetup[kind]
|
|
131
|
-
if (setup.type === 'directCall') {
|
|
132
|
-
this.hasDirectCallKinds = true
|
|
133
|
-
} else if (setup.allowRootAsCandidate) {
|
|
134
|
-
this.hasRootAsCandidateKinds = true
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
252
|
}
|
|
138
253
|
|
|
139
254
|
private async init() {
|
|
@@ -310,68 +425,106 @@ export class ServerFnCompiler {
|
|
|
310
425
|
code,
|
|
311
426
|
id,
|
|
312
427
|
isProviderFile,
|
|
428
|
+
detectedKinds,
|
|
313
429
|
}: {
|
|
314
430
|
code: string
|
|
315
431
|
id: string
|
|
316
432
|
isProviderFile: boolean
|
|
433
|
+
/** Pre-detected kinds present in this file. If not provided, all valid kinds are checked. */
|
|
434
|
+
detectedKinds?: Set<LookupKind>
|
|
317
435
|
}) {
|
|
318
436
|
if (!this.initialized) {
|
|
319
437
|
await this.init()
|
|
320
438
|
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
439
|
+
|
|
440
|
+
// Use detected kinds if provided, otherwise fall back to all valid kinds for this env
|
|
441
|
+
const fileKinds = detectedKinds
|
|
442
|
+
? new Set([...detectedKinds].filter((k) => this.validLookupKinds.has(k)))
|
|
443
|
+
: this.validLookupKinds
|
|
444
|
+
|
|
445
|
+
// Early exit if no kinds to process
|
|
446
|
+
if (fileKinds.size === 0) {
|
|
326
447
|
return null
|
|
327
448
|
}
|
|
328
449
|
|
|
329
|
-
|
|
330
|
-
|
|
450
|
+
const checkDirectCalls = needsDirectCallDetection(fileKinds)
|
|
451
|
+
|
|
452
|
+
const { ast } = this.ingestModule({ code, id })
|
|
453
|
+
|
|
454
|
+
// Single-pass traversal to:
|
|
455
|
+
// 1. Collect candidate paths (only candidates, not all CallExpressions)
|
|
456
|
+
// 2. Build a map for looking up paths of nested calls in method chains
|
|
457
|
+
const candidatePaths: Array<babel.NodePath<t.CallExpression>> = []
|
|
458
|
+
// Map for nested chain lookup - only populated for CallExpressions that are
|
|
459
|
+
// part of a method chain (callee.object is a CallExpression)
|
|
460
|
+
const chainCallPaths = new Map<
|
|
461
|
+
t.CallExpression,
|
|
462
|
+
babel.NodePath<t.CallExpression>
|
|
463
|
+
>()
|
|
464
|
+
|
|
465
|
+
babel.traverse(ast, {
|
|
466
|
+
CallExpression: (path) => {
|
|
467
|
+
const node = path.node
|
|
468
|
+
const parent = path.parent
|
|
469
|
+
|
|
470
|
+
// Check if this call is part of a larger chain (inner call)
|
|
471
|
+
// If so, store it for method chain lookup but don't treat as candidate
|
|
472
|
+
if (
|
|
473
|
+
t.isMemberExpression(parent) &&
|
|
474
|
+
t.isCallExpression(path.parentPath.parent)
|
|
475
|
+
) {
|
|
476
|
+
// This is an inner call in a chain - store for later lookup
|
|
477
|
+
chainCallPaths.set(node, path)
|
|
478
|
+
return
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Pattern 1: Method chain pattern (.handler(), .server(), .client(), etc.)
|
|
482
|
+
if (isMethodChainCandidate(node, fileKinds)) {
|
|
483
|
+
candidatePaths.push(path)
|
|
484
|
+
return
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Pattern 2: Direct call pattern
|
|
488
|
+
if (checkDirectCalls) {
|
|
489
|
+
if (isTopLevelDirectCallCandidate(path)) {
|
|
490
|
+
candidatePaths.push(path)
|
|
491
|
+
} else if (isNestedDirectCallCandidate(node)) {
|
|
492
|
+
candidatePaths.push(path)
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
},
|
|
496
|
+
})
|
|
497
|
+
|
|
498
|
+
if (candidatePaths.length === 0) {
|
|
499
|
+
return null
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Resolve all candidates in parallel to determine their kinds
|
|
331
503
|
const resolvedCandidates = await Promise.all(
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
kind: await this.resolveExprKind(
|
|
504
|
+
candidatePaths.map(async (path) => ({
|
|
505
|
+
path,
|
|
506
|
+
kind: await this.resolveExprKind(path.node, id),
|
|
335
507
|
})),
|
|
336
508
|
)
|
|
337
509
|
|
|
338
|
-
//
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
if (toRewriteMap.size === 0) {
|
|
510
|
+
// Filter to valid candidates
|
|
511
|
+
const validCandidates = resolvedCandidates.filter(({ kind }) =>
|
|
512
|
+
this.validLookupKinds.has(kind as LookupKind),
|
|
513
|
+
) as Array<{ path: babel.NodePath<t.CallExpression>; kind: LookupKind }>
|
|
514
|
+
|
|
515
|
+
if (validCandidates.length === 0) {
|
|
347
516
|
return null
|
|
348
517
|
}
|
|
349
518
|
|
|
350
|
-
//
|
|
519
|
+
// Process valid candidates to collect method chains
|
|
351
520
|
const pathsToRewrite: Array<{
|
|
352
521
|
path: babel.NodePath<t.CallExpression>
|
|
353
522
|
kind: LookupKind
|
|
354
523
|
methodChain: MethodChainPaths
|
|
355
524
|
}> = []
|
|
356
525
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
t.CallExpression,
|
|
360
|
-
babel.NodePath<t.CallExpression>
|
|
361
|
-
>()
|
|
362
|
-
|
|
363
|
-
babel.traverse(ast, {
|
|
364
|
-
CallExpression(path) {
|
|
365
|
-
callExprPaths.set(path.node, path)
|
|
366
|
-
},
|
|
367
|
-
})
|
|
368
|
-
|
|
369
|
-
// Now process candidates - we can look up any CallExpression path in O(1)
|
|
370
|
-
for (const [node, kind] of toRewriteMap) {
|
|
371
|
-
const path = callExprPaths.get(node)
|
|
372
|
-
if (!path) {
|
|
373
|
-
continue
|
|
374
|
-
}
|
|
526
|
+
for (const { path, kind } of validCandidates) {
|
|
527
|
+
const node = path.node
|
|
375
528
|
|
|
376
529
|
// Collect method chain paths by walking DOWN from root through the chain
|
|
377
530
|
const methodChain: MethodChainPaths = {
|
|
@@ -384,6 +537,8 @@ export class ServerFnCompiler {
|
|
|
384
537
|
|
|
385
538
|
// Walk down the call chain using nodes, look up paths from map
|
|
386
539
|
let currentNode: t.CallExpression = node
|
|
540
|
+
let currentPath: babel.NodePath<t.CallExpression> = path
|
|
541
|
+
|
|
387
542
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
388
543
|
while (true) {
|
|
389
544
|
const callee = currentNode.callee
|
|
@@ -395,7 +550,6 @@ export class ServerFnCompiler {
|
|
|
395
550
|
if (t.isIdentifier(callee.property)) {
|
|
396
551
|
const name = callee.property.name as keyof MethodChainPaths
|
|
397
552
|
if (name in methodChain) {
|
|
398
|
-
const currentPath = callExprPaths.get(currentNode)!
|
|
399
553
|
// Get first argument path
|
|
400
554
|
const args = currentPath.get('arguments')
|
|
401
555
|
const firstArgPath =
|
|
@@ -412,18 +566,17 @@ export class ServerFnCompiler {
|
|
|
412
566
|
break
|
|
413
567
|
}
|
|
414
568
|
currentNode = callee.object
|
|
569
|
+
// Look up path from chain map, or use candidate path if not found
|
|
570
|
+
const nextPath = chainCallPaths.get(currentNode)
|
|
571
|
+
if (!nextPath) {
|
|
572
|
+
break
|
|
573
|
+
}
|
|
574
|
+
currentPath = nextPath
|
|
415
575
|
}
|
|
416
576
|
|
|
417
577
|
pathsToRewrite.push({ path, kind, methodChain })
|
|
418
578
|
}
|
|
419
579
|
|
|
420
|
-
// Verify we found all candidates (pathsToRewrite should have same size as toRewriteMap had)
|
|
421
|
-
if (pathsToRewrite.length !== toRewriteMap.size) {
|
|
422
|
-
throw new Error(
|
|
423
|
-
`Internal error: could not find all paths to rewrite. please file an issue`,
|
|
424
|
-
)
|
|
425
|
-
}
|
|
426
|
-
|
|
427
580
|
const refIdents = findReferencedIdentifiers(ast)
|
|
428
581
|
|
|
429
582
|
for (const { path, kind, methodChain } of pathsToRewrite) {
|
|
@@ -461,42 +614,6 @@ export class ServerFnCompiler {
|
|
|
461
614
|
})
|
|
462
615
|
}
|
|
463
616
|
|
|
464
|
-
// collects all candidate CallExpressions at top-level
|
|
465
|
-
private collectCandidates(bindings: Map<string, Binding>) {
|
|
466
|
-
const candidates: Array<t.CallExpression> = []
|
|
467
|
-
|
|
468
|
-
for (const binding of bindings.values()) {
|
|
469
|
-
if (binding.type === 'var' && t.isCallExpression(binding.init)) {
|
|
470
|
-
// Pattern 1: Method chain pattern (.handler(), .server(), etc.)
|
|
471
|
-
const methodChainCandidate = isCandidateCallExpression(
|
|
472
|
-
binding.init,
|
|
473
|
-
this.validLookupKinds,
|
|
474
|
-
)
|
|
475
|
-
if (methodChainCandidate) {
|
|
476
|
-
candidates.push(methodChainCandidate)
|
|
477
|
-
continue
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
// Pattern 2: Direct call pattern
|
|
481
|
-
// Handles:
|
|
482
|
-
// - createServerOnlyFn(), createClientOnlyFn() (direct call kinds)
|
|
483
|
-
// - createIsomorphicFn() (root-as-candidate kinds)
|
|
484
|
-
// - TanStackStart.createServerOnlyFn() (namespace calls)
|
|
485
|
-
if (this.hasDirectCallKinds || this.hasRootAsCandidateKinds) {
|
|
486
|
-
if (
|
|
487
|
-
t.isIdentifier(binding.init.callee) ||
|
|
488
|
-
(t.isMemberExpression(binding.init.callee) &&
|
|
489
|
-
t.isIdentifier(binding.init.callee.property))
|
|
490
|
-
) {
|
|
491
|
-
// Include as candidate - kind resolution will verify it's actually a known export
|
|
492
|
-
candidates.push(binding.init)
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
return candidates
|
|
498
|
-
}
|
|
499
|
-
|
|
500
617
|
private async resolveIdentifierKind(
|
|
501
618
|
ident: string,
|
|
502
619
|
id: string,
|
|
@@ -793,15 +910,17 @@ export class ServerFnCompiler {
|
|
|
793
910
|
}
|
|
794
911
|
}
|
|
795
912
|
|
|
796
|
-
|
|
797
|
-
|
|
913
|
+
/**
|
|
914
|
+
* Checks if a CallExpression has a method chain pattern that matches any of the lookup kinds.
|
|
915
|
+
* E.g., `.handler()`, `.server()`, `.client()`, `.createMiddlewares()`
|
|
916
|
+
*/
|
|
917
|
+
function isMethodChainCandidate(
|
|
918
|
+
node: t.CallExpression,
|
|
798
919
|
lookupKinds: Set<LookupKind>,
|
|
799
|
-
):
|
|
800
|
-
if (!t.isCallExpression(node)) return undefined
|
|
801
|
-
|
|
920
|
+
): boolean {
|
|
802
921
|
const callee = node.callee
|
|
803
922
|
if (!t.isMemberExpression(callee) || !t.isIdentifier(callee.property)) {
|
|
804
|
-
return
|
|
923
|
+
return false
|
|
805
924
|
}
|
|
806
925
|
|
|
807
926
|
// Use pre-computed map for O(1) lookup
|
|
@@ -811,10 +930,10 @@ function isCandidateCallExpression(
|
|
|
811
930
|
// Check if any of the possible kinds are in the valid lookup kinds
|
|
812
931
|
for (const kind of possibleKinds) {
|
|
813
932
|
if (lookupKinds.has(kind)) {
|
|
814
|
-
return
|
|
933
|
+
return true
|
|
815
934
|
}
|
|
816
935
|
}
|
|
817
936
|
}
|
|
818
937
|
|
|
819
|
-
return
|
|
938
|
+
return false
|
|
820
939
|
}
|
|
@@ -1,28 +1,35 @@
|
|
|
1
1
|
import { TRANSFORM_ID_REGEX } from '../constants'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
KindDetectionPatterns,
|
|
4
|
+
LookupKindsPerEnv,
|
|
5
|
+
ServerFnCompiler,
|
|
6
|
+
detectKindsInCode,
|
|
7
|
+
} from './compiler'
|
|
3
8
|
import type { CompileStartFrameworkOptions } from '../types'
|
|
4
9
|
import type { LookupConfig, LookupKind } from './compiler'
|
|
5
10
|
import type { PluginOption } from 'vite'
|
|
6
11
|
|
|
7
12
|
function cleanId(id: string): string {
|
|
13
|
+
// Remove null byte prefix used by Vite/Rollup for virtual modules
|
|
14
|
+
if (id.startsWith('\0')) {
|
|
15
|
+
id = id.slice(1)
|
|
16
|
+
}
|
|
8
17
|
const queryIndex = id.indexOf('?')
|
|
9
18
|
return queryIndex === -1 ? id : id.substring(0, queryIndex)
|
|
10
19
|
}
|
|
11
20
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
'ClientOnlyFn',
|
|
25
|
-
] as const),
|
|
21
|
+
// Derive transform code filter from KindDetectionPatterns (single source of truth)
|
|
22
|
+
function getTransformCodeFilterForEnv(env: 'client' | 'server'): Array<RegExp> {
|
|
23
|
+
const validKinds = LookupKindsPerEnv[env]
|
|
24
|
+
const patterns: Array<RegExp> = []
|
|
25
|
+
for (const [kind, pattern] of Object.entries(KindDetectionPatterns) as Array<
|
|
26
|
+
[LookupKind, RegExp]
|
|
27
|
+
>) {
|
|
28
|
+
if (validKinds.has(kind)) {
|
|
29
|
+
patterns.push(pattern)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return patterns
|
|
26
33
|
}
|
|
27
34
|
|
|
28
35
|
const getLookupConfigurationsForEnv = (
|
|
@@ -77,12 +84,6 @@ function buildDirectiveSplitParam(directive: string) {
|
|
|
77
84
|
return `tsr-directive-${directive.replace(/[^a-zA-Z0-9]/g, '-')}`
|
|
78
85
|
}
|
|
79
86
|
|
|
80
|
-
const commonTransformCodeFilter = [
|
|
81
|
-
/\.\s*handler\(/,
|
|
82
|
-
/createIsomorphicFn/,
|
|
83
|
-
/createServerOnlyFn/,
|
|
84
|
-
/createClientOnlyFn/,
|
|
85
|
-
]
|
|
86
87
|
export function createServerFnPlugin(opts: {
|
|
87
88
|
framework: CompileStartFrameworkOptions
|
|
88
89
|
directive: string
|
|
@@ -95,16 +96,8 @@ export function createServerFnPlugin(opts: {
|
|
|
95
96
|
name: string
|
|
96
97
|
type: 'client' | 'server'
|
|
97
98
|
}): PluginOption {
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
// - `createMiddleware(` for middleware (client only)
|
|
101
|
-
// - `createIsomorphicFn` for isomorphic functions
|
|
102
|
-
// - `createServerOnlyFn` for server-only functions
|
|
103
|
-
// - `createClientOnlyFn` for client-only functions
|
|
104
|
-
const transformCodeFilter =
|
|
105
|
-
environment.type === 'client'
|
|
106
|
-
? [...commonTransformCodeFilter, /createMiddleware\s*\(/]
|
|
107
|
-
: commonTransformCodeFilter
|
|
99
|
+
// Derive transform code filter from KindDetectionPatterns (single source of truth)
|
|
100
|
+
const transformCodeFilter = getTransformCodeFilterForEnv(environment.type)
|
|
108
101
|
|
|
109
102
|
return {
|
|
110
103
|
name: `tanstack-start-core::server-fn:${environment.name}`,
|
|
@@ -172,8 +165,16 @@ export function createServerFnPlugin(opts: {
|
|
|
172
165
|
|
|
173
166
|
const isProviderFile = id.includes(directiveSplitParam)
|
|
174
167
|
|
|
168
|
+
// Detect which kinds are present in this file before parsing
|
|
169
|
+
const detectedKinds = detectKindsInCode(code, environment.type)
|
|
170
|
+
|
|
175
171
|
id = cleanId(id)
|
|
176
|
-
const result = await compiler.compile({
|
|
172
|
+
const result = await compiler.compile({
|
|
173
|
+
id,
|
|
174
|
+
code,
|
|
175
|
+
isProviderFile,
|
|
176
|
+
detectedKinds,
|
|
177
|
+
})
|
|
177
178
|
return result
|
|
178
179
|
},
|
|
179
180
|
},
|