@tanstack/router-vite-plugin 1.32.2 → 1.32.17

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/src/compilers.ts CHANGED
@@ -91,7 +91,35 @@ export async function compileFile(opts: {
91
91
  {
92
92
  CallExpression: (path) => {
93
93
  if (path.node.callee.type === 'Identifier') {
94
- if (
94
+ if (path.node.callee.name === 'createServerFn') {
95
+ // If the function at createServerFn(_, MyFunc) doesn't have a
96
+ // 'use server' directive at the top of the function scope,
97
+ // then add it.
98
+
99
+ const fn = path.node.arguments[1]
100
+
101
+ if (
102
+ t.isFunctionExpression(fn) ||
103
+ t.isArrowFunctionExpression(fn)
104
+ ) {
105
+ if (t.isBlockStatement(fn.body)) {
106
+ const hasUseServerDirective =
107
+ fn.body.directives.some((directive) => {
108
+ return (
109
+ directive.value.value === 'use server'
110
+ )
111
+ })
112
+
113
+ if (!hasUseServerDirective) {
114
+ fn.body.directives.unshift(
115
+ t.directive(
116
+ t.directiveLiteral('use server'),
117
+ ),
118
+ )
119
+ }
120
+ }
121
+ }
122
+ } else if (
95
123
  path.node.callee.name === 'createRoute' ||
96
124
  path.node.callee.name === 'createFileRoute'
97
125
  ) {
@@ -105,6 +133,12 @@ export async function compileFile(opts: {
105
133
 
106
134
  let found = false
107
135
 
136
+ const hasImportedOrDefinedIdentifier = (
137
+ name: string,
138
+ ) => {
139
+ return programPath.scope.hasBinding(name)
140
+ }
141
+
108
142
  if (t.isObjectExpression(options)) {
109
143
  options.properties.forEach((prop) => {
110
144
  if (t.isObjectProperty(prop)) {
@@ -117,15 +151,32 @@ export async function compileFile(opts: {
117
151
  }
118
152
 
119
153
  // Prepend the import statement to the program along with the importer function
154
+ // Check to see if lazyRouteComponent is already imported before attempting
155
+ // to import it again
156
+
157
+ if (
158
+ !hasImportedOrDefinedIdentifier(
159
+ 'lazyRouteComponent',
160
+ )
161
+ ) {
162
+ programPath.unshiftContainer('body', [
163
+ template.smart(
164
+ `import { lazyRouteComponent } from '@tanstack/react-router'`,
165
+ )() as t.Statement,
166
+ ])
167
+ }
120
168
 
121
- programPath.unshiftContainer('body', [
122
- template.smart(
123
- `import { lazyRouteComponent } from '@tanstack/react-router'`,
124
- )() as t.Statement,
125
- template.smart(
126
- `const $$splitComponentImporter = () => import('${splitUrl}')`,
127
- )() as t.Statement,
128
- ])
169
+ if (
170
+ !hasImportedOrDefinedIdentifier(
171
+ '$$splitComponentImporter',
172
+ )
173
+ ) {
174
+ programPath.unshiftContainer('body', [
175
+ template.smart(
176
+ `const $$splitComponentImporter = () => import('${splitUrl}')`,
177
+ )() as t.Statement,
178
+ ])
179
+ }
129
180
 
130
181
  prop.value = template.expression(
131
182
  `lazyRouteComponent($$splitComponentImporter, 'component')`,
@@ -147,14 +198,29 @@ export async function compileFile(opts: {
147
198
 
148
199
  // Prepend the import statement to the program along with the importer function
149
200
 
150
- programPath.unshiftContainer('body', [
151
- template.smart(
152
- `import { lazyFn } from '@tanstack/react-router'`,
153
- )() as t.Statement,
154
- template.smart(
155
- `const $$splitLoaderImporter = () => import('${splitUrl}')`,
156
- )() as t.Statement,
157
- ])
201
+ if (
202
+ !hasImportedOrDefinedIdentifier(
203
+ 'lazyFn',
204
+ )
205
+ ) {
206
+ programPath.unshiftContainer('body', [
207
+ template.smart(
208
+ `import { lazyFn } from '@tanstack/react-router'`,
209
+ )() as t.Statement,
210
+ ])
211
+ }
212
+
213
+ if (
214
+ !hasImportedOrDefinedIdentifier(
215
+ '$$splitLoaderImporter',
216
+ )
217
+ ) {
218
+ programPath.unshiftContainer('body', [
219
+ template.smart(
220
+ `const $$splitLoaderImporter = () => import('${splitUrl}')`,
221
+ )() as t.Statement,
222
+ ])
223
+ }
158
224
 
159
225
  prop.value = template.expression(
160
226
  `lazyFn($$splitLoaderImporter, 'loader')`,
@@ -164,6 +230,8 @@ export async function compileFile(opts: {
164
230
  }
165
231
  }
166
232
  }
233
+
234
+ programPath.scope.crawl()
167
235
  })
168
236
  }
169
237
 
@@ -259,7 +327,38 @@ export async function splitFile(opts: {
259
327
  {
260
328
  CallExpression: (path) => {
261
329
  if (path.node.callee.type === 'Identifier') {
262
- if (path.node.callee.name === 'createFileRoute') {
330
+ if (path.node.callee.name === 'createServerFn') {
331
+ // If the function at createServerFn(_, MyFunc) doesn't have a
332
+ // 'use server' directive at the top of the function scope,
333
+ // then add it.
334
+
335
+ const fn = path.node.arguments[1]
336
+
337
+ if (
338
+ t.isFunctionExpression(fn) ||
339
+ t.isArrowFunctionExpression(fn)
340
+ ) {
341
+ if (t.isBlockStatement(fn.body)) {
342
+ const hasUseServerDirective =
343
+ fn.body.directives.some((directive) => {
344
+ return (
345
+ directive.value.value === 'use server'
346
+ )
347
+ })
348
+
349
+ if (!hasUseServerDirective) {
350
+ fn.body.directives.unshift(
351
+ t.directive(
352
+ t.directiveLiteral('use server'),
353
+ ),
354
+ )
355
+ }
356
+ }
357
+ }
358
+ } else if (
359
+ path.node.callee.name === 'createRoute' ||
360
+ path.node.callee.name === 'createFileRoute'
361
+ ) {
263
362
  if (
264
363
  path.parentPath.node.type === 'CallExpression'
265
364
  ) {
@@ -7,93 +7,6 @@ import type { NodePath } from '@babel/traverse'
7
7
 
8
8
  type IdentifierPath = NodePath<BabelTypes.Identifier>
9
9
 
10
- // export function findReferencedIdentifiers(
11
- // programPath: NodePath<BabelTypes.Program>,
12
- // ): Set<IdentifierPath> {
13
- // const refs = new Set<IdentifierPath>()
14
-
15
- // function markFunction(
16
- // path: NodePath<
17
- // | BabelTypes.FunctionDeclaration
18
- // | BabelTypes.FunctionExpression
19
- // | BabelTypes.ArrowFunctionExpression
20
- // >,
21
- // ) {
22
- // const ident = getIdentifier(path)
23
- // if (ident?.node && isIdentifierReferenced(ident)) {
24
- // refs.add(ident)
25
- // }
26
- // }
27
-
28
- // function markImport(
29
- // path: NodePath<
30
- // | BabelTypes.ImportSpecifier
31
- // | BabelTypes.ImportDefaultSpecifier
32
- // | BabelTypes.ImportNamespaceSpecifier
33
- // >,
34
- // ) {
35
- // const local = path.get('local')
36
- // if (isIdentifierReferenced(local)) {
37
- // refs.add(local)
38
- // }
39
- // }
40
-
41
- // programPath.traverse({
42
- // VariableDeclarator(path) {
43
- // if (path.node.id.type === 'Identifier') {
44
- // const local = path.get('id') as NodePath<BabelTypes.Identifier>
45
- // if (isIdentifierReferenced(local)) {
46
- // refs.add(local)
47
- // }
48
- // } else if (path.node.id.type === 'ObjectPattern') {
49
- // const pattern = path.get('id') as NodePath<BabelTypes.ObjectPattern>
50
-
51
- // const properties = pattern.get('properties')
52
- // properties.forEach((p) => {
53
- // const local = p.get(
54
- // p.node.type === 'ObjectProperty'
55
- // ? 'value'
56
- // : p.node.type === 'RestElement'
57
- // ? 'argument'
58
- // : (function () {
59
- // throw new Error('invariant')
60
- // })(),
61
- // ) as NodePath<BabelTypes.Identifier>
62
- // if (isIdentifierReferenced(local)) {
63
- // refs.add(local)
64
- // }
65
- // })
66
- // } else if (path.node.id.type === 'ArrayPattern') {
67
- // const pattern = path.get('id') as NodePath<BabelTypes.ArrayPattern>
68
-
69
- // const elements = pattern.get('elements')
70
- // elements.forEach((e) => {
71
- // let local: NodePath<BabelTypes.Identifier>
72
- // if (e.node?.type === 'Identifier') {
73
- // local = e as NodePath<BabelTypes.Identifier>
74
- // } else if (e.node?.type === 'RestElement') {
75
- // local = e.get('argument') as NodePath<BabelTypes.Identifier>
76
- // } else {
77
- // return
78
- // }
79
-
80
- // if (isIdentifierReferenced(local)) {
81
- // refs.add(local)
82
- // }
83
- // })
84
- // }
85
- // },
86
-
87
- // FunctionDeclaration: markFunction,
88
- // FunctionExpression: markFunction,
89
- // ArrowFunctionExpression: markFunction,
90
- // ImportSpecifier: markImport,
91
- // ImportDefaultSpecifier: markImport,
92
- // ImportNamespaceSpecifier: markImport,
93
- // })
94
- // return refs
95
- // }
96
-
97
10
  /**
98
11
  * @param refs - If provided, only these identifiers will be considered for removal.
99
12
  */
@@ -150,6 +63,30 @@ export const eliminateUnreferencedIdentifiers = (
150
63
  }
151
64
  }
152
65
 
66
+ const handleObjectPattern = (pattern: NodePath<BabelTypes.ObjectPattern>) => {
67
+ const properties = pattern.get('properties')
68
+ properties.forEach((property) => {
69
+ if (property.node.type === 'ObjectProperty') {
70
+ const value = property.get('value') as any
71
+ if (t.isIdentifier(value)) {
72
+ if (shouldBeRemoved(value as any)) {
73
+ property.remove()
74
+ }
75
+ } else if (t.isObjectPattern(value)) {
76
+ handleObjectPattern(value as any)
77
+ }
78
+ } else if (t.isRestElement(property.node)) {
79
+ const argument = property.get('argument')
80
+ if (
81
+ t.isIdentifier(argument as any) &&
82
+ shouldBeRemoved(argument as NodePath<BabelTypes.Identifier>)
83
+ ) {
84
+ property.remove()
85
+ }
86
+ }
87
+ })
88
+ }
89
+
153
90
  // Traverse again to remove unused references. This happens at least once,
154
91
  // then repeats until no more references are removed.
155
92
  do {
@@ -166,34 +103,9 @@ export const eliminateUnreferencedIdentifiers = (
166
103
  path.remove()
167
104
  }
168
105
  } else if (path.node.id.type === 'ObjectPattern') {
169
- const pattern = path.get('id') as NodePath<BabelTypes.ObjectPattern>
170
-
171
- const beforeCount = referencesRemovedInThisPass
172
- const properties = pattern.get('properties')
173
- properties.forEach((property) => {
174
- const local = property.get(
175
- property.node.type === 'ObjectProperty'
176
- ? 'value'
177
- : // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
178
- property.node.type === 'RestElement'
179
- ? 'argument'
180
- : (function () {
181
- throw new Error('invariant')
182
- })(),
183
- ) as NodePath<BabelTypes.Identifier>
184
-
185
- if (shouldBeRemoved(local)) {
186
- ++referencesRemovedInThisPass
187
- property.remove()
188
- }
189
- })
190
-
191
- if (
192
- beforeCount !== referencesRemovedInThisPass &&
193
- pattern.get('properties').length < 1
194
- ) {
195
- path.remove()
196
- }
106
+ handleObjectPattern(
107
+ path.get('id') as NodePath<BabelTypes.ObjectPattern>,
108
+ )
197
109
  } else if (path.node.id.type === 'ArrayPattern') {
198
110
  const pattern = path.get('id') as NodePath<BabelTypes.ArrayPattern>
199
111
 
package/src/index.ts CHANGED
@@ -22,7 +22,7 @@ export const configSchema = generatorConfigSchema.extend({
22
22
  export type Config = z.infer<typeof configSchema>
23
23
 
24
24
  const CONFIG_FILE_NAME = 'tsr.config.json'
25
- const debug = false as any
25
+ const debug = Boolean(process.env.TSR_VITE_DEBUG)
26
26
 
27
27
  const getConfig = async (inlineConfig: Partial<Config>, root: string) => {
28
28
  const config = await getGeneratorConfig(inlineConfig, root)
@@ -66,10 +66,12 @@ export function TanStackRouterViteGenerator(
66
66
  event: 'create' | 'update' | 'delete',
67
67
  ) => {
68
68
  const filePath = normalize(file)
69
+
69
70
  if (filePath === join(ROOT, CONFIG_FILE_NAME)) {
70
71
  userConfig = await getConfig(inlineConfig, ROOT)
71
72
  return
72
73
  }
74
+
73
75
  if (
74
76
  event === 'update' &&
75
77
  filePath === resolve(userConfig.generatedRouteTree)
@@ -77,9 +79,11 @@ export function TanStackRouterViteGenerator(
77
79
  // skip generating routes if the generated route tree is updated
78
80
  return
79
81
  }
82
+
80
83
  const routesDirectoryPath = isAbsolute(userConfig.routesDirectory)
81
84
  ? userConfig.routesDirectory
82
85
  : join(ROOT, userConfig.routesDirectory)
86
+
83
87
  if (filePath.startsWith(routesDirectoryPath)) {
84
88
  await generate()
85
89
  }
@@ -90,7 +94,6 @@ export function TanStackRouterViteGenerator(
90
94
  configResolved: async (config) => {
91
95
  ROOT = process.cwd()
92
96
  userConfig = await getConfig(inlineConfig, ROOT)
93
-
94
97
  if (userConfig.enableRouteGeneration ?? true) {
95
98
  await generate()
96
99
  }
@@ -175,8 +178,10 @@ export function TanStackRouterViteCodeSplitter(
175
178
 
176
179
  return compiled
177
180
  } else if (
178
- fileIsInRoutesDirectory(id, userConfig.routesDirectory) &&
179
- (code.includes('createRoute(') || code.includes('createFileRoute('))
181
+ (fileIsInRoutesDirectory(id, userConfig.routesDirectory) &&
182
+ (code.includes('createRoute(') ||
183
+ code.includes('createFileRoute('))) ||
184
+ code.includes('createServerFn')
180
185
  ) {
181
186
  if (code.includes('@react-refresh')) {
182
187
  throw new Error(