@tanstack/start-plugin-core 1.132.0-alpha.1 → 1.132.0-alpha.10

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 (63) hide show
  1. package/dist/esm/create-server-fn-plugin/compiler.d.ts +61 -0
  2. package/dist/esm/create-server-fn-plugin/compiler.js +336 -0
  3. package/dist/esm/create-server-fn-plugin/compiler.js.map +1 -0
  4. package/dist/esm/create-server-fn-plugin/handleCreateServerFn.d.ts +6 -0
  5. package/dist/esm/create-server-fn-plugin/handleCreateServerFn.js +85 -0
  6. package/dist/esm/create-server-fn-plugin/handleCreateServerFn.js.map +1 -0
  7. package/dist/esm/create-server-fn-plugin/plugin.d.ts +3 -0
  8. package/dist/esm/create-server-fn-plugin/plugin.js +113 -0
  9. package/dist/esm/create-server-fn-plugin/plugin.js.map +1 -0
  10. package/dist/esm/output-directory.js +5 -2
  11. package/dist/esm/output-directory.js.map +1 -1
  12. package/dist/esm/plugin.d.ts +1 -1
  13. package/dist/esm/plugin.js +12 -15
  14. package/dist/esm/plugin.js.map +1 -1
  15. package/dist/esm/schema.d.ts +20 -28
  16. package/dist/esm/schema.js +10 -14
  17. package/dist/esm/schema.js.map +1 -1
  18. package/dist/esm/start-compiler-plugin/compilers.d.ts +15 -0
  19. package/dist/esm/start-compiler-plugin/compilers.js +131 -0
  20. package/dist/esm/start-compiler-plugin/compilers.js.map +1 -0
  21. package/dist/esm/start-compiler-plugin/constants.d.ts +1 -0
  22. package/dist/esm/start-compiler-plugin/constants.js +13 -0
  23. package/dist/esm/start-compiler-plugin/constants.js.map +1 -0
  24. package/dist/esm/start-compiler-plugin/envOnly.d.ts +5 -0
  25. package/dist/esm/start-compiler-plugin/envOnly.js +41 -0
  26. package/dist/esm/start-compiler-plugin/envOnly.js.map +1 -0
  27. package/dist/esm/start-compiler-plugin/isomorphicFn.d.ts +4 -0
  28. package/dist/esm/start-compiler-plugin/isomorphicFn.js +49 -0
  29. package/dist/esm/start-compiler-plugin/isomorphicFn.js.map +1 -0
  30. package/dist/esm/start-compiler-plugin/middleware.d.ts +4 -0
  31. package/dist/esm/start-compiler-plugin/middleware.js +51 -0
  32. package/dist/esm/start-compiler-plugin/middleware.js.map +1 -0
  33. package/dist/esm/{start-compiler-plugin.d.ts → start-compiler-plugin/plugin.d.ts} +1 -8
  34. package/dist/esm/start-compiler-plugin/plugin.js +96 -0
  35. package/dist/esm/start-compiler-plugin/plugin.js.map +1 -0
  36. package/dist/esm/start-compiler-plugin/serverFileRoute.d.ts +4 -0
  37. package/dist/esm/start-compiler-plugin/serverFileRoute.js +38 -0
  38. package/dist/esm/start-compiler-plugin/serverFileRoute.js.map +1 -0
  39. package/dist/esm/start-compiler-plugin/utils.d.ts +13 -0
  40. package/dist/esm/start-compiler-plugin/utils.js +30 -0
  41. package/dist/esm/start-compiler-plugin/utils.js.map +1 -0
  42. package/package.json +8 -8
  43. package/src/create-server-fn-plugin/compiler.ts +456 -0
  44. package/src/create-server-fn-plugin/handleCreateServerFn.ts +153 -0
  45. package/src/create-server-fn-plugin/plugin.ts +138 -0
  46. package/src/output-directory.ts +13 -6
  47. package/src/plugin.ts +13 -22
  48. package/src/schema.ts +10 -16
  49. package/src/start-compiler-plugin/compilers.ts +195 -0
  50. package/src/start-compiler-plugin/constants.ts +9 -0
  51. package/src/start-compiler-plugin/envOnly.ts +58 -0
  52. package/src/start-compiler-plugin/isomorphicFn.ts +78 -0
  53. package/src/start-compiler-plugin/middleware.ts +79 -0
  54. package/src/start-compiler-plugin/plugin.ts +122 -0
  55. package/src/start-compiler-plugin/serverFileRoute.ts +59 -0
  56. package/src/start-compiler-plugin/utils.ts +41 -0
  57. package/dist/esm/compilers.d.ts +0 -21
  58. package/dist/esm/compilers.js +0 -395
  59. package/dist/esm/compilers.js.map +0 -1
  60. package/dist/esm/start-compiler-plugin.js +0 -78
  61. package/dist/esm/start-compiler-plugin.js.map +0 -1
  62. package/src/compilers.ts +0 -659
  63. package/src/start-compiler-plugin.ts +0 -115
