@tanstack/start-plugin-core 1.169.11 → 1.169.13
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/index.d.ts +1 -1
- package/dist/esm/rsbuild/index.d.ts +1 -0
- package/dist/esm/rsbuild/plugin.js +2 -0
- package/dist/esm/rsbuild/plugin.js.map +1 -1
- package/dist/esm/rsbuild/schema.d.ts +27 -27
- package/dist/esm/rsbuild/start-compiler-host.d.ts +3 -1
- package/dist/esm/rsbuild/start-compiler-host.js +6 -2
- package/dist/esm/rsbuild/start-compiler-host.js.map +1 -1
- package/dist/esm/schema.d.ts +51 -51
- package/dist/esm/start-compiler/compiler.d.ts +21 -5
- package/dist/esm/start-compiler/compiler.js +197 -48
- package/dist/esm/start-compiler/compiler.js.map +1 -1
- package/dist/esm/start-compiler/config.d.ts +7 -3
- package/dist/esm/start-compiler/config.js +19 -7
- package/dist/esm/start-compiler/config.js.map +1 -1
- package/dist/esm/start-compiler/handleCreateServerFn.js +12 -0
- package/dist/esm/start-compiler/handleCreateServerFn.js.map +1 -1
- package/dist/esm/start-compiler/host.d.ts +3 -1
- package/dist/esm/start-compiler/host.js +5 -3
- package/dist/esm/start-compiler/host.js.map +1 -1
- package/dist/esm/start-compiler/types.d.ts +4 -13
- package/dist/esm/types.d.ts +33 -0
- package/dist/esm/vite/index.d.ts +1 -0
- package/dist/esm/vite/plugin.js +2 -0
- package/dist/esm/vite/plugin.js.map +1 -1
- package/dist/esm/vite/schema.d.ts +27 -27
- package/dist/esm/vite/start-compiler-plugin/plugin.d.ts +3 -1
- package/dist/esm/vite/start-compiler-plugin/plugin.js +6 -2
- package/dist/esm/vite/start-compiler-plugin/plugin.js.map +1 -1
- package/package.json +6 -6
- package/src/index.ts +6 -1
- package/src/rsbuild/index.ts +5 -0
- package/src/rsbuild/plugin.ts +3 -0
- package/src/rsbuild/start-compiler-host.ts +22 -3
- package/src/start-compiler/compiler.ts +389 -70
- package/src/start-compiler/config.ts +43 -6
- package/src/start-compiler/handleCreateServerFn.ts +29 -0
- package/src/start-compiler/host.ts +13 -3
- package/src/start-compiler/types.ts +5 -14
- package/src/types.ts +44 -0
- package/src/vite/index.ts +5 -0
- package/src/vite/plugin.ts +3 -0
- package/src/vite/start-compiler-plugin/plugin.ts +22 -3
|
@@ -21,7 +21,11 @@ import type {
|
|
|
21
21
|
RewriteCandidate,
|
|
22
22
|
ServerFn,
|
|
23
23
|
} from './types'
|
|
24
|
-
import type {
|
|
24
|
+
import type {
|
|
25
|
+
CompileStartFrameworkOptions,
|
|
26
|
+
StartCompilerEnvironment,
|
|
27
|
+
StartCompilerImportTransform,
|
|
28
|
+
} from '../types'
|
|
25
29
|
|
|
26
30
|
type Binding =
|
|
27
31
|
| {
|
|
@@ -38,7 +42,7 @@ type Binding =
|
|
|
38
42
|
|
|
39
43
|
type Kind = 'None' | `Root` | `Builder` | LookupKind
|
|
40
44
|
|
|
41
|
-
export type
|
|
45
|
+
export type BuiltInLookupKind =
|
|
42
46
|
| 'ServerFn'
|
|
43
47
|
| 'Middleware'
|
|
44
48
|
| 'IsomorphicFn'
|
|
@@ -46,6 +50,10 @@ export type LookupKind =
|
|
|
46
50
|
| 'ClientOnlyFn'
|
|
47
51
|
| 'ClientOnlyJSX'
|
|
48
52
|
|
|
53
|
+
export type ExternalLookupKind = `External:${string}`
|
|
54
|
+
|
|
55
|
+
export type LookupKind = BuiltInLookupKind | ExternalLookupKind
|
|
56
|
+
|
|
49
57
|
// Detection strategy for each kind
|
|
50
58
|
type MethodChainSetup = {
|
|
51
59
|
type: 'methodChain'
|
|
@@ -54,16 +62,37 @@ type MethodChainSetup = {
|
|
|
54
62
|
type DirectCallSetup = {
|
|
55
63
|
type: 'directCall'
|
|
56
64
|
// The factory function name used to create this kind (e.g., 'createServerOnlyFn')
|
|
57
|
-
|
|
65
|
+
factoryNames: Set<string>
|
|
58
66
|
}
|
|
59
67
|
type JSXSetup = { type: 'jsx'; componentName: string }
|
|
60
68
|
|
|
61
69
|
function isLookupKind(kind: Kind): kind is LookupKind {
|
|
62
|
-
return kind in
|
|
70
|
+
return kind in BuiltInLookupSetup || isExternalLookupKind(kind)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function getExternalLookupKind(
|
|
74
|
+
transform: StartCompilerImportTransform,
|
|
75
|
+
): ExternalLookupKind {
|
|
76
|
+
return `External:${transform.name}`
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function isExternalLookupKind(kind: Kind): kind is ExternalLookupKind {
|
|
80
|
+
return typeof kind === 'string' && kind.startsWith('External:')
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function isCompilerTransformEnabledForEnv(
|
|
84
|
+
transform: StartCompilerImportTransform,
|
|
85
|
+
env: StartCompilerEnvironment,
|
|
86
|
+
): boolean {
|
|
87
|
+
if (!transform.environment) return true
|
|
88
|
+
if (Array.isArray(transform.environment)) {
|
|
89
|
+
return transform.environment.includes(env)
|
|
90
|
+
}
|
|
91
|
+
return transform.environment === env
|
|
63
92
|
}
|
|
64
93
|
|
|
65
|
-
const
|
|
66
|
-
|
|
94
|
+
const BuiltInLookupSetup: Record<
|
|
95
|
+
BuiltInLookupKind,
|
|
67
96
|
MethodChainSetup | DirectCallSetup | JSXSetup
|
|
68
97
|
> = {
|
|
69
98
|
ServerFn: {
|
|
@@ -78,8 +107,14 @@ const LookupSetup: Record<
|
|
|
78
107
|
type: 'methodChain',
|
|
79
108
|
candidateCallIdentifier: new Set(['server', 'client']),
|
|
80
109
|
},
|
|
81
|
-
ServerOnlyFn: {
|
|
82
|
-
|
|
110
|
+
ServerOnlyFn: {
|
|
111
|
+
type: 'directCall',
|
|
112
|
+
factoryNames: new Set(['createServerOnlyFn']),
|
|
113
|
+
},
|
|
114
|
+
ClientOnlyFn: {
|
|
115
|
+
type: 'directCall',
|
|
116
|
+
factoryNames: new Set(['createClientOnlyFn']),
|
|
117
|
+
},
|
|
83
118
|
ClientOnlyJSX: { type: 'jsx', componentName: 'ClientOnly' },
|
|
84
119
|
}
|
|
85
120
|
|
|
@@ -87,7 +122,7 @@ const LookupSetup: Record<
|
|
|
87
122
|
// These patterns are used for:
|
|
88
123
|
// 1. Pre-scanning code to determine which kinds to look for (before AST parsing)
|
|
89
124
|
// 2. Deriving the plugin's transform code filter
|
|
90
|
-
export const KindDetectionPatterns: Record<
|
|
125
|
+
export const KindDetectionPatterns: Record<BuiltInLookupKind, RegExp> = {
|
|
91
126
|
ServerFn: /\bcreateServerFn\b|\.\s*handler\s*\(/,
|
|
92
127
|
Middleware: /createMiddleware/,
|
|
93
128
|
IsomorphicFn: /createIsomorphicFn/,
|
|
@@ -97,7 +132,10 @@ export const KindDetectionPatterns: Record<LookupKind, RegExp> = {
|
|
|
97
132
|
}
|
|
98
133
|
|
|
99
134
|
// Which kinds are valid for each environment
|
|
100
|
-
export const LookupKindsPerEnv: Record<
|
|
135
|
+
export const LookupKindsPerEnv: Record<
|
|
136
|
+
'client' | 'server',
|
|
137
|
+
Set<BuiltInLookupKind>
|
|
138
|
+
> = {
|
|
101
139
|
client: new Set([
|
|
102
140
|
'Middleware',
|
|
103
141
|
'ServerFn',
|
|
@@ -114,6 +152,21 @@ export const LookupKindsPerEnv: Record<'client' | 'server', Set<LookupKind>> = {
|
|
|
114
152
|
] as const),
|
|
115
153
|
}
|
|
116
154
|
|
|
155
|
+
export function getLookupKindsForEnv(
|
|
156
|
+
env: 'client' | 'server',
|
|
157
|
+
opts?: {
|
|
158
|
+
compilerTransforms?: Array<StartCompilerImportTransform> | undefined
|
|
159
|
+
},
|
|
160
|
+
): Set<LookupKind> {
|
|
161
|
+
const kinds: Set<LookupKind> = new Set(LookupKindsPerEnv[env])
|
|
162
|
+
for (const transform of opts?.compilerTransforms ?? []) {
|
|
163
|
+
if (isCompilerTransformEnabledForEnv(transform, env)) {
|
|
164
|
+
kinds.add(getExternalLookupKind(transform))
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return kinds
|
|
168
|
+
}
|
|
169
|
+
|
|
117
170
|
/**
|
|
118
171
|
* Handler type for processing candidates of a specific kind.
|
|
119
172
|
* The kind is passed as the third argument to allow shared handlers (like handleEnvOnlyFn).
|
|
@@ -121,15 +174,15 @@ export const LookupKindsPerEnv: Record<'client' | 'server', Set<LookupKind>> = {
|
|
|
121
174
|
type KindHandler = (
|
|
122
175
|
candidates: Array<RewriteCandidate>,
|
|
123
176
|
context: CompilationContext,
|
|
124
|
-
kind:
|
|
177
|
+
kind: BuiltInLookupKind,
|
|
125
178
|
) => void
|
|
126
179
|
|
|
127
180
|
/**
|
|
128
181
|
* Registry mapping each LookupKind to its handler function.
|
|
129
182
|
* When adding a new kind, add its handler here.
|
|
130
183
|
*/
|
|
131
|
-
const
|
|
132
|
-
Exclude<
|
|
184
|
+
const BuiltInKindHandlers: Record<
|
|
185
|
+
Exclude<BuiltInLookupKind, 'ClientOnlyJSX'>,
|
|
133
186
|
KindHandler
|
|
134
187
|
> = {
|
|
135
188
|
ServerFn: handleCreateServerFn,
|
|
@@ -140,8 +193,14 @@ const KindHandlers: Record<
|
|
|
140
193
|
// ClientOnlyJSX is handled separately via JSX traversal, not here
|
|
141
194
|
}
|
|
142
195
|
|
|
196
|
+
const BuiltInKindHandlerOrder: Array<
|
|
197
|
+
Exclude<BuiltInLookupKind, 'ClientOnlyJSX'>
|
|
198
|
+
> = ['ServerFn', 'Middleware', 'IsomorphicFn', 'ServerOnlyFn', 'ClientOnlyFn']
|
|
199
|
+
|
|
143
200
|
// All lookup kinds as an array for iteration with proper typing
|
|
144
|
-
const
|
|
201
|
+
const AllBuiltInLookupKinds = Object.keys(
|
|
202
|
+
BuiltInLookupSetup,
|
|
203
|
+
) as Array<BuiltInLookupKind>
|
|
145
204
|
|
|
146
205
|
/**
|
|
147
206
|
* Detects which LookupKinds are present in the code using string matching.
|
|
@@ -150,24 +209,34 @@ const AllLookupKinds = Object.keys(LookupSetup) as Array<LookupKind>
|
|
|
150
209
|
export function detectKindsInCode(
|
|
151
210
|
code: string,
|
|
152
211
|
env: 'client' | 'server',
|
|
212
|
+
opts?: {
|
|
213
|
+
compilerTransforms?: Array<StartCompilerImportTransform> | undefined
|
|
214
|
+
},
|
|
153
215
|
): Set<LookupKind> {
|
|
154
216
|
const detected = new Set<LookupKind>()
|
|
155
|
-
const validForEnv =
|
|
217
|
+
const validForEnv = getLookupKindsForEnv(env, opts)
|
|
156
218
|
|
|
157
|
-
for (const kind of
|
|
219
|
+
for (const kind of AllBuiltInLookupKinds) {
|
|
158
220
|
if (validForEnv.has(kind) && KindDetectionPatterns[kind].test(code)) {
|
|
159
221
|
detected.add(kind)
|
|
160
222
|
}
|
|
161
223
|
}
|
|
162
224
|
|
|
225
|
+
for (const transform of opts?.compilerTransforms ?? []) {
|
|
226
|
+
if (!isCompilerTransformEnabledForEnv(transform, env)) continue
|
|
227
|
+
if (transform.detect.test(code)) {
|
|
228
|
+
detected.add(getExternalLookupKind(transform))
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
163
232
|
return detected
|
|
164
233
|
}
|
|
165
234
|
|
|
166
235
|
// Pre-computed map: identifier name -> Set<LookupKind> for fast candidate detection (method chain only)
|
|
167
236
|
// Multiple kinds can share the same identifier (e.g., 'server' and 'client' are used by both Middleware and IsomorphicFn)
|
|
168
237
|
const IdentifierToKinds = new Map<string, Set<LookupKind>>()
|
|
169
|
-
for (const kind of
|
|
170
|
-
const setup =
|
|
238
|
+
for (const kind of AllBuiltInLookupKinds) {
|
|
239
|
+
const setup = BuiltInLookupSetup[kind]
|
|
171
240
|
if (setup.type === 'methodChain') {
|
|
172
241
|
for (const id of setup.candidateCallIdentifier) {
|
|
173
242
|
let kinds = IdentifierToKinds.get(id)
|
|
@@ -180,15 +249,19 @@ for (const kind of AllLookupKinds) {
|
|
|
180
249
|
}
|
|
181
250
|
}
|
|
182
251
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
252
|
+
function getLookupSetup(
|
|
253
|
+
kind: LookupKind,
|
|
254
|
+
externalLookupSetup?: Map<ExternalLookupKind, DirectCallSetup>,
|
|
255
|
+
): MethodChainSetup | DirectCallSetup | JSXSetup | undefined {
|
|
256
|
+
if (kind in BuiltInLookupSetup) {
|
|
257
|
+
return BuiltInLookupSetup[kind as BuiltInLookupKind]
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (isExternalLookupKind(kind)) {
|
|
261
|
+
return externalLookupSetup?.get(kind)
|
|
191
262
|
}
|
|
263
|
+
|
|
264
|
+
return undefined
|
|
192
265
|
}
|
|
193
266
|
|
|
194
267
|
export type LookupConfig = {
|
|
@@ -206,19 +279,6 @@ interface ModuleInfo {
|
|
|
206
279
|
reExportAllSources: Array<string>
|
|
207
280
|
}
|
|
208
281
|
|
|
209
|
-
/**
|
|
210
|
-
* Computes whether any file kinds need direct-call candidate detection.
|
|
211
|
-
* This applies to directCall types (ServerOnlyFn, ClientOnlyFn).
|
|
212
|
-
*/
|
|
213
|
-
function needsDirectCallDetection(kinds: Set<LookupKind>): boolean {
|
|
214
|
-
for (const kind of kinds) {
|
|
215
|
-
if (LookupSetup[kind].type === 'directCall') {
|
|
216
|
-
return true
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
return false
|
|
220
|
-
}
|
|
221
|
-
|
|
222
282
|
/**
|
|
223
283
|
* Checks if all kinds in the set are guaranteed to be top-level only.
|
|
224
284
|
* Only ServerFn is always declared at module level (must be assigned to a variable).
|
|
@@ -232,9 +292,12 @@ function areAllKindsTopLevelOnly(kinds: Set<LookupKind>): boolean {
|
|
|
232
292
|
/**
|
|
233
293
|
* Checks if we need to detect JSX elements (e.g., <ClientOnly>).
|
|
234
294
|
*/
|
|
235
|
-
function needsJSXDetection(
|
|
295
|
+
function needsJSXDetection(
|
|
296
|
+
kinds: Set<LookupKind>,
|
|
297
|
+
externalLookupSetup?: Map<ExternalLookupKind, DirectCallSetup>,
|
|
298
|
+
): boolean {
|
|
236
299
|
for (const kind of kinds) {
|
|
237
|
-
if (
|
|
300
|
+
if (getLookupSetup(kind, externalLookupSetup)?.type === 'jsx') {
|
|
238
301
|
return true
|
|
239
302
|
}
|
|
240
303
|
}
|
|
@@ -247,7 +310,11 @@ function needsJSXDetection(kinds: Set<LookupKind>): boolean {
|
|
|
247
310
|
* This is stricter than top-level detection because we need to filter out
|
|
248
311
|
* invocations of existing server functions (e.g., `myServerFn()`).
|
|
249
312
|
*/
|
|
250
|
-
function isNestedDirectCallCandidate(
|
|
313
|
+
function isNestedDirectCallCandidate(
|
|
314
|
+
node: t.CallExpression,
|
|
315
|
+
lookupKinds: Set<LookupKind>,
|
|
316
|
+
externalLookupSetup?: Map<ExternalLookupKind, DirectCallSetup>,
|
|
317
|
+
): boolean {
|
|
251
318
|
let calleeName: string | undefined
|
|
252
319
|
if (t.isIdentifier(node.callee)) {
|
|
253
320
|
calleeName = node.callee.name
|
|
@@ -257,7 +324,15 @@ function isNestedDirectCallCandidate(node: t.CallExpression): boolean {
|
|
|
257
324
|
) {
|
|
258
325
|
calleeName = node.callee.property.name
|
|
259
326
|
}
|
|
260
|
-
|
|
327
|
+
if (!calleeName) return false
|
|
328
|
+
for (const kind of lookupKinds) {
|
|
329
|
+
if (isExternalLookupKind(kind)) continue
|
|
330
|
+
const setup = getLookupSetup(kind, externalLookupSetup)
|
|
331
|
+
if (setup?.type === 'directCall' && setup.factoryNames.has(calleeName)) {
|
|
332
|
+
return true
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return false
|
|
261
336
|
}
|
|
262
337
|
|
|
263
338
|
function isSimpleDirectCallExpression(node: t.CallExpression): boolean {
|
|
@@ -304,14 +379,87 @@ function isTopLevelDirectCallCandidate(
|
|
|
304
379
|
|
|
305
380
|
function isDirectCallCandidateForKind(
|
|
306
381
|
kind: Exclude<LookupKind, 'ClientOnlyJSX'>,
|
|
382
|
+
externalLookupSetup?: Map<ExternalLookupKind, DirectCallSetup>,
|
|
383
|
+
): boolean {
|
|
384
|
+
return getLookupSetup(kind, externalLookupSetup)?.type === 'directCall'
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function hasBuiltInDirectCallKinds(kinds: Set<LookupKind>): boolean {
|
|
388
|
+
for (const kind of kinds) {
|
|
389
|
+
if (isExternalLookupKind(kind)) continue
|
|
390
|
+
if (BuiltInLookupSetup[kind].type === 'directCall') return true
|
|
391
|
+
}
|
|
392
|
+
return false
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function hasExternalLookupKinds(kinds: Set<LookupKind>): boolean {
|
|
396
|
+
for (const kind of kinds) {
|
|
397
|
+
if (isExternalLookupKind(kind)) return true
|
|
398
|
+
}
|
|
399
|
+
return false
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
interface ExternalDirectCallCandidates {
|
|
403
|
+
identifiers: Map<string, ExternalLookupKind>
|
|
404
|
+
namespaces: Map<string, Map<string, ExternalLookupKind>>
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
interface CallExpressionCandidate {
|
|
408
|
+
path: babel.NodePath<t.CallExpression>
|
|
409
|
+
/** Set when import scanning already proved the call's lookup kind. */
|
|
410
|
+
kind?: Exclude<LookupKind, 'ClientOnlyJSX'>
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function hasExternalDirectCallCandidates(
|
|
414
|
+
candidates: ExternalDirectCallCandidates,
|
|
307
415
|
): boolean {
|
|
308
|
-
return
|
|
416
|
+
return candidates.identifiers.size > 0 || candidates.namespaces.size > 0
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function getExternalDirectCallCandidateKind(
|
|
420
|
+
path: babel.NodePath<t.CallExpression>,
|
|
421
|
+
candidates: ExternalDirectCallCandidates,
|
|
422
|
+
): ExternalLookupKind | undefined {
|
|
423
|
+
const node = path.node
|
|
424
|
+
|
|
425
|
+
if (t.isIdentifier(node.callee)) {
|
|
426
|
+
const kind = candidates.identifiers.get(node.callee.name)
|
|
427
|
+
if (!kind) return undefined
|
|
428
|
+
|
|
429
|
+
const binding = path.scope.getBinding(node.callee.name)
|
|
430
|
+
return binding?.path.isImportSpecifier() ? kind : undefined
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (
|
|
434
|
+
t.isMemberExpression(node.callee) &&
|
|
435
|
+
t.isIdentifier(node.callee.object) &&
|
|
436
|
+
t.isIdentifier(node.callee.property)
|
|
437
|
+
) {
|
|
438
|
+
const kind = candidates.namespaces
|
|
439
|
+
.get(node.callee.object.name)
|
|
440
|
+
?.get(node.callee.property.name)
|
|
441
|
+
if (!kind) return undefined
|
|
442
|
+
|
|
443
|
+
const binding = path.scope.getBinding(node.callee.object.name)
|
|
444
|
+
return binding?.path.isImportNamespaceSpecifier() ? kind : undefined
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
return undefined
|
|
309
448
|
}
|
|
310
449
|
|
|
311
450
|
export class StartCompiler {
|
|
312
451
|
private moduleCache = new Map<string, ModuleInfo>()
|
|
313
452
|
private initialized = false
|
|
314
453
|
private validLookupKinds: Set<LookupKind>
|
|
454
|
+
private externalTransformsByKind = new Map<
|
|
455
|
+
ExternalLookupKind,
|
|
456
|
+
StartCompilerImportTransform
|
|
457
|
+
>()
|
|
458
|
+
private externalLookupSetup = new Map<ExternalLookupKind, DirectCallSetup>()
|
|
459
|
+
private externalDirectCallKindsBySource = new Map<
|
|
460
|
+
string,
|
|
461
|
+
Map<string, ExternalLookupKind>
|
|
462
|
+
>()
|
|
315
463
|
private resolveIdCache = new Map<string, string | null>()
|
|
316
464
|
private exportResolutionCache = new Map<
|
|
317
465
|
string,
|
|
@@ -360,6 +508,8 @@ export class StartCompiler {
|
|
|
360
508
|
* Called after each file is compiled with its new functions.
|
|
361
509
|
*/
|
|
362
510
|
onServerFnsById?: (d: Record<string, ServerFn>) => void
|
|
511
|
+
compilerTransforms?: Array<StartCompilerImportTransform> | undefined
|
|
512
|
+
serverFnProviderModuleDirectives?: ReadonlyArray<string> | undefined
|
|
363
513
|
/**
|
|
364
514
|
* Returns the currently known server functions from previous builds.
|
|
365
515
|
* Used by server callers to look up canonical extracted filenames.
|
|
@@ -369,6 +519,31 @@ export class StartCompiler {
|
|
|
369
519
|
},
|
|
370
520
|
) {
|
|
371
521
|
this.validLookupKinds = options.lookupKinds
|
|
522
|
+
for (const transform of options.compilerTransforms ?? []) {
|
|
523
|
+
const kind = getExternalLookupKind(transform)
|
|
524
|
+
if (!this.validLookupKinds.has(kind)) continue
|
|
525
|
+
|
|
526
|
+
this.externalTransformsByKind.set(kind, transform)
|
|
527
|
+
|
|
528
|
+
const factoryNames = new Set<string>()
|
|
529
|
+
for (const entry of transform.imports) {
|
|
530
|
+
factoryNames.add(entry.rootExport)
|
|
531
|
+
|
|
532
|
+
let rootExports = this.externalDirectCallKindsBySource.get(
|
|
533
|
+
entry.libName,
|
|
534
|
+
)
|
|
535
|
+
if (!rootExports) {
|
|
536
|
+
rootExports = new Map()
|
|
537
|
+
this.externalDirectCallKindsBySource.set(entry.libName, rootExports)
|
|
538
|
+
}
|
|
539
|
+
rootExports.set(entry.rootExport, kind)
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
this.externalLookupSetup.set(kind, {
|
|
543
|
+
type: 'directCall',
|
|
544
|
+
factoryNames,
|
|
545
|
+
})
|
|
546
|
+
}
|
|
372
547
|
}
|
|
373
548
|
|
|
374
549
|
/**
|
|
@@ -446,6 +621,46 @@ export class StartCompiler {
|
|
|
446
621
|
return this.options.mode ?? 'dev'
|
|
447
622
|
}
|
|
448
623
|
|
|
624
|
+
private getExternalDirectCallCandidates(
|
|
625
|
+
kinds: Set<LookupKind>,
|
|
626
|
+
moduleInfo: ModuleInfo,
|
|
627
|
+
): ExternalDirectCallCandidates {
|
|
628
|
+
const identifiers = new Map<string, ExternalLookupKind>()
|
|
629
|
+
const namespaces = new Map<string, Map<string, ExternalLookupKind>>()
|
|
630
|
+
|
|
631
|
+
if (this.externalDirectCallKindsBySource.size === 0) {
|
|
632
|
+
return { identifiers, namespaces }
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
for (const [localName, binding] of moduleInfo.bindings) {
|
|
636
|
+
if (binding.type !== 'import') continue
|
|
637
|
+
|
|
638
|
+
const rootExports = this.externalDirectCallKindsBySource.get(
|
|
639
|
+
binding.source,
|
|
640
|
+
)
|
|
641
|
+
if (!rootExports) continue
|
|
642
|
+
|
|
643
|
+
if (binding.importedName === '*') {
|
|
644
|
+
const namespaceExports = new Map<string, ExternalLookupKind>()
|
|
645
|
+
for (const [rootExport, kind] of rootExports) {
|
|
646
|
+
if (kinds.has(kind)) {
|
|
647
|
+
namespaceExports.set(rootExport, kind)
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
if (namespaceExports.size > 0) {
|
|
651
|
+
namespaces.set(localName, namespaceExports)
|
|
652
|
+
}
|
|
653
|
+
} else {
|
|
654
|
+
const kind = rootExports.get(binding.importedName)
|
|
655
|
+
if (kind && kinds.has(kind)) {
|
|
656
|
+
identifiers.set(localName, kind)
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
return { identifiers, namespaces }
|
|
662
|
+
}
|
|
663
|
+
|
|
449
664
|
private async resolveIdCached(id: string, importer?: string) {
|
|
450
665
|
if (this.mode === 'dev') {
|
|
451
666
|
return this.options.resolveId(id, importer)
|
|
@@ -482,7 +697,7 @@ export class StartCompiler {
|
|
|
482
697
|
]),
|
|
483
698
|
)
|
|
484
699
|
|
|
485
|
-
// Register start-client-core exports for internal package usage
|
|
700
|
+
// Register start-client-core exports for internal package usage.
|
|
486
701
|
// These don't need module resolution - only the knownRootImports fast path.
|
|
487
702
|
this.knownRootImports.set(
|
|
488
703
|
'@tanstack/start-client-core',
|
|
@@ -506,8 +721,8 @@ export class StartCompiler {
|
|
|
506
721
|
// For JSX lookups (e.g., ClientOnlyJSX), we only need the knownRootImports
|
|
507
722
|
// fast path to verify imports. Skip synthetic root module setup.
|
|
508
723
|
if (config.kind !== 'Root') {
|
|
509
|
-
const setup =
|
|
510
|
-
if (setup
|
|
724
|
+
const setup = getLookupSetup(config.kind, this.externalLookupSetup)
|
|
725
|
+
if (setup?.type === 'jsx') {
|
|
511
726
|
continue
|
|
512
727
|
}
|
|
513
728
|
}
|
|
@@ -780,7 +995,12 @@ export class StartCompiler {
|
|
|
780
995
|
return null
|
|
781
996
|
}
|
|
782
997
|
|
|
783
|
-
const
|
|
998
|
+
const hasExternalKinds = hasExternalLookupKinds(fileKinds)
|
|
999
|
+
const checkDirectCalls =
|
|
1000
|
+
hasBuiltInDirectCallKinds(fileKinds) ||
|
|
1001
|
+
(fileKinds.has('ServerFn') &&
|
|
1002
|
+
!hasExternalKinds &&
|
|
1003
|
+
hasBuiltInDirectCallKinds(this.validLookupKinds))
|
|
784
1004
|
// Optimization: ServerFn is always a top-level declaration (must be assigned to a variable).
|
|
785
1005
|
// If the file only has ServerFn, we can skip full AST traversal and only visit
|
|
786
1006
|
// the specific top-level declarations that have candidates.
|
|
@@ -793,7 +1013,7 @@ export class StartCompiler {
|
|
|
793
1013
|
// Single-pass traversal to:
|
|
794
1014
|
// 1. Collect candidate paths (only candidates, not all CallExpressions)
|
|
795
1015
|
// 2. Build a map for looking up paths of nested calls in method chains
|
|
796
|
-
const candidatePaths: Array<
|
|
1016
|
+
const candidatePaths: Array<CallExpressionCandidate> = []
|
|
797
1017
|
// Map for nested chain lookup - only populated for CallExpressions that are
|
|
798
1018
|
// part of a method chain (callee.object is a CallExpression)
|
|
799
1019
|
const chainCallPaths = new Map<
|
|
@@ -803,9 +1023,16 @@ export class StartCompiler {
|
|
|
803
1023
|
|
|
804
1024
|
// JSX candidates (e.g., <ClientOnly>)
|
|
805
1025
|
const jsxCandidatePaths: Array<babel.NodePath<t.JSXElement>> = []
|
|
806
|
-
const checkJSX = needsJSXDetection(fileKinds)
|
|
1026
|
+
const checkJSX = needsJSXDetection(fileKinds, this.externalLookupSetup)
|
|
807
1027
|
// Get module info that was just cached by ingestModule
|
|
808
1028
|
const moduleInfo = this.moduleCache.get(id)!
|
|
1029
|
+
const externalDirectCallCandidates = this.getExternalDirectCallCandidates(
|
|
1030
|
+
fileKinds,
|
|
1031
|
+
moduleInfo,
|
|
1032
|
+
)
|
|
1033
|
+
const checkExternalDirectCalls = hasExternalDirectCallCandidates(
|
|
1034
|
+
externalDirectCallCandidates,
|
|
1035
|
+
)
|
|
809
1036
|
|
|
810
1037
|
if (canUseFastPath) {
|
|
811
1038
|
// Fast path: only visit top-level statements that have potential candidates
|
|
@@ -829,7 +1056,8 @@ export class StartCompiler {
|
|
|
829
1056
|
if (decl.init && t.isCallExpression(decl.init)) {
|
|
830
1057
|
if (
|
|
831
1058
|
isMethodChainCandidate(decl.init, fileKinds) ||
|
|
832
|
-
|
|
1059
|
+
(checkDirectCalls &&
|
|
1060
|
+
isTopLevelDirectCallCandidateNode(decl.init))
|
|
833
1061
|
) {
|
|
834
1062
|
candidateIndices.push(i)
|
|
835
1063
|
break // Only need to mark this statement once
|
|
@@ -870,12 +1098,23 @@ export class StartCompiler {
|
|
|
870
1098
|
|
|
871
1099
|
// Method chain pattern
|
|
872
1100
|
if (isMethodChainCandidate(node, fileKinds)) {
|
|
873
|
-
candidatePaths.push(path)
|
|
1101
|
+
candidatePaths.push({ path })
|
|
874
1102
|
return
|
|
875
1103
|
}
|
|
876
1104
|
|
|
1105
|
+
if (checkExternalDirectCalls) {
|
|
1106
|
+
const kind = getExternalDirectCallCandidateKind(
|
|
1107
|
+
path,
|
|
1108
|
+
externalDirectCallCandidates,
|
|
1109
|
+
)
|
|
1110
|
+
if (kind) {
|
|
1111
|
+
candidatePaths.push({ path, kind })
|
|
1112
|
+
return
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
|
|
877
1116
|
if (isTopLevelDirectCallCandidate(path)) {
|
|
878
|
-
candidatePaths.push(path)
|
|
1117
|
+
candidatePaths.push({ path })
|
|
879
1118
|
}
|
|
880
1119
|
},
|
|
881
1120
|
})
|
|
@@ -904,19 +1143,39 @@ export class StartCompiler {
|
|
|
904
1143
|
|
|
905
1144
|
// Pattern 1: Method chain pattern (.handler(), .server(), .client(), etc.)
|
|
906
1145
|
if (isMethodChainCandidate(node, fileKinds)) {
|
|
907
|
-
candidatePaths.push(path)
|
|
1146
|
+
candidatePaths.push({ path })
|
|
908
1147
|
return
|
|
909
1148
|
}
|
|
910
1149
|
|
|
911
|
-
|
|
912
|
-
|
|
1150
|
+
// External direct-call transforms are import-bound. Direct imports
|
|
1151
|
+
// already identify the transform kind, so skip async import tracing.
|
|
1152
|
+
if (checkExternalDirectCalls) {
|
|
1153
|
+
const kind = getExternalDirectCallCandidateKind(
|
|
1154
|
+
path,
|
|
1155
|
+
externalDirectCallCandidates,
|
|
1156
|
+
)
|
|
1157
|
+
if (kind) {
|
|
1158
|
+
candidatePaths.push({ path, kind })
|
|
1159
|
+
return
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
if (checkDirectCalls && isTopLevelDirectCallCandidate(path)) {
|
|
1164
|
+
candidatePaths.push({ path })
|
|
913
1165
|
return
|
|
914
1166
|
}
|
|
915
1167
|
|
|
916
1168
|
// Pattern 2: Direct call pattern
|
|
917
1169
|
if (checkDirectCalls) {
|
|
918
|
-
if (
|
|
919
|
-
|
|
1170
|
+
if (
|
|
1171
|
+
isNestedDirectCallCandidate(
|
|
1172
|
+
node,
|
|
1173
|
+
fileKinds,
|
|
1174
|
+
this.externalLookupSetup,
|
|
1175
|
+
)
|
|
1176
|
+
) {
|
|
1177
|
+
candidatePaths.push({ path })
|
|
1178
|
+
return
|
|
920
1179
|
}
|
|
921
1180
|
}
|
|
922
1181
|
},
|
|
@@ -955,13 +1214,34 @@ export class StartCompiler {
|
|
|
955
1214
|
return null
|
|
956
1215
|
}
|
|
957
1216
|
|
|
958
|
-
// Resolve
|
|
959
|
-
const resolvedCandidates
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
1217
|
+
// Resolve only candidates whose import scan did not already prove the kind.
|
|
1218
|
+
const resolvedCandidates: Array<{
|
|
1219
|
+
path: babel.NodePath<t.CallExpression>
|
|
1220
|
+
kind: Kind
|
|
1221
|
+
}> = []
|
|
1222
|
+
const unresolvedCandidates: Array<CallExpressionCandidate> = []
|
|
1223
|
+
|
|
1224
|
+
for (const candidate of candidatePaths) {
|
|
1225
|
+
if (candidate.kind) {
|
|
1226
|
+
resolvedCandidates.push({
|
|
1227
|
+
path: candidate.path,
|
|
1228
|
+
kind: candidate.kind,
|
|
1229
|
+
})
|
|
1230
|
+
} else {
|
|
1231
|
+
unresolvedCandidates.push(candidate)
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
if (unresolvedCandidates.length > 0) {
|
|
1236
|
+
resolvedCandidates.push(
|
|
1237
|
+
...(await Promise.all(
|
|
1238
|
+
unresolvedCandidates.map(async (candidate) => ({
|
|
1239
|
+
path: candidate.path,
|
|
1240
|
+
kind: await this.resolveExprKind(candidate.path.node, id),
|
|
1241
|
+
})),
|
|
1242
|
+
)),
|
|
1243
|
+
)
|
|
1244
|
+
}
|
|
965
1245
|
|
|
966
1246
|
// Filter to valid candidates
|
|
967
1247
|
const validCandidates = resolvedCandidates.filter(({ path, kind }) => {
|
|
@@ -976,7 +1256,7 @@ export class StartCompiler {
|
|
|
976
1256
|
kind !== 'ClientOnlyJSX' &&
|
|
977
1257
|
!isMethodChainCandidate(path.node, fileKinds)
|
|
978
1258
|
) {
|
|
979
|
-
return isDirectCallCandidateForKind(kind)
|
|
1259
|
+
return isDirectCallCandidateForKind(kind, this.externalLookupSetup)
|
|
980
1260
|
}
|
|
981
1261
|
|
|
982
1262
|
return true
|
|
@@ -1062,9 +1342,16 @@ export class StartCompiler {
|
|
|
1062
1342
|
root: this.options.root,
|
|
1063
1343
|
framework: this.options.framework,
|
|
1064
1344
|
providerEnvName: this.options.providerEnvName,
|
|
1345
|
+
types: t,
|
|
1346
|
+
parseExpression: (expressionCode) =>
|
|
1347
|
+
babel.template.expression(expressionCode, {
|
|
1348
|
+
placeholderPattern: false,
|
|
1349
|
+
})() as t.Expression,
|
|
1065
1350
|
|
|
1066
1351
|
generateFunctionId: (opts) => this.generateFunctionId(opts),
|
|
1067
1352
|
getKnownServerFns: this.options.getKnownServerFns,
|
|
1353
|
+
serverFnProviderModuleDirectives:
|
|
1354
|
+
this.options.serverFnProviderModuleDirectives,
|
|
1068
1355
|
onServerFnsById: this.options.onServerFnsById,
|
|
1069
1356
|
}
|
|
1070
1357
|
|
|
@@ -1084,12 +1371,19 @@ export class StartCompiler {
|
|
|
1084
1371
|
}
|
|
1085
1372
|
}
|
|
1086
1373
|
|
|
1087
|
-
//
|
|
1088
|
-
|
|
1089
|
-
|
|
1374
|
+
// External transforms run before built-ins by default so they can augment
|
|
1375
|
+
// user handlers before server function extraction clones provider bodies.
|
|
1376
|
+
this.runExternalTransforms('pre', candidatesByKind, context)
|
|
1377
|
+
|
|
1378
|
+
for (const kind of BuiltInKindHandlerOrder) {
|
|
1379
|
+
const candidates = candidatesByKind.get(kind)
|
|
1380
|
+
if (!candidates) continue
|
|
1381
|
+
const handler = BuiltInKindHandlers[kind]
|
|
1090
1382
|
handler(candidates, context, kind)
|
|
1091
1383
|
}
|
|
1092
1384
|
|
|
1385
|
+
this.runExternalTransforms('post', candidatesByKind, context)
|
|
1386
|
+
|
|
1093
1387
|
// Handle JSX candidates (e.g., <ClientOnly>)
|
|
1094
1388
|
// Validation was already done during traversal - just call the handler
|
|
1095
1389
|
for (const jsxPath of jsxCandidatePaths) {
|
|
@@ -1116,6 +1410,24 @@ export class StartCompiler {
|
|
|
1116
1410
|
return result
|
|
1117
1411
|
}
|
|
1118
1412
|
|
|
1413
|
+
private runExternalTransforms(
|
|
1414
|
+
order: 'pre' | 'post',
|
|
1415
|
+
candidatesByKind: Map<
|
|
1416
|
+
Exclude<LookupKind, 'ClientOnlyJSX'>,
|
|
1417
|
+
Array<RewriteCandidate>
|
|
1418
|
+
>,
|
|
1419
|
+
context: CompilationContext,
|
|
1420
|
+
) {
|
|
1421
|
+
for (const [kind, transform] of this.externalTransformsByKind) {
|
|
1422
|
+
if ((transform.order ?? 'pre') !== order) continue
|
|
1423
|
+
|
|
1424
|
+
const candidates = candidatesByKind.get(kind)
|
|
1425
|
+
if (!candidates) continue
|
|
1426
|
+
|
|
1427
|
+
transform.transform(candidates, context)
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1119
1431
|
private async resolveIdentifierKind(
|
|
1120
1432
|
ident: string,
|
|
1121
1433
|
id: string,
|
|
@@ -1293,7 +1605,8 @@ export class StartCompiler {
|
|
|
1293
1605
|
// `const createSO = createServerOnlyFn` should still propagate the kind.
|
|
1294
1606
|
if (
|
|
1295
1607
|
isLookupKind(resolvedKind) &&
|
|
1296
|
-
|
|
1608
|
+
getLookupSetup(resolvedKind, this.externalLookupSetup)?.type ===
|
|
1609
|
+
'directCall' &&
|
|
1297
1610
|
binding.init &&
|
|
1298
1611
|
t.isCallExpression(binding.init)
|
|
1299
1612
|
) {
|
|
@@ -1420,6 +1733,12 @@ export class StartCompiler {
|
|
|
1420
1733
|
binding.type === 'import' &&
|
|
1421
1734
|
binding.importedName === '*'
|
|
1422
1735
|
) {
|
|
1736
|
+
const knownExports = this.knownRootImports.get(binding.source)
|
|
1737
|
+
const knownKind = knownExports?.get(callee.property.name)
|
|
1738
|
+
if (knownKind) {
|
|
1739
|
+
return knownKind
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1423
1742
|
// resolve the property from the target module
|
|
1424
1743
|
const targetModuleId = await this.resolveIdCached(
|
|
1425
1744
|
binding.source,
|