Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

transform documents hooks #8723

Merged
merged 18 commits into from
Feb 21, 2023
Merged
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
102 changes: 102 additions & 0 deletions .changeset/short-toes-relax.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
---
"@graphql-codegen/cli": minor
"@graphql-codegen/core": minor
"@graphql-codegen/plugin-helpers": minor
"@graphql-codegen/client-preset": minor
"@graphql-codegen/gql-tag-operations-preset": minor
"@graphql-codegen/graphql-modules-preset": minor
---

Introduce a new feature called DocumentTransform.

DocumentTransform is a functionality that allows you to modify `documents` before they are processed by plugins. You can use functions passed to the `documentTransforms` option to make changes to GraphQL documents.

To use this feature, you can write `documentTransforms` as follows:

```ts
import type { CodegenConfig } from '@graphql-codegen/cli';

const config: CodegenConfig = {
schema: 'https://localhost:4000/graphql',
documents: ['src/**/*.tsx'],
generates: {
'./src/gql/': {
preset: 'client',
documentTransforms: [
{
transform: ({ documents }) => {
// Make some changes to the documents
return documents;
},
},
],
},
},
};
export default config;
```

For instance, to remove a `@localOnlyDirective` directive from `documents`, you can write the following code:

```js
import type { CodegenConfig } from '@graphql-codegen/cli';
import { visit } from 'graphql';

const config: CodegenConfig = {
schema: 'https://localhost:4000/graphql',
documents: ['src/**/*.tsx'],
generates: {
'./src/gql/': {
preset: 'client',
documentTransforms: [
{
transform: ({ documents }) => {
return documents.map(documentFile => {
documentFile.document = visit(documentFile.document, {
Directive: {
leave(node) {
if (node.name.value === 'localOnlyDirective') return null;
},
},
});
return documentFile;
});
},
},
],
},
},
};
export default config;
```

DocumentTransform can also be specified by file name. You can create a custom file for a specific transformation and pass it to `documentTransforms`.

Let's create the document transform as a file:

```js
module.exports = {
transform: ({ documents }) => {
// Make some changes to the documents
return documents;
},
};
```

Then, you can specify the file name as follows:

```ts
import type { CodegenConfig } from '@graphql-codegen/cli';

const config: CodegenConfig = {
schema: 'https://localhost:4000/graphql',
documents: ['src/**/*.tsx'],
generates: {
'./src/gql/': {
preset: 'client',
documentTransforms: ['./my-document-transform.js'],
},
},
};
export default config;
```
15 changes: 15 additions & 0 deletions packages/graphql-codegen-cli/src/codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { CodegenContext, ensureContext, shouldEmitLegacyCommonJSImports } from '
import { getPluginByName } from './plugins.js';
import { getPresetByName } from './presets.js';
import { debugLog, printLogs } from './utils/debugging.js';
import { getDocumentTransform } from './documentTransforms.js';

/**
* Poor mans ESM detection.
Expand Down Expand Up @@ -316,6 +317,18 @@ export async function executeCodegen(input: CodegenContext | Types.Config): Prom
emitLegacyCommonJSImports: shouldEmitLegacyCommonJSImports(config),
};

const documentTransforms = Array.isArray(outputConfig.documentTransforms)
? await Promise.all(
outputConfig.documentTransforms.map(async (config, index) => {
return await getDocumentTransform(
config,
makeDefaultLoader(context.cwd),
`the element at index ${index} of the documentTransforms`
);
})
)
: [];

const outputs: Types.GenerateOptions[] = preset
? await context.profiler.run(
async () =>
Expand All @@ -330,6 +343,7 @@ export async function executeCodegen(input: CodegenContext | Types.Config): Prom
pluginMap,
pluginContext,
profiler: context.profiler,
documentTransforms,
}),
`Build Generates Section: ${filename}`
)
Expand All @@ -344,6 +358,7 @@ export async function executeCodegen(input: CodegenContext | Types.Config): Prom
pluginMap,
pluginContext,
profiler: context.profiler,
documentTransforms,
},
];

Expand Down
82 changes: 82 additions & 0 deletions packages/graphql-codegen-cli/src/documentTransforms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { resolve } from 'path';
import { Types } from '@graphql-codegen/plugin-helpers';

export async function getDocumentTransform(
documentTransform: Types.OutputDocumentTransform,
loader: Types.PackageLoaderFn<Types.DocumentTransformObject>,
defaultName: string
): Promise<Types.ConfiguredDocumentTransform> {
if (typeof documentTransform === 'string') {
const transformObject = await getDocumentTransformByName(documentTransform, loader);
return { name: documentTransform, transformObject };
}
if (isTransformObject(documentTransform)) {
return { name: defaultName, transformObject: documentTransform };
}
if (isTransformFileConfig(documentTransform)) {
const name = Object.keys(documentTransform)[0];
const transformObject = await getDocumentTransformByName(name, loader);
return { name, transformObject, config: Object.values(documentTransform)[0] };
}
throw new Error(
`
An unknown format document transform: '${defaultName}'.
`
);
}

function isTransformObject(config: Types.OutputDocumentTransform): config is Types.DocumentTransformObject {
return typeof config === 'object' && config.transform && typeof config.transform === 'function';
}

function isTransformFileConfig(config: Types.OutputDocumentTransform): config is Types.DocumentTransformFileConfig {
const keys = Object.keys(config);
return keys.length === 1 && typeof keys[0] === 'string';
}

export async function getDocumentTransformByName(
name: string,
loader: Types.PackageLoaderFn<Types.DocumentTransformObject>
): Promise<Types.DocumentTransformObject> {
const possibleNames = [
`@graphql-codegen/${name}`,
`@graphql-codegen/${name}-document-transform`,
name,
resolve(process.cwd(), name),
];

const possibleModules = possibleNames.concat(resolve(process.cwd(), name));

for (const moduleName of possibleModules) {
try {
return await loader(moduleName);
} catch (err) {
if (err.code !== 'MODULE_NOT_FOUND' && err.code !== 'ERR_MODULE_NOT_FOUND') {
throw new Error(
`
Unable to load document transform matching '${name}'.
Reason:
${err.message}
`
);
}
}
}

const possibleNamesMsg = possibleNames
.map(name =>
`
- ${name}
`.trimEnd()
)
.join('');

throw new Error(
`
Unable to find document transform matching '${name}'
Install one of the following packages:

${possibleNamesMsg}
`
);
}
Loading