Skip to content

Instantly share code, notes, and snippets.

@codebutler
Last active August 21, 2023 22:54
Show Gist options
  • Save codebutler/666c8306fb70aeda845815c9cc0a07b7 to your computer and use it in GitHub Desktop.
Save codebutler/666c8306fb70aeda845815c9cc0a07b7 to your computer and use it in GitHub Desktop.
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