@@ -0,0 +1,138 @@
1
+ import { VITE_ENVIRONMENT_NAMES } from '../constants'
2
+ import { ServerFnCompiler } from './compiler'
3
+ import type { ViteEnvironmentNames } from '../constants'
4
+ import type { PluginOption } from 'vite'
5
+ import type { CompileStartFrameworkOptions } from '../start-compiler-plugin/compilers'
6
+
7
+ function cleanId(id: string): string {
8
+ return id.split('?')[0]!
9
+ }
10
+
11
+ export function createServerFnPlugin(
12
+ framework: CompileStartFrameworkOptions,
13
+ ): PluginOption {
14
+ const libName = `@tanstack/${framework}-start`
15
+ const rootExport = 'createServerFn'
16
+
17
+ const SERVER_FN_LOOKUP = 'server-fn-module-lookup'
18
+
19
+ const compilers: Partial<Record<ViteEnvironmentNames, ServerFnCompiler>> = {}
20
+ return [
21
+ {
22
+ name: 'tanstack-start-core:capture-server-fn-module-lookup',
23
+ // we only need this plugin in dev mode
24
+ apply: 'serve',
25
+ applyToEnvironment(env) {
26
+ return [
27
+ VITE_ENVIRONMENT_NAMES.client,
28
+ VITE_ENVIRONMENT_NAMES.server,
29
+ ].includes(env.name as ViteEnvironmentNames)
30
+ },
31
+ transform: {
32
+ filter: {
33
+ id: new RegExp(`${SERVER_FN_LOOKUP}$`),
34
+ },
35
+ handler(code, id) {
36
+ const compiler =
37
+ compilers[this.environment.name as ViteEnvironmentNames]
38
+ compiler?.ingestModule({ code, id: cleanId(id) })
39
+ },
40
+ },
41
+ },
42
+ {
43
+ name: 'tanstack-start-core::server-fn',
44
+ enforce: 'pre',
45
+
46
+ applyToEnvironment(env) {
47
+ return [
48
+ VITE_ENVIRONMENT_NAMES.client,
49
+ VITE_ENVIRONMENT_NAMES.server,
50
+ ].includes(env.name as ViteEnvironmentNames)
51
+ },
52
+ transform: {
53
+ filter: {
54
+ id: {
55
+ exclude: new RegExp(`${SERVER_FN_LOOKUP}$`),
56
+ },
57
+ code: {
58
+ // only scan files that mention `.handler(`
59
+ include: [/\.handler\(/],
60
+ },
61
+ },
62
+ async handler(code, id) {
63
+ let compiler =
64
+ compilers[this.environment.name as ViteEnvironmentNames]
65
+ if (!compiler) {
66
+ const env =
67
+ this.environment.name === VITE_ENVIRONMENT_NAMES.client
68
+ ? 'client'
69
+ : this.environment.name === VITE_ENVIRONMENT_NAMES.server
70
+ ? 'server'
71
+ : (() => {
72
+ throw new Error(
73
+ `Environment ${this.environment.name} not configured`,
74
+ )
75
+ })()
76
+
77
+ compiler = new ServerFnCompiler({
78
+ env,
79
+ libName,
80
+ rootExport,
81
+ loadModule: async (id: string) => {
82
+ if (this.environment.mode === 'build') {
83
+ const loaded = await this.load({ id })
84
+ if (!loaded.code) {
85
+ throw new Error(`could not load module ${id}`)
86
+ }
87
+ compiler!.ingestModule({ code: loaded.code, id })
88
+ } else if (this.environment.mode === 'dev') {
89
+ /**
90
+ * in dev, vite does not return code from `ctx.load()`
91
+ * so instead, we need to take a different approach
92
+ * we must force vite to load the module and run it through the vite plugin pipeline
93
+ * we can do this by using the `fetchModule` method
94
+ * the `captureServerFnModuleLookupPlugin` captures the module code via its transform hook and invokes analyzeModuleAST
95
+ */
96
+ await this.environment.fetchModule(
97
+ id + '?' + SERVER_FN_LOOKUP,
98
+ )
99
+ } else {
100
+ throw new Error(
101
+ `could not load module ${id}: unknown environment mode ${this.environment.mode}`,
102
+ )
103
+ }
104
+ },
105
+ resolveId: async (source: string, importer?: string) => {
106
+ const r = await this.resolve(source, importer)
107
+ return r ? cleanId(r.id) : null
108
+ },
109
+ })
110
+ compilers[this.environment.name as ViteEnvironmentNames] = compiler
111
+ }
112
+
113
+ id = cleanId(id)
114
+ const result = await compiler.compile({ id, code })
115
+ return result
116
+ },
117
+ },
118
+
119
+ hotUpdate(ctx) {
120
+ const compiler =
121
+ compilers[this.environment.name as ViteEnvironmentNames]
122
+
123
+ ctx.modules.forEach((m) => {
124
+ if (m.id) {
125
+ const deleted = compiler?.invalidateModule(m.id)
126
+ if (deleted) {
127
+ m.importers.forEach((importer) => {
128
+ if (importer.id) {
129
+ compiler?.invalidateModule(importer.id)
130
+ }
131
+ })
132
+ }
133
+ }
134
+ })
135
+ },
136
+ },
137
+ ]
138
+ }
@@ -1,18 +1,25 @@
1
1
  import { join } from 'pathe'
2
2
  import { VITE_ENVIRONMENT_NAMES } from './constants'
3
+ import type { ViteEnvironmentNames } from './constants'
3
4
  import type * as vite from 'vite'
4
5
 
5
6
  export function getClientOutputDirectory(userConfig: vite.UserConfig) {
6
- return (
7
- userConfig.environments?.[VITE_ENVIRONMENT_NAMES.client]?.build?.outDir ??
8
- join(getServerOutputDirectory(userConfig), 'public')
9
- )
7
+ return getOutputDirectory(userConfig, VITE_ENVIRONMENT_NAMES.client, 'client')
10
8
  }
11
9
 
12
10
  export function getServerOutputDirectory(userConfig: vite.UserConfig) {
11
+ return getOutputDirectory(userConfig, VITE_ENVIRONMENT_NAMES.server, 'server')
12
+ }
13
+
14
+ function getOutputDirectory(
15
+ userConfig: vite.UserConfig,
16
+ environmentName: ViteEnvironmentNames,
17
+ directoryName: string,
18
+ ) {
13
19
  const rootOutputDirectory = userConfig.build?.outDir ?? 'dist'
20
+
14
21
  return (
15
- userConfig.environments?.[VITE_ENVIRONMENT_NAMES.server]?.build?.outDir ??
16
- rootOutputDirectory
22
+ userConfig.environments?.[environmentName]?.build?.outDir ??
23
+ join(rootOutputDirectory, directoryName)
17
24
  )
18
25
  }
package/src/plugin.ts CHANGED
@@ -6,7 +6,7 @@ import * as vite from 'vite'
6
6
  import { crawlFrameworkPkgs } from 'vitefu'
7
7
  import { join } from 'pathe'
8
8
  import { startManifestPlugin } from './start-manifest-plugin/plugin'
9
- import { startCompilerPlugin } from './start-compiler-plugin'
9
+ import { startCompilerPlugin } from './start-compiler-plugin/plugin'
10
10
  import { ENTRY_POINTS, VITE_ENVIRONMENT_NAMES } from './constants'
11
11
  import { tanStackStartRouter } from './start-router-plugin/plugin'
12
12
  import { loadEnvPlugin } from './load-env-plugin/plugin'
@@ -18,10 +18,11 @@ import {
18
18
  getServerOutputDirectory,
19
19
  } from './output-directory'
20
20
  import { postServerBuild } from './post-server-build'
21
+ import { createServerFnPlugin } from './create-server-fn-plugin/plugin'
21
22
  import type { ViteEnvironmentNames } from './constants'
22
23
  import type { TanStackStartInputConfig } from './schema'
23
24
  import type { PluginOption } from 'vite'
24
- import type { CompileStartFrameworkOptions } from './compilers'
25
+ import type { CompileStartFrameworkOptions } from './start-compiler-plugin/compilers'
25
26
 
26
27
  export interface TanStackStartVitePluginCoreOptions {
27
28
  framework: CompileStartFrameworkOptions
@@ -56,7 +57,7 @@ export function TanStackStartVitePluginCore(
56
57
 
57
58
  return [
58
59
  tanStackStartRouter({
59
- ...startConfig.tsr,
60
+ ...startConfig.router,
60
61
  target: corePluginOpts.framework,
61
62
  autoCodeSplitting: true,
62
63
  }),
@@ -67,7 +68,7 @@ export function TanStackStartVitePluginCore(
67
68
  globalThis.TSS_APP_BASE = viteAppBase
68
69
 
69
70
  const root = viteConfig.root || process.cwd()
70
- const resolvedSrcDirectory = join(root, startConfig.tsr.srcDirectory)
71
+ const resolvedSrcDirectory = join(root, startConfig.srcDirectory)
71
72
 
72
73
  const routerFilePath = resolveEntry({
73
74
  type: 'router entry',
@@ -187,9 +188,6 @@ export function TanStackStartVitePluginCore(
187
188
  [VITE_ENVIRONMENT_NAMES.client]: {
188
189
  consumer: 'client',
189
190
  build: {
190
- emptyOutDir:
191
- viteConfig.environments?.[VITE_ENVIRONMENT_NAMES.client]
192
- ?.build?.emptyOutDir ?? true,
193
191
  rollupOptions: {
194
192
  input: {
195
193
  main: ENTRY_POINTS.client,
@@ -201,9 +199,6 @@ export function TanStackStartVitePluginCore(
201
199
  [VITE_ENVIRONMENT_NAMES.server]: {
202
200
  consumer: 'server',
203
201
  build: {
204
- emptyOutDir:
205
- viteConfig.environments?.[VITE_ENVIRONMENT_NAMES.server]
206
- ?.build?.emptyOutDir ?? false,
207
202
  ssr: true,
208
203
  rollupOptions: {
209
204
  input:
@@ -223,7 +218,7 @@ export function TanStackStartVitePluginCore(
223
218
  ...Object.values(VIRTUAL_MODULES),
224
219
  ...result.optimizeDeps.exclude.sort(),
225
220
  ...additionalOptimizeDeps.exclude,
226
- `@tanstack/${corePluginOpts.framework}-start/server-functions-server`,
221
+ `@tanstack/${corePluginOpts.framework}-start/server`,
227
222
  ],
228
223
  include: [
229
224
  ...additionalOptimizeDeps.include,
@@ -261,7 +256,7 @@ export function TanStackStartVitePluginCore(
261
256
  // This is not the same as injecting environment variables.
262
257
 
263
258
  ...defineReplaceEnv('TSS_SERVER_FN_BASE', startConfig.serverFns.base),
264
- ...defineReplaceEnv('TSS_OUTPUT_PUBLIC_DIR', getClientOutputDirectory(viteConfig)),
259
+ ...defineReplaceEnv('TSS_CLIENT_OUTPUT_DIR', getClientOutputDirectory(viteConfig)),
265
260
  ...defineReplaceEnv('TSS_APP_BASE', viteAppBase),
266
261
  ...(command === 'serve' ? defineReplaceEnv('TSS_SHELL', startConfig.spa?.enabled ? 'true' : 'false') : {}),
267
262
  ...defineReplaceEnv('TSS_DEV_SERVER', command === 'serve' ? 'true' : 'false'),
@@ -295,27 +290,23 @@ export function TanStackStartVitePluginCore(
295
290
  }
296
291
  },
297
292
  },
293
+ createServerFnPlugin(corePluginOpts.framework),
298
294
  // N.B. TanStackStartCompilerPlugin must be before the TanStackServerFnPluginEnv
299
- startCompilerPlugin(corePluginOpts.framework, {
300
- client: { envName: VITE_ENVIRONMENT_NAMES.client },
301
- server: { envName: VITE_ENVIRONMENT_NAMES.server },
302
- }),
295
+ startCompilerPlugin(corePluginOpts.framework),
303
296
  TanStackServerFnPluginEnv({
304
297
  // This is the ID that will be available to look up and import
305
298
  // our server function manifest and resolve its module
306
299
  manifestVirtualImportId: VIRTUAL_MODULES.serverFnManifest,
307
300
  client: {
308
301
  getRuntimeCode: () =>
309
- `import { createClientRpc } from '@tanstack/${corePluginOpts.framework}-start/server-functions-client'`,
310
- replacer: (d) =>
311
- `createClientRpc('${d.functionId}', '${startConfig.serverFns.base}')`,
302
+ `import { createClientRpc } from '@tanstack/${corePluginOpts.framework}-start/client'`,
303
+ replacer: (d) => `createClientRpc('${d.functionId}')`,
312
304
  envName: VITE_ENVIRONMENT_NAMES.client,
313
305
  },
314
306
  server: {
315
307
  getRuntimeCode: () =>
316
- `import { createServerRpc } from '@tanstack/${corePluginOpts.framework}-start/server-functions-server'`,
317
- replacer: (d) =>
318
- `createServerRpc('${d.functionId}', '${startConfig.serverFns.base}', ${d.fn})`,
308
+ `import { createServerRpc } from '@tanstack/${corePluginOpts.framework}-start/server'`,
309
+ replacer: (d) => `createServerRpc('${d.functionId}', ${d.fn})`,
319
310
  envName: VITE_ENVIRONMENT_NAMES.server,
320
311
  },
321
312
  }),
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.tsr.srcDirectory
12
+ const srcDirectory = options.srcDirectory
20
13
 
21
14
  const routesDirectory =
22
- options.tsr.routesDirectory ?? path.join(srcDirectory, 'routes')
15
+ options.router.routesDirectory ?? path.join(srcDirectory, 'routes')
23
16
 
24
17
  const generatedRouteTree =
25
- options.tsr.generatedRouteTree ??
18
+ options.router.generatedRouteTree ??
26
19
  path.join(srcDirectory, 'routeTree.gen.ts')
27
20
 
28
21
  return {
29
22
  ...options,
30
- tsr: {
31
- ...options.tsr,
23
+ router: {
24
+ ...options.router,
32
25
  ...getConfig({
33
- ...options.tsr,
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
- tsr: tsrConfig.optional().default({}),
117
+ srcDirectory: z.string().optional().default('src'),
125
118
  router: z
126
119
  .object({
127
- // TODO naming?
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
@@ -0,0 +1,195 @@
1
+ import * as babel from '@babel/core'
2
+ import * as t from '@babel/types'
3
+
4
+ import {
5
+ deadCodeElimination,
6
+ findReferencedIdentifiers,
7
+ } from 'babel-dead-code-elimination'
8
+ import { generateFromAst, parseAst } from '@tanstack/router-utils'
9
+ import { transformFuncs } from './constants'
10
+ import { handleCreateServerFileRouteCallExpressionFactory } from './serverFileRoute'
11
+ import { handleCreateIsomorphicFnCallExpression } from './isomorphicFn'
12
+ import { handleCreateMiddlewareCallExpression } from './middleware'
13
+ import {
14
+ handleCreateClientOnlyFnCallExpression,
15
+ handleCreateServerOnlyFnCallExpression,
16
+ } from './envOnly'
17
+ import type { GeneratorResult, ParseAstOptions } from '@tanstack/router-utils'
18
+
19
+ export type CompileStartFrameworkOptions = 'react' | 'solid'
20
+
21
+ type Identifiers = { [K in (typeof transformFuncs)[number]]: IdentifierConfig }
22
+ const getIdentifiers = (
23
+ framework: CompileStartFrameworkOptions,
24
+ ): Identifiers => ({
25
+ createServerRootRoute: {
26
+ name: 'createServerRootRoute',
27
+ handleCallExpression: handleCreateServerFileRouteCallExpressionFactory(
28
+ framework,
29
+ 'createServerRootRoute',
30
+ ),
31
+ paths: [],
32
+ },
33
+ createServerRoute: {
34
+ name: 'createServerRoute',
35
+ handleCallExpression: handleCreateServerFileRouteCallExpressionFactory(
36
+ framework,
37
+ 'createServerRoute',
38
+ ),
39
+ paths: [],
40
+ },
41
+ createServerFileRoute: {
42
+ name: 'createServerFileRoute',
43
+ handleCallExpression: handleCreateServerFileRouteCallExpressionFactory(
44
+ framework,
45
+ 'createServerFileRoute',
46
+ ),
47
+ paths: [],
48
+ },
49
+ createMiddleware: {
50
+ name: 'createMiddleware',
51
+ handleCallExpression: handleCreateMiddlewareCallExpression,
52
+ paths: [],
53
+ },
54
+ createServerOnlyFn: {
55
+ name: 'createServerOnlyFn',
56
+ handleCallExpression: handleCreateServerOnlyFnCallExpression,
57
+ paths: [],
58
+ },
59
+ createClientOnlyFn: {
60
+ name: 'createClientOnlyFn',
61
+ handleCallExpression: handleCreateClientOnlyFnCallExpression,
62
+ paths: [],
63
+ },
64
+ createIsomorphicFn: {
65
+ name: 'createIsomorphicFn',
66
+ handleCallExpression: handleCreateIsomorphicFnCallExpression,
67
+ paths: [],
68
+ },
69
+ })
70
+
71
+ export function compileStartOutputFactory(
72
+ framework: CompileStartFrameworkOptions,
73
+ ) {
74
+ return function compileStartOutput(opts: CompileOptions): GeneratorResult {
75
+ const ast = parseAst(opts)
76
+
77
+ const doDce = opts.dce ?? true
78
+ // find referenced identifiers *before* we transform anything
79
+ const refIdents = doDce ? findReferencedIdentifiers(ast) : undefined
80
+
81
+ babel.traverse(ast, {
82
+ Program: {
83
+ enter(programPath) {
84
+ const identifiers = getIdentifiers(framework)
85
+ programPath.traverse({
86
+ ImportDeclaration: (path) => {
87
+ if (path.node.source.value !== `@tanstack/${framework}-start`) {
88
+ return
89
+ }
90
+
91
+ // handle a destructured imports being renamed like "import { createServerFn as myCreateServerFn } from '@tanstack/react-start';"
92
+ path.node.specifiers.forEach((specifier) => {
93
+ transformFuncs.forEach((identifierKey) => {
94
+ const identifier = identifiers[identifierKey]
95
+
96
+ if (
97
+ specifier.type === 'ImportSpecifier' &&
98
+ specifier.imported.type === 'Identifier'
99
+ ) {
100
+ if (specifier.imported.name === identifierKey) {
101
+ identifier.name = specifier.local.name
102
+ }
103
+ }
104
+
105
+ // handle namespace imports like "import * as TanStackStart from '@tanstack/react-start';"
106
+ if (specifier.type === 'ImportNamespaceSpecifier') {
107
+ identifier.name = `${specifier.local.name}.${identifierKey}`
108
+ }
109
+ })
110
+ })
111
+ },
112
+ CallExpression: (path) => {
113
+ transformFuncs.forEach((identifierKey) => {
114
+ // Check to see if the call expression is a call to the
115
+ // identifiers[identifierKey].name
116
+ if (
117
+ t.isIdentifier(path.node.callee) &&
118
+ path.node.callee.name === identifiers[identifierKey].name
119
+ ) {
120
+ // The identifier could be a call to the original function
121
+ // in the source code. If this is case, we need to ignore it.
122
+ // Check the scope to see if the identifier is a function declaration.
123
+ // if it is, then we can ignore it.
124
+
125
+ if (
126
+ path.scope.getBinding(identifiers[identifierKey].name)?.path
127
+ .node.type === 'FunctionDeclaration'
128
+ ) {
129
+ return
130
+ }
131
+
132
+ return identifiers[identifierKey].paths.push(path)
133
+ }
134
+
135
+ // handle namespace imports like "import * as TanStackStart from '@tanstack/react-start';"
136
+ // which are then called like "TanStackStart.createServerFn()"
137
+ if (t.isMemberExpression(path.node.callee)) {
138
+ if (
139
+ t.isIdentifier(path.node.callee.object) &&
140
+ t.isIdentifier(path.node.callee.property)
141
+ ) {
142
+ const callname = [
143
+ path.node.callee.object.name,
144
+ path.node.callee.property.name,
145
+ ].join('.')
146
+
147
+ if (callname === identifiers[identifierKey].name) {
148
+ identifiers[identifierKey].paths.push(path)
149
+ }
150
+ }
151
+ }
152
+
153
+ return
154
+ })
155
+ },
156
+ })
157
+
158
+ transformFuncs.forEach((identifierKey) => {
159
+ identifiers[identifierKey].paths.forEach((path) => {
160
+ identifiers[identifierKey].handleCallExpression(
161
+ path as babel.NodePath<t.CallExpression>,
162
+ opts,
163
+ )
164
+ })
165
+ })
166
+ },
167
+ },
168
+ })
169
+
170
+ if (doDce) {
171
+ deadCodeElimination(ast, refIdents)
172
+ }
173
+
174
+ return generateFromAst(ast, {
175
+ sourceMaps: true,
176
+ sourceFileName: opts.filename,
177
+ filename: opts.filename,
178
+ })
179
+ }
180
+ }
181
+
182
+ export type CompileOptions = ParseAstOptions & {
183
+ env: 'server' | 'client'
184
+ dce?: boolean
185
+ filename: string
186
+ }
187
+
188
+ export type IdentifierConfig = {
189
+ name: string
190
+ handleCallExpression: (
191
+ path: babel.NodePath<t.CallExpression>,
192
+ opts: CompileOptions,
193
+ ) => void
194
+ paths: Array<babel.NodePath>
195
+ }
@@ -0,0 +1,9 @@
1
+ export const transformFuncs = [
2
+ 'createMiddleware',
3
+ 'createServerOnlyFn',
4
+ 'createClientOnlyFn',
5
+ 'createIsomorphicFn',
6
+ 'createServerRoute',
7
+ 'createServerFileRoute',
8
+ 'createServerRootRoute',
9
+ ] as const
@@ -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
+ }