@tanstack/start-plugin-core 1.132.0-alpha.6 → 1.132.0-alpha.8

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 (36) hide show
  1. package/dist/esm/start-compiler-plugin/compilers.d.ts +0 -6
  2. package/dist/esm/start-compiler-plugin/compilers.js +12 -271
  3. package/dist/esm/start-compiler-plugin/compilers.js.map +1 -1
  4. package/dist/esm/start-compiler-plugin/constants.d.ts +1 -1
  5. package/dist/esm/start-compiler-plugin/constants.js +2 -2
  6. package/dist/esm/start-compiler-plugin/constants.js.map +1 -1
  7. package/dist/esm/start-compiler-plugin/envOnly.d.ts +5 -0
  8. package/dist/esm/start-compiler-plugin/envOnly.js +41 -0
  9. package/dist/esm/start-compiler-plugin/envOnly.js.map +1 -0
  10. package/dist/esm/start-compiler-plugin/isomorphicFn.d.ts +4 -0
  11. package/dist/esm/start-compiler-plugin/isomorphicFn.js +49 -0
  12. package/dist/esm/start-compiler-plugin/isomorphicFn.js.map +1 -0
  13. package/dist/esm/start-compiler-plugin/middleware.d.ts +4 -0
  14. package/dist/esm/start-compiler-plugin/middleware.js +51 -0
  15. package/dist/esm/start-compiler-plugin/middleware.js.map +1 -0
  16. package/dist/esm/start-compiler-plugin/plugin.js +59 -26
  17. package/dist/esm/start-compiler-plugin/plugin.js.map +1 -1
  18. package/dist/esm/start-compiler-plugin/serverFileRoute.d.ts +4 -0
  19. package/dist/esm/start-compiler-plugin/serverFileRoute.js +38 -0
  20. package/dist/esm/start-compiler-plugin/serverFileRoute.js.map +1 -0
  21. package/dist/esm/start-compiler-plugin/serverFn.d.ts +4 -0
  22. package/dist/esm/start-compiler-plugin/serverFn.js +87 -0
  23. package/dist/esm/start-compiler-plugin/serverFn.js.map +1 -0
  24. package/dist/esm/start-compiler-plugin/utils.d.ts +13 -0
  25. package/dist/esm/start-compiler-plugin/utils.js +30 -0
  26. package/dist/esm/start-compiler-plugin/utils.js.map +1 -0
  27. package/package.json +5 -5
  28. package/src/start-compiler-plugin/compilers.ts +16 -462
  29. package/src/start-compiler-plugin/constants.ts +2 -2
  30. package/src/start-compiler-plugin/envOnly.ts +58 -0
  31. package/src/start-compiler-plugin/isomorphicFn.ts +78 -0
  32. package/src/start-compiler-plugin/middleware.ts +79 -0
  33. package/src/start-compiler-plugin/plugin.ts +67 -36
  34. package/src/start-compiler-plugin/serverFileRoute.ts +59 -0
  35. package/src/start-compiler-plugin/serverFn.ts +163 -0
  36. package/src/start-compiler-plugin/utils.ts +41 -0
@@ -1,6 +1,5 @@
1
1
  import * as babel from '@babel/core'
2
2
  import * as t from '@babel/types'
3
- import { codeFrameColumns } from '@babel/code-frame'
4
3
 
