Repository: TanStack/router (ts)

Issue:
Experimental splitting: not removing the import statement
### Describe the bug

See this [snapshot](https://github.com/TanStack/router/blob/main/packages/router-plugin/tests/code-splitter/snapshots/imported.tsx) for the [`imported.tsx`](https://github.com/TanStack/router/blob/main/packages/router-plugin/tests/code-splitter/test-files/imported.tsx) test file compiled file when using the `experimental.enableCodeSplitting` flag.

In the compiled output, a single import string-literal is being left behind.

```tsx
const $$splitLoaderImporter = () => import('tsr-split:imported.tsx?tsr-split');
import { lazyFn } from '@tanstack/react-router';
const $$splitComponentImporter = () => import('tsr-split:imported.tsx?tsr-split');
import { lazyRouteComponent } from '@tanstack/react-router';
import { createFileRoute } from '@tanstack/react-router';
import '../shared/imported'; // <- this one here
export const Route = createFileRoute('/')({
  component: lazyRouteComponent($$splitComponentImporter, 'component'),
  loader: lazyFn($$splitLoaderImporter, 'loader')
});
```

Having this in means, that the code isn't really being split out during the build process, since the entire file is being imported anyway.

An edge case to be considered however is that sometimes a user may intentionally (on the user-side) make such an import (like how `react-i18next` recommends the [setup](https://react.i18next.com/guides/quick-start#configure-i18next) to be done).

As such, the code elimination should only evaluate removing import string-literal statements if they are about components or loaders.

Pull request description:
fix(router-plugin): removing trailing import string literals with no references for `experimental.enableCodesplitting`
Fixes #1808

Patch:
diff --git a/packages/router-plugin/src/compilers.ts b/packages/router-plugin/src/compilers.ts
index 7e0e3749b..c798bb5c6 100644
--- a/packages/router-plugin/src/compilers.ts
+++ b/packages/router-plugin/src/compilers.ts
@@ -40,134 +40,160 @@ export async function compileFile(opts: {
                 enter(programPath: babel.NodePath<t.Program>, state: State) {
                   const splitUrl = `${splitPrefix}:${opts.filename}?${splitPrefix}`
 
+                  /**
+                   * If the component for the route is being imported from
+                   * another file, this is to track the path to that file
+                   * the path itself doesn't matter, we just need to keep
+                   * track of it so that we can remove it from the imports
+                   * list if it's not being used like:
+                   *
+                   * `import '../shared/imported'`
+                   */
+                  let existingCompImportPath: string | null = null
+                  let existingLoaderImportPath: string | null = null
+
                   programPath.traverse(
                     {
                       CallExpression: (path) => {
-                        if (path.node.callee.type === 'Identifier') {
-                          if (
+                        if (!t.isIdentifier(path.node.callee)) {
+                          return
+                        }
+
+                        if (
+                          !(
                             path.node.callee.name === 'createRoute' ||
                             path.node.callee.name === 'createFileRoute'
-                          ) {
-                            if (
-                              path.parentPath.node.type === 'CallExpression'
-                            ) {
-                              const options = resolveIdentifier(
-                                path,
-                                path.parentPath.node.arguments[0],
-                              )
-
-                              let found = false
-
-                              const hasImportedOrDefinedIdentifier = (
-                                name: string,
-                              ) => {
-                                return programPath.scope.hasBinding(name)
-                              }
+                          )
+                        ) {
+                          return
+                        }
 
-                              if (t.isObjectExpression(options)) {
-                                options.properties.forEach((prop) => {
-                                  if (t.isObjectProperty(prop)) {
-                                    if (t.isIdentifier(prop.key)) {
-                                      if (prop.key.name === 'component') {
-                                        const value = prop.value
-
-                                        if (t.isIdentifier(value)) {
-                                          removeIdentifierLiteral(path, value)
-                                        }
-
-                                        // Prepend the import statement to the program along with the importer function
-                                        // Check to see if lazyRouteComponent is already imported before attempting
-                                        // to import it again
-
-                                        if (
-                                          !hasImportedOrDefinedIdentifier(
-                                            'lazyRouteComponent',
-                                          )
-                                        ) {
-                                          programPath.unshiftContainer('body', [
-                                            template.smart(
-                                              `import { lazyRouteComponent } from '@tanstack/react-router'`,
-                                            )() as t.Statement,
-                                          ])
-                                        }
-
-                                        if (
-                                          !hasImportedOrDefinedIdentifier(
-                                            '$$splitComponentImporter',
-                                          )
-                                        ) {
-                                          programPath.unshiftContainer('body', [
-                                            template.smart(
-                                              `const $$splitComponentImporter = () => import('${splitUrl}')`,
-                                            )() as t.Statement,
-                                          ])
-                                        }
-
-                                        prop.value = template.expression(
-                                          `lazyRouteComponent($$splitComponentImporter, 'component')`,
-                                        )() as any
-
-                                        programPath.pushContainer('body', [
-                                          template.smart(
-                                            `function DummyComponent() { return null }`,
-                                          )() as t.Statement,
-                                        ])
-
-                                        found = true
-                                      } else if (prop.key.name === 'loader') {
-                                        const value = prop.value
-
-                                        if (t.isIdentifier(value)) {
-                                          removeIdentifierLiteral(path, value)
-                                        }
-
-                                        // Prepend the import statement to the program along with the importer function
-
-                                        if (
-                                          !hasImportedOrDefinedIdentifier(
-                                            'lazyFn',
-                                          )
-                                        ) {
-                                          programPath.unshiftContainer('body', [
-                                            template.smart(
-                                              `import { lazyFn } from '@tanstack/react-router'`,
-                                            )() as t.Statement,
-                                          ])
-                                        }
-
-                                        if (
-                                          !hasImportedOrDefinedIdentifier(
-                                            '$$splitLoaderImporter',
-                                          )
-                                        ) {
-                                          programPath.unshiftContainer('body', [
-                                            template.smart(
-                                              `const $$splitLoaderImporter = () => import('${splitUrl}')`,
-                                            )() as t.Statement,
-                                          ])
-                                        }
-
-                                        prop.value = template.expression(
-                                          `lazyFn($$splitLoaderImporter, 'loader')`,
-                                        )() as any
-
-                                        found = true
-                                      }
+                        if (t.isCallExpression(path.parentPath.node)) {
+                          const options = resolveIdentifier(
+                            path,
+                            path.parentPath.node.arguments[0],
+                          )
+
+                          let found = false
+
+                          const hasImportedOrDefinedIdentifier = (
+                            name: string,
+                          ) => {
+                            return programPath.scope.hasBinding(name)
+                          }
+
+                          if (t.isObjectExpression(options)) {
+                            options.properties.forEach((prop) => {
+                              if (t.isObjectProperty(prop)) {
+                                if (t.isIdentifier(prop.key)) {
+                                  if (prop.key.name === 'component') {
+                                    const value = prop.value
+
+                                    if (t.isIdentifier(value)) {
+                                      existingCompImportPath =
+                                        getImportSpecifierAndPathFromLocalName(
+                                          programPath,
+                                          value.name,
+                                        ).path
+
+                                      removeIdentifierLiteral(path, value)
                                     }
-                                  }
 
-                                  programPath.scope.crawl()
-                                })
-                              }
+                                    // Prepend the import statement to the program along with the importer function
+                                    // Check to see if lazyRouteComponent is already imported before attempting
+                                    // to import it again
+
+                                    if (
+                                      !hasImportedOrDefinedIdentifier(
+                                        'lazyRouteComponent',
+                                      )
+                                    ) {
+                                      programPath.unshiftContainer('body', [
+                                        template.statement(
+                                          `import { lazyRouteComponent } from '@tanstack/react-router'`,
+                                        )(),
+                                      ])
+                                    }
+
+                                    if (
+                                      !hasImportedOrDefinedIdentifier(
+                                        '$$splitComponentImporter',
+                                      )
+                                    ) {
+                                      programPath.unshiftContainer('body', [
+                                        template.statement(
+                                          `const $$splitComponentImporter = () => import('${splitUrl}')`,
+                                        )(),
+                                      ])
+                                    }
+
+                                    prop.value = template.expression(
+                                      `lazyRouteComponent($$splitComponentImporter, 'component')`,
+                                    )()
+
+                                    programPath.pushContainer('body', [
+                                      template.statement(
+                                        `function DummyComponent() { return null }`,
+                                      )(),
+                                    ])
+
+                                    found = true
+                                  } else if (prop.key.name === 'loader') {
+                                    const value = prop.value
+
+                                    if (t.isIdentifier(value)) {
+                                      existingLoaderImportPath =
+                                        getImportSpecifierAndPathFromLocalName(
+                                          programPath,
+                                          value.name,
+                                        ).path
+
+                                      removeIdentifierLiteral(path, value)
+                                    }
+
+                                    // Prepend the import statement to the program along with the importer function
+
+                                    if (
+                                      !hasImportedOrDefinedIdentifier('lazyFn')
+                                    ) {
+                                      programPath.unshiftContainer('body', [
+                                        template.smart(
+                                          `import { lazyFn } from '@tanstack/react-router'`,
+                                        )() as t.Statement,
+                                      ])
+                                    }
 
-                              if (found as boolean) {
-                                programPath.pushContainer('body', [
-                                  template.smart(
-                                    `function TSR_Dummy_Component() {}`,
-                                  )() as t.Statement,
-                                ])
+                                    if (
+                                      !hasImportedOrDefinedIdentifier(
+                                        '$$splitLoaderImporter',
+                                      )
+                                    ) {
+                                      programPath.unshiftContainer('body', [
+                                        template.statement(
+                                          `const $$splitLoaderImporter = () => import('${splitUrl}')`,
+                                        )(),
+                                      ])
+                                    }
+
+                                    prop.value = template.expression(
+                                      `lazyFn($$splitLoaderImporter, 'loader')`,
+                                    )()
+
+                                    found = true
+                                  }
+                                }
                               }
-                            }
+
+                              programPath.scope.crawl()
+                            })
+                          }
+
+                          if (found as boolean) {
+                            programPath.pushContainer('body', [
+                              template.statement(
+                                `function TSR_Dummy_Component() {}`,
+                              )(),
+                            ])
                           }
                         }
                       },
@@ -176,6 +202,29 @@ export async function compileFile(opts: {
                   )
 
                   eliminateUnreferencedIdentifiers(programPath)
+
+                  /**
+                   * If the component for the route is being imported,
+                   * and it's not being used, remove the import statement
+                   * from the program, by checking that the import has no
+                   * specifiers
+                   */
+                  if (
+                    (existingCompImportPath as string | null) ||
+                    (existingLoaderImportPath as string | null)
+                  ) {
+                    programPath.traverse({
+                      ImportDeclaration(path) {
+                        if (path.node.specifiers.length > 0) return
+                        if (
+                          path.node.source.value === existingCompImportPath ||
+                          path.node.source.value === existingLoaderImportPath
+                        ) {
+                          path.remove()
+                        }
+                      },
+                    })
+                  }
                 },
               },
             },
@@ -190,6 +239,39 @@ export async function compileFile(opts: {
   })
 }
 
+function getImportSpecifierAndPathFromLocalName(
+  programPath: babel.NodePath<t.Program>,
+  name: string,
+): {
+  specifier:
+    | t.ImportSpecifier
+    | t.ImportDefaultSpecifier
+    | t.ImportNamespaceSpecifier
+    | null
+  path: string | null
+} {
+  let specifier:
+    | t.ImportSpecifier
+    | t.ImportDefaultSpecifier
+    | t.ImportNamespaceSpecifier
+    | null = null
+  let path: string | null = null
+
+  programPath.traverse({
+    ImportDeclaration(importPath) {
+      const found = importPath.node.specifiers.find(
+        (specifier) => specifier.local.name === name,
+      )
+      if (found) {
+        specifier = found
+        path = importPath.node.source.value
+      }
+    },
+  })
+
+  return { specifier, path }
+}
+
 // Reusable function to get literal value or resolve variable to literal
 function resolveIdentifier(path: any, node: any) {
   if (t.isIdentifier(node)) {
@@ -250,36 +332,40 @@ export async function splitFile(opts: {
                   programPath.traverse(
                     {
                       CallExpression: (path) => {
-                        if (path.node.callee.type === 'Identifier') {
-                          if (
+                        if (!t.isIdentifier(path.node.callee)) {
+                          return
+                        }
+
+                        if (
+                          !(
                             path.node.callee.name === 'createRoute' ||
                             path.node.callee.name === 'createFileRoute'
-                          ) {
-                            if (
-                              path.parentPath.node.type === 'CallExpression'
-                            ) {
-                              const options = resolveIdentifier(
-                                path,
-                                path.parentPath.node.arguments[0],
-                              )
-
-                              if (t.isObjectExpression(options)) {
-                                options.properties.forEach((prop) => {
-                                  if (t.isObjectProperty(prop)) {
-                                    splitNodeTypes.forEach((type) => {
-                                      if (t.isIdentifier(prop.key)) {
-                                        if (prop.key.name === type) {
-                                          splitNodesByType[type] = prop.value
-                                        }
-                                      }
-                                    })
+                          )
+                        ) {
+                          return
+                        }
+
+                        if (t.isCallExpression(path.parentPath.node)) {
+                          const options = resolveIdentifier(
+                            path,
+                            path.parentPath.node.arguments[0],
+                          )
+
+                          if (t.isObjectExpression(options)) {
+                            options.properties.forEach((prop) => {
+                              if (t.isObjectProperty(prop)) {
+                                splitNodeTypes.forEach((type) => {
+                                  if (t.isIdentifier(prop.key)) {
+                                    if (prop.key.name === type) {
+                                      splitNodesByType[type] = prop.value
+                                    }
                                   }
                                 })
-
-                                // Remove all of the options
-                                options.properties = []
                               }
-                            }
+                            })
+
+                            // Remove all of the options
+                            options.properties = []
                           }
                         }
                       },


Test patch:
diff --git a/packages/router-plugin/tests/code-splitter/snapshots/function-declaration.tsx b/packages/router-plugin/tests/code-splitter/snapshots/function-declaration.tsx
index b5b471c7e..20796bb91 100644
--- a/packages/router-plugin/tests/code-splitter/snapshots/function-declaration.tsx
+++ b/packages/router-plugin/tests/code-splitter/snapshots/function-declaration.tsx
@@ -3,7 +3,6 @@ import { lazyRouteComponent } from '@tanstack/react-router';
 const $$splitLoaderImporter = () => import('tsr-split:function-declaration.tsx?tsr-split');
 import { lazyFn } from '@tanstack/react-router';
 import { createFileRoute } from '@tanstack/react-router';
-import '../posts';
 export const Route = createFileRoute('/posts')({
   loader: lazyFn($$splitLoaderImporter, 'loader'),
   component: lazyRouteComponent($$splitComponentImporter, 'component')
diff --git a/packages/router-plugin/tests/code-splitter/snapshots/imported-default-component-destructured-loader.tsx b/packages/router-plugin/tests/code-splitter/snapshots/imported-default-component-destructured-loader.tsx
index 39c0b4969..3d7bb3b9a 100644
--- a/packages/router-plugin/tests/code-splitter/snapshots/imported-default-component-destructured-loader.tsx
+++ b/packages/router-plugin/tests/code-splitter/snapshots/imported-default-component-destructured-loader.tsx
@@ -3,7 +3,6 @@ import { lazyFn } from '@tanstack/react-router';
 const $$splitComponentImporter = () => import('tsr-split:imported-default-component-destructured-loader.tsx?tsr-split');
 import { lazyRouteComponent } from '@tanstack/react-router';
 import { createFileRoute } from '@tanstack/react-router';
-import '../shared/imported';
 export const Route = createFileRoute('/')({
   component: lazyRouteComponent($$splitComponentImporter, 'component'),
   loader: lazyFn($$splitLoaderImporter, 'loader')
diff --git a/packages/router-plugin/tests/code-splitter/snapshots/imported-default-component.tsx b/packages/router-plugin/tests/code-splitter/snapshots/imported-default-component.tsx
index a85159c68..a0884d96b 100644
--- a/packages/router-plugin/tests/code-splitter/snapshots/imported-default-component.tsx
+++ b/packages/router-plugin/tests/code-splitter/snapshots/imported-default-component.tsx
@@ -1,7 +1,6 @@
 const $$splitComponentImporter = () => import('tsr-split:imported-default-component.tsx?tsr-split');
 import { lazyRouteComponent } from '@tanstack/react-router';
 import { createFileRoute } from '@tanstack/react-router';
-import '../shared/imported';
 export const Route = createFileRoute('/')({
   component: lazyRouteComponent($$splitComponentImporter, 'component')
 });
\ No newline at end of file
diff --git a/packages/router-plugin/tests/code-splitter/snapshots/imported.tsx b/packages/router-plugin/tests/code-splitter/snapshots/imported.tsx
index d48f6e2cc..7541aab98 100644
--- a/packages/router-plugin/tests/code-splitter/snapshots/imported.tsx
+++ b/packages/router-plugin/tests/code-splitter/snapshots/imported.tsx
@@ -3,7 +3,6 @@ import { lazyFn } from '@tanstack/react-router';
 const $$splitComponentImporter = () => import('tsr-split:imported.tsx?tsr-split');
 import { lazyRouteComponent } from '@tanstack/react-router';
 import { createFileRoute } from '@tanstack/react-router';
-import '../shared/imported';
 export const Route = createFileRoute('/')({
   component: lazyRouteComponent($$splitComponentImporter, 'component'),
   loader: lazyFn($$splitLoaderImporter, 'loader')