@tanstack/router-plugin 1.39.2

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 (66) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +5 -0
  3. package/dist/cjs/ast.cjs +51 -0
  4. package/dist/cjs/ast.cjs.map +1 -0
  5. package/dist/cjs/ast.d.cts +22 -0
  6. package/dist/cjs/code-splitter.cjs +157 -0
  7. package/dist/cjs/code-splitter.cjs.map +1 -0
  8. package/dist/cjs/code-splitter.d.cts +22 -0
  9. package/dist/cjs/compilers.cjs +327 -0
  10. package/dist/cjs/compilers.cjs.map +1 -0
  11. package/dist/cjs/compilers.d.cts +18 -0
  12. package/dist/cjs/config.cjs +16 -0
  13. package/dist/cjs/config.cjs.map +1 -0
  14. package/dist/cjs/config.d.cts +79 -0
  15. package/dist/cjs/constants.cjs +7 -0
  16. package/dist/cjs/constants.cjs.map +1 -0
  17. package/dist/cjs/constants.d.cts +2 -0
  18. package/dist/cjs/eliminateUnreferencedIdentifiers.cjs +149 -0
  19. package/dist/cjs/eliminateUnreferencedIdentifiers.cjs.map +1 -0
  20. package/dist/cjs/eliminateUnreferencedIdentifiers.d.cts +9 -0
  21. package/dist/cjs/index.cjs +7 -0
  22. package/dist/cjs/index.cjs.map +1 -0
  23. package/dist/cjs/index.d.cts +2 -0
  24. package/dist/cjs/router-generator.cjs +67 -0
  25. package/dist/cjs/router-generator.cjs.map +1 -0
  26. package/dist/cjs/router-generator.d.cts +18 -0
  27. package/dist/cjs/vite.cjs +16 -0
  28. package/dist/cjs/vite.cjs.map +1 -0
  29. package/dist/cjs/vite.d.cts +41 -0
  30. package/dist/esm/ast.d.ts +22 -0
  31. package/dist/esm/ast.js +34 -0
  32. package/dist/esm/ast.js.map +1 -0
  33. package/dist/esm/code-splitter.d.ts +22 -0
  34. package/dist/esm/code-splitter.js +157 -0
  35. package/dist/esm/code-splitter.js.map +1 -0
  36. package/dist/esm/compilers.d.ts +18 -0
  37. package/dist/esm/compilers.js +309 -0
  38. package/dist/esm/compilers.js.map +1 -0
  39. package/dist/esm/config.d.ts +79 -0
  40. package/dist/esm/config.js +16 -0
  41. package/dist/esm/config.js.map +1 -0
  42. package/dist/esm/constants.d.ts +2 -0
  43. package/dist/esm/constants.js +7 -0
  44. package/dist/esm/constants.js.map +1 -0
  45. package/dist/esm/eliminateUnreferencedIdentifiers.d.ts +9 -0
  46. package/dist/esm/eliminateUnreferencedIdentifiers.js +132 -0
  47. package/dist/esm/eliminateUnreferencedIdentifiers.js.map +1 -0
  48. package/dist/esm/index.d.ts +2 -0
  49. package/dist/esm/index.js +7 -0
  50. package/dist/esm/index.js.map +1 -0
  51. package/dist/esm/router-generator.d.ts +18 -0
  52. package/dist/esm/router-generator.js +67 -0
  53. package/dist/esm/router-generator.js.map +1 -0
  54. package/dist/esm/vite.d.ts +41 -0
  55. package/dist/esm/vite.js +16 -0
  56. package/dist/esm/vite.js.map +1 -0
  57. package/package.json +88 -0
  58. package/src/ast.ts +49 -0
  59. package/src/code-splitter.ts +162 -0
  60. package/src/compilers.ts +414 -0
  61. package/src/config.ts +25 -0
  62. package/src/constants.ts +2 -0
  63. package/src/eliminateUnreferencedIdentifiers.ts +211 -0
  64. package/src/index.ts +2 -0
  65. package/src/router-generator.ts +92 -0
  66. package/src/vite.ts +19 -0
