@tanstack/start-plugin-core 1.143.4 → 1.143.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/dist/esm/plugin.js +11 -46
  2. package/dist/esm/plugin.js.map +1 -1
  3. package/dist/esm/{create-server-fn-plugin → start-compiler-plugin}/compiler.d.ts +39 -4
  4. package/dist/esm/{create-server-fn-plugin → start-compiler-plugin}/compiler.js +82 -24
  5. package/dist/esm/start-compiler-plugin/compiler.js.map +1 -0
  6. package/dist/esm/start-compiler-plugin/handleClientOnlyJSX.js.map +1 -0
  7. package/dist/esm/start-compiler-plugin/handleCreateIsomorphicFn.d.ts +8 -0
  8. package/dist/esm/start-compiler-plugin/handleCreateIsomorphicFn.js +33 -0
  9. package/dist/esm/start-compiler-plugin/handleCreateIsomorphicFn.js.map +1 -0
  10. package/dist/esm/start-compiler-plugin/handleCreateMiddleware.d.ts +8 -0
  11. package/dist/esm/start-compiler-plugin/handleCreateMiddleware.js +25 -0
  12. package/dist/esm/start-compiler-plugin/handleCreateMiddleware.js.map +1 -0
  13. package/dist/esm/start-compiler-plugin/handleCreateServerFn.d.ts +19 -0
  14. package/dist/esm/start-compiler-plugin/handleCreateServerFn.js +262 -0
  15. package/dist/esm/start-compiler-plugin/handleCreateServerFn.js.map +1 -0
  16. package/dist/esm/start-compiler-plugin/handleEnvOnly.d.ts +10 -0
  17. package/dist/esm/start-compiler-plugin/handleEnvOnly.js +38 -0
  18. package/dist/esm/start-compiler-plugin/handleEnvOnly.js.map +1 -0
  19. package/dist/esm/start-compiler-plugin/plugin.d.ts +19 -0
  20. package/dist/esm/start-compiler-plugin/plugin.js +314 -0
  21. package/dist/esm/start-compiler-plugin/plugin.js.map +1 -0
  22. package/dist/esm/start-compiler-plugin/types.d.ts +116 -0
  23. package/dist/esm/start-compiler-plugin/utils.d.ts +23 -0
  24. package/dist/esm/start-compiler-plugin/utils.js +34 -0
  25. package/dist/esm/start-compiler-plugin/utils.js.map +1 -0
  26. package/dist/esm/types.d.ts +0 -1
  27. package/package.json +3 -4
  28. package/src/plugin.ts +10 -50
  29. package/src/{create-server-fn-plugin → start-compiler-plugin}/compiler.ts +162 -30
  30. package/src/start-compiler-plugin/handleCreateIsomorphicFn.ts +54 -0
  31. package/src/start-compiler-plugin/handleCreateMiddleware.ts +39 -0
  32. package/src/start-compiler-plugin/handleCreateServerFn.ts +491 -0
  33. package/src/start-compiler-plugin/handleEnvOnly.ts +56 -0
  34. package/src/start-compiler-plugin/plugin.ts +423 -0
  35. package/src/start-compiler-plugin/types.ts +133 -0
  36. package/src/start-compiler-plugin/utils.ts +52 -0
  37. package/src/types.ts +0 -1
  38. package/dist/esm/create-server-fn-plugin/compiler.js.map +0 -1
  39. package/dist/esm/create-server-fn-plugin/handleClientOnlyJSX.js.map +0 -1
  40. package/dist/esm/create-server-fn-plugin/handleCreateIsomorphicFn.d.ts +0 -4
  41. package/dist/esm/create-server-fn-plugin/handleCreateIsomorphicFn.js +0 -31
  42. package/dist/esm/create-server-fn-plugin/handleCreateIsomorphicFn.js.map +0 -1
  43. package/dist/esm/create-server-fn-plugin/handleCreateMiddleware.d.ts +0 -10
  44. package/dist/esm/create-server-fn-plugin/handleCreateMiddleware.js +0 -29
  45. package/dist/esm/create-server-fn-plugin/handleCreateMiddleware.js.map +0 -1
  46. package/dist/esm/create-server-fn-plugin/handleCreateServerFn.d.ts +0 -17
  47. package/dist/esm/create-server-fn-plugin/handleCreateServerFn.js +0 -82
  48. package/dist/esm/create-server-fn-plugin/handleCreateServerFn.js.map +0 -1
  49. package/dist/esm/create-server-fn-plugin/handleEnvOnly.d.ts +0 -6
  50. package/dist/esm/create-server-fn-plugin/handleEnvOnly.js +0 -36
  51. package/dist/esm/create-server-fn-plugin/handleEnvOnly.js.map +0 -1
  52. package/dist/esm/create-server-fn-plugin/plugin.d.ts +0 -10
  53. package/dist/esm/create-server-fn-plugin/plugin.js +0 -186
  54. package/dist/esm/create-server-fn-plugin/plugin.js.map +0 -1
  55. package/dist/esm/create-server-fn-plugin/types.d.ts +0 -30
  56. package/dist/esm/create-server-fn-plugin/utils.d.ts +0 -10
  57. package/dist/esm/create-server-fn-plugin/utils.js +0 -19
  58. package/dist/esm/create-server-fn-plugin/utils.js.map +0 -1
  59. package/src/create-server-fn-plugin/handleCreateIsomorphicFn.ts +0 -46
  60. package/src/create-server-fn-plugin/handleCreateMiddleware.ts +0 -45
  61. package/src/create-server-fn-plugin/handleCreateServerFn.ts +0 -145
  62. package/src/create-server-fn-plugin/handleEnvOnly.ts +0 -45
  63. package/src/create-server-fn-plugin/plugin.ts +0 -234
  64. package/src/create-server-fn-plugin/types.ts +0 -34
  65. package/src/create-server-fn-plugin/utils.ts +0 -24
  66. /package/dist/esm/{create-server-fn-plugin → start-compiler-plugin}/handleClientOnlyJSX.d.ts +0 -0
  67. /package/dist/esm/{create-server-fn-plugin → start-compiler-plugin}/handleClientOnlyJSX.js +0 -0
  68. /package/src/{create-server-fn-plugin → start-compiler-plugin}/handleClientOnlyJSX.ts +0 -0
