Safely and systematically manage exports in your TypeScript codebase
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:
processed_imports =set()forfilein codebase.files:# Only process files under /src/sharedif'/src/shared'notinfile.filepath:continue# Gather all reexports that are not external exports all_reexports =[]for export_stmt infile.export_statements:for export in export_stmt.exports:if export.is_reexport()andnot export.is_external_export: all_reexports.append(export)
This snippet shows how to:
Track processed imports to avoid duplicates
Filter for shared module files
Collect non-external reexports
# 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 filerelative_path = codebase.get_relative_path( from_file=resolved_public_file, to_file=export.resolved_symbol.filepath)# Ensure the "public" file existsifnot 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)
This code demonstrates:
Converting paths to shared module format
Computing relative paths between files
Creating or accessing target files
# A) Wildcard exportif export.is_wildcard_export(): target_file.insert_before(f'export * from "{relative_path}"')# B) Type exportelif export.is_type_export():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 exportelse: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}"')
Handles three types of exports:
Wildcard exports (export * from ...)
Type exports (export type { ... })
Regular named exports (export { ... })
The codemod automatically handles dependencies and import updates, so you don’t need to manage them manually.
There are two main aspects to managing imports: updating references and cleaning up old imports.
# Update all import usages that refer to this exportfor usage in export.symbol_usages():ifisinstance(usage, TSImport)and usage notin 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
Here’s a complete example that puts all these concepts together:
processed_imports =set()forfilein codebase.files:# Only process files under /src/sharedif'/src/shared'notinfile.filepath:continue# Gather all reexports that are not external exports all_reexports =[]for export_stmt infile.export_statements:for export in export_stmt.exports:if export.is_reexport()andnot export.is_external_export: all_reexports.append(export)# Skip if there are noneifnot all_reexports:continuefor 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 existsifnot 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, skipif target_file.has_export_statement_for_path(relative_path,"WILDCARD"): has_wildcard =Truecontinue# Compare "public" path to the local file’s export.filepathif 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 statementif 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 statementif 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 statementif 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 statementif 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 exportfor usage in export.symbol_usages():ifisinstance(usage, TSImport)and usage notin 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.nameif 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 entirelyifnotfile.export_statements andlen(file.symbols)==0:file.remove()
Remember to test your changes incrementally and commit after major modifications to keep the codebase graph up-to-date.