@@ -0,0 +1,414 @@
1
+ import * as t from '@babel/types'
2
+ import * as template from '@babel/template'
3
+ import { splitPrefix } from './constants'
4
+ import { eliminateUnreferencedIdentifiers } from './eliminateUnreferencedIdentifiers'
5
+ import type * as babel from '@babel/core'
6
+ import type { CompileAstFn } from './ast'
7
+
8
+ type SplitModulesById = Record<
9
+ string,
10
+ { id: string; node: t.FunctionExpression }
11
+ >
12
+
13
+ interface State {
14
+ filename: string
15
+ opts: {
16
+ minify: boolean
17
+ root: string
18
+ }
19
+ imported: Record<string, boolean>
20
+ refs: Set<any>
21
+ serverIndex: number
22
+ splitIndex: number
23
+ splitModulesById: SplitModulesById
24
+ }
25
+
26
+ export async function compileFile(opts: {
27
+ code: string
28
+ compileAst: CompileAstFn
29
+ filename: string
30
+ }) {
31
+ return await opts.compileAst({
32
+ code: opts.code,
33
+ filename: opts.filename,
34
+ getBabelConfig: () => ({
35
+ plugins: [
36
+ [
37
+ {
38
+ visitor: {
39
+ Program: {
40
+ enter(programPath: babel.NodePath<t.Program>, state: State) {
41
+ const splitUrl = `${splitPrefix}:${opts.filename}?${splitPrefix}`
42
+
43
+ programPath.traverse(
44
+ {
45
+ CallExpression: (path) => {
46
+ if (path.node.callee.type === 'Identifier') {
47
+ if (
48
+ path.node.callee.name === 'createRoute' ||
49
+ path.node.callee.name === 'createFileRoute'
50
+ ) {
51
+ if (
52
+ path.parentPath.node.type === 'CallExpression'
53
+ ) {
54
+ const options = resolveIdentifier(
55
+ path,
56
+ path.parentPath.node.arguments[0],
57
+ )
58
+
59
+ let found = false
60
+
61
+ const hasImportedOrDefinedIdentifier = (
62
+ name: string,
63
+ ) => {
64
+ return programPath.scope.hasBinding(name)
65
+ }
66
+
67
+ if (t.isObjectExpression(options)) {
68
+ options.properties.forEach((prop) => {
69
+ if (t.isObjectProperty(prop)) {
70
+ if (t.isIdentifier(prop.key)) {
71
+ if (prop.key.name === 'component') {
72
+ const value = prop.value
73
+
74
+ if (t.isIdentifier(value)) {
75
+ removeIdentifierLiteral(path, value)
76
+ }
77
+
78
+ // Prepend the import statement to the program along with the importer function
79
+ // Check to see if lazyRouteComponent is already imported before attempting
80
+ // to import it again
81
+
82
+ if (
83
+ !hasImportedOrDefinedIdentifier(
84
+ 'lazyRouteComponent',
85
+ )
86
+ ) {
87
+ programPath.unshiftContainer('body', [
88
+ template.smart(
89
+ `import { lazyRouteComponent } from '@tanstack/react-router'`,
90
+ )() as t.Statement,
91
+ ])
92
+ }
93
+
94
+ if (
95
+ !hasImportedOrDefinedIdentifier(
96
+ '$$splitComponentImporter',
97
+ )
98
+ ) {
99
+ programPath.unshiftContainer('body', [
100
+ template.smart(
101
+ `const $$splitComponentImporter = () => import('${splitUrl}')`,
102
+ )() as t.Statement,
103
+ ])
104
+ }
105
+
106
+ prop.value = template.expression(
107
+ `lazyRouteComponent($$splitComponentImporter, 'component')`,
108
+ )() as any
109
+
110
+ programPath.pushContainer('body', [
111
+ template.smart(
112
+ `function DummyComponent() { return null }`,
113
+ )() as t.Statement,
114
+ ])
115
+
116
+ found = true
117
+ } else if (prop.key.name === 'loader') {
118
+ const value = prop.value
119
+
120
+ if (t.isIdentifier(value)) {
121
+ removeIdentifierLiteral(path, value)
122
+ }
123
+
124
+ // Prepend the import statement to the program along with the importer function
125
+
126
+ if (
127
+ !hasImportedOrDefinedIdentifier(
128
+ 'lazyFn',
129
+ )
130
+ ) {
131
+ programPath.unshiftContainer('body', [
132
+ template.smart(
133
+ `import { lazyFn } from '@tanstack/react-router'`,
134
+ )() as t.Statement,
135
+ ])
136
+ }
137
+
138
+ if (
139
+ !hasImportedOrDefinedIdentifier(
140
+ '$$splitLoaderImporter',
141
+ )
142
+ ) {
143
+ programPath.unshiftContainer('body', [
144
+ template.smart(
145
+ `const $$splitLoaderImporter = () => import('${splitUrl}')`,
146
+ )() as t.Statement,
147
+ ])
148
+ }
149
+
150
+ prop.value = template.expression(
151
+ `lazyFn($$splitLoaderImporter, 'loader')`,
152
+ )() as any
153
+
154
+ found = true
155
+ }
156
+ }
157
+ }
158
+
159
+ programPath.scope.crawl()
160
+ })
161
+ }
162
+
163
+ if (found as boolean) {
164
+ programPath.pushContainer('body', [
165
+ template.smart(
166
+ `function TSR_Dummy_Component() {}`,
167
+ )() as t.Statement,
168
+ ])
169
+ }
170
+ }
171
+ }
172
+ }
173
+ },
174
+ },
175
+ state,
176
+ )
177
+
178
+ eliminateUnreferencedIdentifiers(programPath)
179
+ },
180
+ },
181
+ },
182
+ },
183
+ {
184
+ root: process.cwd(),
185
+ minify: process.env.NODE_ENV === 'production',
186
+ },
187
+ ],
188
+ ].filter(Boolean),
189
+ }),
190
+ })
191
+ }
192
+
193
+ // Reusable function to get literal value or resolve variable to literal
194
+ function resolveIdentifier(path: any, node: any) {
195
+ if (t.isIdentifier(node)) {
196
+ const binding = path.scope.getBinding(node.name)
197
+ if (
198
+ binding
199
+ // && binding.kind === 'const'
200
+ ) {
201
+ const declarator = binding.path.node
202
+ if (t.isObjectExpression(declarator.init)) {
203
+ return declarator.init
204
+ } else if (t.isFunctionDeclaration(declarator.init)) {
205
+ return declarator.init
206
+ }
207
+ }
208
+ return undefined
209
+ }
210
+
211
+ return node
212
+ }
213
+
214
+ function removeIdentifierLiteral(path: any, node: any) {
215
+ if (t.isIdentifier(node)) {
216
+ const binding = path.scope.getBinding(node.name)
217
+ if (binding) {
218
+ binding.path.remove()
219
+ }
220
+ }
221
+ }
222
+
223
+ const splitNodeTypes = ['component', 'loader'] as const
224
+ type SplitNodeType = (typeof splitNodeTypes)[number]
225
+
226
+ export async function splitFile(opts: {
227
+ code: string
228
+ compileAst: CompileAstFn
229
+ filename: string
230
+ }) {
231
+ return await opts.compileAst({
232
+ code: opts.code,
233
+ filename: opts.filename,
234
+ getBabelConfig: () => ({
235
+ plugins: [
236
+ [
237
+ {
238
+ visitor: {
239
+ Program: {
240
+ enter(programPath: babel.NodePath<t.Program>, state: State) {
241
+ const splitNodesByType: Record<
242
+ SplitNodeType,
243
+ t.Node | undefined
244
+ > = {
245
+ component: undefined,
246
+ loader: undefined,
247
+ }
248
+
249
+ // Find the node
250
+ programPath.traverse(
251
+ {
252
+ CallExpression: (path) => {
253
+ if (path.node.callee.type === 'Identifier') {
254
+ if (
255
+ path.node.callee.name === 'createRoute' ||
256
+ path.node.callee.name === 'createFileRoute'
257
+ ) {
258
+ if (
259
+ path.parentPath.node.type === 'CallExpression'
260
+ ) {
261
+ const options = resolveIdentifier(
262
+ path,
263
+ path.parentPath.node.arguments[0],
264
+ )
265
+
266
+ if (t.isObjectExpression(options)) {
267
+ options.properties.forEach((prop) => {
268
+ if (t.isObjectProperty(prop)) {
269
+ splitNodeTypes.forEach((type) => {
270
+ if (t.isIdentifier(prop.key)) {
271
+ if (prop.key.name === type) {
272
+ splitNodesByType[type] = prop.value
273
+ }
274
+ }
275
+ })
276
+ }
277
+ })
278
+
279
+ // Remove all of the options
280
+ options.properties = []
281
+ }
282
+ }
283
+ }
284
+ }
285
+ },
286
+ },
287
+ state,
288
+ )
289
+
290
+ splitNodeTypes.forEach((splitType) => {
291
+ let splitNode = splitNodesByType[splitType]
292
+
293
+ if (!splitNode) {
294
+ return
295
+ }
296
+
297
+ while (t.isIdentifier(splitNode)) {
298
+ const binding = programPath.scope.getBinding(
299
+ splitNode.name,
300
+ )
301
+ splitNode = binding?.path.node
302
+ }
303
+
304
+ // Add the node to the program
305
+ if (splitNode) {
306
+ if (t.isFunctionDeclaration(splitNode)) {
307
+ programPath.pushContainer(
308
+ 'body',
309
+ t.variableDeclaration('const', [
310
+ t.variableDeclarator(
311
+ t.identifier(splitType),
312
+ t.functionExpression(
313
+ splitNode.id || null, // Anonymize the function expression
314
+ splitNode.params,
315
+ splitNode.body,
316
+ splitNode.generator,
317
+ splitNode.async,
318
+ ),
319
+ ),
320
+ ]),
321
+ )
322
+ } else if (
323
+ t.isFunctionExpression(splitNode) ||
324
+ t.isArrowFunctionExpression(splitNode)
325
+ ) {
326
+ programPath.pushContainer(
327
+ 'body',
328
+ t.variableDeclaration('const', [
329
+ t.variableDeclarator(
330
+ t.identifier(splitType),
331
+ splitNode as any,
332
+ ),
333
+ ]),
334
+ )
335
+ } else if (t.isImportSpecifier(splitNode)) {
336
+ programPath.pushContainer(
337
+ 'body',
338
+ t.variableDeclaration('const', [
339
+ t.variableDeclarator(
340
+ t.identifier(splitType),
341
+ splitNode.local,
342
+ ),
343
+ ]),
344
+ )
345
+ } else {
346
+ console.info(splitNode)
347
+ throw new Error(
348
+ `Unexpected splitNode type ☝️: ${splitNode.type}`,
349
+ )
350
+ }
351
+ }
352
+
353
+ // If the splitNode exists at the top of the program
354
+ // then we need to remove that copy
355
+ programPath.node.body = programPath.node.body.filter(
356
+ (node) => {
357
+ return node !== splitNode
358
+ },
359
+ )
360
+
361
+ // Export the node
362
+ programPath.pushContainer('body', [
363
+ t.exportNamedDeclaration(null, [
364
+ t.exportSpecifier(
365
+ t.identifier(splitType),
366
+ t.identifier(splitType),
367
+ ),
368
+ ]),
369
+ ])
370
+ })
371
+
372
+ // convert exports to imports from the original file
373
+ programPath.traverse({
374
+ ExportNamedDeclaration(path) {
375
+ // e.g. export const x = 1 or export { x }
376
+ // becomes
377
+ // import { x } from '${opts.id}'
378
+
379
+ if (path.node.declaration) {
380
+ if (t.isVariableDeclaration(path.node.declaration)) {
381
+ path.replaceWith(
382
+ t.importDeclaration(
383
+ path.node.declaration.declarations.map((decl) =>
384
+ t.importSpecifier(
385
+ t.identifier((decl.id as any).name),
386
+ t.identifier((decl.id as any).name),
387
+ ),
388
+ ),
389
+ t.stringLiteral(
390
+ opts.filename.split(
391
+ `?${splitPrefix}`,
392
+ )[0] as string,
393
+ ),
394
+ ),
395
+ )
396
+ }
397
+ }
398
+ },
399
+ })
400
+
401
+ eliminateUnreferencedIdentifiers(programPath)
402
+ },
403
+ },
404
+ },
405
+ },
406
+ {
407
+ root: process.cwd(),
408
+ minify: process.env.NODE_ENV === 'production',
409
+ },
410
+ ],
411
+ ].filter(Boolean),
412
+ }),
413
+ })
414
+ }
package/src/config.ts ADDED
@@ -0,0 +1,25 @@
1
+ import { z } from 'zod'
2
+ import {
3
+ configSchema as generatorConfigSchema,
4
+ getConfig as getGeneratorConfig,
5
+ } from '@tanstack/router-generator'
6
+
7
+ const configSchema = generatorConfigSchema.extend({
8
+ enableRouteGeneration: z.boolean().optional(),
9
+ experimental: z
10
+ .object({
11
+ enableCodeSplitting: z.boolean().optional(),
12
+ })
13
+ .optional(),
14
+ })
15
+
16
+ export const getConfig = async (
17
+ inlineConfig: Partial<PluginOptions>,
18
+ root: string,
19
+ ) => {
20
+ const config = await getGeneratorConfig(inlineConfig, root)
21
+
22
+ return configSchema.parse({ ...config, ...inlineConfig })
23
+ }
24
+
25
+ export type PluginOptions = z.infer<typeof configSchema>
@@ -0,0 +1,2 @@
1
+ export const CONFIG_FILE_NAME = 'tsr.config.json'
2
+ export const splitPrefix = 'tsr-split'
@@ -0,0 +1,211 @@
1
+ // Copied from https://github.com/pcattori/vite-env-only/blob/main/src/dce.ts
2
+ // Adapted with some minor changes for the purpose of this project
3
+
4
+ import * as t from '@babel/types'
5
+ import type { types as BabelTypes } from '@babel/core'
6
+ import type { NodePath } from '@babel/traverse'
7
+
8
+ type IdentifierPath = NodePath<BabelTypes.Identifier>
9
+
10
+ /**
11
+ * @param refs - If provided, only these identifiers will be considered for removal.
12
+ */
13
+ export const eliminateUnreferencedIdentifiers = (
14
+ programPath: NodePath<BabelTypes.Program>,
15
+ refs?: Set<IdentifierPath>,
16
+ ) => {
17
+ let referencesRemovedInThisPass: number
18
+
19
+ const shouldBeRemoved = (ident: IdentifierPath) => {
20
+ if (isIdentifierReferenced(ident)) return false
21
+ if (!refs) return true
22
+ return refs.has(ident)
23
+ }
24
+
25
+ const sweepFunction = (
26
+ path: NodePath<
27
+ | BabelTypes.FunctionDeclaration
28
+ | BabelTypes.FunctionExpression
29
+ | BabelTypes.ArrowFunctionExpression
30
+ >,
31
+ ) => {
32
+ const identifier = getIdentifier(path)
33
+ if (identifier?.node && shouldBeRemoved(identifier)) {
34
+ ++referencesRemovedInThisPass
35
+
36
+ if (
37
+ t.isAssignmentExpression(path.parentPath.node) ||
38
+ t.isVariableDeclarator(path.parentPath.node)
39
+ ) {
40
+ path.parentPath.remove()
41
+ } else {
42
+ path.remove()
43
+ }
44
+ }
45
+ }
46
+
47
+ const sweepImport = (
48
+ path: NodePath<
49
+ | BabelTypes.ImportSpecifier
50
+ | BabelTypes.ImportDefaultSpecifier
51
+ | BabelTypes.ImportNamespaceSpecifier
52
+ >,
53
+ ) => {
54
+ const local = path.get('local')
55
+ if (shouldBeRemoved(local)) {
56
+ ++referencesRemovedInThisPass
57
+ path.remove()
58
+ if (
59
+ (path.parent as BabelTypes.ImportDeclaration).specifiers.length === 0
60
+ ) {
61
+ path.parentPath.remove()
62
+ }
63
+ }
64
+ }
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
+
90
+ // Traverse again to remove unused references. This happens at least once,
91
+ // then repeats until no more references are removed.
92
+ do {
93
+ referencesRemovedInThisPass = 0
94
+
95
+ programPath.scope.crawl()
96
+
97
+ programPath.traverse({
98
+ VariableDeclarator(path) {
99
+ if (path.node.id.type === 'Identifier') {
100
+ const local = path.get('id') as NodePath<BabelTypes.Identifier>
101
+ if (shouldBeRemoved(local)) {
102
+ ++referencesRemovedInThisPass
103
+ path.remove()
104
+ }
105
+ } else if (path.node.id.type === 'ObjectPattern') {
106
+ handleObjectPattern(
107
+ path.get('id') as NodePath<BabelTypes.ObjectPattern>,
108
+ )
109
+ } else if (path.node.id.type === 'ArrayPattern') {
110
+ const pattern = path.get('id') as NodePath<BabelTypes.ArrayPattern>
111
+
112
+ let hasRemoved = false as boolean
113
+
114
+ pattern.get('elements').forEach((element, index) => {
115
+ // if (!element) return // Skip holes in the pattern
116
+
117
+ let identifierPath: NodePath<BabelTypes.Identifier>
118
+
119
+ if (t.isIdentifier(element.node)) {
120
+ identifierPath = element as NodePath<BabelTypes.Identifier>
121
+ } else if (t.isRestElement(element.node)) {
122
+ identifierPath = element.get(
123
+ 'argument',
124
+ ) as NodePath<BabelTypes.Identifier>
125
+ } else {
126
+ // For now, ignore other types like AssignmentPattern
127
+ return
128
+ }
129
+
130
+ if (shouldBeRemoved(identifierPath)) {
131
+ hasRemoved = true
132
+ pattern.node.elements[index] = null // Remove the element by setting it to null
133
+ }
134
+ })
135
+
136
+ // If any elements were removed and no elements are left, remove the entire declaration
137
+ if (
138
+ hasRemoved &&
139
+ pattern.node.elements.every((element) => element === null)
140
+ ) {
141
+ path.remove()
142
+ ++referencesRemovedInThisPass
143
+ }
144
+ }
145
+ },
146
+ FunctionDeclaration: sweepFunction,
147
+ FunctionExpression: sweepFunction,
148
+ ArrowFunctionExpression: sweepFunction,
149
+ ImportSpecifier: sweepImport,
150
+ ImportDefaultSpecifier: sweepImport,
151
+ ImportNamespaceSpecifier: sweepImport,
152
+ })
153
+ } while (referencesRemovedInThisPass)
154
+ }
155
+
156
+ function getIdentifier(
157
+ path: NodePath<
158
+ | BabelTypes.FunctionDeclaration
159
+ | BabelTypes.FunctionExpression
160
+ | BabelTypes.ArrowFunctionExpression
161
+ >,
162
+ ): NodePath<BabelTypes.Identifier> | null {
163
+ const parentPath = path.parentPath
164
+ if (parentPath.type === 'VariableDeclarator') {
165
+ const variablePath = parentPath as NodePath<BabelTypes.VariableDeclarator>
166
+ const name = variablePath.get('id')
167
+ return name.node.type === 'Identifier'
168
+ ? (name as NodePath<BabelTypes.Identifier>)
169
+ : null
170
+ }
171
+
172
+ if (parentPath.type === 'AssignmentExpression') {
173
+ const variablePath = parentPath as NodePath<BabelTypes.AssignmentExpression>
174
+ const name = variablePath.get('left')
175
+ return name.node.type === 'Identifier'
176
+ ? (name as NodePath<BabelTypes.Identifier>)
177
+ : null
178
+ }
179
+
180
+ if (path.node.type === 'ArrowFunctionExpression') {
181
+ return null
182
+ }
183
+
184
+ if (path.node.type === 'FunctionExpression') {
185
+ return null
186
+ }
187
+
188
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
189
+ return path.node.id && path.node.id.type === 'Identifier'
190
+ ? (path.get('id') as NodePath<BabelTypes.Identifier>)
191
+ : null
192
+ }
193
+
194
+ function isIdentifierReferenced(
195
+ ident: NodePath<BabelTypes.Identifier>,
196
+ ): boolean {
197
+ const binding = ident.scope.getBinding(ident.node.name)
198
+ if (binding?.referenced) {
199
+ // Functions can reference themselves, so we need to check if there's a
200
+ // binding outside the function scope or not.
201
+ if (binding.path.type === 'FunctionDeclaration') {
202
+ return !binding.constantViolations
203
+ .concat(binding.referencePaths)
204
+ // Check that every reference is contained within the function:
205
+ .every((ref) => ref.findParent((parent) => parent === binding.path))
206
+ }
207
+
208
+ return true
209
+ }
210
+ return false
211
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { unpluginRouterCodeSplitter } from './code-splitter'
2
+ export { unpluginRouterGenerator } from './router-generator'