@@ -0,0 +1,491 @@
1
+ import * as t from '@babel/types'
2
+ import babel from '@babel/core'
3
+ import path from 'pathe'
4
+ import { VITE_ENVIRONMENT_NAMES } from '../constants'
5
+ import { cleanId, codeFrameError, stripMethodCall } from './utils'
6
+ import type { CompilationContext, RewriteCandidate, ServerFn } from './types'
7
+ import type { CompileStartFrameworkOptions } from '../types'
8
+
9
+ const TSS_SERVERFN_SPLIT_PARAM = 'tss-serverfn-split'
10
+
11
+ // ============================================================================
12
+ // Pre-compiled babel templates (compiled once at module load time)
13
+ // ============================================================================
14
+
15
+ // Template for provider files: createServerRpc('id', fn)
16
+ const serverRpcTemplate = babel.template.expression(
17
+ `createServerRpc(%%functionId%%, %%fn%%)`,
18
+ )
19
+
20
+ // Template for client caller files: createClientRpc('id')
21
+ const clientRpcTemplate = babel.template.expression(
22
+ `createClientRpc(%%functionId%%)`,
23
+ )
24
+
25
+ // Template for SSR caller files (manifest lookup): createSsrRpc('id')
26
+ const ssrRpcManifestTemplate = babel.template.expression(
27
+ `createSsrRpc(%%functionId%%)`,
28
+ )
29
+
30
+ // Template for SSR caller files (with importer): createSsrRpc('id', () => import(...).then(m => m['name']))
31
+ const ssrRpcImporterTemplate = babel.template.expression(
32
+ `createSsrRpc(%%functionId%%, () => import(%%extractedFilename%%).then(m => m[%%functionName%%]))`,
33
+ )
34
+
35
+ // ============================================================================
36
+ // Runtime code cache (cached per framework to avoid repeated AST generation)
37
+ // ============================================================================
38
+
39
+ type RuntimeCodeType = 'provider' | 'client' | 'ssr'
40
+ type FrameworkRuntimeCache = Record<RuntimeCodeType, t.Statement>
41
+ const RuntimeCodeCache = new Map<
42
+ CompileStartFrameworkOptions,
43
+ FrameworkRuntimeCache
44
+ >()
45
+
46
+ function getCachedRuntimeCode(
47
+ framework: CompileStartFrameworkOptions,
48
+ type: RuntimeCodeType,
49
+ ): t.Statement {
50
+ let cache = RuntimeCodeCache.get(framework)
51
+ if (!cache) {
52
+ cache = {
53
+ provider: babel.template.ast(
54
+ `import { createServerRpc } from '@tanstack/${framework}-start/server-rpc'`,
55
+ { placeholderPattern: false },
56
+ ) as t.Statement,
57
+ client: babel.template.ast(
58
+ `import { createClientRpc } from '@tanstack/${framework}-start/client-rpc'`,
59
+ { placeholderPattern: false },
60
+ ) as t.Statement,
61
+ ssr: babel.template.ast(
62
+ `import { createSsrRpc } from '@tanstack/${framework}-start/ssr-rpc'`,
63
+ { placeholderPattern: false },
64
+ ) as t.Statement,
65
+ }
66
+ RuntimeCodeCache.set(framework, cache)
67
+ }
68
+ return cache[type]
69
+ }
70
+
71
+ /**
72
+ * Environment-specific configuration for server function transformation.
73
+ * This is computed internally based on the compilation context.
74
+ */
75
+ interface EnvConfig {
76
+ /** Whether this environment is a client environment */
77
+ isClientEnvironment: boolean
78
+ /** Whether SSR is the provider environment */
79
+ ssrIsProvider: boolean
80
+ /** The runtime code type to use for imports */
81
+ runtimeCodeType: RuntimeCodeType
82
+ }
83
+
84
+ /**
85
+ * Gets the environment configuration for the current compilation context.
86
+ */
87
+ function getEnvConfig(
88
+ context: CompilationContext,
89
+ isProviderFile: boolean,
90
+ ): EnvConfig {
91
+ const { providerEnvName, env } = context
92
+
93
+ // SSR is the provider when the provider environment is the default server environment
94
+ const ssrIsProvider = providerEnvName === VITE_ENVIRONMENT_NAMES.server
95
+
96
+ if (isProviderFile) {
97
+ return {
98
+ isClientEnvironment: false,
99
+ ssrIsProvider,
100
+ runtimeCodeType: 'provider',
101
+ }
102
+ }
103
+
104
+ if (env === 'client') {
105
+ return {
106
+ isClientEnvironment: true,
107
+ ssrIsProvider,
108
+ runtimeCodeType: 'client',
109
+ }
110
+ }
111
+
112
+ // Server caller (SSR)
113
+ return {
114
+ isClientEnvironment: false,
115
+ ssrIsProvider,
116
+ runtimeCodeType: 'ssr',
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Generates the RPC stub expression for provider files.
122
+ * Uses pre-compiled template for performance.
123
+ */
124
+ function generateProviderRpcStub(
125
+ functionId: string,
126
+ fn: t.Expression,
127
+ ): t.Expression {
128
+ return serverRpcTemplate({
129
+ functionId: t.stringLiteral(functionId),
130
+ fn,
131
+ })
132
+ }
133
+
134
+ /**
135
+ * Generates the RPC stub expression for caller files.
136
+ * Uses pre-compiled templates for performance.
137
+ */
138
+ function generateCallerRpcStub(
139
+ functionId: string,
140
+ functionName: string,
141
+ extractedFilename: string,
142
+ isClientReferenced: boolean,
143
+ envConfig: EnvConfig,
144
+ ): t.Expression {
145
+ if (envConfig.runtimeCodeType === 'client') {
146
+ return clientRpcTemplate({
147
+ functionId: t.stringLiteral(functionId),
148
+ })
149
+ }
150
+
151
+ // SSR caller
152
+ // When the function is client-referenced, it's in the manifest - use manifest lookup
153
+ // When SSR is NOT the provider, always use manifest lookup (no import() for different env)
154
+ // Otherwise, use the importer for functions only referenced on the server when SSR is the provider
155
+ if (isClientReferenced || !envConfig.ssrIsProvider) {
156
+ return ssrRpcManifestTemplate({
157
+ functionId: t.stringLiteral(functionId),
158
+ })
159
+ }
160
+
161
+ return ssrRpcImporterTemplate({
162
+ functionId: t.stringLiteral(functionId),
163
+ extractedFilename: t.stringLiteral(extractedFilename),
164
+ functionName: t.stringLiteral(functionName),
165
+ })
166
+ }
167
+
168
+ /**
169
+ * Handles createServerFn transformations for a batch of candidates.
170
+ *
171
+ * This function performs extraction and replacement of server functions
172
+ *
173
+ * For caller files (non-provider):
174
+ * - Replaces the server function with an RPC stub
175
+ * - Does not include the handler function body
176
+ *
177
+ * For provider files:
178
+ * - Creates an extractedFn that calls __executeServer
179
+ * - Modifies .handler() to pass (extractedFn, serverFn) - two arguments
180
+ *
181
+ * @param candidates - All ServerFn candidates to process
182
+ * @param context - The compilation context with helpers and mutable state
183
+ * @returns Result containing runtime code to add, or null if no candidates processed
184
+ */
185
+ export function handleCreateServerFn(
186
+ candidates: Array<RewriteCandidate>,
187
+ context: CompilationContext,
188
+ ) {
189
+ if (candidates.length === 0) {
190
+ return
191
+ }
192
+
193
+ const isProviderFile = context.id.includes(TSS_SERVERFN_SPLIT_PARAM)
194
+ // Get environment-specific configuration
195
+ const envConfig = getEnvConfig(context, isProviderFile)
196
+
197
+ // Track function names to ensure uniqueness within this file
198
+ const functionNameSet = new Set<string>()
199
+
200
+ const exportNames = new Set<string>()
201
+ const serverFnsById: Record<string, ServerFn> = {}
202
+
203
+ const [baseFilename] = context.id.split('?') as [string]
204
+ const extractedFilename = `${baseFilename}?${TSS_SERVERFN_SPLIT_PARAM}`
205
+ const relativeFilename = path.relative(context.root, baseFilename)
206
+ const knownFns = context.getKnownServerFns()
207
+ const cleanedContextId = cleanId(context.id)
208
+
209
+ for (const candidate of candidates) {
210
+ const { path: candidatePath, methodChain } = candidate
211
+ const { inputValidator, handler } = methodChain
212
+
213
+ // Check if the call is assigned to a variable
214
+ if (!candidatePath.parentPath.isVariableDeclarator()) {
215
+ throw new Error('createServerFn must be assigned to a variable!')
216
+ }
217
+
218
+ // Get the identifier name of the variable
219
+ const variableDeclarator = candidatePath.parentPath.node
220
+ if (!t.isIdentifier(variableDeclarator.id)) {
221
+ throw codeFrameError(
222
+ context.code,
223
+ variableDeclarator.id.loc!,
224
+ 'createServerFn must be assigned to a simple identifier, not a destructuring pattern',
225
+ )
226
+ }
227
+ const existingVariableName = variableDeclarator.id.name
228
+
229
+ // Generate unique function name with _createServerFn_handler suffix
230
+ // The function name is derived from the variable name
231
+ let functionName = `${existingVariableName}_createServerFn_handler`
232
+ while (functionNameSet.has(functionName)) {
233
+ functionName = incrementFunctionNameVersion(functionName)
234
+ }
235
+ functionNameSet.add(functionName)
236
+
237
+ // Generate function ID using pre-computed relative filename
238
+ const functionId = context.generateFunctionId({
239
+ filename: relativeFilename,
240
+ functionName,
241
+ extractedFilename,
242
+ })
243
+
244
+ // Check if this function was already discovered by the client build
245
+ const knownFn = knownFns[functionId]
246
+ const isClientReferenced = envConfig.isClientEnvironment || !!knownFn
247
+
248
+ // Use canonical extracted filename from known functions if available
249
+ const canonicalExtractedFilename =
250
+ knownFn?.extractedFilename ?? extractedFilename
251
+
252
+ // Handle input validator - remove on client
253
+ if (inputValidator) {
254
+ const innerInputExpression = inputValidator.callPath.node.arguments[0]
255
+
256
+ if (!innerInputExpression) {
257
+ throw new Error(
258
+ 'createServerFn().inputValidator() must be called with a validator!',
259
+ )
260
+ }
261
+
262
+ // If we're on the client, remove the validator call expression
263
+ if (context.env === 'client') {
264
+ stripMethodCall(inputValidator.callPath)
265
+ }
266
+ }
267
+
268
+ const handlerFnPath = handler?.firstArgPath
269
+
270
+ if (!handler || !handlerFnPath?.node) {
271
+ throw codeFrameError(
272
+ context.code,
273
+ candidatePath.node.callee.loc!,
274
+ `createServerFn must be called with a "handler" property!`,
275
+ )
276
+ }
277
+
278
+ // Validate the handler argument is an expression (not a SpreadElement, etc.)
279
+ if (!t.isExpression(handlerFnPath.node)) {
280
+ throw codeFrameError(
281
+ context.code,
282
+ handlerFnPath.node.loc!,
283
+ `handler() must be called with an expression, not a ${handlerFnPath.node.type}`,
284
+ )
285
+ }
286
+
287
+ const handlerFn = handlerFnPath.node
288
+
289
+ // Register function only from caller files (not provider files)
290
+ // to avoid duplicates - provider files process the same functions
291
+
292
+ if (!isProviderFile) {
293
+ serverFnsById[functionId] = {
294
+ functionName,
295
+ functionId,
296
+ filename: cleanedContextId,
297
+ extractedFilename: canonicalExtractedFilename,
298
+ isClientReferenced,
299
+ }
300
+ }
301
+
302
+ if (isProviderFile) {
303
+ // PROVIDER FILE: This is the extracted file that contains the actual implementation
304
+ // We need to:
305
+ // 1. Create an extractedFn that calls __executeServer
306
+ // 2. Modify .handler() to pass (extractedFn, serverFn) - two arguments
307
+ //
308
+ // Expected output format:
309
+ // const extractedFn = createServerRpc("id", (opts) => varName.__executeServer(opts));
310
+ // const varName = createServerFn().handler(extractedFn, originalHandler);
311
+
312
+ // Build the arrow function: (opts, signal) => varName.__executeServer(opts, signal)
313
+ // The signal parameter is passed through to allow abort signal propagation
314
+ const executeServerArrowFn = t.arrowFunctionExpression(
315
+ [t.identifier('opts'), t.identifier('signal')],
316
+ t.callExpression(
317
+ t.memberExpression(
318
+ t.identifier(existingVariableName),
319
+ t.identifier('__executeServer'),
320
+ ),
321
+ [t.identifier('opts'), t.identifier('signal')],
322
+ ),
323
+ )
324
+
325
+ // Generate the replacement using pre-compiled template
326
+ const extractedFnInit = generateProviderRpcStub(
327
+ functionId,
328
+ executeServerArrowFn,
329
+ )
330
+
331
+ // Build the extracted function statement
332
+ const extractedFnStatement = t.variableDeclaration('const', [
333
+ t.variableDeclarator(t.identifier(functionName), extractedFnInit),
334
+ ])
335
+
336
+ // Find the variable declaration statement containing our createServerFn
337
+ const variableDeclaration = candidatePath.parentPath.parentPath
338
+ if (!variableDeclaration.isVariableDeclaration()) {
339
+ throw new Error(
340
+ 'Expected createServerFn to be in a VariableDeclaration',
341
+ )
342
+ }
343
+
344
+ // Insert the extracted function statement before the variable declaration
345
+ variableDeclaration.insertBefore(extractedFnStatement)
346
+
347
+ // Modify the .handler() call to pass two arguments: (extractedFn, serverFn)
348
+ // The handlerFnPath.node contains the original serverFn
349
+ const extractedFnIdentifier = t.identifier(functionName)
350
+ const serverFnNode = t.cloneNode(handlerFn, true)
351
+
352
+ // Replace handler's arguments with [extractedFn, serverFn]
353
+ handler.callPath.node.arguments = [extractedFnIdentifier, serverFnNode]
354
+
355
+ // Only export the extracted handler (e.g., myFn_createServerFn_handler)
356
+ // The manifest and all import paths only look up this suffixed name.
357
+ // The original variable (e.g., myFn) stays in the file but is not exported
358
+ // since it's only used internally.
359
+ exportNames.add(functionName)
360
+ } else {
361
+ // CALLER FILE: This file calls the server function but doesn't contain the implementation
362
+ // We need to:
363
+ // 1. Remove the handler function body (it will be in the provider file)
364
+ // 2. Replace the handler argument with an RPC stub
365
+ //
366
+ // IMPORTANT: We must keep the createServerFn().handler(extractedFn) structure
367
+ // so that the client middleware chain can unwrap the {result, error, context} response.
368
+ //
369
+ // Expected output format:
370
+ // const myFn = createServerFn().handler(createClientRpc("id"))
371
+ // or
372
+ // const myFn = createServerFn().handler(createSsrRpc("id", () => import(...)))
373
+
374
+ // If the handler function is an identifier, we need to remove the bound function
375
+ // from the file since it won't be needed
376
+ if (t.isIdentifier(handlerFn)) {
377
+ const binding = handlerFnPath.scope.getBinding(handlerFn.name)
378
+ if (binding) {
379
+ binding.path.remove()
380
+ }
381
+ }
382
+
383
+ // Generate the RPC stub using pre-compiled templates
384
+ const rpcStub = generateCallerRpcStub(
385
+ functionId,
386
+ functionName,
387
+ canonicalExtractedFilename,
388
+ isClientReferenced,
389
+ envConfig,
390
+ )
391
+
392
+ // Replace ONLY the handler argument with the RPC stub
393
+ // Keep the createServerFn().handler() wrapper intact for client middleware
394
+ handlerFnPath.replaceWith(rpcStub)
395
+ }
396
+ }
397
+
398
+ // For provider files, add exports for all extracted functions
399
+ if (isProviderFile) {
400
+ // Remove all existing exports first
401
+ safeRemoveExports(context.ast)
402
+
403
+ // Export all server function related variables from exportNames
404
+ // These were populated by handleCreateServerFn:
405
+ // 1. Extracted handlers: const fn_createServerFn_handler = createServerRpc(...)
406
+ // 2. Original variables: const fn = createServerFn().handler(...)
407
+ if (exportNames.size > 0) {
408
+ context.ast.program.body.push(
409
+ t.exportNamedDeclaration(
410
+ undefined,
411
+ Array.from(exportNames).map((name) =>
412
+ t.exportSpecifier(t.identifier(name), t.identifier(name)),
413
+ ),
414
+ ),
415
+ )
416
+ }
417
+ }
418
+
419
+ // Notify about discovered functions (only for non-provider files)
420
+ if (
421
+ !isProviderFile &&
422
+ Object.keys(serverFnsById).length > 0 &&
423
+ context.onServerFnsById
424
+ ) {
425
+ context.onServerFnsById(serverFnsById)
426
+ }
427
+
428
+ // Add runtime import using cached AST node
429
+ const runtimeCode = getCachedRuntimeCode(
430
+ context.framework,
431
+ envConfig.runtimeCodeType,
432
+ )
433
+ context.ast.program.body.unshift(t.cloneNode(runtimeCode))
434
+ }
435
+
436
+ /**
437
+ * Makes an identifier safe for use as a JavaScript identifier.
438
+ */
439
+ function makeIdentifierSafe(identifier: string): string {
440
+ return identifier
441
+ .replace(/[^a-zA-Z0-9_$]/g, '_') // Replace unsafe chars with underscore
442
+ .replace(/^[0-9]/, '_$&') // Prefix leading number with underscore
443
+ .replace(/^\$/, '_$') // Prefix leading $ with underscore
444
+ .replace(/_{2,}/g, '_') // Collapse multiple underscores
445
+ .replace(/^_|_$/g, '') // Trim leading/trailing underscores
446
+ }
447
+
448
+ /**
449
+ * Increments the version number suffix on a function name.
450
+ */
451
+ function incrementFunctionNameVersion(functionName: string): string {
452
+ const [realReferenceName, count] = functionName.split(/_(\d+)$/)
453
+ const resolvedCount = Number(count || '0')
454
+ const suffix = `_${resolvedCount + 1}`
455
+ return makeIdentifierSafe(realReferenceName!) + suffix
456
+ }
457
+
458
+ /**
459
+ * Removes all exports from the AST while preserving the declarations.
460
+ * Used for provider files where we want to re-export only the server functions.
461
+ */
462
+ function safeRemoveExports(ast: t.File): void {
463
+ ast.program.body = ast.program.body.flatMap((node) => {
464
+ if (
465
+ t.isExportNamedDeclaration(node) ||
466
+ t.isExportDefaultDeclaration(node)
467
+ ) {
468
+ if (
469
+ t.isFunctionDeclaration(node.declaration) ||
470
+ t.isClassDeclaration(node.declaration) ||
471
+ t.isVariableDeclaration(node.declaration)
472
+ ) {
473
+ // do not remove export if it is an anonymous function / class,
474
+ // otherwise this would produce a syntax error
475
+ if (
476
+ t.isFunctionDeclaration(node.declaration) ||
477
+ t.isClassDeclaration(node.declaration)
478
+ ) {
479
+ if (!node.declaration.id) {
480
+ return node
481
+ }
482
+ }
483
+ return node.declaration
484
+ } else if (node.declaration === null) {
485
+ // remove e.g. `export { RouteComponent as component }`
486
+ return []
487
+ }
488
+ }
489
+ return node
490
+ })
491
+ }
@@ -0,0 +1,56 @@
1
+ import * as t from '@babel/types'
2
+ import type { CompilationContext, RewriteCandidate } from './types'
3
+ import type { LookupKind } from './compiler'
4
+
5
+ function capitalize(str: string) {
6
+ if (!str) return ''
7
+ return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase()
8
+ }
9
+
10
+ /**
11
+ * Handles serverOnly/clientOnly function transformations for a batch of candidates.
12
+ *
13
+ * @param candidates - All EnvOnly candidates to process (all same kind)
14
+ * @param context - The compilation context
15
+ * @param kind - The specific kind (ServerOnlyFn or ClientOnlyFn)
16
+ */
17
+ export function handleEnvOnlyFn(
18
+ candidates: Array<RewriteCandidate>,
19
+ context: CompilationContext,
20
+ kind: LookupKind,
21
+ ): void {
22
+ const targetEnv = kind === 'ClientOnlyFn' ? 'client' : 'server'
23
+
24
+ for (const candidate of candidates) {
25
+ const { path } = candidate
26
+
27
+ if (context.env === targetEnv) {
28
+ // Matching environment - extract the inner function
29
+ const innerFn = path.node.arguments[0]
30
+
31
+ if (!t.isExpression(innerFn)) {
32
+ throw new Error(
33
+ `create${capitalize(targetEnv)}OnlyFn() must be called with a function!`,
34
+ )
35
+ }
36
+
37
+ path.replaceWith(innerFn)
38
+ } else {
39
+ // Wrong environment - replace with a function that throws an error
40
+ path.replaceWith(
41
+ t.arrowFunctionExpression(
42
+ [],
43
+ t.blockStatement([
44
+ t.throwStatement(
45
+ t.newExpression(t.identifier('Error'), [
46
+ t.stringLiteral(
47
+ `create${capitalize(targetEnv)}OnlyFn() functions can only be called on the ${targetEnv}!`,
48
+ ),
49
+ ]),
50
+ ),
51
+ ]),
52
+ ),
53
+ )
54
+ }
55
+ }
56
+ }