Last active
August 21, 2023 22:54
-
-
Save codebutler/666c8306fb70aeda845815c9cc0a07b7 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { ImportDeclaration, Project, SourceFile, SyntaxKind } from "ts-morph"; | |
/* | |
* This script uses ts-morph to convert all (react-router-dom) routes to be lazy. | |
* It also updates all route components to be exported by name rather than as default. | |
* | |
* This script is not very robust and will probably need some tweaking to work with your | |
* code, but hopefully its a helpful starting point. | |
* | |
* Run with: | |
* pnpm exec ts-node --esm morphs/lazy-imports.ts | |
* | |
* Authors: | |
* Eric Butler <eric@codebutler.com> | |
* | |
* Any copyright is dedicated to the Public Domain. | |
* https://creativecommons.org/publicdomain/zero/1.0/ | |
*/ | |
const project = new Project({ | |
tsConfigFilePath: "tsconfig.json", | |
}); | |
const replaceRoutes = (file: SourceFile) => { | |
console.log("Replace routes: " + file.getFilePath()); | |
const importsToRemove = new Set<string>(); | |
file.forEachDescendant((node) => { | |
if (node.getKind() === SyntaxKind.PropertyAssignment) { | |
const assignment = node.asKindOrThrow(SyntaxKind.PropertyAssignment); | |
if (assignment.getName() !== "element") { | |
return; | |
} | |
const elementTag = assignment | |
.getInitializerOrThrow() | |
.asKindOrThrow(SyntaxKind.JsxSelfClosingElement); | |
const tagNameNode = elementTag.getTagNameNode(); | |
const componentName = tagNameNode.getText(); | |
const id = tagNameNode.asKindOrThrow(SyntaxKind.Identifier); | |
const importReferenceNode = id | |
.findReferencesAsNodes() | |
.find( | |
(node) => !!node.getFirstAncestorByKind(SyntaxKind.ImportDeclaration), | |
); | |
if (!importReferenceNode) { | |
console.warn(" No import reference found for " + componentName); | |
return; | |
} | |
const importDeclarationNode = | |
importReferenceNode.getFirstAncestorByKindOrThrow( | |
SyntaxKind.ImportDeclaration, | |
); | |
const importPath = importDeclarationNode.getModuleSpecifierValue(); | |
if (!importPath.includes("features")) { | |
return; | |
} | |
node.replaceWithText( | |
`lazy: async () => { | |
const { ${componentName} } = await import("${importPath}"); | |
return { Component: ${componentName} }; | |
}`, | |
); | |
importsToRemove.add(importDeclarationNode.getModuleSpecifierValue()); | |
console.log(` ${importPath}`); | |
} | |
for (let importDeclaration of file.getImportDeclarations()) { | |
if (importsToRemove.has(importDeclaration.getModuleSpecifierValue())) { | |
console.log( | |
" Removing: " + importDeclaration.getModuleSpecifierValue(), | |
); | |
importDeclaration.remove(); | |
} | |
} | |
file.saveSync(); | |
}); | |
}; | |
const replaceExports = (file: SourceFile) => { | |
console.log("Replace exports: " + file.getFilePath()); | |
for (const variableStatement of file.getVariableStatements()) { | |
const variableDeclaration = variableStatement.getDeclarations()[0]; | |
const variableName = variableDeclaration.getNameNode().getText(); | |
if (variableName === file.getBaseNameWithoutExtension()) { | |
console.log(` ${variableName}`); | |
variableStatement.setIsExported(true); | |
break; | |
} | |
} | |
file.removeDefaultExport(); | |
file.saveSync(); | |
}; | |
project.getSourceFiles().forEach((file) => { | |
const baseName = file.getBaseName(); | |
console.log(baseName); | |
if (baseName.endsWith("Routes.tsx")) { | |
replaceRoutes(file); | |
} else { | |
// Update to match where your routes live | |
if (file.getFilePath().includes("features") && ((baseName.endsWith("Page.tsx"))) { | |
replaceExports(file); | |
} | |
} | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment