@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.
- package/dist/esm/create-server-fn-plugin/compiler.d.ts +61 -0
- package/dist/esm/create-server-fn-plugin/compiler.js +336 -0
- package/dist/esm/create-server-fn-plugin/compiler.js.map +1 -0
- package/dist/esm/create-server-fn-plugin/handleCreateServerFn.d.ts +6 -0
- package/dist/esm/create-server-fn-plugin/handleCreateServerFn.js +85 -0
- package/dist/esm/create-server-fn-plugin/handleCreateServerFn.js.map +1 -0
- package/dist/esm/create-server-fn-plugin/plugin.d.ts +3 -0
- package/dist/esm/create-server-fn-plugin/plugin.js +113 -0
- package/dist/esm/create-server-fn-plugin/plugin.js.map +1 -0
- package/dist/esm/output-directory.js +5 -2
- package/dist/esm/output-directory.js.map +1 -1
- package/dist/esm/plugin.d.ts +1 -1
- package/dist/esm/plugin.js +12 -15
- 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 +15 -0
- package/dist/esm/start-compiler-plugin/compilers.js +131 -0
- package/dist/esm/start-compiler-plugin/compilers.js.map +1 -0
- package/dist/esm/start-compiler-plugin/constants.d.ts +1 -0
- package/dist/esm/start-compiler-plugin/constants.js +13 -0
- package/dist/esm/start-compiler-plugin/constants.js.map +1 -0
- 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.d.ts → start-compiler-plugin/plugin.d.ts} +1 -8
- package/dist/esm/start-compiler-plugin/plugin.js +96 -0
- package/dist/esm/start-compiler-plugin/plugin.js.map +1 -0
- 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/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 +8 -8
- package/src/create-server-fn-plugin/compiler.ts +456 -0
- package/src/create-server-fn-plugin/handleCreateServerFn.ts +153 -0
- package/src/create-server-fn-plugin/plugin.ts +138 -0
- package/src/output-directory.ts +13 -6
- package/src/plugin.ts +13 -22
- package/src/schema.ts +10 -16
- package/src/start-compiler-plugin/compilers.ts +195 -0
- package/src/start-compiler-plugin/constants.ts +9 -0
- 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 +122 -0
- package/src/start-compiler-plugin/serverFileRoute.ts +59 -0
- package/src/start-compiler-plugin/utils.ts +41 -0
- package/dist/esm/compilers.d.ts +0 -21
- package/dist/esm/compilers.js +0 -395
- package/dist/esm/compilers.js.map +0 -1
- package/dist/esm/start-compiler-plugin.js +0 -78
- package/dist/esm/start-compiler-plugin.js.map +0 -1
- package/src/compilers.ts +0 -659
- 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
|
+
}
|
package/src/output-directory.ts
CHANGED
|
@@ -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?.[
|
|
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.
|
|
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.
|
|
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
|
|
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('
|
|
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/
|
|
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
|
|
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.
|
|
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
|
|
@@ -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,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
|
+
}
|