diff --git a/packages/rtk-query-codegen-openapi/src/generate.ts b/packages/rtk-query-codegen-openapi/src/generate.ts index 44d8a31b58..789457e9ef 100644 --- a/packages/rtk-query-codegen-openapi/src/generate.ts +++ b/packages/rtk-query-codegen-openapi/src/generate.ts @@ -118,6 +118,7 @@ export async function generateApi( mergeReadWriteOnly = false, httpResolverOptions, useUnknown = false, + esmExtensions = false, }: GenerationOptions ) { const v3Doc = (v3DocCache[spec] ??= await getV3Doc(spec, httpResolverOptions)); @@ -163,7 +164,17 @@ export async function generateApi( if (!apiFile.startsWith('.')) apiFile = `./${apiFile}`; } } - apiFile = apiFile.replace(/\.[jt]sx?$/, ''); + + if (esmExtensions === true) { + // Convert TS/JSX extensions to their JS equivalents + apiFile = apiFile + .replace(/\.mts$/, '.mjs') + .replace(/\.[jt]sx$/, '.jsx') + .replace(/\.ts$/, '.js'); + } else { + // Remove all extensions + apiFile = apiFile.replace(/\.[jt]sx?$/, ''); + } return printer.printNode( ts.EmitHint.Unspecified, @@ -451,11 +462,7 @@ export async function generateApi( const encodedValue = encodeQueryParams && param.param?.in === 'query' ? factory.createConditionalExpression( - factory.createBinaryExpression( - value, - ts.SyntaxKind.ExclamationEqualsToken, - factory.createNull() - ), + factory.createBinaryExpression(value, ts.SyntaxKind.ExclamationEqualsToken, factory.createNull()), undefined, factory.createCallExpression(factory.createIdentifier('encodeURIComponent'), undefined, [ factory.createCallExpression(factory.createIdentifier('String'), undefined, [value]), diff --git a/packages/rtk-query-codegen-openapi/src/types.ts b/packages/rtk-query-codegen-openapi/src/types.ts index 64473ba32f..f72dcece35 100644 --- a/packages/rtk-query-codegen-openapi/src/types.ts +++ b/packages/rtk-query-codegen-openapi/src/types.ts @@ -121,6 +121,11 @@ export interface CommonOptions { * @since 2.1.0 */ useUnknown?: boolean; + /** + * @default false + * Will generate imports with file extension matching the expected compiled output of the api file + */ + esmExtensions?: boolean; } export type TextMatcher = string | RegExp | (string | RegExp)[]; diff --git a/packages/rtk-query-codegen-openapi/test/generateEndpoints.test.ts b/packages/rtk-query-codegen-openapi/test/generateEndpoints.test.ts index e542f161f6..bb823e913e 100644 --- a/packages/rtk-query-codegen-openapi/test/generateEndpoints.test.ts +++ b/packages/rtk-query-codegen-openapi/test/generateEndpoints.test.ts @@ -647,3 +647,86 @@ describe('query parameters', () => { expect(api).toMatchSnapshot(); }); }); + +describe('esmExtensions option', () => { + beforeAll(async () => { + if (!(await isDir(tmpDir))) { + await fs.mkdir(tmpDir, { recursive: true }); + } + }); + + afterEach(async () => { + await rimraf(`${tmpDir}/*.ts`, { glob: true }); + }); + + test('should convert .ts to .js when esmExtensions is true', async () => { + await generateEndpoints({ + apiFile: './fixtures/emptyApi.ts', + outputFile: './test/tmp/out.ts', + schemaFile: resolve(__dirname, 'fixtures/petstore.json'), + filterEndpoints: [], + esmExtensions: true, + }); + const content = await fs.readFile('./test/tmp/out.ts', 'utf8'); + expect(content).toContain("import { api } from '../../fixtures/emptyApi.js'"); + }); + + test('should convert .mts to .mjs when esmExtensions is true', async () => { + await generateEndpoints({ + apiFile: './fixtures/emptyApi.mts', + outputFile: './test/tmp/out.ts', + schemaFile: resolve(__dirname, 'fixtures/petstore.json'), + filterEndpoints: [], + esmExtensions: true, + }); + const content = await fs.readFile('./test/tmp/out.ts', 'utf8'); + expect(content).toContain("import { api } from '../../fixtures/emptyApi.mjs'"); + }); + + test('should preserve .jsx when esmExtensions is true', async () => { + await generateEndpoints({ + apiFile: './fixtures/emptyApi.jsx', + outputFile: './test/tmp/out.ts', + schemaFile: resolve(__dirname, 'fixtures/petstore.json'), + filterEndpoints: [], + esmExtensions: true, + }); + const content = await fs.readFile('./test/tmp/out.ts', 'utf8'); + expect(content).toContain("import { api } from '../../fixtures/emptyApi.jsx'"); + }); + + test('should convert .tsx to .jsx when esmExtensions is true', async () => { + await generateEndpoints({ + apiFile: './fixtures/emptyApi.tsx', + outputFile: './test/tmp/out.ts', + schemaFile: resolve(__dirname, 'fixtures/petstore.json'), + filterEndpoints: [], + esmExtensions: true, + }); + const content = await fs.readFile('./test/tmp/out.ts', 'utf8'); + expect(content).toContain("import { api } from '../../fixtures/emptyApi.jsx'"); + }); + + test('should strip extensions when esmExtensions is false', async () => { + await generateEndpoints({ + apiFile: './fixtures/emptyApi.ts', + outputFile: './test/tmp/out.ts', + schemaFile: resolve(__dirname, 'fixtures/petstore.json'), + filterEndpoints: [], + esmExtensions: false, + }); + const content = await fs.readFile('./test/tmp/out.ts', 'utf8'); + expect(content).toContain("import { api } from '../../fixtures/emptyApi'"); + }); + + test('should strip extensions when esmExtensions is undefined (default)', async () => { + await generateEndpoints({ + apiFile: './fixtures/emptyApi.ts', + outputFile: './test/tmp/out.ts', + schemaFile: resolve(__dirname, 'fixtures/petstore.json'), + filterEndpoints: [], + }); + const content = await fs.readFile('./test/tmp/out.ts', 'utf8'); + expect(content).toContain("import { api } from '../../fixtures/emptyApi'"); + }); +});