Codegen’s export management tools provide a powerful way to reorganize and manage exports in your TypeScript codebase. This guide will walk you through the process of automating export management.

Common use cases include:

The codemod automatically handles dependencies and import updates, so you don’t need to manage them manually.

Import Management

There are two main aspects to managing imports: updating references and cleaning up old imports.

# Update all import usages that refer to this export
for usage in export.symbol_usages():
    if isinstance(usage, TSImport) and usage not in processed_imports:
        processed_imports.add(usage)
        
        # Translate the path for the new import
        new_path = usage.file.ts_config.translate_import_path(resolved_public_file)
        
        if has_wildcard and export.name != export.resolved_symbol.name:
            name = f"{export.resolved_symbol.name} as {export.name}"
        else:
            name = usage.name

Handling Export Statements

When working with export statements, you need to handle both existing and new statements:

# Check for existing statement
statement = file.get_export_statement_for_path(relative_path, "TYPE")
if statement:
    # Insert into existing statement
    if export.is_aliased():
        statement.insert(0, f"{export.resolved_symbol.name} as {export.name}")
    else:
        statement.insert(0, f"{export.name}")

Best Practices

  1. Check for Wildcards First: Always check for existing wildcard exports before adding new ones:
if target_file.has_export_statement_for_path(relative_path, "WILDCARD"):
    has_wildcard = True
    continue
  1. Handle Path Translations: Use the TypeScript config for path translations:
new_path = usage.file.ts_config.translate_import_path(resolved_public_file)
  1. Clean Up Empty Files: Remove files that no longer contain exports or symbols:
if not file.export_statements and len(file.symbols) == 0:
    file.remove()

Complete Example

Here’s a complete example that puts all these concepts together:

processed_imports = set()

for file in codebase.files:
  # Only process files under /src/shared
  if '/src/shared' not in file.filepath:
      continue

  # Gather all reexports that are not external exports
  all_reexports = []
  for export_stmt in file.export_statements:
      for export in export_stmt.exports:
          if export.is_reexport() and not export.is_external_export:
              all_reexports.append(export)
  
  # Skip if there are none
  if not all_reexports:
      continue

  for export in all_reexports:
      has_wildcard = False

      # Replace "src/" with "src/shared/"
      resolved_public_file = export.resolved_symbol.filepath.replace("src/", "src/shared/")

      # Get relative path from the “public” file back to the original file
      relative_path = codebase.get_relative_path(
          from_file=resolved_public_file,
          to_file=export.resolved_symbol.filepath
      )

      # Ensure the “public” file exists
      if not codebase.has_file(resolved_public_file):
          target_file = codebase.create_file(resolved_public_file, sync=True)
      else:
          target_file = codebase.get_file(resolved_public_file)

      # If target file already has a wildcard export for this relative path, skip
      if target_file.has_export_statement_for_path(relative_path, "WILDCARD"):
          has_wildcard = True
          continue

      # Compare "public" path to the local file’s export.filepath
      if codebase._remove_extension(resolved_public_file) != codebase._remove_extension(export.filepath):

          # A) Wildcard export, e.g. `export * from "..."`
          if export.is_wildcard_export():
              target_file.insert_before(f'export * from "{relative_path}"')

          # B) Type export, e.g. `export type { Foo, Bar } from "..."`
          elif export.is_type_export():
              # Does this file already have a type export statement for the path?
              statement = file.get_export_statement_for_path(relative_path, "TYPE")
              if statement:
                  # Insert into existing statement
                  if export.is_aliased():
                      statement.insert(0, f"{export.resolved_symbol.name} as {export.name}")
                  else:
                      statement.insert(0, f"{export.name}")
              else:
                  # Insert a new type export statement
                  if export.is_aliased():
                      target_file.insert_before(
                          f'export type {{ {export.resolved_symbol.name} as {export.name} }} '
                          f'from "{relative_path}"'
                      )
                  else:
                      target_file.insert_before(
                          f'export type {{ {export.name} }} from "{relative_path}"'
                      )

          # C) Normal export, e.g. `export { Foo, Bar } from "..."`
          else:
              statement = file.get_export_statement_for_path(relative_path, "EXPORT")
              if statement:
                  # Insert into existing export statement
                  if export.is_aliased():
                      statement.insert(0, f"{export.resolved_symbol.name} as {export.name}")
                  else:
                      statement.insert(0, f"{export.name}")
              else:
                  # Insert a brand-new normal export statement
                  if export.is_aliased():
                      target_file.insert_before(
                          f'export {{ {export.resolved_symbol.name} as {export.name} }} '
                          f'from "{relative_path}"'
                      )
                  else:
                      target_file.insert_before(
                          f'export {{ {export.name} }} from "{relative_path}"'
                      )

      # Now update all import usages that refer to this export
      for usage in export.symbol_usages():
          if isinstance(usage, TSImport) and usage not in processed_imports:
              processed_imports.add(usage)

              # Translate the resolved_public_file to the usage file’s TS config import path
              new_path = usage.file.ts_config.translate_import_path(resolved_public_file)

              if has_wildcard and export.name != export.resolved_symbol.name:
                  name = f"{export.resolved_symbol.name} as {export.name}"
              else:
                  name = usage.name

              if usage.is_type_import():
                  new_import = f'import type {{ {name} }} from "{new_path}"'
              else:
                  new_import = f'import {{ {name} }} from "{new_path}"'

              usage.file.insert_before(new_import)
              usage.remove()

      # Remove the old export from the original file
      export.remove()

  # If the file ends up with no exports, remove it entirely
  if not file.export_statements and len(file.symbols) == 0:
      file.remove()
Remember to test your changes incrementally and commit after major modifications to keep the codebase graph up-to-date.

Next Steps

After implementing these export management strategies:

  1. Run your test suite to verify everything still works

  2. Review the generated import statements

  3. Check for any empty files that should be removed

  4. Verify that all export types (wildcard, type, named) are working as expected

Remember that managing exports is an iterative process - you may need to run the codemod multiple times as your codebase evolves.

Was this page helpful?