5
4
  import {
6
5
  deadCodeElimination,
@@ -8,6 +7,14 @@ import {
8
7
  } from 'babel-dead-code-elimination'
9
8
  import { generateFromAst, parseAst } from '@tanstack/router-utils'
10
9
  import { transformFuncs } from './constants'
10
+ import { handleCreateServerFileRouteCallExpressionFactory } from './serverFileRoute'
11
+ import { handleCreateIsomorphicFnCallExpression } from './isomorphicFn'
12
+ import { handleCreateMiddlewareCallExpression } from './middleware'
13
+ import { handleCreateServerFnCallExpression } from './serverFn'
14
+ import {
15
+ handleCreateClientOnlyFnCallExpression,
16
+ handleCreateServerOnlyFnCallExpression,
17
+ } from './envOnly'
11
18
  import type { GeneratorResult, ParseAstOptions } from '@tanstack/router-utils'
12
19
 
13
20
  export type CompileStartFrameworkOptions = 'react' | 'solid'
@@ -50,14 +57,14 @@ const getIdentifiers = (
50
57
  handleCallExpression: handleCreateMiddlewareCallExpression,
51
58
  paths: [],
52
59
  },
53
- serverOnly: {
54
- name: 'serverOnly',
55
- handleCallExpression: handleServerOnlyCallExpression,
60
+ createServerOnlyFn: {
61
+ name: 'createServerOnlyFn',
62
+ handleCallExpression: handleCreateServerOnlyFnCallExpression,
56
63
  paths: [],
57
64
  },
58
- clientOnly: {
59
- name: 'clientOnly',
60
- handleCallExpression: handleClientOnlyCallExpression,
65
+ createClientOnlyFn: {
66
+ name: 'createClientOnlyFn',
67
+ handleCallExpression: handleCreateClientOnlyFnCallExpression,
61
68
  paths: [],
62
69
  },
63
70
  createIsomorphicFn: {
@@ -131,6 +138,8 @@ export function compileStartOutputFactory(
131
138
  return identifiers[identifierKey].paths.push(path)
132
139
  }
133
140
 
141
+ // handle namespace imports like "import * as TanStackStart from '@tanstack/react-start';"
142
+ // which are then called like "TanStackStart.createServerFn()"
134
143
  if (t.isMemberExpression(path.node.callee)) {
135
144
  if (
136
145
  t.isIdentifier(path.node.callee.object) &&
@@ -176,67 +185,6 @@ export function compileStartOutputFactory(
176
185
  }
177
186
  }
178
187
 
179
- function handleCreateServerFileRouteCallExpressionFactory(
180
- framework: CompileStartFrameworkOptions,
181
- method:
182
- | 'createServerFileRoute'
183
- | 'createServerRoute'
184
- | 'createServerRootRoute',
185
- ) {
186
- return function handleCreateServerFileRouteCallExpression(
187
- path: babel.NodePath<t.CallExpression>,
188
- opts: CompileOptions,
189
- ) {
190
- const PACKAGES = { start: `@tanstack/${framework}-start/server` }
191
-
192
- let highestParent: babel.NodePath<any> = path
193
-
194
- while (highestParent.parentPath && !highestParent.parentPath.isProgram()) {
195
- highestParent = highestParent.parentPath
196
- }
197
-
198
- const programPath = highestParent.parentPath as babel.NodePath<t.Program>
199
-
200
- // If we're on the client, remove the entire variable
201
- if (opts.env === 'client') {
202
- highestParent.remove()
203
- return
204
- }
205
-
206
- let isCreateServerFileRouteImported = false as boolean
207
-
208
- programPath.traverse({
209
- ImportDeclaration(importPath) {
210
- const importSource = importPath.node.source.value
211
- if (importSource === PACKAGES.start) {
212
- const specifiers = importPath.node.specifiers
213
- isCreateServerFileRouteImported ||= specifiers.some((specifier) => {
214
- return (
215
- t.isImportSpecifier(specifier) &&
216
- t.isIdentifier(specifier.imported) &&
217
- specifier.imported.name === method
218
- )
219
- })
220
- }
221
- },
222
- })
223
-
224
- if (!isCreateServerFileRouteImported) {
225
- const importDeclaration = t.importDeclaration(
226
- [t.importSpecifier(t.identifier(method), t.identifier(method))],
227
- t.stringLiteral(PACKAGES.start),
228
- )
229
- programPath.node.body.unshift(importDeclaration)
230
- }
231
- }
232
- }
233
-
234
- // build these once and reuse them
235
- export const handleServerOnlyCallExpression =
236
- buildEnvOnlyCallExpressionHandler('server')
237
- export const handleClientOnlyCallExpression =
238
- buildEnvOnlyCallExpressionHandler('client')
239
-
240
188
  export type CompileOptions = ParseAstOptions & {
241
189
  env: 'server' | 'client'
242
190
  dce?: boolean
@@ -251,397 +199,3 @@ export type IdentifierConfig = {
251
199
  ) => void
252
200
  paths: Array<babel.NodePath>
253
201
  }
254
-
255
- export function handleCreateServerFnCallExpression(
256
- path: babel.NodePath<t.CallExpression>,
257
- opts: CompileOptions,
258
- ) {
259
- // The function is the 'fn' property of the object passed to createServerFn
260
-
261
- // const firstArg = path.node.arguments[0]
262
- // if (t.isObjectExpression(firstArg)) {
263
- // // Was called with some options
264
- // }
265
-
266
- // Traverse the member expression and find the call expressions for
267
- // the validator, handler, and middleware methods. Check to make sure they
268
- // are children of the createServerFn call expression.
269
-
270
- const calledOptions = path.node.arguments[0]
271
- ? (path.get('arguments.0') as babel.NodePath<t.ObjectExpression>)
272
- : null
273
-
274
- const shouldValidateClient = !!calledOptions?.node.properties.find((prop) => {
275
- return (
276
- t.isObjectProperty(prop) &&
277
- t.isIdentifier(prop.key) &&
278
- prop.key.name === 'validateClient' &&
279
- t.isBooleanLiteral(prop.value) &&
280
- prop.value.value === true
281
- )
282
- })
283
-
284
- const callExpressionPaths = {
285
- middleware: null as babel.NodePath<t.CallExpression> | null,
286
- validator: null as babel.NodePath<t.CallExpression> | null,
287
- handler: null as babel.NodePath<t.CallExpression> | null,
288
- }
289
-
290
- const validMethods = Object.keys(callExpressionPaths)
291
-
292
- const rootCallExpression = getRootCallExpression(path)
293
-
294
- // if (debug)
295
- // console.info(
296
- // 'Handling createServerFn call expression:',
297
- // rootCallExpression.toString(),
298
- // )
299
-
300
- // Check if the call is assigned to a variable
301
- if (!rootCallExpression.parentPath.isVariableDeclarator()) {
302
- throw new Error('createServerFn must be assigned to a variable!')
303
- }
304
-
305
- // Get the identifier name of the variable
306
- const variableDeclarator = rootCallExpression.parentPath.node
307
- const existingVariableName = (variableDeclarator.id as t.Identifier).name
308
-
309
- rootCallExpression.traverse({
310
- MemberExpression(memberExpressionPath) {
311
- if (t.isIdentifier(memberExpressionPath.node.property)) {
312
- const name = memberExpressionPath.node.property
313
- .name as keyof typeof callExpressionPaths
314
-
315
- if (
316
- validMethods.includes(name) &&
317
- memberExpressionPath.parentPath.isCallExpression()
318
- ) {
319
- callExpressionPaths[name] = memberExpressionPath.parentPath
320
- }
321
- }
322
- },
323
- })
324
-
325
- if (callExpressionPaths.validator) {
326
- const innerInputExpression = callExpressionPaths.validator.node.arguments[0]
327
-
328
- if (!innerInputExpression) {
329
- throw new Error(
330
- 'createServerFn().validator() must be called with a validator!',
331
- )
332
- }
333
-
334
- // If we're on the client, and we're not validating the client, remove the validator call expression
335
- if (
336
- opts.env === 'client' &&
337
- !shouldValidateClient &&
338
- t.isMemberExpression(callExpressionPaths.validator.node.callee)
339
- ) {
340
- callExpressionPaths.validator.replaceWith(
341
- callExpressionPaths.validator.node.callee.object,
342
- )
343
- }
344
- }
345
-
346
- // First, we need to move the handler function to a nested function call
347
- // that is applied to the arguments passed to the server function.
348
-
349
- const handlerFnPath = callExpressionPaths.handler?.get(
350
- 'arguments.0',
351
- ) as babel.NodePath<any>
352
-
353
- if (!callExpressionPaths.handler || !handlerFnPath.node) {
354
- throw codeFrameError(
355
- opts.code,
356
- path.node.callee.loc!,
357
- `createServerFn must be called with a "handler" property!`,
358
- )
359
- }
360
-
361
- const handlerFn = handlerFnPath.node
362
-
363
- // So, the way we do this is we give the handler function a way
364
- // to access the serverFn ctx on the server via function scope.
365
- // The 'use server' extracted function will be called with the
366
- // payload from the client, then use the scoped serverFn ctx
367
- // to execute the handler function.
368
- // This way, we can do things like data and middleware validation
369
- // in the __execute function without having to AST transform the
370
- // handler function too much itself.
371
-
372
- // .handler((optsOut, ctx) => {
373
- // return ((optsIn) => {
374
- // 'use server'
375
- // ctx.__execute(handlerFn, optsIn)
376
- // })(optsOut)
377
- // })
378
-
379
- // If the handler function is an identifier and we're on the client, we need to
380
- // remove the bound function from the file.
381
- // If we're on the server, you can leave it, since it will get referenced
382
- // as a second argument.
383
-
384
- if (t.isIdentifier(handlerFn)) {
385
- if (opts.env === 'client') {
386
- // Find the binding for the handler function
387
- const binding = handlerFnPath.scope.getBinding(handlerFn.name)
388
- // Remove it
389
- if (binding) {
390
- binding.path.remove()
391
- }
392
- }
393
- // If the env is server, just leave it alone
394
- }
395
-
396
- handlerFnPath.replaceWith(
397
- t.arrowFunctionExpression(
398
- [t.identifier('opts'), t.identifier('signal')],
399
- t.blockStatement(
400
- // Everything in here is server-only, since the client
401
- // will strip out anything in the 'use server' directive.
402
- [
403
- t.returnStatement(
404
- t.callExpression(
405
- t.identifier(`${existingVariableName}.__executeServer`),
406
- [t.identifier('opts'), t.identifier('signal')],
407
- ),
408
- ),
409
- ],
410
- [t.directive(t.directiveLiteral('use server'))],
411
- ),
412
- ),
413
- )
414
-
415
- if (opts.env === 'server') {
416
- callExpressionPaths.handler.node.arguments.push(handlerFn)
417
- }
418
- }
419
-
420
- export function handleCreateMiddlewareCallExpression(
421
- path: babel.NodePath<t.CallExpression>,
422
- opts: CompileOptions,
423
- ) {
424
- const rootCallExpression = getRootCallExpression(path)
425
-
426
- // if (debug)
427
- // console.info(
428
- // 'Handling createMiddleware call expression:',
429
- // rootCallExpression.toString(),
430
- // )
431
-
432
- const callExpressionPaths = {
433
- middleware: null as babel.NodePath<t.CallExpression> | null,
434
- validator: null as babel.NodePath<t.CallExpression> | null,
435
- client: null as babel.NodePath<t.CallExpression> | null,
436
- server: null as babel.NodePath<t.CallExpression> | null,
437
- }
438
-
439
- const validMethods = Object.keys(callExpressionPaths)
440
-
441
- rootCallExpression.traverse({
442
- MemberExpression(memberExpressionPath) {
443
- if (t.isIdentifier(memberExpressionPath.node.property)) {
444
- const name = memberExpressionPath.node.property
445
- .name as keyof typeof callExpressionPaths
446
-
447
- if (
448
- validMethods.includes(name) &&
449
- memberExpressionPath.parentPath.isCallExpression()
450
- ) {
451
- callExpressionPaths[name] = memberExpressionPath.parentPath
452
- }
453
- }
454
- },
455
- })
456
-
457
- if (callExpressionPaths.validator) {
458
- const innerInputExpression = callExpressionPaths.validator.node.arguments[0]
459
-
460
- if (!innerInputExpression) {
461
- throw new Error(
462
- 'createMiddleware().validator() must be called with a validator!',
463
- )
464
- }
465
-
466
- // If we're on the client, remove the validator call expression
467
- if (opts.env === 'client') {
468
- if (t.isMemberExpression(callExpressionPaths.validator.node.callee)) {
469
- callExpressionPaths.validator.replaceWith(
470
- callExpressionPaths.validator.node.callee.object,
471
- )
472
- }
473
- }
474
- }
475
-
476
- const serverFnPath = callExpressionPaths.server?.get(
477
- 'arguments.0',
478
- ) as babel.NodePath<any>
479
-
480
- if (
481
- callExpressionPaths.server &&
482
- serverFnPath.node &&
483
- opts.env === 'client'
484
- ) {
485
- // If we're on the client, remove the server call expression
486
- if (t.isMemberExpression(callExpressionPaths.server.node.callee)) {
487
- callExpressionPaths.server.replaceWith(
488
- callExpressionPaths.server.node.callee.object,
489
- )
490
- }
491
- }
492
- }
493
-
494
- function buildEnvOnlyCallExpressionHandler(env: 'client' | 'server') {
495
- return function envOnlyCallExpressionHandler(
496
- path: babel.NodePath<t.CallExpression>,
497
- opts: CompileOptions,
498
- ) {
499
- // if (debug)
500
- // console.info(`Handling ${env}Only call expression:`, path.toString())
501
-
502
- const isEnvMatch =
503
- env === 'client' ? opts.env === 'client' : opts.env === 'server'
504
-
505
- if (isEnvMatch) {
506
- // extract the inner function from the call expression
507
- const innerInputExpression = path.node.arguments[0]
508
-
509
- if (!t.isExpression(innerInputExpression)) {
510
- throw new Error(
511
- `${env}Only() functions must be called with a function!`,
512
- )
513
- }
514
-
515
- path.replaceWith(innerInputExpression)
516
- return
517
- }
518
-
519
- // If we're on the wrong environment, replace the call expression
520
- // with a function that always throws an error.
521
- path.replaceWith(
522
- t.arrowFunctionExpression(
523
- [],
524
- t.blockStatement([
525
- t.throwStatement(
526
- t.newExpression(t.identifier('Error'), [
527
- t.stringLiteral(
528
- `${env}Only() functions can only be called on the ${env}!`,
529
- ),
530
- ]),
531
- ),
532
- ]),
533
- ),
534
- )
535
- }
536
- }
537
-
538
- export function handleCreateIsomorphicFnCallExpression(
539
- path: babel.NodePath<t.CallExpression>,
540
- opts: CompileOptions,
541
- ) {
542
- const rootCallExpression = getRootCallExpression(path)
543
-
544
- // if (debug)
545
- // console.info(
546
- // 'Handling createIsomorphicFn call expression:',
547
- // rootCallExpression.toString(),
548
- // )
549
-
550
- const callExpressionPaths = {
551
- client: null as babel.NodePath<t.CallExpression> | null,
552
- server: null as babel.NodePath<t.CallExpression> | null,
553
- }
554
-
555
- const validMethods = Object.keys(callExpressionPaths)
556
-
557
- rootCallExpression.traverse({
558
- MemberExpression(memberExpressionPath) {
559
- if (t.isIdentifier(memberExpressionPath.node.property)) {
560
- const name = memberExpressionPath.node.property
561
- .name as keyof typeof callExpressionPaths
562
-
563
- if (
564
- validMethods.includes(name) &&
565
- memberExpressionPath.parentPath.isCallExpression()
566
- ) {
567
- callExpressionPaths[name] = memberExpressionPath.parentPath
568
- }
569
- }
570
- },
571
- })
572
-
573
- if (
574
- validMethods.every(
575
- (method) =>
576
- !callExpressionPaths[method as keyof typeof callExpressionPaths],
577
- )
578
- ) {
579
- const variableId = rootCallExpression.parentPath.isVariableDeclarator()
580
- ? rootCallExpression.parentPath.node.id
581
- : null
582
- console.warn(
583
- 'createIsomorphicFn called without a client or server implementation!',
584
- 'This will result in a no-op function.',
585
- 'Variable name:',
586
- t.isIdentifier(variableId) ? variableId.name : 'unknown',
587
- )
588
- }
589
-
590
- const envCallExpression = callExpressionPaths[opts.env]
591
-
592
- if (!envCallExpression) {
593
- // if we don't have an implementation for this environment, default to a no-op
594
- rootCallExpression.replaceWith(
595
- t.arrowFunctionExpression([], t.blockStatement([])),
596
- )
597
- return
598
- }
599
-
600
- const innerInputExpression = envCallExpression.node.arguments[0]
601
-
602
- if (!t.isExpression(innerInputExpression)) {
603
- throw new Error(
604
- `createIsomorphicFn().${opts.env}(func) must be called with a function!`,
605
- )
606
- }
607
-
608
- rootCallExpression.replaceWith(innerInputExpression)
609
- }
610
-
611
- export function getRootCallExpression(path: babel.NodePath<t.CallExpression>) {
612
- // Find the highest callExpression parent
613
- let rootCallExpression: babel.NodePath<t.CallExpression> = path
614
-
615
- // Traverse up the chain of CallExpressions
616
- while (rootCallExpression.parentPath.isMemberExpression()) {
617
- const parent = rootCallExpression.parentPath
618
- if (parent.parentPath.isCallExpression()) {
619
- rootCallExpression = parent.parentPath
620
- }
621
- }
622
-
623
- return rootCallExpression
624
- }
625
-
626
- function codeFrameError(
627
- code: string,
628
- loc: {
629
- start: { line: number; column: number }
630
- end: { line: number; column: number }
631
- },
632
- message: string,
633
- ) {
634
- const frame = codeFrameColumns(
635
- code,
636
- {
637
- start: loc.start,
638
- end: loc.end,
639
- },
640
- {
641
- highlightCode: true,
642
- message,
643
- },
644
- )
645
-
646
- return new Error(frame)
647
- }
@@ -1,8 +1,8 @@
1
1
  export const transformFuncs = [
2
2
  'createServerFn',
3
3
  'createMiddleware',
4
- 'serverOnly',
5
- 'clientOnly',
4
+ 'createServerOnlyFn',
5
+ 'createClientOnlyFn',
6
6
  'createIsomorphicFn',
7
7
  'createServerRoute',
8
8
  'createServerFileRoute',
@@ -0,0 +1,58 @@
1
+ import * as t from '@babel/types'
2
+ import type * as babel from '@babel/core'
3
+
4
+ import type { CompileOptions } from './compilers'
5
+
6
+ function capitalize(str: string) {
7
+ if (!str) return ''
8
+ return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase()
9
+ }
10
+
11
+ function buildEnvOnlyCallExpressionHandler(env: 'client' | 'server') {
12
+ return function envOnlyCallExpressionHandler(
13
+ path: babel.NodePath<t.CallExpression>,
14
+ opts: CompileOptions,
15
+ ) {
16
+ // if (debug)
17
+ // console.info(`Handling ${env}Only call expression:`, path.toString())
18
+
19
+ const isEnvMatch =
20
+ env === 'client' ? opts.env === 'client' : opts.env === 'server'
21
+
22
+ if (isEnvMatch) {
23
+ // extract the inner function from the call expression
24
+ const innerInputExpression = path.node.arguments[0]
25
+
26
+ if (!t.isExpression(innerInputExpression)) {
27
+ throw new Error(
28
+ `${env}Only() functions must be called with a function!`,
29
+ )
30
+ }
31
+
32
+ path.replaceWith(innerInputExpression)
33
+ return
34
+ }
35
+
36
+ // If we're on the wrong environment, replace the call expression
37
+ // with a function that always throws an error.
38
+ path.replaceWith(
39
+ t.arrowFunctionExpression(
40
+ [],
41
+ t.blockStatement([
42
+ t.throwStatement(
43
+ t.newExpression(t.identifier('Error'), [
44
+ t.stringLiteral(
45
+ `create${capitalize(env)}OnlyFn() functions can only be called on the ${env}!`,
46
+ ),
47
+ ]),
48
+ ),
49
+ ]),
50
+ ),
51
+ )
52
+ }
53
+ }
54
+
55
+ export const handleCreateServerOnlyFnCallExpression =
56
+ buildEnvOnlyCallExpressionHandler('server')
57
+ export const handleCreateClientOnlyFnCallExpression =
58
+ buildEnvOnlyCallExpressionHandler('client')
@@ -0,0 +1,78 @@
1
+ import * as t from '@babel/types'
2
+ import { getRootCallExpression } from './utils'
3
+ import type * as babel from '@babel/core'
4
+
5
+ import type { CompileOptions } from './compilers'
6
+
7
+ export function handleCreateIsomorphicFnCallExpression(
8
+ path: babel.NodePath<t.CallExpression>,
9
+ opts: CompileOptions,
10
+ ) {
11
+ const rootCallExpression = getRootCallExpression(path)
12
+
13
+ // if (debug)
14
+ // console.info(
15
+ // 'Handling createIsomorphicFn call expression:',
16
+ // rootCallExpression.toString(),
17
+ // )
18
+
19
+ const callExpressionPaths = {
20
+ client: null as babel.NodePath<t.CallExpression> | null,
21
+ server: null as babel.NodePath<t.CallExpression> | null,
22
+ }
23
+
24
+ const validMethods = Object.keys(callExpressionPaths)
25
+
26
+ rootCallExpression.traverse({
27
+ MemberExpression(memberExpressionPath) {
28
+ if (t.isIdentifier(memberExpressionPath.node.property)) {
29
+ const name = memberExpressionPath.node.property
30
+ .name as keyof typeof callExpressionPaths
31
+
32
+ if (
33
+ validMethods.includes(name) &&
34
+ memberExpressionPath.parentPath.isCallExpression()
35
+ ) {
36
+ callExpressionPaths[name] = memberExpressionPath.parentPath
37
+ }
38
+ }
39
+ },
40
+ })
41
+
42
+ if (
43
+ validMethods.every(
44
+ (method) =>
45
+ !callExpressionPaths[method as keyof typeof callExpressionPaths],
46
+ )
47
+ ) {
48
+ const variableId = rootCallExpression.parentPath.isVariableDeclarator()
49
+ ? rootCallExpression.parentPath.node.id
50
+ : null
51
+ console.warn(
52
+ 'createIsomorphicFn called without a client or server implementation!',
53
+ 'This will result in a no-op function.',
54
+ 'Variable name:',
55
+ t.isIdentifier(variableId) ? variableId.name : 'unknown',
56
+ )
57
+ }
58
+
59
+ const envCallExpression = callExpressionPaths[opts.env]
60
+
61
+ if (!envCallExpression) {
62
+ // if we don't have an implementation for this environment, default to a no-op
63
+ rootCallExpression.replaceWith(
64
+ t.arrowFunctionExpression([], t.blockStatement([])),
65
+ )
66
+ return
67
+ }
68
+
69
+ const innerInputExpression = envCallExpression.node.arguments[0]
70
+
71
+ if (!t.isExpression(innerInputExpression)) {
72
+ throw new Error(
73
+ `createIsomorphicFn().${opts.env}(func) must be called with a function!`,
74
+ )
75
+ }
76
+
77
+ rootCallExpression.replaceWith(innerInputExpression)
78
+ }