@tanstack/start-plugin-core 1.143.4 → 1.143.5
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 +11 -46
- package/dist/esm/plugin.js.map +1 -1
- package/dist/esm/{create-server-fn-plugin → start-compiler-plugin}/compiler.d.ts +39 -4
- package/dist/esm/{create-server-fn-plugin → start-compiler-plugin}/compiler.js +82 -24
- package/dist/esm/start-compiler-plugin/compiler.js.map +1 -0
- package/dist/esm/start-compiler-plugin/handleClientOnlyJSX.js.map +1 -0
- package/dist/esm/start-compiler-plugin/handleCreateIsomorphicFn.d.ts +8 -0
- package/dist/esm/start-compiler-plugin/handleCreateIsomorphicFn.js +33 -0
- package/dist/esm/start-compiler-plugin/handleCreateIsomorphicFn.js.map +1 -0
- package/dist/esm/start-compiler-plugin/handleCreateMiddleware.d.ts +8 -0
- package/dist/esm/start-compiler-plugin/handleCreateMiddleware.js +25 -0
- package/dist/esm/start-compiler-plugin/handleCreateMiddleware.js.map +1 -0
- package/dist/esm/start-compiler-plugin/handleCreateServerFn.d.ts +19 -0
- package/dist/esm/start-compiler-plugin/handleCreateServerFn.js +262 -0
- package/dist/esm/start-compiler-plugin/handleCreateServerFn.js.map +1 -0
- package/dist/esm/start-compiler-plugin/handleEnvOnly.d.ts +10 -0
- package/dist/esm/start-compiler-plugin/handleEnvOnly.js +38 -0
- package/dist/esm/start-compiler-plugin/handleEnvOnly.js.map +1 -0
- package/dist/esm/start-compiler-plugin/plugin.d.ts +19 -0
- package/dist/esm/start-compiler-plugin/plugin.js +314 -0
- package/dist/esm/start-compiler-plugin/plugin.js.map +1 -0
- package/dist/esm/start-compiler-plugin/types.d.ts +116 -0
- package/dist/esm/start-compiler-plugin/utils.d.ts +23 -0
- package/dist/esm/start-compiler-plugin/utils.js +34 -0
- package/dist/esm/start-compiler-plugin/utils.js.map +1 -0
- package/dist/esm/types.d.ts +0 -1
- package/package.json +3 -4
- package/src/plugin.ts +10 -50
- package/src/{create-server-fn-plugin → start-compiler-plugin}/compiler.ts +162 -30
- package/src/start-compiler-plugin/handleCreateIsomorphicFn.ts +54 -0
- package/src/start-compiler-plugin/handleCreateMiddleware.ts +39 -0
- package/src/start-compiler-plugin/handleCreateServerFn.ts +491 -0
- package/src/start-compiler-plugin/handleEnvOnly.ts +56 -0
- package/src/start-compiler-plugin/plugin.ts +423 -0
- package/src/start-compiler-plugin/types.ts +133 -0
- package/src/start-compiler-plugin/utils.ts +52 -0
- package/src/types.ts +0 -1
- package/dist/esm/create-server-fn-plugin/compiler.js.map +0 -1
- package/dist/esm/create-server-fn-plugin/handleClientOnlyJSX.js.map +0 -1
- package/dist/esm/create-server-fn-plugin/handleCreateIsomorphicFn.d.ts +0 -4
- package/dist/esm/create-server-fn-plugin/handleCreateIsomorphicFn.js +0 -31
- package/dist/esm/create-server-fn-plugin/handleCreateIsomorphicFn.js.map +0 -1
- package/dist/esm/create-server-fn-plugin/handleCreateMiddleware.d.ts +0 -10
- package/dist/esm/create-server-fn-plugin/handleCreateMiddleware.js +0 -29
- package/dist/esm/create-server-fn-plugin/handleCreateMiddleware.js.map +0 -1
- package/dist/esm/create-server-fn-plugin/handleCreateServerFn.d.ts +0 -17
- package/dist/esm/create-server-fn-plugin/handleCreateServerFn.js +0 -82
- package/dist/esm/create-server-fn-plugin/handleCreateServerFn.js.map +0 -1
- package/dist/esm/create-server-fn-plugin/handleEnvOnly.d.ts +0 -6
- package/dist/esm/create-server-fn-plugin/handleEnvOnly.js +0 -36
- package/dist/esm/create-server-fn-plugin/handleEnvOnly.js.map +0 -1
- package/dist/esm/create-server-fn-plugin/plugin.d.ts +0 -10
- package/dist/esm/create-server-fn-plugin/plugin.js +0 -186
- package/dist/esm/create-server-fn-plugin/plugin.js.map +0 -1
- package/dist/esm/create-server-fn-plugin/types.d.ts +0 -30
- package/dist/esm/create-server-fn-plugin/utils.d.ts +0 -10
- package/dist/esm/create-server-fn-plugin/utils.js +0 -19
- package/dist/esm/create-server-fn-plugin/utils.js.map +0 -1
- package/src/create-server-fn-plugin/handleCreateIsomorphicFn.ts +0 -46
- package/src/create-server-fn-plugin/handleCreateMiddleware.ts +0 -45
- package/src/create-server-fn-plugin/handleCreateServerFn.ts +0 -145
- package/src/create-server-fn-plugin/handleEnvOnly.ts +0 -45
- package/src/create-server-fn-plugin/plugin.ts +0 -234
- package/src/create-server-fn-plugin/types.ts +0 -34
- package/src/create-server-fn-plugin/utils.ts +0 -24
- /package/dist/esm/{create-server-fn-plugin → start-compiler-plugin}/handleClientOnlyJSX.d.ts +0 -0
- /package/dist/esm/{create-server-fn-plugin → start-compiler-plugin}/handleClientOnlyJSX.js +0 -0
- /package/src/{create-server-fn-plugin → start-compiler-plugin}/handleClientOnlyJSX.ts +0 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import { VIRTUAL_MODULES } from "@tanstack/start-server-core";
|
|
2
|
+
import { VITE_ENVIRONMENT_NAMES, TRANSFORM_ID_REGEX } from "../constants.js";
|
|
3
|
+
import { StartCompiler, LookupKindsPerEnv, detectKindsInCode, KindDetectionPatterns } from "./compiler.js";
|
|
4
|
+
import { cleanId } from "./utils.js";
|
|
5
|
+
function getTransformCodeFilterForEnv(env) {
|
|
6
|
+
const validKinds = LookupKindsPerEnv[env];
|
|
7
|
+
const patterns = [];
|
|
8
|
+
for (const [kind, pattern] of Object.entries(KindDetectionPatterns)) {
|
|
9
|
+
if (validKinds.has(kind)) {
|
|
10
|
+
patterns.push(pattern);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return patterns;
|
|
14
|
+
}
|
|
15
|
+
const getLookupConfigurationsForEnv = (env, framework) => {
|
|
16
|
+
const commonConfigs = [
|
|
17
|
+
{
|
|
18
|
+
libName: `@tanstack/${framework}-start`,
|
|
19
|
+
rootExport: "createServerFn",
|
|
20
|
+
kind: "Root"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
libName: `@tanstack/${framework}-start`,
|
|
24
|
+
rootExport: "createIsomorphicFn",
|
|
25
|
+
kind: "IsomorphicFn"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
libName: `@tanstack/${framework}-start`,
|
|
29
|
+
rootExport: "createServerOnlyFn",
|
|
30
|
+
kind: "ServerOnlyFn"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
libName: `@tanstack/${framework}-start`,
|
|
34
|
+
rootExport: "createClientOnlyFn",
|
|
35
|
+
kind: "ClientOnlyFn"
|
|
36
|
+
}
|
|
37
|
+
];
|
|
38
|
+
if (env === "client") {
|
|
39
|
+
return [
|
|
40
|
+
{
|
|
41
|
+
libName: `@tanstack/${framework}-start`,
|
|
42
|
+
rootExport: "createMiddleware",
|
|
43
|
+
kind: "Root"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
libName: `@tanstack/${framework}-start`,
|
|
47
|
+
rootExport: "createStart",
|
|
48
|
+
kind: "Root"
|
|
49
|
+
},
|
|
50
|
+
...commonConfigs
|
|
51
|
+
];
|
|
52
|
+
} else {
|
|
53
|
+
return [
|
|
54
|
+
...commonConfigs,
|
|
55
|
+
{
|
|
56
|
+
libName: `@tanstack/${framework}-router`,
|
|
57
|
+
rootExport: "ClientOnly",
|
|
58
|
+
kind: "ClientOnlyJSX"
|
|
59
|
+
}
|
|
60
|
+
];
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
const SERVER_FN_LOOKUP = "server-fn-module-lookup";
|
|
64
|
+
function resolveViteId(id) {
|
|
65
|
+
return `\0${id}`;
|
|
66
|
+
}
|
|
67
|
+
const validateServerFnIdVirtualModule = `virtual:tanstack-start-validate-server-fn-id`;
|
|
68
|
+
function parseIdQuery(id) {
|
|
69
|
+
if (!id.includes("?")) return { filename: id, query: {} };
|
|
70
|
+
const [filename, rawQuery] = id.split(`?`, 2);
|
|
71
|
+
const query = Object.fromEntries(new URLSearchParams(rawQuery));
|
|
72
|
+
return { filename, query };
|
|
73
|
+
}
|
|
74
|
+
function generateManifestModule(serverFnsById, includeClientReferencedCheck) {
|
|
75
|
+
const manifestEntries = Object.entries(serverFnsById).map(([id, fn]) => {
|
|
76
|
+
const baseEntry = `'${id}': {
|
|
77
|
+
functionName: '${fn.functionName}',
|
|
78
|
+
importer: () => import(${JSON.stringify(fn.extractedFilename)})${includeClientReferencedCheck ? `,
|
|
79
|
+
isClientReferenced: ${fn.isClientReferenced ?? true}` : ""}
|
|
80
|
+
}`;
|
|
81
|
+
return baseEntry;
|
|
82
|
+
}).join(",");
|
|
83
|
+
const getServerFnByIdParams = includeClientReferencedCheck ? "id, opts" : "id";
|
|
84
|
+
const clientReferencedCheck = includeClientReferencedCheck ? `
|
|
85
|
+
// If called from client, only allow client-referenced functions
|
|
86
|
+
if (opts?.fromClient && !serverFnInfo.isClientReferenced) {
|
|
87
|
+
throw new Error('Server function not accessible from client: ' + id)
|
|
88
|
+
}
|
|
89
|
+
` : "";
|
|
90
|
+
return `
|
|
91
|
+
const manifest = {${manifestEntries}}
|
|
92
|
+
|
|
93
|
+
export async function getServerFnById(${getServerFnByIdParams}) {
|
|
94
|
+
const serverFnInfo = manifest[id]
|
|
95
|
+
if (!serverFnInfo) {
|
|
96
|
+
throw new Error('Server function info not found for ' + id)
|
|
97
|
+
}
|
|
98
|
+
${clientReferencedCheck}
|
|
99
|
+
const fnModule = await serverFnInfo.importer()
|
|
100
|
+
|
|
101
|
+
if (!fnModule) {
|
|
102
|
+
console.info('serverFnInfo', serverFnInfo)
|
|
103
|
+
throw new Error('Server function module not resolved for ' + id)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const action = fnModule[serverFnInfo.functionName]
|
|
107
|
+
|
|
108
|
+
if (!action) {
|
|
109
|
+
console.info('serverFnInfo', serverFnInfo)
|
|
110
|
+
console.info('fnModule', fnModule)
|
|
111
|
+
|
|
112
|
+
throw new Error(
|
|
113
|
+
\`Server function module export not resolved for serverFn ID: \${id}\`,
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
return action
|
|
117
|
+
}
|
|
118
|
+
`;
|
|
119
|
+
}
|
|
120
|
+
function startCompilerPlugin(opts) {
|
|
121
|
+
const compilers = {};
|
|
122
|
+
const serverFnsById = {};
|
|
123
|
+
const onServerFnsById = (d) => {
|
|
124
|
+
Object.assign(serverFnsById, d);
|
|
125
|
+
};
|
|
126
|
+
let root = process.cwd();
|
|
127
|
+
const resolvedResolverVirtualImportId = resolveViteId(
|
|
128
|
+
VIRTUAL_MODULES.serverFnResolver
|
|
129
|
+
);
|
|
130
|
+
const ssrEnvName = VITE_ENVIRONMENT_NAMES.server;
|
|
131
|
+
const ssrIsProvider = opts.providerEnvName === ssrEnvName;
|
|
132
|
+
const appliedResolverEnvironments = new Set(
|
|
133
|
+
ssrIsProvider ? [opts.providerEnvName] : [ssrEnvName, opts.providerEnvName]
|
|
134
|
+
);
|
|
135
|
+
function perEnvServerFnPlugin(environment) {
|
|
136
|
+
const transformCodeFilter = getTransformCodeFilterForEnv(environment.type);
|
|
137
|
+
return {
|
|
138
|
+
name: `tanstack-start-core::server-fn:${environment.name}`,
|
|
139
|
+
enforce: "pre",
|
|
140
|
+
applyToEnvironment(env) {
|
|
141
|
+
return env.name === environment.name;
|
|
142
|
+
},
|
|
143
|
+
configResolved(config) {
|
|
144
|
+
root = config.root;
|
|
145
|
+
config.command;
|
|
146
|
+
},
|
|
147
|
+
transform: {
|
|
148
|
+
filter: {
|
|
149
|
+
id: {
|
|
150
|
+
exclude: new RegExp(`${SERVER_FN_LOOKUP}$`),
|
|
151
|
+
include: TRANSFORM_ID_REGEX
|
|
152
|
+
},
|
|
153
|
+
code: {
|
|
154
|
+
include: transformCodeFilter
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
async handler(code, id) {
|
|
158
|
+
let compiler = compilers[this.environment.name];
|
|
159
|
+
if (!compiler) {
|
|
160
|
+
const mode = this.environment.mode === "build" ? "build" : "dev";
|
|
161
|
+
compiler = new StartCompiler({
|
|
162
|
+
env: environment.type,
|
|
163
|
+
envName: environment.name,
|
|
164
|
+
root,
|
|
165
|
+
lookupKinds: LookupKindsPerEnv[environment.type],
|
|
166
|
+
lookupConfigurations: getLookupConfigurationsForEnv(
|
|
167
|
+
environment.type,
|
|
168
|
+
opts.framework
|
|
169
|
+
),
|
|
170
|
+
mode,
|
|
171
|
+
framework: opts.framework,
|
|
172
|
+
providerEnvName: opts.providerEnvName,
|
|
173
|
+
generateFunctionId: opts.generateFunctionId,
|
|
174
|
+
onServerFnsById,
|
|
175
|
+
getKnownServerFns: () => serverFnsById,
|
|
176
|
+
loadModule: async (id2) => {
|
|
177
|
+
if (this.environment.mode === "build") {
|
|
178
|
+
const loaded = await this.load({ id: id2 });
|
|
179
|
+
const code2 = loaded.code ?? "";
|
|
180
|
+
compiler.ingestModule({ code: code2, id: id2 });
|
|
181
|
+
} else if (this.environment.mode === "dev") {
|
|
182
|
+
await this.environment.fetchModule(
|
|
183
|
+
id2 + "?" + SERVER_FN_LOOKUP
|
|
184
|
+
);
|
|
185
|
+
} else {
|
|
186
|
+
throw new Error(
|
|
187
|
+
`could not load module ${id2}: unknown environment mode ${this.environment.mode}`
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
resolveId: async (source, importer) => {
|
|
192
|
+
const r = await this.resolve(source, importer);
|
|
193
|
+
if (r) {
|
|
194
|
+
if (!r.external) {
|
|
195
|
+
return cleanId(r.id);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
compilers[this.environment.name] = compiler;
|
|
202
|
+
}
|
|
203
|
+
const detectedKinds = detectKindsInCode(code, environment.type);
|
|
204
|
+
const result = await compiler.compile({
|
|
205
|
+
id,
|
|
206
|
+
code,
|
|
207
|
+
detectedKinds
|
|
208
|
+
});
|
|
209
|
+
return result;
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
hotUpdate(ctx) {
|
|
213
|
+
const compiler = compilers[this.environment.name];
|
|
214
|
+
ctx.modules.forEach((m) => {
|
|
215
|
+
if (m.id) {
|
|
216
|
+
const deleted = compiler?.invalidateModule(m.id);
|
|
217
|
+
if (deleted) {
|
|
218
|
+
m.importers.forEach((importer) => {
|
|
219
|
+
if (importer.id) {
|
|
220
|
+
compiler?.invalidateModule(importer.id);
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
return [
|
|
230
|
+
...opts.environments.map(perEnvServerFnPlugin),
|
|
231
|
+
{
|
|
232
|
+
name: "tanstack-start-core:capture-server-fn-module-lookup",
|
|
233
|
+
// we only need this plugin in dev mode
|
|
234
|
+
apply: "serve",
|
|
235
|
+
applyToEnvironment(env) {
|
|
236
|
+
return !!opts.environments.find((e) => e.name === env.name);
|
|
237
|
+
},
|
|
238
|
+
transform: {
|
|
239
|
+
filter: {
|
|
240
|
+
id: new RegExp(`${SERVER_FN_LOOKUP}$`)
|
|
241
|
+
},
|
|
242
|
+
handler(code, id) {
|
|
243
|
+
const compiler = compilers[this.environment.name];
|
|
244
|
+
compiler?.ingestModule({ code, id: cleanId(id) });
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
// Validate server function ID in dev mode
|
|
249
|
+
{
|
|
250
|
+
name: "tanstack-start-core:validate-server-fn-id",
|
|
251
|
+
apply: "serve",
|
|
252
|
+
load: {
|
|
253
|
+
filter: {
|
|
254
|
+
id: new RegExp(resolveViteId(validateServerFnIdVirtualModule))
|
|
255
|
+
},
|
|
256
|
+
handler(id) {
|
|
257
|
+
const parsed = parseIdQuery(id);
|
|
258
|
+
if (parsed.query.id && serverFnsById[parsed.query.id]) {
|
|
259
|
+
return `export {}`;
|
|
260
|
+
}
|
|
261
|
+
this.error(`Invalid server function ID: ${parsed.query.id}`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
// Manifest plugin for server environments
|
|
266
|
+
{
|
|
267
|
+
name: "tanstack-start-core:server-fn-resolver",
|
|
268
|
+
enforce: "pre",
|
|
269
|
+
applyToEnvironment: (env) => {
|
|
270
|
+
return appliedResolverEnvironments.has(env.name);
|
|
271
|
+
},
|
|
272
|
+
configResolved(config) {
|
|
273
|
+
root = config.root;
|
|
274
|
+
config.command;
|
|
275
|
+
},
|
|
276
|
+
resolveId: {
|
|
277
|
+
filter: { id: new RegExp(VIRTUAL_MODULES.serverFnResolver) },
|
|
278
|
+
handler() {
|
|
279
|
+
return resolvedResolverVirtualImportId;
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
load: {
|
|
283
|
+
filter: { id: new RegExp(resolvedResolverVirtualImportId) },
|
|
284
|
+
handler() {
|
|
285
|
+
if (this.environment.name !== opts.providerEnvName) {
|
|
286
|
+
return `export { getServerFnById } from '@tanstack/start-server-core/server-fn-ssr-caller'`;
|
|
287
|
+
}
|
|
288
|
+
if (this.environment.mode !== "build") {
|
|
289
|
+
const mod = `
|
|
290
|
+
export async function getServerFnById(id) {
|
|
291
|
+
const validateIdImport = ${JSON.stringify(validateServerFnIdVirtualModule)} + '?id=' + id
|
|
292
|
+
await import(/* @vite-ignore */ '/@id/__x00__' + validateIdImport)
|
|
293
|
+
const decoded = Buffer.from(id, 'base64url').toString('utf8')
|
|
294
|
+
const devServerFn = JSON.parse(decoded)
|
|
295
|
+
const mod = await import(/* @vite-ignore */ devServerFn.file)
|
|
296
|
+
return mod[devServerFn.export]
|
|
297
|
+
}
|
|
298
|
+
`;
|
|
299
|
+
return mod;
|
|
300
|
+
}
|
|
301
|
+
const includeClientReferencedCheck = !ssrIsProvider;
|
|
302
|
+
return generateManifestModule(
|
|
303
|
+
serverFnsById,
|
|
304
|
+
includeClientReferencedCheck
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
];
|
|
310
|
+
}
|
|
311
|
+
export {
|
|
312
|
+
startCompilerPlugin
|
|
313
|
+
};
|
|
314
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.js","sources":["../../../src/start-compiler-plugin/plugin.ts"],"sourcesContent":["import { VIRTUAL_MODULES } from '@tanstack/start-server-core'\nimport { TRANSFORM_ID_REGEX, VITE_ENVIRONMENT_NAMES } from '../constants'\nimport {\n KindDetectionPatterns,\n LookupKindsPerEnv,\n StartCompiler,\n detectKindsInCode,\n} from './compiler'\nimport { cleanId } from './utils'\nimport type { CompileStartFrameworkOptions } from '../types'\nimport type { LookupConfig, LookupKind } from './compiler'\nimport type { GenerateFunctionIdFnOptional, ServerFn } from './types'\nimport type { PluginOption } from 'vite'\n\n// Derive transform code filter from KindDetectionPatterns (single source of truth)\nfunction getTransformCodeFilterForEnv(env: 'client' | 'server'): Array<RegExp> {\n const validKinds = LookupKindsPerEnv[env]\n const patterns: Array<RegExp> = []\n for (const [kind, pattern] of Object.entries(KindDetectionPatterns) as Array<\n [LookupKind, RegExp]\n >) {\n if (validKinds.has(kind)) {\n patterns.push(pattern)\n }\n }\n return patterns\n}\n\nconst getLookupConfigurationsForEnv = (\n env: 'client' | 'server',\n framework: CompileStartFrameworkOptions,\n): Array<LookupConfig> => {\n // Common configs for all environments\n const commonConfigs: Array<LookupConfig> = [\n {\n libName: `@tanstack/${framework}-start`,\n rootExport: 'createServerFn',\n kind: 'Root',\n },\n {\n libName: `@tanstack/${framework}-start`,\n rootExport: 'createIsomorphicFn',\n kind: 'IsomorphicFn',\n },\n {\n libName: `@tanstack/${framework}-start`,\n rootExport: 'createServerOnlyFn',\n kind: 'ServerOnlyFn',\n },\n {\n libName: `@tanstack/${framework}-start`,\n rootExport: 'createClientOnlyFn',\n kind: 'ClientOnlyFn',\n },\n ]\n\n if (env === 'client') {\n return [\n {\n libName: `@tanstack/${framework}-start`,\n rootExport: 'createMiddleware',\n kind: 'Root',\n },\n {\n libName: `@tanstack/${framework}-start`,\n rootExport: 'createStart',\n kind: 'Root',\n },\n ...commonConfigs,\n ]\n } else {\n // Server-only: add ClientOnly JSX component lookup\n return [\n ...commonConfigs,\n {\n libName: `@tanstack/${framework}-router`,\n rootExport: 'ClientOnly',\n kind: 'ClientOnlyJSX',\n },\n ]\n }\n}\nconst SERVER_FN_LOOKUP = 'server-fn-module-lookup'\n\nfunction resolveViteId(id: string) {\n return `\\0${id}`\n}\n\nconst validateServerFnIdVirtualModule = `virtual:tanstack-start-validate-server-fn-id`\n\nfunction parseIdQuery(id: string): {\n filename: string\n query: {\n [k: string]: string\n }\n} {\n if (!id.includes('?')) return { filename: id, query: {} }\n const [filename, rawQuery] = id.split(`?`, 2) as [string, string]\n const query = Object.fromEntries(new URLSearchParams(rawQuery))\n return { filename, query }\n}\n\n/**\n * Generates the manifest module code for server functions.\n * @param serverFnsById - Map of function IDs to their server function info\n * @param includeClientReferencedCheck - Whether to include isClientReferenced flag and runtime check.\n * This is needed when SSR is NOT the provider, so server-only-referenced functions in the manifest\n * can be blocked from client HTTP requests.\n */\nfunction generateManifestModule(\n serverFnsById: Record<string, ServerFn>,\n includeClientReferencedCheck: boolean,\n): string {\n const manifestEntries = Object.entries(serverFnsById)\n .map(([id, fn]) => {\n const baseEntry = `'${id}': {\n functionName: '${fn.functionName}',\n importer: () => import(${JSON.stringify(fn.extractedFilename)})${\n includeClientReferencedCheck\n ? `,\n isClientReferenced: ${fn.isClientReferenced ?? true}`\n : ''\n }\n }`\n return baseEntry\n })\n .join(',')\n\n const getServerFnByIdParams = includeClientReferencedCheck ? 'id, opts' : 'id'\n const clientReferencedCheck = includeClientReferencedCheck\n ? `\n // If called from client, only allow client-referenced functions\n if (opts?.fromClient && !serverFnInfo.isClientReferenced) {\n throw new Error('Server function not accessible from client: ' + id)\n }\n`\n : ''\n\n return `\n const manifest = {${manifestEntries}}\n\n export async function getServerFnById(${getServerFnByIdParams}) {\n const serverFnInfo = manifest[id]\n if (!serverFnInfo) {\n throw new Error('Server function info not found for ' + id)\n }\n${clientReferencedCheck}\n const fnModule = await serverFnInfo.importer()\n\n if (!fnModule) {\n console.info('serverFnInfo', serverFnInfo)\n throw new Error('Server function module not resolved for ' + id)\n }\n\n const action = fnModule[serverFnInfo.functionName]\n\n if (!action) {\n console.info('serverFnInfo', serverFnInfo)\n console.info('fnModule', fnModule)\n\n throw new Error(\n \\`Server function module export not resolved for serverFn ID: \\${id}\\`,\n )\n }\n return action\n }\n `\n}\n\nexport interface StartCompilerPluginOptions {\n framework: CompileStartFrameworkOptions\n environments: Array<{ name: string; type: 'client' | 'server' }>\n /**\n * Custom function ID generator (optional).\n */\n generateFunctionId?: GenerateFunctionIdFnOptional\n /**\n * The Vite environment name for the server function provider.\n */\n providerEnvName: string\n}\n\nexport function startCompilerPlugin(\n opts: StartCompilerPluginOptions,\n): PluginOption {\n const compilers: Record<string /* envName */, StartCompiler> = {}\n\n // Shared registry of server functions across all environments\n const serverFnsById: Record<string, ServerFn> = {}\n\n const onServerFnsById = (d: Record<string, ServerFn>) => {\n Object.assign(serverFnsById, d)\n }\n\n let root = process.cwd()\n let command: 'build' | 'serve' = 'build'\n\n const resolvedResolverVirtualImportId = resolveViteId(\n VIRTUAL_MODULES.serverFnResolver,\n )\n\n // Determine which environments need the resolver (getServerFnById)\n // SSR environment always needs the resolver for server-side calls\n // Provider environment needs it for the actual implementation\n const ssrEnvName = VITE_ENVIRONMENT_NAMES.server\n\n // SSR is the provider when the provider environment is the default server environment\n const ssrIsProvider = opts.providerEnvName === ssrEnvName\n\n // Environments that need the resolver: SSR (for server calls) and provider (for implementation)\n const appliedResolverEnvironments = new Set(\n ssrIsProvider ? [opts.providerEnvName] : [ssrEnvName, opts.providerEnvName],\n )\n\n function perEnvServerFnPlugin(environment: {\n name: string\n type: 'client' | 'server'\n }): PluginOption {\n // Derive transform code filter from KindDetectionPatterns (single source of truth)\n const transformCodeFilter = getTransformCodeFilterForEnv(environment.type)\n\n return {\n name: `tanstack-start-core::server-fn:${environment.name}`,\n enforce: 'pre',\n applyToEnvironment(env) {\n return env.name === environment.name\n },\n configResolved(config) {\n root = config.root\n command = config.command\n },\n transform: {\n filter: {\n id: {\n exclude: new RegExp(`${SERVER_FN_LOOKUP}$`),\n include: TRANSFORM_ID_REGEX,\n },\n code: {\n include: transformCodeFilter,\n },\n },\n async handler(code, id) {\n let compiler = compilers[this.environment.name]\n if (!compiler) {\n // Default to 'dev' mode for unknown environments (conservative: no caching)\n const mode = this.environment.mode === 'build' ? 'build' : 'dev'\n compiler = new StartCompiler({\n env: environment.type,\n envName: environment.name,\n root,\n lookupKinds: LookupKindsPerEnv[environment.type],\n lookupConfigurations: getLookupConfigurationsForEnv(\n environment.type,\n opts.framework,\n ),\n mode,\n framework: opts.framework,\n providerEnvName: opts.providerEnvName,\n generateFunctionId: opts.generateFunctionId,\n onServerFnsById,\n getKnownServerFns: () => serverFnsById,\n loadModule: async (id: string) => {\n if (this.environment.mode === 'build') {\n const loaded = await this.load({ id })\n // Handle modules with no runtime code (e.g., type-only exports).\n // After TypeScript compilation, these become empty modules.\n // Create an empty module info instead of throwing.\n const code = loaded.code ?? ''\n compiler!.ingestModule({ code, id })\n } else if (this.environment.mode === 'dev') {\n /**\n * in dev, vite does not return code from `ctx.load()`\n * so instead, we need to take a different approach\n * we must force vite to load the module and run it through the vite plugin pipeline\n * we can do this by using the `fetchModule` method\n * the `captureServerFnModuleLookupPlugin` captures the module code via its transform hook and invokes analyzeModuleAST\n */\n await this.environment.fetchModule(\n id + '?' + SERVER_FN_LOOKUP,\n )\n } else {\n throw new Error(\n `could not load module ${id}: unknown environment mode ${this.environment.mode}`,\n )\n }\n },\n resolveId: async (source: string, importer?: string) => {\n const r = await this.resolve(source, importer)\n if (r) {\n if (!r.external) {\n return cleanId(r.id)\n }\n }\n return null\n },\n })\n compilers[this.environment.name] = compiler\n }\n\n // Detect which kinds are present in this file before parsing\n const detectedKinds = detectKindsInCode(code, environment.type)\n\n const result = await compiler.compile({\n id,\n code,\n detectedKinds,\n })\n return result\n },\n },\n\n hotUpdate(ctx) {\n const compiler = compilers[this.environment.name]\n\n ctx.modules.forEach((m) => {\n if (m.id) {\n const deleted = compiler?.invalidateModule(m.id)\n if (deleted) {\n m.importers.forEach((importer) => {\n if (importer.id) {\n compiler?.invalidateModule(importer.id)\n }\n })\n }\n }\n })\n },\n }\n }\n\n return [\n ...opts.environments.map(perEnvServerFnPlugin),\n {\n name: 'tanstack-start-core:capture-server-fn-module-lookup',\n // we only need this plugin in dev mode\n apply: 'serve',\n applyToEnvironment(env) {\n return !!opts.environments.find((e) => e.name === env.name)\n },\n transform: {\n filter: {\n id: new RegExp(`${SERVER_FN_LOOKUP}$`),\n },\n handler(code, id) {\n const compiler = compilers[this.environment.name]\n compiler?.ingestModule({ code, id: cleanId(id) })\n },\n },\n },\n // Validate server function ID in dev mode\n {\n name: 'tanstack-start-core:validate-server-fn-id',\n apply: 'serve',\n load: {\n filter: {\n id: new RegExp(resolveViteId(validateServerFnIdVirtualModule)),\n },\n handler(id) {\n const parsed = parseIdQuery(id)\n if (parsed.query.id && serverFnsById[parsed.query.id]) {\n return `export {}`\n }\n this.error(`Invalid server function ID: ${parsed.query.id}`)\n },\n },\n },\n // Manifest plugin for server environments\n {\n name: 'tanstack-start-core:server-fn-resolver',\n enforce: 'pre',\n applyToEnvironment: (env) => {\n return appliedResolverEnvironments.has(env.name)\n },\n configResolved(config) {\n root = config.root\n command = config.command\n },\n resolveId: {\n filter: { id: new RegExp(VIRTUAL_MODULES.serverFnResolver) },\n handler() {\n return resolvedResolverVirtualImportId\n },\n },\n load: {\n filter: { id: new RegExp(resolvedResolverVirtualImportId) },\n handler() {\n // When SSR is not the provider, SSR callers need to use HTTP to call server functions\n // since they can't directly import from the provider environment\n if (this.environment.name !== opts.providerEnvName) {\n // SSR caller: use HTTP-based getServerFnById\n // This re-exports from the start-server-core package which handles HTTP calls\n return `export { getServerFnById } from '@tanstack/start-server-core/server-fn-ssr-caller'`\n }\n\n if (this.environment.mode !== 'build') {\n const mod = `\n export async function getServerFnById(id) {\n const validateIdImport = ${JSON.stringify(validateServerFnIdVirtualModule)} + '?id=' + id\n await import(/* @vite-ignore */ '/@id/__x00__' + validateIdImport)\n const decoded = Buffer.from(id, 'base64url').toString('utf8')\n const devServerFn = JSON.parse(decoded)\n const mod = await import(/* @vite-ignore */ devServerFn.file)\n return mod[devServerFn.export]\n }\n `\n return mod\n }\n\n // When SSR is the provider, server-only-referenced functions aren't in the manifest,\n // so no isClientReferenced check is needed.\n // When SSR is NOT the provider (custom provider env), server-only-referenced\n // functions ARE in the manifest and need the isClientReferenced check to\n // block direct client HTTP requests to server-only-referenced functions.\n const includeClientReferencedCheck = !ssrIsProvider\n return generateManifestModule(\n serverFnsById,\n includeClientReferencedCheck,\n )\n },\n },\n },\n ]\n}\n"],"names":["id","code"],"mappings":"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"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { CompileStartFrameworkOptions } from '../types.js';
|
|
2
|
+
import type * as babel from '@babel/core';
|
|
3
|
+
import type * as t from '@babel/types';
|
|
4
|
+
/**
|
|
5
|
+
* Context passed to all plugin handlers during compilation.
|
|
6
|
+
* Contains both read-only input data and mutable state that handlers update.
|
|
7
|
+
*/
|
|
8
|
+
export interface CompilationContext {
|
|
9
|
+
readonly ast: t.File;
|
|
10
|
+
readonly code: string;
|
|
11
|
+
readonly id: string;
|
|
12
|
+
readonly env: 'client' | 'server';
|
|
13
|
+
readonly envName: string;
|
|
14
|
+
readonly root: string;
|
|
15
|
+
/** The framework being used (e.g., 'react', 'solid') */
|
|
16
|
+
readonly framework: CompileStartFrameworkOptions;
|
|
17
|
+
/** The Vite environment name for the server function provider */
|
|
18
|
+
readonly providerEnvName: string;
|
|
19
|
+
/** Generate a unique function ID */
|
|
20
|
+
generateFunctionId: GenerateFunctionIdFn;
|
|
21
|
+
/** Get known server functions from previous builds (e.g., client build) */
|
|
22
|
+
getKnownServerFns: () => Record<string, ServerFn>;
|
|
23
|
+
/**
|
|
24
|
+
* Callback when server functions are discovered.
|
|
25
|
+
* Called after each file is compiled with its new functions.
|
|
26
|
+
*/
|
|
27
|
+
onServerFnsById: ((d: Record<string, ServerFn>) => void) | undefined;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Batched plugin handler signature.
|
|
31
|
+
* Receives ALL candidates of a specific kind in one call.
|
|
32
|
+
* Mutates the CompilationContext directly.
|
|
33
|
+
*/
|
|
34
|
+
export type BatchedPluginHandler<TOpts = unknown> = (candidates: Array<RewriteCandidate>, context: CompilationContext, opts: TOpts) => void;
|
|
35
|
+
/**
|
|
36
|
+
* Info about a method call in the chain, including the call expression path
|
|
37
|
+
* and the path to its first argument (if any).
|
|
38
|
+
*/
|
|
39
|
+
export interface MethodCallInfo {
|
|
40
|
+
callPath: babel.NodePath<t.CallExpression>;
|
|
41
|
+
/** Path to the first argument, or null if no arguments */
|
|
42
|
+
firstArgPath: babel.NodePath | null;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Pre-collected method chain paths for a root call expression.
|
|
46
|
+
* This avoids needing to traverse the AST again in handlers.
|
|
47
|
+
*/
|
|
48
|
+
export interface MethodChainPaths {
|
|
49
|
+
middleware: MethodCallInfo | null;
|
|
50
|
+
inputValidator: MethodCallInfo | null;
|
|
51
|
+
handler: MethodCallInfo | null;
|
|
52
|
+
server: MethodCallInfo | null;
|
|
53
|
+
client: MethodCallInfo | null;
|
|
54
|
+
}
|
|
55
|
+
export type MethodChainKey = keyof MethodChainPaths;
|
|
56
|
+
/**
|
|
57
|
+
* Information about a candidate that needs to be rewritten.
|
|
58
|
+
*/
|
|
59
|
+
export interface RewriteCandidate {
|
|
60
|
+
path: babel.NodePath<t.CallExpression>;
|
|
61
|
+
methodChain: MethodChainPaths;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Represents an extracted server function that has been registered.
|
|
65
|
+
* Used for manifest generation and tracking function metadata.
|
|
66
|
+
*/
|
|
67
|
+
export interface ServerFn {
|
|
68
|
+
/** The unique name used to export this function */
|
|
69
|
+
functionName: string;
|
|
70
|
+
/** The unique ID for this function (used in RPC calls) */
|
|
71
|
+
functionId: string;
|
|
72
|
+
/** The filename with query param where the extracted implementation lives */
|
|
73
|
+
extractedFilename: string;
|
|
74
|
+
/** The original source filename */
|
|
75
|
+
filename: string;
|
|
76
|
+
/**
|
|
77
|
+
* True when this function was discovered by the client build.
|
|
78
|
+
* Used to restrict HTTP access to only client-referenced functions.
|
|
79
|
+
*/
|
|
80
|
+
isClientReferenced?: boolean;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Function type for generating unique function IDs.
|
|
84
|
+
*/
|
|
85
|
+
export type GenerateFunctionIdFn = (opts: {
|
|
86
|
+
filename: string;
|
|
87
|
+
functionName: string;
|
|
88
|
+
extractedFilename: string;
|
|
89
|
+
}) => string;
|
|
90
|
+
/**
|
|
91
|
+
* Optional version that allows returning undefined to use default ID generation.
|
|
92
|
+
*/
|
|
93
|
+
export type GenerateFunctionIdFnOptional = (opts: Omit<Parameters<GenerateFunctionIdFn>[0], 'extractedFilename'>) => string | undefined;
|
|
94
|
+
/**
|
|
95
|
+
* Function type for generating replacement code for server functions.
|
|
96
|
+
* Used internally by handleCreateServerFn.
|
|
97
|
+
*/
|
|
98
|
+
export type ReplacerFn = (opts: {
|
|
99
|
+
/** Placeholder for the original function expression */
|
|
100
|
+
fn: string;
|
|
101
|
+
/** The filename where the extracted implementation lives */
|
|
102
|
+
extractedFilename: string;
|
|
103
|
+
/** The original source filename */
|
|
104
|
+
filename: string;
|
|
105
|
+
/** The unique function ID */
|
|
106
|
+
functionId: string;
|
|
107
|
+
/** The export name for this function */
|
|
108
|
+
functionName: string;
|
|
109
|
+
/** True if this is the source/provider file (has the implementation) */
|
|
110
|
+
isSourceFn: boolean;
|
|
111
|
+
/**
|
|
112
|
+
* True when this function was already discovered by a previous build (e.g., client).
|
|
113
|
+
* For SSR callers, this means the function is in the manifest.
|
|
114
|
+
*/
|
|
115
|
+
isClientReferenced: boolean;
|
|
116
|
+
}) => string;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { default as babel } from '@babel/core';
|
|
2
|
+
import * as t from '@babel/types';
|
|
3
|
+
export declare function codeFrameError(code: string, loc: {
|
|
4
|
+
start: {
|
|
5
|
+
line: number;
|
|
6
|
+
column: number;
|
|
7
|
+
};
|
|
8
|
+
end: {
|
|
9
|
+
line: number;
|
|
10
|
+
column: number;
|
|
11
|
+
};
|
|
12
|
+
}, message: string): Error;
|
|
13
|
+
export declare function cleanId(id: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* Strips a method call by replacing it with its callee object.
|
|
16
|
+
* E.g., `foo().bar()` -> `foo()`
|
|
17
|
+
*
|
|
18
|
+
* This is a common pattern used when removing method calls from chains
|
|
19
|
+
* (e.g., removing .server() from middleware on client, or .inputValidator() on client).
|
|
20
|
+
*
|
|
21
|
+
* @param callPath - The path to the CallExpression to strip
|
|
22
|
+
*/
|
|
23
|
+
export declare function stripMethodCall(callPath: babel.NodePath<t.CallExpression>): void;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { codeFrameColumns } from "@babel/code-frame";
|
|
2
|
+
import * as t from "@babel/types";
|
|
3
|
+
function codeFrameError(code, loc, message) {
|
|
4
|
+
const frame = codeFrameColumns(
|
|
5
|
+
code,
|
|
6
|
+
{
|
|
7
|
+
start: loc.start,
|
|
8
|
+
end: loc.end
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
highlightCode: true,
|
|
12
|
+
message
|
|
13
|
+
}
|
|
14
|
+
);
|
|
15
|
+
return new Error(frame);
|
|
16
|
+
}
|
|
17
|
+
function cleanId(id) {
|
|
18
|
+
if (id.startsWith("\0")) {
|
|
19
|
+
id = id.slice(1);
|
|
20
|
+
}
|
|
21
|
+
const queryIndex = id.indexOf("?");
|
|
22
|
+
return queryIndex === -1 ? id : id.substring(0, queryIndex);
|
|
23
|
+
}
|
|
24
|
+
function stripMethodCall(callPath) {
|
|
25
|
+
if (t.isMemberExpression(callPath.node.callee)) {
|
|
26
|
+
callPath.replaceWith(callPath.node.callee.object);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export {
|
|
30
|
+
cleanId,
|
|
31
|
+
codeFrameError,
|
|
32
|
+
stripMethodCall
|
|
33
|
+
};
|
|
34
|
+
//# 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 * as t from '@babel/types'\nimport type babel from '@babel/core'\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\nexport function cleanId(id: string): string {\n // Remove null byte prefix used by Vite/Rollup for virtual modules\n if (id.startsWith('\\0')) {\n id = id.slice(1)\n }\n const queryIndex = id.indexOf('?')\n return queryIndex === -1 ? id : id.substring(0, queryIndex)\n}\n\n/**\n * Strips a method call by replacing it with its callee object.\n * E.g., `foo().bar()` -> `foo()`\n *\n * This is a common pattern used when removing method calls from chains\n * (e.g., removing .server() from middleware on client, or .inputValidator() on client).\n *\n * @param callPath - The path to the CallExpression to strip\n */\nexport function stripMethodCall(\n callPath: babel.NodePath<t.CallExpression>,\n): void {\n if (t.isMemberExpression(callPath.node.callee)) {\n callPath.replaceWith(callPath.node.callee.object)\n }\n}\n"],"names":[],"mappings":";;AAIO,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;AAEO,SAAS,QAAQ,IAAoB;AAE1C,MAAI,GAAG,WAAW,IAAI,GAAG;AACvB,SAAK,GAAG,MAAM,CAAC;AAAA,EACjB;AACA,QAAM,aAAa,GAAG,QAAQ,GAAG;AACjC,SAAO,eAAe,KAAK,KAAK,GAAG,UAAU,GAAG,UAAU;AAC5D;AAWO,SAAS,gBACd,UACM;AACN,MAAI,EAAE,mBAAmB,SAAS,KAAK,MAAM,GAAG;AAC9C,aAAS,YAAY,SAAS,KAAK,OAAO,MAAM;AAAA,EAClD;AACF;"}
|
package/dist/esm/types.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/start-plugin-core",
|
|
3
|
-
"version": "1.143.
|
|
3
|
+
"version": "1.143.5",
|
|
4
4
|
"description": "Modern and scalable routing for React applications",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -61,11 +61,10 @@
|
|
|
61
61
|
"zod": "^3.24.2",
|
|
62
62
|
"@tanstack/router-core": "1.143.4",
|
|
63
63
|
"@tanstack/router-generator": "1.143.4",
|
|
64
|
-
"@tanstack/router-utils": "1.141.0",
|
|
65
|
-
"@tanstack/server-functions-plugin": "1.142.1",
|
|
66
64
|
"@tanstack/router-plugin": "1.143.4",
|
|
65
|
+
"@tanstack/router-utils": "1.141.0",
|
|
67
66
|
"@tanstack/start-client-core": "1.143.4",
|
|
68
|
-
"@tanstack/start-server-core": "1.143.
|
|
67
|
+
"@tanstack/start-server-core": "1.143.5"
|
|
69
68
|
},
|
|
70
69
|
"devDependencies": {
|
|
71
70
|
"@types/babel__code-frame": "^7.0.6",
|
package/src/plugin.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { joinPaths } from '@tanstack/router-core'
|
|
2
|
-
import { VIRTUAL_MODULES } from '@tanstack/start-server-core'
|
|
3
|
-
import { TanStackServerFnPlugin } from '@tanstack/server-functions-plugin'
|
|
4
2
|
import * as vite from 'vite'
|
|
5
3
|
import { crawlFrameworkPkgs } from 'vitefu'
|
|
6
4
|
import { join } from 'pathe'
|
|
@@ -18,7 +16,7 @@ import {
|
|
|
18
16
|
getServerOutputDirectory,
|
|
19
17
|
} from './output-directory'
|
|
20
18
|
import { postServerBuild } from './post-server-build'
|
|
21
|
-
import {
|
|
19
|
+
import { startCompilerPlugin } from './start-compiler-plugin/plugin'
|
|
22
20
|
import type {
|
|
23
21
|
GetConfigFn,
|
|
24
22
|
ResolvedStartConfig,
|
|
@@ -59,8 +57,6 @@ export function TanStackStartVitePluginCore(
|
|
|
59
57
|
serverFnProviderEnv,
|
|
60
58
|
}
|
|
61
59
|
|
|
62
|
-
const directive = corePluginOpts.serverFn?.directive ?? 'use server'
|
|
63
|
-
|
|
64
60
|
let startConfig: TanStackStartOutputConfig | null
|
|
65
61
|
const getConfig: GetConfigFn = () => {
|
|
66
62
|
if (!resolvedStartConfig.root) {
|
|
@@ -356,55 +352,19 @@ export function TanStackStartVitePluginCore(
|
|
|
356
352
|
},
|
|
357
353
|
},
|
|
358
354
|
},
|
|
359
|
-
|
|
360
|
-
//
|
|
361
|
-
//
|
|
362
|
-
//
|
|
363
|
-
//
|
|
364
|
-
//
|
|
365
|
-
|
|
366
|
-
createServerFnPlugin({
|
|
355
|
+
// Server function plugin handles:
|
|
356
|
+
// 1. Identifying createServerFn().handler() calls
|
|
357
|
+
// 2. Extracting server functions to separate modules
|
|
358
|
+
// 3. Replacing call sites with RPC stubs
|
|
359
|
+
// 4. Generating the server function manifest
|
|
360
|
+
// Also handles createIsomorphicFn, createServerOnlyFn, createClientOnlyFn, createMiddleware
|
|
361
|
+
startCompilerPlugin({
|
|
367
362
|
framework: corePluginOpts.framework,
|
|
368
|
-
directive,
|
|
369
363
|
environments,
|
|
370
|
-
}),
|
|
371
|
-
TanStackServerFnPlugin({
|
|
372
|
-
// This is the ID that will be available to look up and import
|
|
373
|
-
// our server function manifest and resolve its module
|
|
374
|
-
manifestVirtualImportId: VIRTUAL_MODULES.serverFnManifest,
|
|
375
|
-
directive,
|
|
376
364
|
generateFunctionId: startPluginOpts?.serverFns?.generateFunctionId,
|
|
377
|
-
|
|
378
|
-
{
|
|
379
|
-
envConsumer: 'client',
|
|
380
|
-
getRuntimeCode: () =>
|
|
381
|
-
`import { createClientRpc } from '@tanstack/${corePluginOpts.framework}-start/client-rpc'`,
|
|
382
|
-
replacer: (d) => `createClientRpc('${d.functionId}')`,
|
|
383
|
-
envName: VITE_ENVIRONMENT_NAMES.client,
|
|
384
|
-
},
|
|
385
|
-
{
|
|
386
|
-
envConsumer: 'server' as const,
|
|
387
|
-
getRuntimeCode: () =>
|
|
388
|
-
`import { createSsrRpc } from '@tanstack/${corePluginOpts.framework}-start/ssr-rpc'`,
|
|
389
|
-
envName: VITE_ENVIRONMENT_NAMES.server,
|
|
390
|
-
replacer: (d: any) =>
|
|
391
|
-
// When the function is client-referenced, it's in the manifest - use manifest lookup
|
|
392
|
-
// When SSR is NOT the provider, always use manifest lookup (no import() for different env)
|
|
393
|
-
// Otherwise, use the importer for functions only referenced on the server when SSR is the provider
|
|
394
|
-
d.isClientReferenced || !ssrIsProvider
|
|
395
|
-
? `createSsrRpc('${d.functionId}')`
|
|
396
|
-
: `createSsrRpc('${d.functionId}', () => import(${JSON.stringify(d.extractedFilename)}).then(m => m['${d.functionName}']))`,
|
|
397
|
-
},
|
|
398
|
-
],
|
|
399
|
-
provider: {
|
|
400
|
-
getRuntimeCode: () =>
|
|
401
|
-
`import { createServerRpc } from '@tanstack/${corePluginOpts.framework}-start/server-rpc'`,
|
|
402
|
-
replacer: (d) => `createServerRpc('${d.functionId}', ${d.fn})`,
|
|
403
|
-
envName: serverFnProviderEnv,
|
|
404
|
-
},
|
|
365
|
+
providerEnvName: serverFnProviderEnv,
|
|
405
366
|
}),
|
|
406
|
-
|
|
407
|
-
// is now merged into createServerFnPlugin above
|
|
367
|
+
tanStackStartRouter(startPluginOpts, getConfig, corePluginOpts),
|
|
408
368
|
loadEnvPlugin(),
|
|
409
369
|
startManifestPlugin({
|
|
410
370
|
getClientBundle: () => getBundle(VITE_ENVIRONMENT_NAMES.client),
|