Forked from walkerdb/migrate-default-exports-to-named-exports.js
Last active
October 16, 2025 09:22
-
-
Save akx/90344a3f2c950fa819bf4ae037020784 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
| /* | |
| This codemod migrates usage of default exports to named exports instead | |
| See migrate-default-exports-to-named-exports.test.js for before/after examples. | |
| */ | |
| export const parser = 'tsx'; | |
| function generateNamedExportDeclaration(jscodeshift, inlineDefaultExport, filePath) { | |
| const declarationType = inlineDefaultExport.node.declaration.type; | |
| const comments = inlineDefaultExport.node.comments; | |
| if (declarationType === 'FunctionDeclaration' || declarationType === 'ClassDeclaration') { | |
| return Object.assign(jscodeshift.exportNamedDeclaration(inlineDefaultExport.node.declaration), { | |
| comments, | |
| }); | |
| } | |
| const dynamicExportName = filePath.split('/').pop().split('.')[0]; | |
| const exportName = jscodeshift.identifier(dynamicExportName); | |
| return Object.assign( | |
| jscodeshift.exportNamedDeclaration( | |
| jscodeshift.variableDeclaration('const', [ | |
| jscodeshift.variableDeclarator(exportName, inlineDefaultExport.node.declaration), | |
| ]), | |
| ), | |
| {comments}, | |
| ); | |
| } | |
| function migrateDefaultExports(source, jscodeshift, filePath) { | |
| if (filePath.includes('.story.') || filePath.includes('.stories.')) { | |
| return; | |
| } | |
| // migrate inline default exports, giving them an export name of the filename | |
| source | |
| .find(jscodeshift.ExportDefaultDeclaration, t => t.declaration.type !== 'Identifier') | |
| .replaceWith(inlineDefaultExport => generateNamedExportDeclaration(jscodeshift, inlineDefaultExport, filePath)); | |
| // migrate default exports that just reference a variable | |
| source.find(jscodeshift.ExportDefaultDeclaration, {declaration: {type: 'Identifier'}}).forEach(defaultExport => { | |
| const constThatWasBeingDefaultExported = defaultExport.value.declaration.name; | |
| const filter = {id: {name: constThatWasBeingDefaultExported}}; | |
| // remove the old default export | |
| defaultExport.get().prune(); | |
| // check if it's already being exported as a const | |
| if (source.find(jscodeshift.ExportNamedDeclaration, {declaration: {declarations: [filter]}}).length) { | |
| return; | |
| } | |
| // check if it's already being exported as a function or a class | |
| if (source.find(jscodeshift.ExportNamedDeclaration, {declaration: filter}).length) { | |
| return; | |
| } | |
| // update original declarations with export keyword | |
| const namedExportGenerator = original => jscodeshift.exportNamedDeclaration(original.node); | |
| source.find(jscodeshift.VariableDeclaration, {declarations: [filter]}).replaceWith(namedExportGenerator); | |
| source.find(jscodeshift.FunctionDeclaration, filter).replaceWith(namedExportGenerator); | |
| }); | |
| } | |
| function migrateExportDefaultAsStatements(source, jscodeshift) { | |
| // delete manual type exports since they'll be covered by next change in this chain | |
| source | |
| .find(jscodeshift.ExportNamedDeclaration, { | |
| exportKind: 'type', | |
| source: {type: 'StringLiteral'}, | |
| }) | |
| .forEach(typeExport => typeExport.prune()); | |
| // migrate "export { default as SomeName } from './thing'" to "export * from './thing'" | |
| source | |
| .find(jscodeshift.ExportNamedDeclaration, { | |
| specifiers: [{type: 'ExportSpecifier', local: {name: 'default'}}], | |
| }) | |
| .forEach(t => t.replace(jscodeshift.exportAllDeclaration(t.value.source, null))); | |
| } | |
| const filetypesToIgnore = ['.png', '.jpg', '.jpeg', '.md']; | |
| function migrateDefaultImports(source, jscodeshift) { | |
| source.find(jscodeshift.ImportDefaultSpecifier).forEach(defaultImport => { | |
| const importPath = defaultImport.parent.value.source.value; | |
| if (importPath.includes('./') && !filetypesToIgnore.some(filetype => importPath.endsWith(filetype))) { | |
| defaultImport.replace(jscodeshift.importSpecifier(defaultImport.value.local, null)); | |
| } | |
| }); | |
| } | |
| export default function transformer(file, api) { | |
| const {jscodeshift} = api; | |
| const source = jscodeshift.withParser(parser)(file.source); | |
| migrateDefaultExports(source, jscodeshift, file.path); | |
| migrateDefaultImports(source, jscodeshift); | |
| migrateExportDefaultAsStatements(source, jscodeshift); | |
| return source.toSource(); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment