@tanstack/start-plugin-core 1.20.3-alpha.1

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 (111) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +12 -0
  3. package/dist/cjs/compilers.cjs +402 -0
  4. package/dist/cjs/compilers.cjs.map +1 -0
  5. package/dist/cjs/compilers.d.cts +21 -0
  6. package/dist/cjs/extractHtmlScripts.cjs +35 -0
  7. package/dist/cjs/extractHtmlScripts.cjs.map +1 -0
  8. package/dist/cjs/extractHtmlScripts.d.cts +4 -0
  9. package/dist/cjs/index.cjs +15 -0
  10. package/dist/cjs/index.cjs.map +1 -0
  11. package/dist/cjs/index.d.cts +7 -0
  12. package/dist/cjs/nitro/build-nitro.cjs +18 -0
  13. package/dist/cjs/nitro/build-nitro.cjs.map +1 -0
  14. package/dist/cjs/nitro/build-nitro.d.cts +2 -0
  15. package/dist/cjs/nitro/build-sitemap.cjs +54 -0
  16. package/dist/cjs/nitro/build-sitemap.cjs.map +1 -0
  17. package/dist/cjs/nitro/build-sitemap.d.cts +9 -0
  18. package/dist/cjs/nitro/dev-server-plugin.cjs +128 -0
  19. package/dist/cjs/nitro/dev-server-plugin.cjs.map +1 -0
  20. package/dist/cjs/nitro/dev-server-plugin.d.cts +5 -0
  21. package/dist/cjs/nitro/nitro-plugin.cjs +128 -0
  22. package/dist/cjs/nitro/nitro-plugin.cjs.map +1 -0
  23. package/dist/cjs/nitro/nitro-plugin.d.cts +3 -0
  24. package/dist/cjs/plugin.cjs +117 -0
  25. package/dist/cjs/plugin.cjs.map +1 -0
  26. package/dist/cjs/plugin.d.cts +2713 -0
  27. package/dist/cjs/prerender.cjs +171 -0
  28. package/dist/cjs/prerender.cjs.map +1 -0
  29. package/dist/cjs/prerender.d.cts +8 -0
  30. package/dist/cjs/queue.cjs +131 -0
  31. package/dist/cjs/queue.cjs.map +1 -0
  32. package/dist/cjs/queue.d.cts +32 -0
  33. package/dist/cjs/routesManifestPlugin.cjs +165 -0
  34. package/dist/cjs/routesManifestPlugin.cjs.map +1 -0
  35. package/dist/cjs/routesManifestPlugin.d.cts +3 -0
  36. package/dist/cjs/schema.cjs +136 -0
  37. package/dist/cjs/schema.cjs.map +1 -0
  38. package/dist/cjs/schema.d.cts +8128 -0
  39. package/dist/cjs/start-compiler-plugin.cjs +72 -0
  40. package/dist/cjs/start-compiler-plugin.cjs.map +1 -0
  41. package/dist/cjs/start-compiler-plugin.d.cts +13 -0
  42. package/dist/cjs/start-server-routes-plugin/config.d.cts +49 -0
  43. package/dist/cjs/start-server-routes-plugin/plugin.cjs +608 -0
  44. package/dist/cjs/start-server-routes-plugin/plugin.cjs.map +1 -0
  45. package/dist/cjs/start-server-routes-plugin/plugin.d.cts +3 -0
  46. package/dist/cjs/start-server-routes-plugin/template.cjs +111 -0
  47. package/dist/cjs/start-server-routes-plugin/template.cjs.map +1 -0
  48. package/dist/cjs/start-server-routes-plugin/template.d.cts +34 -0
  49. package/dist/esm/compilers.d.ts +21 -0
  50. package/dist/esm/compilers.js +384 -0
  51. package/dist/esm/compilers.js.map +1 -0
  52. package/dist/esm/extractHtmlScripts.d.ts +4 -0
  53. package/dist/esm/extractHtmlScripts.js +18 -0
  54. package/dist/esm/extractHtmlScripts.js.map +1 -0
  55. package/dist/esm/index.d.ts +7 -0
  56. package/dist/esm/index.js +15 -0
  57. package/dist/esm/index.js.map +1 -0
  58. package/dist/esm/nitro/build-nitro.d.ts +2 -0
  59. package/dist/esm/nitro/build-nitro.js +18 -0
  60. package/dist/esm/nitro/build-nitro.js.map +1 -0
  61. package/dist/esm/nitro/build-sitemap.d.ts +9 -0
  62. package/dist/esm/nitro/build-sitemap.js +54 -0
  63. package/dist/esm/nitro/build-sitemap.js.map +1 -0
  64. package/dist/esm/nitro/dev-server-plugin.d.ts +5 -0
  65. package/dist/esm/nitro/dev-server-plugin.js +128 -0
  66. package/dist/esm/nitro/dev-server-plugin.js.map +1 -0
  67. package/dist/esm/nitro/nitro-plugin.d.ts +3 -0
  68. package/dist/esm/nitro/nitro-plugin.js +128 -0
  69. package/dist/esm/nitro/nitro-plugin.js.map +1 -0
  70. package/dist/esm/plugin.d.ts +2713 -0
  71. package/dist/esm/plugin.js +117 -0
  72. package/dist/esm/plugin.js.map +1 -0
  73. package/dist/esm/prerender.d.ts +8 -0
  74. package/dist/esm/prerender.js +171 -0
  75. package/dist/esm/prerender.js.map +1 -0
  76. package/dist/esm/queue.d.ts +32 -0
  77. package/dist/esm/queue.js +131 -0
  78. package/dist/esm/queue.js.map +1 -0
  79. package/dist/esm/routesManifestPlugin.d.ts +3 -0
  80. package/dist/esm/routesManifestPlugin.js +165 -0
  81. package/dist/esm/routesManifestPlugin.js.map +1 -0
  82. package/dist/esm/schema.d.ts +8128 -0
  83. package/dist/esm/schema.js +136 -0
  84. package/dist/esm/schema.js.map +1 -0
  85. package/dist/esm/start-compiler-plugin.d.ts +13 -0
  86. package/dist/esm/start-compiler-plugin.js +72 -0
  87. package/dist/esm/start-compiler-plugin.js.map +1 -0
  88. package/dist/esm/start-server-routes-plugin/config.d.ts +49 -0
  89. package/dist/esm/start-server-routes-plugin/plugin.d.ts +3 -0
  90. package/dist/esm/start-server-routes-plugin/plugin.js +608 -0
  91. package/dist/esm/start-server-routes-plugin/plugin.js.map +1 -0
  92. package/dist/esm/start-server-routes-plugin/template.d.ts +34 -0
  93. package/dist/esm/start-server-routes-plugin/template.js +111 -0
  94. package/dist/esm/start-server-routes-plugin/template.js.map +1 -0
  95. package/package.json +72 -0
  96. package/src/compilers.ts +759 -0
  97. package/src/extractHtmlScripts.ts +19 -0
  98. package/src/index.ts +15 -0
  99. package/src/nitro/build-nitro.ts +27 -0
  100. package/src/nitro/build-sitemap.ts +79 -0
  101. package/src/nitro/dev-server-plugin.ts +159 -0
  102. package/src/nitro/nitro-plugin.ts +161 -0
  103. package/src/plugin.ts +145 -0
  104. package/src/prerender.ts +245 -0
  105. package/src/queue.ts +153 -0
  106. package/src/routesManifestPlugin.ts +216 -0
  107. package/src/schema.ts +193 -0
  108. package/src/start-compiler-plugin.ts +111 -0
  109. package/src/start-server-routes-plugin/config.ts +8 -0
  110. package/src/start-server-routes-plugin/plugin.ts +890 -0
  111. package/src/start-server-routes-plugin/template.ts +164 -0
