Skip to content

Commit

Permalink
feat: attach .js extension to all relative/absolute file imports/expo…
Browse files Browse the repository at this point in the history
…rts (#8034)

* fix: attach .js extension to all gql-tag-operation files

* feat: attach .js extension to all imports generated by near-operation-preset

* chore: add changesets

* chore: add test for esm import path

* fix: attach .js extension for relative import emitted by gql-tag-preset

* feat: handle oclif plugin
  • Loading branch information
n1ru4l committed Jul 6, 2022
1 parent 1de927e commit 68bb30e
Show file tree
Hide file tree
Showing 31 changed files with 111 additions and 57 deletions.
5 changes: 5 additions & 0 deletions .changeset/angry-elephants-learn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@graphql-codegen/visitor-plugin-common': minor
---

Attach `.js` extension to imports starting with either a `.` or `/` character.
14 changes: 14 additions & 0 deletions .changeset/fuzzy-elephants-ring.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
'@graphql-codegen/gql-tag-operations-preset': minor
'@graphql-codegen/near-operation-file-preset': minor
'@graphql-codegen/typescript-oclif': minor
---

Attach `.js` extension to relative file imports for compliance with ESM module resolution. Since in CommonJS the `.js` extension is optional, this is not a breaking change.

If you have path configuration within your configuration, consider attaching `.js` if you are migrating to ESM.

```yml
mappers:
MyOtherType: './my-file.js#MyCustomOtherType',
```

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions dev-test/gql-tag-operations-masking-star-wars/gql/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export * from './gql';
export * from './fragment-masking';
export * from './gql.js';
export * from './fragment-masking.js';
4 changes: 2 additions & 2 deletions dev-test/gql-tag-operations-masking/gql/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export * from './gql';
export * from './fragment-masking';
export * from './gql.js';
export * from './fragment-masking.js';
6 changes: 3 additions & 3 deletions dev-test/gql-tag-operations-urql/gql/gql.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/
declare module '@urql/core' {
export function gql(
source: '\n query Foo {\n Tweets {\n id\n }\n }\n'
): typeof import('./graphql').FooDocument;
): typeof import('./graphql.js').FooDocument;
export function gql(
source: '\n fragment Lel on Tweet {\n id\n body\n }\n'
): typeof import('./graphql').LelFragmentDoc;
): typeof import('./graphql.js').LelFragmentDoc;
export function gql(
source: '\n query Bar {\n Tweets {\n ...Lel\n }\n }\n'
): typeof import('./graphql').BarDocument;
): typeof import('./graphql.js').BarDocument;
export function gql(source: string): unknown;

export type DocumentType<TDocumentNode extends DocumentNode<any, any>> = TDocumentNode extends DocumentNode<
Expand Down
2 changes: 1 addition & 1 deletion dev-test/gql-tag-operations/gql/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export * from './gql';
export * from './gql.js';

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dev-test/star-wars/__generated__/HeroAndFriendsNames.tsx

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dev-test/star-wars/__generated__/HeroAppearsIn.tsx

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dev-test/star-wars/__generated__/HeroDetails.tsx

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dev-test/star-wars/__generated__/HeroDetailsFragment.tsx

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions dev-test/star-wars/__generated__/HeroDetailsWithFragment.tsx

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dev-test/star-wars/__generated__/HeroName.tsx

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dev-test/star-wars/__generated__/HeroNameConditional.tsx

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dev-test/star-wars/__generated__/HumanFields.tsx

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions dev-test/star-wars/__generated__/HumanWithNullWeight.tsx

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dev-test/star-wars/__generated__/TwoHeroes.tsx

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion packages/plugins/other/visitor-plugin-common/src/imports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ export function generateImportStatement(statement: ImportDeclaration): string {
: '*';
const importAlias = importSource.namespace ? ` as ${importSource.namespace}` : '';
const importStatement = typesImport ? 'import type' : 'import';
return `${importStatement} ${importNames}${importAlias} from '${importPath}';${importAlias ? '\n' : ''}`;
const importExtension = importPath.startsWith('/') || importPath.startsWith('.') ? '.js' : '';
return `${importStatement} ${importNames}${importAlias} from '${importPath}${importExtension}';${
importAlias ? '\n' : ''
}`;
}

