diff --git a/.changeset/serious-lamps-change.md b/.changeset/serious-lamps-change.md new file mode 100644 index 00000000000..fda74d59ad1 --- /dev/null +++ b/.changeset/serious-lamps-change.md @@ -0,0 +1,5 @@ +--- +"@remix-run/dev": patch +--- + +Process named exports in `convert-to-javascript` migration diff --git a/packages/remix-dev/__tests__/migrations/convert-to-javascript-test.ts b/packages/remix-dev/__tests__/migrations/convert-to-javascript-test.ts index 942e2eba6ef..8dbe7384b2a 100644 --- a/packages/remix-dev/__tests__/migrations/convert-to-javascript-test.ts +++ b/packages/remix-dev/__tests__/migrations/convert-to-javascript-test.ts @@ -109,12 +109,22 @@ const checkMigrationRanSuccessfully = async (projectDir: string) => { expect(exportDefaultResult.stdout.trim()).toBe(""); expect(exportDefaultResult.stderr).toBeNull(); expect(exportDefaultResult.code).toBe(0); - + let exportClassResult = shell.grep("-l", 'export class "', JSFiles); + expect(exportClassResult.stdout.trim()).toBe(""); + expect(exportClassResult.stderr).toBeNull(); + expect(exportClassResult.code).toBe(0); + let exportConstResult = shell.grep("-l", 'export const "', JSFiles); + expect(exportConstResult.stdout.trim()).toBe(""); + expect(exportConstResult.stderr).toBeNull(); + expect(exportConstResult.code).toBe(0); + let exportFunctionResult = shell.grep("-l", 'export function "', JSFiles); + expect(exportFunctionResult.stdout.trim()).toBe(""); + expect(exportFunctionResult.stderr).toBeNull(); + expect(exportFunctionResult.code).toBe(0); let rootRouteContent = await readFile( path.join(projectDir, "app", "root.jsx"), "utf-8" ); - expect(rootRouteContent).not.toContain('require("@remix-run/react")'); }; diff --git a/packages/remix-dev/cli/migrate/migrations/convert-to-javascript/transform/createExportExpressionStatementFromExportDefaultDeclaration.ts b/packages/remix-dev/cli/migrate/migrations/convert-to-javascript/transform/createExportExpressionStatementFromExportDefaultDeclaration.ts index 32e84e717de..60a0194c725 100644 --- a/packages/remix-dev/cli/migrate/migrations/convert-to-javascript/transform/createExportExpressionStatementFromExportDefaultDeclaration.ts +++ b/packages/remix-dev/cli/migrate/migrations/convert-to-javascript/transform/createExportExpressionStatementFromExportDefaultDeclaration.ts @@ -35,7 +35,6 @@ export const createExportExpressionStatementFromExportDefaultDeclaration = ( exportDefaultDeclaration.declaration.type === "ExportDeclaration" || exportDefaultDeclaration.declaration.type === "ExportDefaultDeclaration" || exportDefaultDeclaration.declaration.type === "ExportNamedDeclaration" || - exportDefaultDeclaration.declaration.type === "FunctionDeclaration" || exportDefaultDeclaration.declaration.type === "ImportDeclaration" || exportDefaultDeclaration.declaration.type === "InterfaceDeclaration" || exportDefaultDeclaration.declaration.type === "MethodDefinition" || @@ -68,9 +67,9 @@ export const createExportExpressionStatementFromExportDefaultDeclaration = ( let expressionKind = exportDefaultDeclaration.declaration.type === "ClassDeclaration" ? j.classExpression.from(exportDefaultDeclaration.declaration) - : // : exportDefaultDeclaration.declaration.type === "FunctionDeclaration" - // ? j.functionExpression.from(exportDefaultDeclaration.declaration) - exportDefaultDeclaration.declaration; + : exportDefaultDeclaration.declaration.type === "FunctionDeclaration" + ? j.functionExpression.from(exportDefaultDeclaration.declaration) + : exportDefaultDeclaration.declaration; return j.expressionStatement( j.assignmentExpression( "=", diff --git a/packages/remix-dev/cli/migrate/migrations/convert-to-javascript/transform/createExportExpressionStatementFromExportNamedDeclaration.ts b/packages/remix-dev/cli/migrate/migrations/convert-to-javascript/transform/createExportExpressionStatementFromExportNamedDeclaration.ts new file mode 100644 index 00000000000..75c9b61d6d3 --- /dev/null +++ b/packages/remix-dev/cli/migrate/migrations/convert-to-javascript/transform/createExportExpressionStatementFromExportNamedDeclaration.ts @@ -0,0 +1,149 @@ +import type { + ClassDeclaration, + ExportNamedDeclaration, + FunctionDeclaration, + JSCodeshift, + VariableDeclaration, +} from "jscodeshift"; + +/** + * export class Foo {} + * export const foo = bar + * export function foo() {} + * => + * module.Foo = class Foo {} + * module.foo = bar + * module.foo = function foo() {} + */ +export const createExportExpressionStatementFromExportNamedDeclaration = ( + j: JSCodeshift, + exportNamedDeclaration: ExportNamedDeclaration +) => { + /** + * HACK: Can't use casts nor type guards in a `jscodeshift` transform + * https://github.com/facebook/jscodeshift/issues/467 + * + * So to narrow declaration type, we check it against convertable values + * instead. + */ + if ( + !( + exportNamedDeclaration.declaration?.type === "ClassDeclaration" || + exportNamedDeclaration.declaration?.type === "FunctionDeclaration" || + exportNamedDeclaration.declaration?.type === "VariableDeclaration" + ) + ) { + return exportNamedDeclaration; + } + + // export class Foo {} + if (exportNamedDeclaration.declaration.type === "ClassDeclaration") { + return createExportExpressionStatementFromExportNamedClassDeclaration( + j, + exportNamedDeclaration.declaration + ); + } + + // export function foo() {} + if (exportNamedDeclaration.declaration.type === "FunctionDeclaration") { + return createExportExpressionStatementFromExportNamedFunctionDeclaration( + j, + exportNamedDeclaration.declaration + ); + } + + // export const foo = bar + if (exportNamedDeclaration.declaration.type === "VariableDeclaration") { + return createExportExpressionStatementFromExportNamedVariableDeclaration( + j, + exportNamedDeclaration.declaration + ); + } +}; + +/** + * export class Foo {} + * => + * module.Foo = class Foo {} + */ +const createExportExpressionStatementFromExportNamedClassDeclaration = ( + j: JSCodeshift, + classDeclaration: ClassDeclaration +) => + j.expressionStatement( + j.assignmentExpression( + "=", + j.memberExpression( + j.identifier("module"), + classDeclaration.id || j.identifier("") + ), + j.classExpression.from(classDeclaration) + ) + ); + +/** + * export function foo() {} + * => + * module.foo = function foo() {} + */ +const createExportExpressionStatementFromExportNamedFunctionDeclaration = ( + j: JSCodeshift, + functionDeclaration: FunctionDeclaration +) => + j.expressionStatement( + j.assignmentExpression( + "=", + j.memberExpression( + j.identifier("module"), + functionDeclaration.id || j.identifier("") + ), + j.functionExpression.from(functionDeclaration) + ) + ); + +/** + * export const foo = bar + * export const foo = 5 + * export const foo = [] + * export const foo = function foo(){} + * => + * module.foo = bar + * module.foo = 5 + * module.foo = [] + * module.foo = function foo(){} + */ +const createExportExpressionStatementFromExportNamedVariableDeclaration = ( + j: JSCodeshift, + variableDeclaration: VariableDeclaration +) => + variableDeclaration.declarations.flatMap((declaration) => { + /** + * HACK: Can't use casts nor type guards in a `jscodeshift` transform + * https://github.com/facebook/jscodeshift/issues/467 + * + * So to narrow declaration id type, we check it against convertable values + * instead. + */ + if ( + declaration.type !== "VariableDeclarator" || + declaration.id.type === "ArrayPattern" || + declaration.id.type === "AssignmentPattern" || + declaration.id.type === "ObjectPattern" || + declaration.id.type === "PropertyPattern" || + declaration.id.type === "RestElement" || + declaration.id.type === "SpreadElementPattern" || + declaration.id.type === "SpreadPropertyPattern" || + declaration.id.type === "TSParameterProperty" || + !declaration.init + ) { + return []; + } + + return j.expressionStatement( + j.assignmentExpression( + "=", + j.memberExpression(j.identifier("module"), declaration.id), + declaration.init + ) + ); + }); diff --git a/packages/remix-dev/cli/migrate/migrations/convert-to-javascript/transform/index.ts b/packages/remix-dev/cli/migrate/migrations/convert-to-javascript/transform/index.ts index d2f85d2243a..5b2d18bcd83 100644 --- a/packages/remix-dev/cli/migrate/migrations/convert-to-javascript/transform/index.ts +++ b/packages/remix-dev/cli/migrate/migrations/convert-to-javascript/transform/index.ts @@ -2,6 +2,7 @@ import type { Transform } from "jscodeshift"; import { checkNoDifferentImportTypesCombined } from "./checkNoDifferentImportTypesCombined"; import { createExportExpressionStatementFromExportDefaultDeclaration } from "./createExportExpressionStatementFromExportDefaultDeclaration"; +import { createExportExpressionStatementFromExportNamedDeclaration } from "./createExportExpressionStatementFromExportNamedDeclaration"; import { createImportExpressionStatement } from "./createImportExpressionStatement"; import { createVariableDeclarationIdentifier } from "./createVariableDeclarationIdentifier"; import { createVariableDeclarationObjectPattern } from "./createVariableDeclarationObjectPattern"; @@ -12,9 +13,11 @@ const transform: Transform = (file, api, options) => { let allImportDeclarations = root.find(j.ImportDeclaration); let allExportDefaultDeclarations = root.find(j.ExportDefaultDeclaration); + let allExportNamedDeclarations = root.find(j.ExportNamedDeclaration); if ( allImportDeclarations.length === 0 && - allExportDefaultDeclarations.length === 0 + allExportDefaultDeclarations.length === 0 && + allExportNamedDeclarations.length === 0 ) { // This transform doesn't need to run if there are no ES imports/exports return null; @@ -68,6 +71,16 @@ const transform: Transform = (file, api, options) => { ); }); + allExportNamedDeclarations.forEach((exportNamedDeclaration) => { + // export class Foo {} || export const foo = bar || export function foo() {} + j(exportNamedDeclaration).replaceWith( + createExportExpressionStatementFromExportNamedDeclaration( + j, + exportNamedDeclaration.node + ) + ); + }); + // If the first node has been modified or deleted, reattach the comments let newFirstNode = getFirstNode(); if (newFirstNode !== oldFirstNode) {