@@ -0,0 +1,759 @@
1
+ import * as babel from '@babel/core'
2
+ import * as t from '@babel/types'
3
+ import { codeFrameColumns } from '@babel/code-frame'
4
+
5
+ import {
6
+ deadCodeElimination,
7
+ findReferencedIdentifiers,
8
+ } from 'babel-dead-code-elimination'
9
+ import { generateFromAst, parseAst } from '@tanstack/router-utils'
10
+ import type { GeneratorResult, ParseAstOptions } from '@tanstack/router-utils'
11
+
12
+ export type CompileStartFrameworkOptions = 'react' | 'solid'
13
+
14
+ export function compileStartOutputFactory(
15
+ framework: CompileStartFrameworkOptions,
16
+ ) {
17
+ return function compileStartOutput(opts: CompileOptions): GeneratorResult {
18
+ const ast = parseAst(opts)
19
+
20
+ const doDce = opts.dce ?? true
21
+ // find referenced identifiers *before* we transform anything
22
+ const refIdents = doDce ? findReferencedIdentifiers(ast) : undefined
23
+
24
+ babel.traverse(ast, {
25
+ Program: {
26
+ enter(programPath) {
27
+ const identifiers: {
28
+ createServerFileRoute: IdentifierConfig
29
+ createServerFn: IdentifierConfig
30
+ createMiddleware: IdentifierConfig
31
+ serverOnly: IdentifierConfig
32
+ clientOnly: IdentifierConfig
33
+ createIsomorphicFn: IdentifierConfig
34
+ } = {
35
+ createServerFileRoute: {
36
+ name: 'createServerFileRoute',
37
+ handleCallExpression:
38
+ handleCreateServerFileRouteCallExpressionFactory(framework),
39
+ paths: [],
40
+ },
41
+ createServerFn: {
42
+ name: 'createServerFn',
43
+ handleCallExpression: handleCreateServerFnCallExpression,
44
+ paths: [],
45
+ },
46
+ createMiddleware: {
47
+ name: 'createMiddleware',
48
+ handleCallExpression: handleCreateMiddlewareCallExpression,
49
+ paths: [],
50
+ },
51
+ serverOnly: {
52
+ name: 'serverOnly',
53
+ handleCallExpression: handleServerOnlyCallExpression,
54
+ paths: [],
55
+ },
56
+ clientOnly: {
57
+ name: 'clientOnly',
58
+ handleCallExpression: handleClientOnlyCallExpression,
59
+ paths: [],
60
+ },
61
+ createIsomorphicFn: {
62
+ name: 'createIsomorphicFn',
63
+ handleCallExpression: handleCreateIsomorphicFnCallExpression,
64
+ paths: [],
65
+ },
66
+ }
67
+
68
+ const identifierKeys = Object.keys(identifiers) as Array<
69
+ keyof typeof identifiers
70
+ >
71
+
72
+ programPath.traverse({
73
+ ImportDeclaration: (path) => {
74
+ if (
75
+ path.node.source.value !== '@tanstack/react-start' &&
76
+ path.node.source.value !== '@tanstack/solid-start'
77
+ ) {
78
+ return
79
+ }
80
+
81
+ // handle a destructured imports being renamed like "import { createServerFn as myCreateServerFn } from '@tanstack/react-start';"
82
+ path.node.specifiers.forEach((specifier) => {
83
+ identifierKeys.forEach((identifierKey) => {
84
+ const identifier = identifiers[identifierKey]
85
+
86
+ if (
87
+ specifier.type === 'ImportSpecifier' &&
88
+ specifier.imported.type === 'Identifier'
89
+ ) {
90
+ if (specifier.imported.name === identifierKey) {
91
+ identifier.name = specifier.local.name
92
+ }
93
+ }
94
+
95
+ // handle namespace imports like "import * as TanStackStart from '@tanstack/react-start';"
96
+ if (specifier.type === 'ImportNamespaceSpecifier') {
97
+ identifier.name = `${specifier.local.name}.${identifierKey}`
98
+ }
99
+ })
100
+ })
101
+ },
102
+ CallExpression: (path) => {
103
+ identifierKeys.forEach((identifierKey) => {
104
+ // Check to see if the call expression is a call to the
105
+ // identifiers[identifierKey].name
106
+ if (
107
+ t.isIdentifier(path.node.callee) &&
108
+ path.node.callee.name === identifiers[identifierKey].name
109
+ ) {
110
+ // The identifier could be a call to the original function
111
+ // in the source code. If this is case, we need to ignore it.
112
+ // Check the scope to see if the identifier is a function declaration.
113
+ // if it is, then we can ignore it.
114
+
115
+ if (
116
+ path.scope.getBinding(identifiers[identifierKey].name)?.path
117
+ .node.type === 'FunctionDeclaration'
118
+ ) {
119
+ return
120
+ }
121
+
122
+ return identifiers[identifierKey].paths.push(path)
123
+ }
124
+
125
+ if (t.isMemberExpression(path.node.callee)) {
126
+ if (
127
+ t.isIdentifier(path.node.callee.object) &&
128
+ t.isIdentifier(path.node.callee.property)
129
+ ) {
130
+ const callname = [
131
+ path.node.callee.object.name,
132
+ path.node.callee.property.name,
133
+ ].join('.')
134
+
135
+ if (callname === identifiers[identifierKey].name) {
136
+ identifiers[identifierKey].paths.push(path)
137
+ }
138
+ }
139
+ }
140
+
141
+ return
142
+ })
143
+ },
144
+ })
145
+
146
+ identifierKeys.forEach((identifierKey) => {
147
+ identifiers[identifierKey].paths.forEach((path) => {
148
+ identifiers[identifierKey].handleCallExpression(
149
+ path as babel.NodePath<t.CallExpression>,
150
+ opts,
151
+ )
152
+ })
153
+ })
154
+ },
155
+ },
156
+ })
157
+
158
+ if (doDce) {
159
+ deadCodeElimination(ast, refIdents)
160
+ }
161
+
162
+ return generateFromAst(ast, {
163
+ sourceMaps: true,
164
+ sourceFileName: opts.filename,
165
+ filename: opts.filename,
166
+ })
167
+ }
168
+ }
169
+
170
+ function handleCreateServerFileRouteCallExpressionFactory(
171
+ factory: CompileStartFrameworkOptions,
172
+ ) {
173
+ return function handleCreateServerFileRouteCallExpression(
174
+ path: babel.NodePath<t.CallExpression>,
175
+ opts: CompileOptions,
176
+ ) {
177
+ const PACKAGES = { start: `@tanstack/${factory}-start/server` }
178
+
179
+ let highestParent: babel.NodePath<any> = path
180
+
181
+ while (highestParent.parentPath && !highestParent.parentPath.isProgram()) {
182
+ highestParent = highestParent.parentPath
183
+ }
184
+
185
+ const programPath = highestParent.parentPath as babel.NodePath<t.Program>
186
+
187
+ // // Find the root call expression and all of the methods that are called on it
188
+ // const rootCallExpression = getRootCallExpression(path)
189
+
190
+ // const callExpressionPaths = {
191
+ // validator: null as babel.NodePath<t.CallExpression> | null,
192
+ // middleware: null as babel.NodePath<t.CallExpression> | null,
193
+ // methods: null as babel.NodePath<t.CallExpression> | null,
194
+ // }
195
+
196
+ // const validMethods = Object.keys(callExpressionPaths)
197
+
198
+ // rootCallExpression.traverse({
199
+ // MemberExpression(memberExpressionPath) {
200
+ // if (t.isIdentifier(memberExpressionPath.node.property)) {
201
+ // const name = memberExpressionPath.node.property
202
+ // .name as keyof typeof callExpressionPaths
203
+
204
+ // if (
205
+ // validMethods.includes(name) &&
206
+ // memberExpressionPath.parentPath.isCallExpression()
207
+ // ) {
208
+ // callExpressionPaths[name] = memberExpressionPath.parentPath
209
+ // }
210
+ // }
211
+ // },
212
+ // })
213
+
214
+ // const manifest = { middleware: false, methods: {} as any }
215
+
216
+ // Object.entries(callExpressionPaths).forEach(([key, callPath]) => {
217
+ // if (callPath && t.isMemberExpression(callPath.node.callee)) {
218
+ // if (key === 'middleware') {
219
+ // manifest.middleware = true
220
+ // } else if (key === 'methods') {
221
+ // // Get the methods object from the methods call
222
+ // const methodsArg = callPath.node.arguments[0]
223
+
224
+ // // Handle the case where methods is a function that returns an object
225
+ // if (
226
+ // t.isArrowFunctionExpression(methodsArg) &&
227
+ // t.isObjectExpression(methodsArg.body)
228
+ // ) {
229
+ // methodsArg.body.properties.forEach((prop) => {
230
+ // if (t.isObjectProperty(prop) && t.isIdentifier(prop.key)) {
231
+ // const methodName = prop.key.name
232
+ // manifest.methods[methodName] = {
233
+ // middleware: false,
234
+ // }
235
+
236
+ // // Check if this method has a middleware
237
+ // if (t.isCallExpression(prop.value)) {
238
+ // const method = prop.value
239
+ // method.arguments.forEach((arg) => {
240
+ // if (t.isObjectExpression(arg)) {
241
+ // arg.properties.forEach((methodProp) => {
242
+ // if (
243
+ // t.isObjectProperty(methodProp) &&
244
+ // t.isIdentifier(methodProp.key)
245
+ // ) {
246
+ // if (methodProp.key.name === 'middleware') {
247
+ // manifest.methods[methodName].middleware = true
248
+ // }
249
+ // }
250
+ // })
251
+ // }
252
+ // })
253
+ // }
254
+ // }
255
+ // })
256
+ // }
257
+ // // Handle the case where methods is a direct object
258
+ // else if (t.isObjectExpression(methodsArg)) {
259
+ // methodsArg.properties.forEach((prop) => {
260
+ // if (t.isObjectProperty(prop) && t.isIdentifier(prop.key)) {
261
+ // const methodName = prop.key.name
262
+ // manifest.methods[methodName] = {
263
+ // middleware: false,
264
+ // }
265
+ // }
266
+ // })
267
+ // }
268
+ // }
269
+
270
+ // if (opts.env === 'client') {
271
+ // callPath.replaceWith(callPath.node.callee.object)
272
+ // }
273
+ // }
274
+ // })
275
+
276
+ // If we're on the client, remove the entire variable
277
+ if (opts.env === 'client') {
278
+ // console.debug('createServerFileRoute -> manifest:\n', manifest)
279
+ highestParent.remove()
280
+ return
281
+ }
282
+
283
+ // path.replaceWith(
284
+ // t.callExpression(t.identifier('createServerFileRoute'), [
285
+ // t.identifier('undefined'),
286
+ // t.callExpression(
287
+ // t.memberExpression(t.identifier('Object'), t.identifier('assign')),
288
+ // [
289
+ // t.objectExpression(
290
+ // path.node.arguments
291
+ // .map((arg) => {
292
+ // if (t.isIdentifier(arg)) {
293
+ // return t.objectProperty(t.identifier(arg.name), arg)
294
+ // }
295
+ // // Handle other cases or return a default value if necessary
296
+ // return null // or throw an error, or handle accordingly
297
+ // })
298
+ // .filter(
299
+ // (property): property is t.ObjectProperty => property !== null,
300
+ // ),
301
+ // ),
302
+ // t.objectExpression([
303
+ // t.objectProperty(
304
+ // t.identifier('manifest'),
305
+ // t.valueToNode(manifest),
306
+ // ),
307
+ // ]),
308
+ // ],
309
+ // ),
310
+ // ]),
311
+ // )
312
+
313
+ let isCreateServerFileRouteImported = false as boolean
314
+
315
+ programPath.traverse({
316
+ ImportDeclaration(importPath) {
317
+ const importSource = importPath.node.source.value
318
+ if (importSource === PACKAGES.start) {
319
+ const specifiers = importPath.node.specifiers
320
+ isCreateServerFileRouteImported = specifiers.some((specifier) => {
321
+ return (
322
+ t.isImportSpecifier(specifier) &&
323
+ t.isIdentifier(specifier.imported) &&
324
+ specifier.imported.name === 'createServerFileRoute'
325
+ )
326
+ })
327
+ }
328
+ },
329
+ })
330
+
331
+ if (!isCreateServerFileRouteImported) {
332
+ const importDeclaration = t.importDeclaration(
333
+ [
334
+ t.importSpecifier(
335
+ t.identifier('createServerFileRoute'),
336
+ t.identifier('createServerFileRoute'),
337
+ ),
338
+ ],
339
+ t.stringLiteral(PACKAGES.start),
340
+ )
341
+ programPath.node.body.unshift(importDeclaration)
342
+ }
343
+ }
344
+ }
345
+
346
+ // build these once and reuse them
347
+ export const handleServerOnlyCallExpression =
348
+ buildEnvOnlyCallExpressionHandler('server')
349
+ export const handleClientOnlyCallExpression =
350
+ buildEnvOnlyCallExpressionHandler('client')
351
+
352
+ export type CompileOptions = ParseAstOptions & {
353
+ env: 'server' | 'client'
354
+ dce?: boolean
355
+ filename: string
356
+ }
357
+
358
+ export type IdentifierConfig = {
359
+ name: string
360
+ handleCallExpression: (
361
+ path: babel.NodePath<t.CallExpression>,
362
+ opts: CompileOptions,
363
+ ) => void
364
+ paths: Array<babel.NodePath>
365
+ }
366
+
367
+ export function handleCreateServerFnCallExpression(
368
+ path: babel.NodePath<t.CallExpression>,
369
+ opts: CompileOptions,
370
+ ) {
371
+ // The function is the 'fn' property of the object passed to createServerFn
372
+
373
+ // const firstArg = path.node.arguments[0]
374
+ // if (t.isObjectExpression(firstArg)) {
375
+ // // Was called with some options
376
+ // }
377
+
378
+ // Traverse the member expression and find the call expressions for
379
+ // the validator, handler, and middleware methods. Check to make sure they
380
+ // are children of the createServerFn call expression.
381
+
382
+ const calledOptions = path.node.arguments[0]
383
+ ? (path.get('arguments.0') as babel.NodePath<t.ObjectExpression>)
384
+ : null
385
+
386
+ const shouldValidateClient = !!calledOptions?.node.properties.find((prop) => {
387
+ return (
388
+ t.isObjectProperty(prop) &&
389
+ t.isIdentifier(prop.key) &&
390
+ prop.key.name === 'validateClient' &&
391
+ t.isBooleanLiteral(prop.value) &&
392
+ prop.value.value === true
393
+ )
394
+ })
395
+
396
+ const callExpressionPaths = {
397
+ middleware: null as babel.NodePath<t.CallExpression> | null,
398
+ validator: null as babel.NodePath<t.CallExpression> | null,
399
+ handler: null as babel.NodePath<t.CallExpression> | null,
400
+ }
401
+
402
+ const validMethods = Object.keys(callExpressionPaths)
403
+
404
+ const rootCallExpression = getRootCallExpression(path)
405
+
406
+ // if (debug)
407
+ // console.info(
408
+ // 'Handling createServerFn call expression:',
409
+ // rootCallExpression.toString(),
410
+ // )
411
+
412
+ // Check if the call is assigned to a variable
413
+ if (!rootCallExpression.parentPath.isVariableDeclarator()) {
414
+ throw new Error('createServerFn must be assigned to a variable!')
415
+ }
416
+
417
+ // Get the identifier name of the variable
418
+ const variableDeclarator = rootCallExpression.parentPath.node
419
+ const existingVariableName = (variableDeclarator.id as t.Identifier).name
420
+
421
+ rootCallExpression.traverse({
422
+ MemberExpression(memberExpressionPath) {
423
+ if (t.isIdentifier(memberExpressionPath.node.property)) {
424
+ const name = memberExpressionPath.node.property
425
+ .name as keyof typeof callExpressionPaths
426
+
427
+ if (
428
+ validMethods.includes(name) &&
429
+ memberExpressionPath.parentPath.isCallExpression()
430
+ ) {
431
+ callExpressionPaths[name] = memberExpressionPath.parentPath
432
+ }
433
+ }
434
+ },
435
+ })
436
+
437
+ if (callExpressionPaths.validator) {
438
+ const innerInputExpression = callExpressionPaths.validator.node.arguments[0]
439
+
440
+ if (!innerInputExpression) {
441
+ throw new Error(
442
+ 'createServerFn().validator() must be called with a validator!',
443
+ )
444
+ }
445
+
446
+ // If we're on the client, and we're not validating the client, remove the validator call expression
447
+ if (
448
+ opts.env === 'client' &&
449
+ !shouldValidateClient &&
450
+ t.isMemberExpression(callExpressionPaths.validator.node.callee)
451
+ ) {
452
+ callExpressionPaths.validator.replaceWith(
453
+ callExpressionPaths.validator.node.callee.object,
454
+ )
455
+ }
456
+ }
457
+
458
+ // First, we need to move the handler function to a nested function call
459
+ // that is applied to the arguments passed to the server function.
460
+
461
+ const handlerFnPath = callExpressionPaths.handler?.get(
462
+ 'arguments.0',
463
+ ) as babel.NodePath<any>
464
+
465
+ if (!callExpressionPaths.handler || !handlerFnPath.node) {
466
+ throw codeFrameError(
467
+ opts.code,
468
+ path.node.callee.loc!,
469
+ `createServerFn must be called with a "handler" property!`,
470
+ )
471
+ }
472
+
473
+ const handlerFn = handlerFnPath.node
474
+
475
+ // So, the way we do this is we give the handler function a way
476
+ // to access the serverFn ctx on the server via function scope.
477
+ // The 'use server' extracted function will be called with the
478
+ // payload from the client, then use the scoped serverFn ctx
479
+ // to execute the handler function.
480
+ // This way, we can do things like data and middleware validation
481
+ // in the __execute function without having to AST transform the
482
+ // handler function too much itself.
483
+
484
+ // .handler((optsOut, ctx) => {
485
+ // return ((optsIn) => {
486
+ // 'use server'
487
+ // ctx.__execute(handlerFn, optsIn)
488
+ // })(optsOut)
489
+ // })
490
+
491
+ // If the handler function is an identifier and we're on the client, we need to
492
+ // remove the bound function from the file.
493
+ // If we're on the server, you can leave it, since it will get referenced
494
+ // as a second argument.
495
+
496
+ if (t.isIdentifier(handlerFn)) {
497
+ if (opts.env === 'client') {
498
+ // Find the binding for the handler function
499
+ const binding = handlerFnPath.scope.getBinding(handlerFn.name)
500
+ // Remove it
501
+ if (binding) {
502
+ binding.path.remove()
503
+ }
504
+ }
505
+ // If the env is server, just leave it alone
506
+ }
507
+
508
+ handlerFnPath.replaceWith(
509
+ t.arrowFunctionExpression(
510
+ [t.identifier('opts'), t.identifier('signal')],
511
+ t.blockStatement(
512
+ // Everything in here is server-only, since the client
513
+ // will strip out anything in the 'use server' directive.
514
+ [
515
+ t.returnStatement(
516
+ t.callExpression(
517
+ t.identifier(`${existingVariableName}.__executeServer`),
518
+ [t.identifier('opts'), t.identifier('signal')],
519
+ ),
520
+ ),
521
+ ],
522
+ [t.directive(t.directiveLiteral('use server'))],
523
+ ),
524
+ ),
525
+ )
526
+
527
+ if (opts.env === 'server') {
528
+ callExpressionPaths.handler.node.arguments.push(handlerFn)
529
+ }
530
+ }
531
+
532
+ export function handleCreateMiddlewareCallExpression(
533
+ path: babel.NodePath<t.CallExpression>,
534
+ opts: CompileOptions,
535
+ ) {
536
+ const rootCallExpression = getRootCallExpression(path)
537
+
538
+ // if (debug)
539
+ // console.info(
540
+ // 'Handling createMiddleware call expression:',
541
+ // rootCallExpression.toString(),
542
+ // )
543
+
544
+ const callExpressionPaths = {
545
+ middleware: null as babel.NodePath<t.CallExpression> | null,
546
+ validator: null as babel.NodePath<t.CallExpression> | null,
547
+ client: null as babel.NodePath<t.CallExpression> | null,
548
+ server: null as babel.NodePath<t.CallExpression> | null,
549
+ }
550
+
551
+ const validMethods = Object.keys(callExpressionPaths)
552
+
553
+ rootCallExpression.traverse({
554
+ MemberExpression(memberExpressionPath) {
555
+ if (t.isIdentifier(memberExpressionPath.node.property)) {
556
+ const name = memberExpressionPath.node.property
557
+ .name as keyof typeof callExpressionPaths
558
+
559
+ if (
560
+ validMethods.includes(name) &&
561
+ memberExpressionPath.parentPath.isCallExpression()
562
+ ) {
563
+ callExpressionPaths[name] = memberExpressionPath.parentPath
564
+ }
565
+ }
566
+ },
567
+ })
568
+
569
+ if (callExpressionPaths.validator) {
570
+ const innerInputExpression = callExpressionPaths.validator.node.arguments[0]
571
+
572
+ if (!innerInputExpression) {
573
+ throw new Error(
574
+ 'createMiddleware().validator() must be called with a validator!',
575
+ )
576
+ }
577
+
578
+ // If we're on the client, remove the validator call expression
579
+ if (opts.env === 'client') {
580
+ if (t.isMemberExpression(callExpressionPaths.validator.node.callee)) {
581
+ callExpressionPaths.validator.replaceWith(
582
+ callExpressionPaths.validator.node.callee.object,
583
+ )
584
+ }
585
+ }
586
+ }
587
+
588
+ const serverFnPath = callExpressionPaths.server?.get(
589
+ 'arguments.0',
590
+ ) as babel.NodePath<any>
591
+
592
+ if (
593
+ callExpressionPaths.server &&
594
+ serverFnPath.node &&
595
+ opts.env === 'client'
596
+ ) {
597
+ // If we're on the client, remove the server call expression
598
+ if (t.isMemberExpression(callExpressionPaths.server.node.callee)) {
599
+ callExpressionPaths.server.replaceWith(
600
+ callExpressionPaths.server.node.callee.object,
601
+ )
602
+ }
603
+ }
604
+ }
605
+
606
+ function buildEnvOnlyCallExpressionHandler(env: 'client' | 'server') {
607
+ return function envOnlyCallExpressionHandler(
608
+ path: babel.NodePath<t.CallExpression>,
609
+ opts: CompileOptions,
610
+ ) {
611
+ // if (debug)
612
+ // console.info(`Handling ${env}Only call expression:`, path.toString())
613
+
614
+ const isEnvMatch =
615
+ env === 'client' ? opts.env === 'client' : opts.env === 'server'
616
+
617
+ if (isEnvMatch) {
618
+ // extract the inner function from the call expression
619
+ const innerInputExpression = path.node.arguments[0]
620
+
621
+ if (!t.isExpression(innerInputExpression)) {
622
+ throw new Error(
623
+ `${env}Only() functions must be called with a function!`,
624
+ )
625
+ }
626
+
627
+ path.replaceWith(innerInputExpression)
628
+ return
629
+ }
630
+
631
+ // If we're on the wrong environment, replace the call expression
632
+ // with a function that always throws an error.
633
+ path.replaceWith(
634
+ t.arrowFunctionExpression(
635
+ [],
636
+ t.blockStatement([
637
+ t.throwStatement(
638
+ t.newExpression(t.identifier('Error'), [
639
+ t.stringLiteral(
640
+ `${env}Only() functions can only be called on the ${env}!`,
641
+ ),
642
+ ]),
643
+ ),
644
+ ]),
645
+ ),
646
+ )
647
+ }
648
+ }
649
+
650
+ export function handleCreateIsomorphicFnCallExpression(
651
+ path: babel.NodePath<t.CallExpression>,
652
+ opts: CompileOptions,
653
+ ) {
654
+ const rootCallExpression = getRootCallExpression(path)
655
+
656
+ // if (debug)
657
+ // console.info(
658
+ // 'Handling createIsomorphicFn call expression:',
659
+ // rootCallExpression.toString(),
660
+ // )
661
+
662
+ const callExpressionPaths = {
663
+ client: null as babel.NodePath<t.CallExpression> | null,
664
+ server: null as babel.NodePath<t.CallExpression> | null,
665
+ }
666
+
667
+ const validMethods = Object.keys(callExpressionPaths)
668
+
669
+ rootCallExpression.traverse({
670
+ MemberExpression(memberExpressionPath) {
671
+ if (t.isIdentifier(memberExpressionPath.node.property)) {
672
+ const name = memberExpressionPath.node.property
673
+ .name as keyof typeof callExpressionPaths
674
+
675
+ if (
676
+ validMethods.includes(name) &&
677
+ memberExpressionPath.parentPath.isCallExpression()
678
+ ) {
679
+ callExpressionPaths[name] = memberExpressionPath.parentPath
680
+ }
681
+ }
682
+ },
683
+ })
684
+
685
+ if (
686
+ validMethods.every(
687
+ (method) =>
688
+ !callExpressionPaths[method as keyof typeof callExpressionPaths],
689
+ )
690
+ ) {
691
+ const variableId = rootCallExpression.parentPath.isVariableDeclarator()
692
+ ? rootCallExpression.parentPath.node.id
693
+ : null
694
+ console.warn(
695
+ 'createIsomorphicFn called without a client or server implementation!',
696
+ 'This will result in a no-op function.',
697
+ 'Variable name:',
698
+ t.isIdentifier(variableId) ? variableId.name : 'unknown',
699
+ )
700
+ }
701
+
702
+ const envCallExpression = callExpressionPaths[opts.env]
703
+
704
+ if (!envCallExpression) {
705
+ // if we don't have an implementation for this environment, default to a no-op
706
+ rootCallExpression.replaceWith(
707
+ t.arrowFunctionExpression([], t.blockStatement([])),
708
+ )
709
+ return
710
+ }
711
+
712
+ const innerInputExpression = envCallExpression.node.arguments[0]
713
+
714
+ if (!t.isExpression(innerInputExpression)) {
715
+ throw new Error(
716
+ `createIsomorphicFn().${opts.env}(func) must be called with a function!`,
717
+ )
718
+ }
719
+
720
+ rootCallExpression.replaceWith(innerInputExpression)
721
+ }
722
+
723
+ export function getRootCallExpression(path: babel.NodePath<t.CallExpression>) {
724
+ // Find the highest callExpression parent
725
+ let rootCallExpression: babel.NodePath<t.CallExpression> = path
726
+
727
+ // Traverse up the chain of CallExpressions
728
+ while (rootCallExpression.parentPath.isMemberExpression()) {
729
+ const parent = rootCallExpression.parentPath
730
+ if (parent.parentPath.isCallExpression()) {
731
+ rootCallExpression = parent.parentPath
732
+ }
733
+ }
734
+
735
+ return rootCallExpression
736
+ }
737
+
738
+ function codeFrameError(
739
+ code: string,
740
+ loc: {
741
+ start: { line: number; column: number }
742
+ end: { line: number; column: number }
743
+ },
744
+ message: string,
745
+ ) {
746
+ const frame = codeFrameColumns(
747
+ code,
748
+ {
749
+ start: loc.start,
750
+ end: loc.end,
751
+ },
752
+ {
753
+ highlightCode: true,
754
+ message,
755
+ },
756
+ )
757
+
758
+ return new Error(frame)
759
+ }