function resolveImportPath(baseDir: string, outputPath: string, sourcePath: string) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ function getGqlOverloadChunk(sourcesWithOperations: Array<SourceWithOperations>,
const returnType =
mode === 'lookup'
? `(typeof documents)[${JSON.stringify(originalString)}]`
: `typeof import('./graphql').${operations[0].initialName}`;
: `typeof import('./graphql.js').${operations[0].initialName}`;
lines.add(`export function gql(source: ${JSON.stringify(originalString)}): ${returnType};\n`);
}

Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/typescript/oclif/src/visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export class GraphQLRequestVisitor extends ClientSideBaseVisitor<Config, ClientS
super(schema, fragments, rawConfig, {});
this._info = info;

const { handlerPath = '../../handler' } = rawConfig;
const { handlerPath = '../../handler.js' } = rawConfig;

// FIXME: This is taken in part from
// presets/near-operation-file/src/index.ts:139. How do I build a path relative to the outputFile in the same way?
Expand Down
16 changes: 16 additions & 0 deletions packages/plugins/typescript/resolvers/tests/mapping.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,22 @@ describe('ResolversTypes', () => {
spy.mockRestore();
});

it('Should generate basic type resolvers with external mappers using a .js extension', async () => {
const result = (await plugin(
schema,
[],
{
noSchemaStitching: true,
mappers: {
MyOtherType: './my-file.js#MyCustomOtherType',
},
},
{ outputFile: '' }
)) as Types.ComplexPluginOutput;

expect(result.prepend).toContain(`import { MyCustomOtherType } from './my-file.js';`);
});

it('Should generate basic type resolvers with external mappers', async () => {
const result = (await plugin(
schema,
Expand Down
2 changes: 1 addition & 1 deletion packages/presets/gql-tag-operations/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ export const preset: Types.OutputPreset<GqlTagConfig> = {
},
plugins: [
{
[`add`]: { content: reexports.map(moduleName => `export * from "./${moduleName}"`).join('\n') },
[`add`]: { content: reexports.map(moduleName => `export * from "./${moduleName}.js"`).join('\n') },
},
],
schema: options.schema,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ describe('gql-tag-operations-preset', () => {
expect(result).toHaveLength(3);
// index.ts (re-exports)
const indexFile = result.find(file => file.filename === 'out1/index.ts');
expect(indexFile.content).toEqual('export * from "./gql"');
expect(indexFile.content).toEqual('export * from "./gql.js"');

// gql.ts
const gqlFile = result.find(file => file.filename === 'out1/gql.ts');
Expand Down Expand Up @@ -85,7 +85,7 @@ describe('gql-tag-operations-preset', () => {
expect(result).toHaveLength(3);
// index.ts (re-exports)
const indexFile = result.find(file => file.filename === 'out1/index.ts');
expect(indexFile.content).toEqual('export * from "./gql"');
expect(indexFile.content).toEqual('export * from "./gql.js"');

// gql.ts
const gqlFile = result.find(file => file.filename === 'out1/gql.ts');
Expand Down Expand Up @@ -367,9 +367,9 @@ describe('gql-tag-operations-preset', () => {

const gqlFile = result.find(file => file.filename === 'out1/index.ts');
expect(gqlFile.content).toMatchInlineSnapshot(`
"export * from \\"./gql\\"
export * from \\"./fragment-masking\\""
`);
"export * from \\"./gql.js\\"
export * from \\"./fragment-masking.js\\""
`);
const fragmentMaskingFile = result.find(file => file.filename === 'out1/fragment-masking.ts');
expect(fragmentMaskingFile.content).toMatchInlineSnapshot(`
"import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
Expand Down
16 changes: 16 additions & 0 deletions packages/presets/graphql-modules/tests/builder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,22 @@ test('should include import statement', () => {
`);
});

test('should include esm import statement', () => {
const output = buildModule('test', testDoc, {
importPath: '../types.js',
importNamespace: 'core',
encapsulate: 'none',
shouldDeclare: false,
rootTypes: ROOT_TYPES,
baseVisitor,
useGraphQLModules: true,
});

expect(output).toBeSimilarStringTo(`
import * as core from "../types.js";
`);
});

test('should work with naming conventions', () => {
const output = buildModule('test', parse(`type query_root { test: ID! } schema { query: query_root }`), {
importPath: '../types',
Expand Down

1 comment on commit 68bb30e

@vercel
Copy link

@vercel vercel bot commented on 68bb30e Jul 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.