@tanstack/start-plugin-core 1.132.0-alpha.5 → 1.132.0-alpha.7
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/plugin.js +2 -2
- package/dist/esm/plugin.js.map +1 -1
- package/dist/esm/schema.d.ts +20 -28
- package/dist/esm/schema.js +10 -14
- package/dist/esm/schema.js.map +1 -1
- package/dist/esm/start-compiler-plugin/compilers.d.ts +0 -6
- package/dist/esm/start-compiler-plugin/compilers.js +12 -271
- package/dist/esm/start-compiler-plugin/compilers.js.map +1 -1
- package/dist/esm/start-compiler-plugin/constants.d.ts +1 -1
- package/dist/esm/start-compiler-plugin/constants.js +2 -2
- package/dist/esm/start-compiler-plugin/constants.js.map +1 -1
- package/dist/esm/start-compiler-plugin/envOnly.d.ts +5 -0
- package/dist/esm/start-compiler-plugin/envOnly.js +41 -0
- package/dist/esm/start-compiler-plugin/envOnly.js.map +1 -0
- package/dist/esm/start-compiler-plugin/isomorphicFn.d.ts +4 -0
- package/dist/esm/start-compiler-plugin/isomorphicFn.js +49 -0
- package/dist/esm/start-compiler-plugin/isomorphicFn.js.map +1 -0
- package/dist/esm/start-compiler-plugin/middleware.d.ts +4 -0
- package/dist/esm/start-compiler-plugin/middleware.js +51 -0
- package/dist/esm/start-compiler-plugin/middleware.js.map +1 -0
- package/dist/esm/start-compiler-plugin/plugin.js +59 -26
- package/dist/esm/start-compiler-plugin/plugin.js.map +1 -1
- package/dist/esm/start-compiler-plugin/serverFileRoute.d.ts +4 -0
- package/dist/esm/start-compiler-plugin/serverFileRoute.js +38 -0
- package/dist/esm/start-compiler-plugin/serverFileRoute.js.map +1 -0
- package/dist/esm/start-compiler-plugin/serverFn.d.ts +4 -0
- package/dist/esm/start-compiler-plugin/serverFn.js +87 -0
- package/dist/esm/start-compiler-plugin/serverFn.js.map +1 -0
- package/dist/esm/start-compiler-plugin/utils.d.ts +13 -0
- package/dist/esm/start-compiler-plugin/utils.js +30 -0
- package/dist/esm/start-compiler-plugin/utils.js.map +1 -0
- package/package.json +2 -2
- package/src/plugin.ts +2 -2
- package/src/schema.ts +10 -16
- package/src/start-compiler-plugin/compilers.ts +16 -462
- package/src/start-compiler-plugin/constants.ts +2 -2
- package/src/start-compiler-plugin/envOnly.ts +58 -0
- package/src/start-compiler-plugin/isomorphicFn.ts +78 -0
- package/src/start-compiler-plugin/middleware.ts +79 -0
- package/src/start-compiler-plugin/plugin.ts +67 -36
- package/src/start-compiler-plugin/serverFileRoute.ts +59 -0
- package/src/start-compiler-plugin/serverFn.ts +163 -0
- package/src/start-compiler-plugin/utils.ts +41 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serverFn.js","sources":["../../../src/start-compiler-plugin/serverFn.ts"],"sourcesContent":["import * as t from '@babel/types'\nimport { codeFrameError, getRootCallExpression } from './utils'\nimport type * as babel from '@babel/core'\n\nimport type { CompileOptions } from './compilers'\n\nexport function handleCreateServerFnCallExpression(\n path: babel.NodePath<t.CallExpression>,\n opts: CompileOptions,\n) {\n // Traverse the member expression and find the call expressions for\n // the validator, handler, and middleware methods. Check to make sure they\n // are children of the createServerFn call expression.\n\n const calledOptions = path.node.arguments[0]\n ? (path.get('arguments.0') as babel.NodePath<t.ObjectExpression>)\n : null\n\n const shouldValidateClient = !!calledOptions?.node.properties.find((prop) => {\n return (\n t.isObjectProperty(prop) &&\n t.isIdentifier(prop.key) &&\n prop.key.name === 'validateClient' &&\n t.isBooleanLiteral(prop.value) &&\n prop.value.value === true\n )\n })\n\n const callExpressionPaths = {\n middleware: null as babel.NodePath<t.CallExpression> | null,\n validator: null as babel.NodePath<t.CallExpression> | null,\n handler: null as babel.NodePath<t.CallExpression> | null,\n }\n\n const validMethods = Object.keys(callExpressionPaths)\n\n const rootCallExpression = getRootCallExpression(path)\n\n // if (debug)\n // console.info(\n // 'Handling createServerFn call expression:',\n // rootCallExpression.toString(),\n // )\n\n // Check if the call is assigned to a variable\n if (!rootCallExpression.parentPath.isVariableDeclarator()) {\n throw new Error('createServerFn must be assigned to a variable!')\n }\n\n // Get the identifier name of the variable\n const variableDeclarator = rootCallExpression.parentPath.node\n const existingVariableName = (variableDeclarator.id as t.Identifier).name\n\n rootCallExpression.traverse({\n MemberExpression(memberExpressionPath) {\n if (t.isIdentifier(memberExpressionPath.node.property)) {\n const name = memberExpressionPath.node.property\n .name as keyof typeof callExpressionPaths\n\n if (\n validMethods.includes(name) &&\n memberExpressionPath.parentPath.isCallExpression()\n ) {\n callExpressionPaths[name] = memberExpressionPath.parentPath\n }\n }\n },\n })\n\n if (callExpressionPaths.validator) {\n const innerInputExpression = callExpressionPaths.validator.node.arguments[0]\n\n if (!innerInputExpression) {\n throw new Error(\n 'createServerFn().validator() must be called with a validator!',\n )\n }\n\n // If we're on the client, and we're not validating the client, remove the validator call expression\n if (\n opts.env === 'client' &&\n !shouldValidateClient &&\n t.isMemberExpression(callExpressionPaths.validator.node.callee)\n ) {\n callExpressionPaths.validator.replaceWith(\n callExpressionPaths.validator.node.callee.object,\n )\n }\n }\n\n // First, we need to move the handler function to a nested function call\n // that is applied to the arguments passed to the server function.\n\n const handlerFnPath = callExpressionPaths.handler?.get(\n 'arguments.0',\n ) as babel.NodePath<any>\n\n if (!callExpressionPaths.handler || !handlerFnPath.node) {\n throw codeFrameError(\n opts.code,\n path.node.callee.loc!,\n `createServerFn must be called with a \"handler\" property!`,\n )\n }\n\n const handlerFn = handlerFnPath.node\n\n // So, the way we do this is we give the handler function a way\n // to access the serverFn ctx on the server via function scope.\n // The 'use server' extracted function will be called with the\n // payload from the client, then use the scoped serverFn ctx\n // to execute the handler function.\n // This way, we can do things like data and middleware validation\n // in the __execute function without having to AST transform the\n // handler function too much itself.\n\n // .handler((optsOut, ctx) => {\n // return ((optsIn) => {\n // 'use server'\n // ctx.__execute(handlerFn, optsIn)\n // })(optsOut)\n // })\n\n // If the handler function is an identifier and we're on the client, we need to\n // remove the bound function from the file.\n // If we're on the server, you can leave it, since it will get referenced\n // as a second argument.\n\n if (t.isIdentifier(handlerFn)) {\n if (opts.env === 'client') {\n // Find the binding for the handler function\n const binding = handlerFnPath.scope.getBinding(handlerFn.name)\n // Remove it\n if (binding) {\n binding.path.remove()\n }\n }\n // If the env is server, just leave it alone\n }\n\n handlerFnPath.replaceWith(\n t.arrowFunctionExpression(\n [t.identifier('opts'), t.identifier('signal')],\n t.blockStatement(\n // Everything in here is server-only, since the client\n // will strip out anything in the 'use server' directive.\n [\n t.returnStatement(\n t.callExpression(\n t.identifier(`${existingVariableName}.__executeServer`),\n [t.identifier('opts'), t.identifier('signal')],\n ),\n ),\n ],\n [t.directive(t.directiveLiteral('use server'))],\n ),\n ),\n )\n\n if (opts.env === 'server') {\n callExpressionPaths.handler.node.arguments.push(handlerFn)\n }\n}\n"],"names":[],"mappings":";;AAMO,SAAS,mCACd,MACA,MACA;AAKA,QAAM,gBAAgB,KAAK,KAAK,UAAU,CAAC,IACtC,KAAK,IAAI,aAAa,IACvB;AAEJ,QAAM,uBAAuB,CAAC,CAAC,eAAe,KAAK,WAAW,KAAK,CAAC,SAAS;AAC3E,WACE,EAAE,iBAAiB,IAAI,KACvB,EAAE,aAAa,KAAK,GAAG,KACvB,KAAK,IAAI,SAAS,oBAClB,EAAE,iBAAiB,KAAK,KAAK,KAC7B,KAAK,MAAM,UAAU;AAAA,EAEzB,CAAC;AAED,QAAM,sBAAsB;AAAA,IAC1B,YAAY;AAAA,IACZ,WAAW;AAAA,IACX,SAAS;AAAA,EAAA;AAGX,QAAM,eAAe,OAAO,KAAK,mBAAmB;AAEpD,QAAM,qBAAqB,sBAAsB,IAAI;AASrD,MAAI,CAAC,mBAAmB,WAAW,wBAAwB;AACzD,UAAM,IAAI,MAAM,gDAAgD;AAAA,EAClE;AAGA,QAAM,qBAAqB,mBAAmB,WAAW;AACzD,QAAM,uBAAwB,mBAAmB,GAAoB;AAErE,qBAAmB,SAAS;AAAA,IAC1B,iBAAiB,sBAAsB;AACrC,UAAI,EAAE,aAAa,qBAAqB,KAAK,QAAQ,GAAG;AACtD,cAAM,OAAO,qBAAqB,KAAK,SACpC;AAEH,YACE,aAAa,SAAS,IAAI,KAC1B,qBAAqB,WAAW,oBAChC;AACA,8BAAoB,IAAI,IAAI,qBAAqB;AAAA,QACnD;AAAA,MACF;AAAA,IACF;AAAA,EAAA,CACD;AAED,MAAI,oBAAoB,WAAW;AACjC,UAAM,uBAAuB,oBAAoB,UAAU,KAAK,UAAU,CAAC;AAE3E,QAAI,CAAC,sBAAsB;AACzB,YAAM,IAAI;AAAA,QACR;AAAA,MAAA;AAAA,IAEJ;AAGA,QACE,KAAK,QAAQ,YACb,CAAC,wBACD,EAAE,mBAAmB,oBAAoB,UAAU,KAAK,MAAM,GAC9D;AACA,0BAAoB,UAAU;AAAA,QAC5B,oBAAoB,UAAU,KAAK,OAAO;AAAA,MAAA;AAAA,IAE9C;AAAA,EACF;AAKA,QAAM,gBAAgB,oBAAoB,SAAS;AAAA,IACjD;AAAA,EAAA;AAGF,MAAI,CAAC,oBAAoB,WAAW,CAAC,cAAc,MAAM;AACvD,UAAM;AAAA,MACJ,KAAK;AAAA,MACL,KAAK,KAAK,OAAO;AAAA,MACjB;AAAA,IAAA;AAAA,EAEJ;AAEA,QAAM,YAAY,cAAc;AAuBhC,MAAI,EAAE,aAAa,SAAS,GAAG;AAC7B,QAAI,KAAK,QAAQ,UAAU;AAEzB,YAAM,UAAU,cAAc,MAAM,WAAW,UAAU,IAAI;AAE7D,UAAI,SAAS;AACX,gBAAQ,KAAK,OAAA;AAAA,MACf;AAAA,IACF;AAAA,EAEF;AAEA,gBAAc;AAAA,IACZ,EAAE;AAAA,MACA,CAAC,EAAE,WAAW,MAAM,GAAG,EAAE,WAAW,QAAQ,CAAC;AAAA,MAC7C,EAAE;AAAA;AAAA;AAAA,QAGA;AAAA,UACE,EAAE;AAAA,YACA,EAAE;AAAA,cACA,EAAE,WAAW,GAAG,oBAAoB,kBAAkB;AAAA,cACtD,CAAC,EAAE,WAAW,MAAM,GAAG,EAAE,WAAW,QAAQ,CAAC;AAAA,YAAA;AAAA,UAC/C;AAAA,QACF;AAAA,QAEF,CAAC,EAAE,UAAU,EAAE,iBAAiB,YAAY,CAAC,CAAC;AAAA,MAAA;AAAA,IAChD;AAAA,EACF;AAGF,MAAI,KAAK,QAAQ,UAAU;AACzB,wBAAoB,QAAQ,KAAK,UAAU,KAAK,SAAS;AAAA,EAC3D;AACF;"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type * as t from '@babel/types';
|
|
2
|
+
import type * as babel from '@babel/core';
|
|
3
|
+
export declare function getRootCallExpression(path: babel.NodePath<t.CallExpression>): babel.NodePath<t.CallExpression>;
|
|
4
|
+
export declare function codeFrameError(code: string, loc: {
|
|
5
|
+
start: {
|
|
6
|
+
line: number;
|
|
7
|
+
column: number;
|
|
8
|
+
};
|
|
9
|
+
end: {
|
|
10
|
+
line: number;
|
|
11
|
+
column: number;
|
|
12
|
+
};
|
|
13
|
+
}, message: string): Error;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { codeFrameColumns } from "@babel/code-frame";
|
|
2
|
+
function getRootCallExpression(path) {
|
|
3
|
+
let rootCallExpression = path;
|
|
4
|
+
while (rootCallExpression.parentPath.isMemberExpression()) {
|
|
5
|
+
const parent = rootCallExpression.parentPath;
|
|
6
|
+
if (parent.parentPath.isCallExpression()) {
|
|
7
|
+
rootCallExpression = parent.parentPath;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
return rootCallExpression;
|
|
11
|
+
}
|
|
12
|
+
function codeFrameError(code, loc, message) {
|
|
13
|
+
const frame = codeFrameColumns(
|
|
14
|
+
code,
|
|
15
|
+
{
|
|
16
|
+
start: loc.start,
|
|
17
|
+
end: loc.end
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
highlightCode: true,
|
|
21
|
+
message
|
|
22
|
+
}
|
|
23
|
+
);
|
|
24
|
+
return new Error(frame);
|
|
25
|
+
}
|
|
26
|
+
export {
|
|
27
|
+
codeFrameError,
|
|
28
|
+
getRootCallExpression
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sources":["../../../src/start-compiler-plugin/utils.ts"],"sourcesContent":["import { codeFrameColumns } from '@babel/code-frame'\nimport type * as t from '@babel/types'\nimport type * as babel from '@babel/core'\n\nexport function getRootCallExpression(path: babel.NodePath<t.CallExpression>) {\n // Find the highest callExpression parent\n let rootCallExpression: babel.NodePath<t.CallExpression> = path\n\n // Traverse up the chain of CallExpressions\n while (rootCallExpression.parentPath.isMemberExpression()) {\n const parent = rootCallExpression.parentPath\n if (parent.parentPath.isCallExpression()) {\n rootCallExpression = parent.parentPath\n }\n }\n\n return rootCallExpression\n}\n\nexport function codeFrameError(\n code: string,\n loc: {\n start: { line: number; column: number }\n end: { line: number; column: number }\n },\n message: string,\n) {\n const frame = codeFrameColumns(\n code,\n {\n start: loc.start,\n end: loc.end,\n },\n {\n highlightCode: true,\n message,\n },\n )\n\n return new Error(frame)\n}\n"],"names":[],"mappings":";AAIO,SAAS,sBAAsB,MAAwC;AAE5E,MAAI,qBAAuD;AAG3D,SAAO,mBAAmB,WAAW,sBAAsB;AACzD,UAAM,SAAS,mBAAmB;AAClC,QAAI,OAAO,WAAW,oBAAoB;AACxC,2BAAqB,OAAO;AAAA,IAC9B;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,eACd,MACA,KAIA,SACA;AACA,QAAM,QAAQ;AAAA,IACZ;AAAA,IACA;AAAA,MACE,OAAO,IAAI;AAAA,MACX,KAAK,IAAI;AAAA,IAAA;AAAA,IAEX;AAAA,MACE,eAAe;AAAA,MACf;AAAA,IAAA;AAAA,EACF;AAGF,SAAO,IAAI,MAAM,KAAK;AACxB;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/start-plugin-core",
|
|
3
|
-
"version": "1.132.0-alpha.
|
|
3
|
+
"version": "1.132.0-alpha.7",
|
|
4
4
|
"description": "Modern and scalable routing for React applications",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"@tanstack/router-plugin": "1.132.0-alpha.4",
|
|
63
63
|
"@tanstack/router-utils": "1.132.0-alpha.1",
|
|
64
64
|
"@tanstack/server-functions-plugin": "1.132.0-alpha.3",
|
|
65
|
-
"@tanstack/start-server-core": "1.132.0-alpha.
|
|
65
|
+
"@tanstack/start-server-core": "1.132.0-alpha.7"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
68
|
"@types/babel__code-frame": "^7.0.6",
|
package/src/plugin.ts
CHANGED
|
@@ -56,7 +56,7 @@ export function TanStackStartVitePluginCore(
|
|
|
56
56
|
|
|
57
57
|
return [
|
|
58
58
|
tanStackStartRouter({
|
|
59
|
-
...startConfig.
|
|
59
|
+
...startConfig.router,
|
|
60
60
|
target: corePluginOpts.framework,
|
|
61
61
|
autoCodeSplitting: true,
|
|
62
62
|
}),
|
|
@@ -67,7 +67,7 @@ export function TanStackStartVitePluginCore(
|
|
|
67
67
|
globalThis.TSS_APP_BASE = viteAppBase
|
|
68
68
|
|
|
69
69
|
const root = viteConfig.root || process.cwd()
|
|
70
|
-
const resolvedSrcDirectory = join(root, startConfig.
|
|
70
|
+
const resolvedSrcDirectory = join(root, startConfig.srcDirectory)
|
|
71
71
|
|
|
72
72
|
const routerFilePath = resolveEntry({
|
|
73
73
|
type: 'router entry',
|
package/src/schema.ts
CHANGED
|
@@ -2,35 +2,28 @@ import path from 'node:path'
|
|
|
2
2
|
import { z } from 'zod'
|
|
3
3
|
import { configSchema, getConfig } from '@tanstack/router-generator'
|
|
4
4
|
|
|
5
|
-
const tsrConfig = configSchema
|
|
6
|
-
.omit({ autoCodeSplitting: true })
|
|
7
|
-
.partial()
|
|
8
|
-
.extend({
|
|
9
|
-
// this is relative to vite root
|
|
10
|
-
// TODO why is this nested under tsr?
|
|
11
|
-
srcDirectory: z.string().optional().default('src'),
|
|
12
|
-
})
|
|
5
|
+
const tsrConfig = configSchema.omit({ autoCodeSplitting: true }).partial()
|
|
13
6
|
|
|
14
7
|
export function parseStartConfig(
|
|
15
8
|
opts?: z.input<typeof tanstackStartOptionsSchema>,
|
|
16
9
|
) {
|
|
17
10
|
const options = tanstackStartOptionsSchema.parse(opts)
|
|
18
11
|
|
|
19
|
-
const srcDirectory = options.
|
|
12
|
+
const srcDirectory = options.srcDirectory
|
|
20
13
|
|
|
21
14
|
const routesDirectory =
|
|
22
|
-
options.
|
|
15
|
+
options.router.routesDirectory ?? path.join(srcDirectory, 'routes')
|
|
23
16
|
|
|
24
17
|
const generatedRouteTree =
|
|
25
|
-
options.
|
|
18
|
+
options.router.generatedRouteTree ??
|
|
26
19
|
path.join(srcDirectory, 'routeTree.gen.ts')
|
|
27
20
|
|
|
28
21
|
return {
|
|
29
22
|
...options,
|
|
30
|
-
|
|
31
|
-
...options.
|
|
23
|
+
router: {
|
|
24
|
+
...options.router,
|
|
32
25
|
...getConfig({
|
|
33
|
-
...options.
|
|
26
|
+
...options.router,
|
|
34
27
|
routesDirectory,
|
|
35
28
|
generatedRouteTree,
|
|
36
29
|
}),
|
|
@@ -121,12 +114,13 @@ const pageSchema = pageBaseSchema.extend({
|
|
|
121
114
|
|
|
122
115
|
const tanstackStartOptionsSchema = z
|
|
123
116
|
.object({
|
|
124
|
-
|
|
117
|
+
srcDirectory: z.string().optional().default('src'),
|
|
125
118
|
router: z
|
|
126
119
|
.object({
|
|
127
|
-
// TODO
|
|
120
|
+
// TODO this will move to 'start' once we have `createStart`
|
|
128
121
|
entry: z.string().optional(),
|
|
129
122
|
})
|
|
123
|
+
.and(tsrConfig.optional().default({}))
|
|
130
124
|
.optional()
|
|
131
125
|
.default({}),
|
|
132
126
|
client: z
|
|
@@ -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
|
-
|
|
54
|
-
name: '
|
|
55
|
-
handleCallExpression:
|
|
60
|
+
createServerOnlyFn: {
|
|
61
|
+
name: 'createServerOnlyFn',
|
|
62
|
+
handleCallExpression: handleCreateServerOnlyFnCallExpression,
|
|
56
63
|
paths: [],
|
|
57
64
|
},
|
|
58
|
-
|
|
59
|
-
name: '
|
|
60
|
-
handleCallExpression:
|
|
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
|
-
}
|