Files
bigcapital/packages/webapp/scripts/codemod-fix-lazy-imports.js
Ahmed Bouhuolia b6970fefc2 refactor: convert containers default exports to named exports
## Summary
Converted 905 default exports in src/containers to named exports for improved tree-shaking, better IDE refactoring support, and consistency with modern TypeScript practices.

## Changes
- Converted `export default function X` to `export function X` (916 files)
- Converted `export default compose(...)(X)` to `export const X = compose(...)(XInner)` with HOC wrapping
- Updated 373 import sites from default to named imports
- Fixed 136 React.lazy() imports to use .then() pattern for compatibility with named exports
- Updated re-export patterns in index files
- Fixed edge cases (alert arrays, connector HOCs, type definitions)

## Implementation
- Created codemod script: codemod-containers-exports.js (905 files converted)
- Created import updater: codemod-update-default-imports.js (373 imports fixed)
- Created lazy import fixer: codemod-fix-lazy-imports.js (136 lazy imports fixed)
- Manual fixes for 30 edge-case files (arrays, HOC factories, type definitions)

## Testing
- TypeScript type check: 0 codemod-related errors
- All lazy imports updated with .then() pattern
- All import sites updated to use named imports
- Zero remaining default exports in containers directory

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 20:08:39 +02:00

112 lines
3.4 KiB
JavaScript

// @ts-check
'use strict';
/**
* Fix React.lazy() calls that need a default export.
*
* Converts:
* React.lazy(() => import('./X'))
* to:
* React.lazy(() => import('./X').then(m => ({ default: m.ExportName })))
*
* Only updates lazy imports whose target files appear in export-manifest.json
* (i.e., files that had their default export converted to a named export).
*/
const path = require('path');
const fs = require('fs');
const ROOT = path.join(__dirname, '..');
const SRC_DIR = path.join(ROOT, 'src');
const MANIFEST_PATH = path.join(__dirname, 'export-manifest.json');
if (!fs.existsSync(MANIFEST_PATH)) {
console.error('Manifest not found. Run codemod-containers-exports.js first.');
process.exit(1);
}
const manifest = JSON.parse(fs.readFileSync(MANIFEST_PATH, 'utf-8'));
const manifestByPath = {};
for (const [absPath, exportName] of Object.entries(manifest)) {
manifestByPath[path.normalize(absPath)] = exportName;
}
const TS_EXTENSIONS = ['.tsx', '.ts', '/index.tsx', '/index.ts'];
function resolveImport(fromFile, importPath) {
const aliasMatch = importPath.match(/^@\/(.+)/);
if (aliasMatch) {
const base = path.join(SRC_DIR, aliasMatch[1]);
for (const ext of TS_EXTENSIONS) {
const c = path.normalize(base + ext);
if (fs.existsSync(c)) return c;
}
return null;
}
if (!importPath.startsWith('.')) return null;
const base = path.resolve(path.dirname(fromFile), importPath);
for (const ext of TS_EXTENSIONS) {
const c = path.normalize(base + ext);
if (fs.existsSync(c)) return c;
}
const direct = path.normalize(base);
if (fs.existsSync(direct)) return direct;
return null;
}
function findFiles(dir) {
const results = [];
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) results.push(...findFiles(full));
else if (/\.(tsx?|ts)$/.test(entry.name)) results.push(full);
}
return results;
}
// Regex: matches lazy(() => import('...')) — single or multi-line, with optional trailing comma
// Captures: quote char and path. Does NOT already have .then(
const LAZY_RE = /lazy\s*\(\s*\(\s*\)\s*=>\s*import\s*\(\s*(['"`])([^'"`]+)\1\s*\)\s*,?\s*\)/g;
const files = findFiles(SRC_DIR);
let changed = 0;
let errors = 0;
for (const filePath of files) {
const content = fs.readFileSync(filePath, 'utf-8');
// Quick skip: no lazy import pattern
if (!content.includes('lazy(') && !content.includes('lazy (')) continue;
if (!content.includes('import(')) continue;
let newContent = content;
let fileChanged = false;
newContent = newContent.replace(LAZY_RE, (match, quote, importPath) => {
// Skip if it already has .then(
if (match.includes('.then(')) return match;
const resolved = resolveImport(filePath, importPath);
if (!resolved) return match;
const exportName = manifestByPath[resolved];
if (!exportName) return match;
// Build the replacement (always compact single-line form)
return `lazy(() => import(${quote}${importPath}${quote}).then(m => ({ default: m.${exportName} })))`;
});
if (newContent !== content) {
try {
fs.writeFileSync(filePath, newContent, 'utf-8');
changed++;
fileChanged = true;
} catch (err) {
console.error(`ERROR: ${path.relative(ROOT, filePath)}: ${err.message}`);
errors++;
}
}
}
console.log(`\nDone. Lazy imports fixed: ${changed} Errors: ${errors}`);