Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/graphql-codegen-cli/src/codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
import { NoTypeDefinitionsFound } from '@graphql-tools/load';
import { DocumentNode, GraphQLError, GraphQLSchema } from 'graphql';
import { Listr, ListrTask } from 'listr2';
import { CodegenContext, ensureContext, shouldEmitLegacyCommonJSImports } from './config.js';
import { CodegenContext, ensureContext, shouldEmitLegacyCommonJSImports, shouldPreserveTSExtension } from './config.js';
import { getPluginByName } from './plugins.js';
import { getPresetByName } from './presets.js';
import { debugLog, printLogs } from './utils/debugging.js';
Expand Down Expand Up @@ -327,6 +327,7 @@ export async function executeCodegen(
? { value: outputFileTemplateConfig }
: outputFileTemplateConfig),
emitLegacyCommonJSImports: shouldEmitLegacyCommonJSImports(config),
preserveTSExtension: shouldPreserveTSExtension(config),
};

const documentTransforms = Array.isArray(outputConfig.documentTransforms)
Expand Down Expand Up @@ -379,6 +380,7 @@ export async function executeCodegen(
...outputArgs,
// @ts-expect-error todo: fix 'emitLegacyCommonJSImports' does not exist in type 'GenerateOptions'
emitLegacyCommonJSImports: shouldEmitLegacyCommonJSImports(config, outputArgs.filename),
preserveTSExtension: shouldPreserveTSExtension(config),
cache,
});
result.push({
Expand Down
9 changes: 9 additions & 0 deletions packages/graphql-codegen-cli/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export type YamlCliFlags = {
debug?: boolean;
ignoreNoDocuments?: boolean;
emitLegacyCommonJSImports?: boolean;
preserveTSExtension?: boolean;
};

export function generateSearchPlaces(moduleName: string) {
Expand Down Expand Up @@ -322,6 +323,10 @@ export function updateContextWithCliFlags(context: CodegenContext, cliFlags: Yam
config.emitLegacyCommonJSImports = cliFlags['emit-legacy-common-js-imports'] === true;
}

if (cliFlags['preserve-ts-extension'] !== undefined) {
config.preserveTSExtension = cliFlags['preserve-ts-extension'] === true;
}

if (cliFlags.project) {
context.useProject(cliFlags.project);
}
Expand Down Expand Up @@ -504,3 +509,7 @@ export function shouldEmitLegacyCommonJSImports(config: Types.Config): boolean {

return globalValue;
}

export function shouldPreserveTSExtension(config: Types.Config): boolean {
return config.preserveTSExtension === true;
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export interface ParsedConfig {
allowEnumStringTypes: boolean;
inlineFragmentTypes: InlineFragmentTypeOptions;
emitLegacyCommonJSImports: boolean;
preserveTSExtension: boolean;
printFieldsOnNewLines: boolean;
includeExternalFragments: boolean;
}
Expand Down Expand Up @@ -365,6 +366,11 @@ export interface RawConfig {
* Default it will be `true` this way it ensure that generated code works with [non-compliant bundlers](https://github.com/dotansimha/graphql-code-generator/issues/8065).
*/
emitLegacyCommonJSImports?: boolean;
/**
* @description A flag to preserve `.ts` extension to the output file. Default: `false`.
* @default false
*/
preserveTSExtension?: boolean;

/**
* @default false
Expand Down Expand Up @@ -410,6 +416,7 @@ export class BaseVisitor<TRawConfig extends RawConfig = RawConfig, TPluginConfig
inlineFragmentTypes: rawConfig.inlineFragmentTypes ?? 'inline',
emitLegacyCommonJSImports:
rawConfig.emitLegacyCommonJSImports === undefined ? true : !!rawConfig.emitLegacyCommonJSImports,
preserveTSExtension: !!rawConfig.preserveTSExtension,
extractAllFieldsToTypes: rawConfig.extractAllFieldsToTypes ?? false,
printFieldsOnNewLines: rawConfig.printFieldsOnNewLines ?? false,
includeExternalFragments: rawConfig.includeExternalFragments ?? false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,10 @@ export class ClientSideBaseVisitor<
return path;
}

if (this.config.preserveTSExtension && extension === '.ts') {
return path;
}

if (EXTENSIONS_TO_REMOVE.includes(extension)) {
return path.replace(/\.[^/.]+$/, '');
}
Expand Down Expand Up @@ -636,7 +640,9 @@ export class ClientSideBaseVisitor<
if (this._collectedOperations.length > 0) {
if (this.config.importDocumentNodeExternallyFrom === 'near-operation-file' && this._documents.length === 1) {
let documentPath = `./${this.clearExtension(basename(this._documents[0].location))}`;
if (!this.config.emitLegacyCommonJSImports) {
if (this.config.preserveTSExtension) {
documentPath += '.ts';
} else if (!this.config.emitLegacyCommonJSImports) {
documentPath += '.js';
}

Expand Down Expand Up @@ -674,6 +680,7 @@ export class ClientSideBaseVisitor<
),
},
emitLegacyCommonJSImports: this.config.emitLegacyCommonJSImports,
preserveTSExtension: this.config.preserveTSExtension,
})
)
.filter(fragmentImport => fragmentImport.outputPath !== fragmentImport.importSource.path);
Expand Down
15 changes: 12 additions & 3 deletions packages/plugins/other/visitor-plugin-common/src/imports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export type ImportDeclaration<T = string> = {
baseDir: string;
typesImport: boolean;
emitLegacyCommonJSImports: boolean;
preserveTSExtension: boolean;
};

export type ImportSource<T = string> = {
Expand Down Expand Up @@ -51,13 +52,21 @@ export function generateFragmentImportStatement(
}

export function generateImportStatement(statement: ImportDeclaration): string {
const { baseDir, importSource, outputPath, typesImport } = statement;
const { baseDir, importSource, outputPath, typesImport, preserveTSExtension } = statement;
const importPath = resolveImportPath(baseDir, outputPath, importSource.path);
const importNames = importSource.identifiers?.length
? `{ ${Array.from(new Set(importSource.identifiers)).join(', ')} }`
: '*';
const importExtension =
importPath.startsWith('/') || importPath.startsWith('.') ? (statement.emitLegacyCommonJSImports ? '' : '.js') : '';

let importExtension = '';
if (importPath.startsWith('/') || importPath.startsWith('.')) {
if (preserveTSExtension) {
importExtension = '.ts';
} else if (!statement.emitLegacyCommonJSImports) {
importExtension = '.js';
}
}

const importAlias = importSource.namespace ? ` as ${importSource.namespace}` : '';
const importStatement = typesImport ? 'import type' : 'import';
return `${importStatement} ${importNames}${importAlias} from '${importPath}${importExtension}';${
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,76 @@ describe('getImports', () => {
expect(imports[0]).toBe(`import * as Operations from './${fileName}.js';`);
});
});

describe('when preserveTSExtension is true', () => {
it('appends `.ts` to Operations import path', () => {
const fileName = 'fooBarQuery';
const importPath = `src/queries/${fileName}`;

const document = parse(
`query fooBarQuery {
a {
foo
bar
}
}
`
);

const visitor = new ClientSideBaseVisitor(
schema,
[],
{
emitLegacyCommonJSImports: false,
preserveTSExtension: true,
importDocumentNodeExternallyFrom: 'near-operation-file',
documentMode: DocumentMode.external,
},
{},
[{ document, location: importPath }]
);

visitor.OperationDefinition(document.definitions[0] as OperationDefinitionNode);

const imports = visitor.getImports();
expect(imports[0]).toBe(`import * as Operations from './${fileName}.ts';`);
});
});

describe('when preserveTSExtension is false and emitLegacyCommonJSImports is true', () => {
it('does not append extension to Operations import path', () => {
const fileName = 'fooBarQuery';
const importPath = `src/queries/${fileName}`;

const document = parse(
`query fooBarQuery {
a {
foo
bar
}
}
`
);

const visitor = new ClientSideBaseVisitor(
schema,
[],
{
emitLegacyCommonJSImports: true,
preserveTSExtension: false,
importDocumentNodeExternallyFrom: 'near-operation-file',
documentMode: DocumentMode.external,
},
{},
[{ document, location: importPath }]
);

visitor.OperationDefinition(document.definitions[0] as OperationDefinitionNode);

const imports = visitor.getImports();
expect(imports[0]).toBe(`import * as Operations from './${fileName}';`);
});
});
});

describe('when documentMode "external", importDocumentNodeExternallyFrom is relative path', () => {
Expand Down Expand Up @@ -186,6 +256,7 @@ describe('getImports', () => {
],
},
emitLegacyCommonJSImports: true,
preserveTSExtension: false,
typesImport: false,
},
],
Expand Down Expand Up @@ -259,6 +330,7 @@ describe('getImports', () => {
],
},
emitLegacyCommonJSImports: true,
preserveTSExtension: false,
typesImport: false,
},
],
Expand Down
12 changes: 10 additions & 2 deletions packages/presets/client/src/fragment-masking-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,19 @@ export const plugin: PluginFunction<{
augmentedModuleName?: string;
unmaskFunctionName?: string;
emitLegacyCommonJSImports?: boolean;
preserveTSExtension?: boolean;
isStringDocumentMode?: boolean;
}> = (
_,
__,
{ useTypeImports, augmentedModuleName, unmaskFunctionName, emitLegacyCommonJSImports, isStringDocumentMode },
{
useTypeImports,
augmentedModuleName,
unmaskFunctionName,
emitLegacyCommonJSImports,
preserveTSExtension,
isStringDocumentMode,
},
_info
) => {
const documentNodeImport = `${useTypeImports ? 'import type' : 'import'} { ResultOf, DocumentTypeDecoration${
Expand All @@ -141,7 +149,7 @@ export const plugin: PluginFunction<{

const deferFragmentHelperImports = `${useTypeImports ? 'import type' : 'import'} { Incremental${
isStringDocumentMode ? ', TypedDocumentString' : ''
} } from './graphql${emitLegacyCommonJSImports ? '' : '.js'}';\n`;
} } from './graphql${preserveTSExtension ? '.ts' : emitLegacyCommonJSImports ? '' : '.js'}';\n`;

const fragmentDefinitionNodeImport = isStringDocumentMode
? ''
Expand Down
7 changes: 6 additions & 1 deletion packages/presets/client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@ export const preset: Types.OutputPreset<ClientPresetConfig> = {
useTypeImports: options.config.useTypeImports,
unmaskFunctionName: fragmentMaskingConfig.unmaskFunctionName,
emitLegacyCommonJSImports: options.config.emitLegacyCommonJSImports,
preserveTSExtension: options.config.preserveTSExtension,
isStringDocumentMode: options.config.documentMode === DocumentMode.string,
},
documents: [],
Expand All @@ -282,7 +283,11 @@ export const preset: Types.OutputPreset<ClientPresetConfig> = {

let indexFileGenerateConfig: Types.GenerateOptions | null = null;

const reexportsExtension = options.config.emitLegacyCommonJSImports ? '' : '.js';
const reexportsExtension = options.config.preserveTSExtension
? '.ts'
: options.config.emitLegacyCommonJSImports
? ''
: '.js';

if (reexports.length) {
indexFileGenerateConfig = {
Expand Down
4 changes: 2 additions & 2 deletions packages/presets/graphql-modules/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ export const preset: Types.OutputPreset<ModulesConfig> = {

const baseTypesFilename = baseTypesPath.replace(
/\.(js|ts|d.ts)$/,
// we need extension if ESM modules are used
options.config.emitLegacyCommonJSImports ? '' : '.js'
// we need extension if ESM modules are used or if preserving TS extension
options.config.preserveTSExtension ? '.ts' : options.config.emitLegacyCommonJSImports ? '' : '.js'
);
const baseTypesDir = stripFilename(baseOutput.filename);

Expand Down
4 changes: 4 additions & 0 deletions packages/utils/plugins-helpers/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,10 @@ export namespace Types {
* @description A flag to disable adding `.js` extension to the output file. Default: `true`.
*/
emitLegacyCommonJSImports?: boolean;
/**
* @description A flag to preserve `.ts` extension to the output file. Default: `false`.
*/
preserveTSExtension?: boolean;
/**
* @description A flag to suppress printing errors when they occur.
*/
Expand Down
24 changes: 24 additions & 0 deletions website/public/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@
"description": "A flag to disable adding `.js` extension to the output file. Default: `true`.",
"type": "boolean"
},
"preserveTSExtension": {
"description": "A flag to preserve `.ts` extension to the output file. Default: `false`.",
"type": "boolean"
},
"silent": { "description": "A flag to suppress printing errors when they occur.", "type": "boolean" },
"verbose": { "description": "A flag to output more detailed information about tasks", "type": "boolean" },
"debug": { "description": "A flag to output debug logs", "type": "boolean" },
Expand Down Expand Up @@ -656,6 +660,10 @@
"description": "Emit legacy common js imports.\nDefault it will be `true` this way it ensure that generated code works with [non-compliant bundlers](https://github.com/dotansimha/graphql-code-generator/issues/8065).\nDefault value: \"true\"",
"type": "boolean"
},
"preserveTSExtension": {
"description": "A flag to preserve `.ts` extension to the output file. Default: `false`.\nDefault value: \"false\"",
"type": "boolean"
},
"extractAllFieldsToTypes": {
"description": "Extract all field types to their own types, instead of inlining them.\nThis helps to reduce type duplication, and makes type errors more readable.\nIt can also significantly reduce the size of the generated code, the generation time,\nand the typechecking time.\nDefault value: \"false\"",
"type": "boolean"
Expand Down Expand Up @@ -849,6 +857,10 @@
"description": "Emit legacy common js imports.\nDefault it will be `true` this way it ensure that generated code works with [non-compliant bundlers](https://github.com/dotansimha/graphql-code-generator/issues/8065).\nDefault value: \"true\"",
"type": "boolean"
},
"preserveTSExtension": {
"description": "A flag to preserve `.ts` extension to the output file. Default: `false`.\nDefault value: \"false\"",
"type": "boolean"
},
"extractAllFieldsToTypes": {
"description": "Extract all field types to their own types, instead of inlining them.\nThis helps to reduce type duplication, and makes type errors more readable.\nIt can also significantly reduce the size of the generated code, the generation time,\nand the typechecking time.\nDefault value: \"false\"",
"type": "boolean"
Expand Down Expand Up @@ -1626,6 +1638,10 @@
"type": "boolean",
"description": "If true, recursively goes through all object type's fields, checks if they have abstract types and generates expected types correctly.\nThis may not work for cases where provided default mapper types are also nested e.g. `defaultMapper: DeepPartial<{T}>` or `defaultMapper: Partial<{T}>`.\nDefault value: \"false\""
},
"addInterfaceFieldResolverTypes": {
"description": "If true, add field resolver types to Interfaces.\nBy default, GraphQL Interfaces do not trigger any field resolvers,\nmeaning every implementing type must implement the same resolver for the shared fields.\n\nSome tools provide a way to change the default behaviour by making GraphQL Objects inherit\nmissing resolvers from their Interface types. In these cases, it is fine to turn this option to true.\n\nFor example, if you are using `@graphql-tools/schema#makeExecutableSchema` with `inheritResolversFromInterfaces: true`,\nyou can make `addInterfaceFieldResolverTypes: true` as well\nhttps://the-guild.dev/graphql/tools/docs/generate-schema#makeexecutableschema\nDefault value: \"false\"",
"type": "boolean"
},
"strictScalars": {
"description": "Makes scalars strict.\n\nIf scalars are found in the schema that are not defined in `scalars`\nan error will be thrown during codegen.\nDefault value: \"false\"",
"type": "boolean"
Expand Down Expand Up @@ -1664,6 +1680,10 @@
"description": "Emit legacy common js imports.\nDefault it will be `true` this way it ensure that generated code works with [non-compliant bundlers](https://github.com/dotansimha/graphql-code-generator/issues/8065).\nDefault value: \"true\"",
"type": "boolean"
},
"preserveTSExtension": {
"description": "A flag to preserve `.ts` extension to the output file. Default: `false`.\nDefault value: \"false\"",
"type": "boolean"
},
"extractAllFieldsToTypes": {
"description": "Extract all field types to their own types, instead of inlining them.\nThis helps to reduce type duplication, and makes type errors more readable.\nIt can also significantly reduce the size of the generated code, the generation time,\nand the typechecking time.\nDefault value: \"false\"",
"type": "boolean"
Expand Down Expand Up @@ -2789,6 +2809,10 @@
"description": "Emit legacy common js imports.\nDefault it will be `true` this way it ensure that generated code works with [non-compliant bundlers](https://github.com/dotansimha/graphql-code-generator/issues/8065).\nDefault value: \"true\"",
"type": "boolean"
},
"preserveTSExtension": {
"description": "A flag to preserve `.ts` extension to the output file. Default: `false`.\nDefault value: \"false\"",
"type": "boolean"
},
"extractAllFieldsToTypes": {
"description": "Extract all field types to their own types, instead of inlining them.\nThis helps to reduce type duplication, and makes type errors more readable.\nIt can also significantly reduce the size of the generated code, the generation time,\nand the typechecking time.\nDefault value: \"false\"",
"type": "boolean"
Expand Down