Skip to content

Commit

Permalink
transform documents hooks (#8723)
Browse files Browse the repository at this point in the history
  • Loading branch information
kazekyo authored Feb 21, 2023
1 parent 4083f10 commit a3309e6
Show file tree
Hide file tree
Showing 15 changed files with 711 additions and 11 deletions.
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

0 comments on commit a3309e6

Please sign in to comment.