import path from "path"; import ts from "typescript"; export default function stripServerSideLogic({ txt_code, file_path }) { const file_dir_name = path.dirname(file_path); // 1. Initial Parse of the source const sourceFile = ts.createSourceFile("file.tsx", txt_code, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX); const printer = ts.createPrinter(); /** * PASS 1: Remove 'server' export and resolve absolute paths */ const stripTransformer = (context) => (rootNode) => { const visitor = (node) => { // Remove 'export const server' if (ts.isVariableStatement(node) && node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)) { if (node.declarationList.declarations.some((d) => ts.isIdentifier(d.name) && d.name.text === "server")) { return undefined; } } // Convert relative imports to absolute if (ts.isImportDeclaration(node)) { const specifier = node.moduleSpecifier; if (ts.isStringLiteral(specifier) && specifier.text.startsWith(".")) { const absolutePath = path.resolve(file_dir_name, specifier.text); return ts.factory.updateImportDeclaration(node, node.modifiers, node.importClause, ts.factory.createStringLiteral(absolutePath), node.attributes); } } return ts.visitEachChild(node, visitor, context); }; return ts.visitNode(rootNode, visitor); }; const step1Result = ts.transform(sourceFile, [stripTransformer]); const intermediateCode = printer.printFile(step1Result.transformed[0]); // 2. Re-parse to get a "Clean Slate" AST const cleanSource = ts.createSourceFile("clean.tsx", intermediateCode, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX); /** * PASS 2: Collect all used Identifiers and Prune Imports */ const usedIdentifiers = new Set(); function walkAndFindUsages(node) { if (ts.isIdentifier(node)) { // We only care about identifiers that AREN'T the names in the import statements themselves const parent = node.parent; const isImportName = ts.isImportSpecifier(parent) || ts.isImportClause(parent) || ts.isNamespaceImport(parent); if (!isImportName) { usedIdentifiers.add(node.text); } } ts.forEachChild(node, walkAndFindUsages); } walkAndFindUsages(cleanSource); const cleanupTransformer = (context) => (rootNode) => { const visitor = (node) => { if (ts.isImportDeclaration(node)) { const clause = node.importClause; if (!clause) return node; // 1. Clean Named Imports: { A, B } if (clause.namedBindings && ts.isNamedImports(clause.namedBindings)) { const activeElements = clause.namedBindings.elements.filter((el) => usedIdentifiers.has(el.name.text)); // If no named imports are used and there is no default import, nix the whole line if (activeElements.length === 0 && !clause.name) return undefined; // If we have some named imports left, update the node return ts.factory.updateImportDeclaration(node, node.modifiers, ts.factory.updateImportClause(clause, clause.isTypeOnly, // Only keep default import if it's used clause.name && usedIdentifiers.has(clause.name.text) ? clause.name : undefined, ts.factory.createNamedImports(activeElements)), node.moduleSpecifier, node.attributes); } // 2. Clean Default Imports: import X from '...' if (clause.name && !usedIdentifiers.has(clause.name.text)) { // If there are no named bindings attached, nix the whole line if (!clause.namedBindings) return undefined; // Otherwise, just strip the default name and keep the named bindings return ts.factory.updateImportDeclaration(node, node.modifiers, ts.factory.updateImportClause(clause, clause.isTypeOnly, undefined, clause.namedBindings), node.moduleSpecifier, node.attributes); } } return ts.visitEachChild(node, visitor, context); }; return ts.visitNode(rootNode, visitor); }; const finalResult = ts.transform(cleanSource, [cleanupTransformer]); return printer.printFile(finalResult.transformed[0]); }