apollographql / graphql-tag

A JavaScript template literal tag that parses GraphQL queries
MIT License
2.32k stars 177 forks source link

Typescript: import fail: Cannot find module 'query.gql' #59

Closed estaub closed 6 years ago

estaub commented 7 years ago

This is a reopen of https://github.com/apollographql/graphql-tag/issues/42 .

Nothing has changed, it's just that the presence of a workaround is not a fix.

As described before, attempts to import a graphql module result in Cannot find module.... Using require instead of import, eg:

const query = require('gql/Query.gql');

works fine... up to a point. Somewhere, the Apollo graphql decorator performs a deep copy of the query The query DocumentNode returned above includes a default element that has itself as a property. As a result, the deep copy blows out with a soft stack overflow. So the full workaround is:

const query = require('gql/Query.gql'); delete query['default']

It's possible that the default problem is dependent on the tsconfig.json setting:

"allowSyntheticDefaultImports": true

I need it for other reasons, and was unable to easily test with it turned off.

gungorkocak commented 7 years ago

I am also having the same issue. I have also tried workarounds mentioned in #42 and here #59 . Also tried adding custom module declaration for gql files and playing around with alternatives.

Workarounds and outputs are like these:

  1. Naive approach: Try direct import query.gql
import query from './query.gql'

/* =>

Ts output: `error TS2307: Cannot find module './query.gql'.`
Browser console output: -

*/
  1. Add wildcard custom declaration to index.t.ds
// index.d.ts
declare module "*.gql" {
  const content: any;
  export default content;
}

// component.tsx
import query from './query.gql'

/* =>

Ts output: Fine / No error
Browser console output: browser.js:40 Uncaught Error: Argument of undefined passed to parser was not a valid GraphQL DocumentNode. You may need to use 'graphql-tag' or another method to convert your operation into a document

*/
  1. Try with import = require syntax.
// index.d.ts
// // ...same as 2

// component.tsx
import query = require('./query.gql')

/* =>

Ts output: `error TS2345: Argument of type 'typeof "*.gql"' is not assignable to parameter of type 'DocumentNode'.
  Property 'kind' is missing in type 'typeof "*.gql"'.`
Browser console output: Fine / No error (actually works)

*/
  1. Try adding type declaration DocumentNode from graphql typings.
// index.d.ts
import {
  DocumentNode
} from 'graphql'

declare module "*.gql" {
  const content: DocumentNode;
  export default content;
}

// component.tsx
import query = require('./query.gql')

/* =>

Ts output: `error TS2307: Cannot find module './query.gql'.`
Browser output: -

*/

I can live with typescript error Argument of type 'typeof "*.gql"' is not assignable since it does not break actual usage, but it would be great to fix that in some way.

I have tried both "allowSyntheticDefaultImports": true and delete query['default'] workarounds, could not get an errorless flow for both typescript and browser usage.

ddetkovs commented 7 years ago

I found that this works:

// graphql.d.ts file
declare module '*.graphql' {
    import {DocumentNode} from 'graphql';

    const value: DocumentNode;
    export = value;
}

then, when I need to import *.graphql

/// <reference path="./graphql.d.ts" />
import schema = require('./schema.graphql');
ScallyGames commented 7 years ago

Changing the solution by @ddetkovs to

// graphql.d.ts file
declare module '*.graphql' {
    import {DocumentNode} from 'graphql';

    const value: DocumentNode;
    export default value;
}
/// <reference path="./graphql.d.ts" />
import schema from './schema.graphql';

works for ES2015 import.

ddetkovs commented 7 years ago

@Aides359 graphql loader exports schema like this: module.exports = doc;

This module is then resolved with webpack and since you're using es2015 import syntax it tries to access its default member, which is undefined, thus causing a runtime error.

I get the following generated code:

var graphql_tools_1 = __webpack_require__(6);
var schema_graphql_1 = __webpack_require__(4);
var resolvers_1 = __webpack_require__(1);
exports.default = graphql_tools_1.makeExecutableSchema({ typeDefs: [schema_graphql_1.default], resolvers: resolvers_1.default });

Also, typescript handbook says this:

When importing a module using export =, TypeScript-specific import module = require("module") must be used to import the module.

http://www.typescriptlang.org/docs/handbook/modules.html

ScallyGames commented 7 years ago

@ddetkovs odd, my described setup works in my project, without any runtime errors :thinking: I'll check the generated code and see how it behaves, however I probably won't find time to do so before next monday.

jasonzoladz commented 7 years ago

Any updates on this?

jnwng commented 7 years ago

unfortunately i dont know enough about typescript to know what the solution space for this issue might be like. if there's any chance someone can send me a repo (or just some code) that exhibits exactly this issue, i can look into figuring out how to resolve this

ScallyGames commented 7 years ago

@ddetkovs this fell of my radar, however I have now taken a look at my generated output.

While I don't know about makeExecutableSchema as I am currently only using it to write queries for me the generated code is

/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__request_foo_graphql__ = __webpack_require__(5);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__request_foo_graphql___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_4__request_foo_graphql__);

apollo.watchQuery({
   query:  __WEBPACK_IMPORTED_MODULE_4__request_foo_graphql___default
})

which works just fine.

ddetkovs commented 7 years ago

@Aides359 what I meant is when graphql-tag/loader compiles your query, it exports it as: module.exports = doc here

when you do: import schema from './schema.graphql';, you import default property of the module, but exported doc doesn't have it, so it fails.

This works too: import * as schema from './schema.graphql';

buggy commented 7 years ago

This solution works for me. It's similar to what @ddetkovs posted earlier but allows you to use the import syntax and doesn't require using /// <reference path="..." />

All of my .graphql files are in a single folder so I could add an index.d.ts in that folder with the following

declare module '*.graphql' {
    import {DocumentNode} from 'graphql';

    const value: DocumentNode;
    export = value;
}

Having all of the .graphql files in a single directory and naming it index.d.ts avoids the need to add /// <reference path="./graphql.d.ts" /> in other files.

I can then import using import * as MyQuery from "../graphql/MyQuery.graphql"

Thanks to @ddetkovs, @Aides359

gustavoalfaro commented 6 years ago

Any updates here?

nitingupta910 commented 6 years ago

@gustavoalfaro Solution by @Aides359 works for me.

gustavoalfaro commented 6 years ago

@nitingupta910 Changing this: import MyQuery from "../graphql/MyQuery.graphql" for this: import * as MyQuery from "../graphql/MyQuery.graphql" doesn't work for me

estaub commented 6 years ago

@gustavoalfaro

Did you include the declare module '*.graphql'... declaration suggested above? I figure that with 20 up-votes, it's probably worth a shot!

What error do you see?

rajeshkumarcc commented 6 years ago

Facing the same issue. Any update on this?

jnwng commented 6 years ago

again, unfortunately i dont know enough about typescript in order to figure out what i can do here. please open a PR to provide better documentation about TypeScript usage if that is the general consensus on how to avoid other folks getting hit by this.

closing for now, but please re-open if there's more to discuss.

suciuvlad commented 6 years ago

+1

eug48 commented 6 years ago

Could anyone having issues with this please post the error message they're getting? I had trouble implementing the above solution because I didn't have the import { DocumentNode } from "graphql" inside the declare module..

Falieson commented 6 years ago

Could someone upload a reproducible example of it working? I am getting this error:

[server] Module build failed (from ./node_modules/graphql-tag/loader.js):
[server] GraphQLError: Syntax Error: Unexpected Name "module"
// Module.tsx
/// <reference path="../../types/graphql.d.ts" />
import getStatus from './NetworkQueries.graphql'
// const  getStatus = require('./NetworkQueries.gql')
// NetworkQueries.graphql
query {
  networkStatus @client {
    isConnected
  }
}
pabl-o-ce commented 6 years ago

what is the current status on this issue? webpack ts loader compile without any problem my import of graphql files using like

import * as type from 'type.grpahql';
// it get me the files like I want
// but gets my this error on console and vscode [ts] Cannot find module './type.graphql'

I think this is more in tsconfig problem if anyone can get me some guide I will be great

Solved just add to tsconfig includes the d.ts definition of @buggy thanks

mrdulin commented 6 years ago

@buggy thanks. After add d.ts,tslint still throw some error:

My case is:


import * as Q from 'gqlMod/queries/library.gql';
import * as M from 'gqlMod/mutations/library.gql';

//...
//...

export default compose(
  graphql(Q.CART, {
    props: ({ data: { cart } }) => {
      return {
        cart
      };
    }
  }),
  graphql(M.REMOVE_ALL_FROM_COUNT, { name: 'removeAllFromCart' }),
  graphql(M.ADD_TO_CART, { name: 'addToCart' }),
  graphql(M.REMOVE_FROM_CART, { name: 'removeFromCart' }),
  graphql(M.REMOVE_COUNT_FROM_CART, { name: 'removeCountFromCart' })
)(Cart);

tslint give me some errors:

[ts] Property 'CART' does not exist on type 'DocumentNode'. [ts] Property 'REMOVE_ALL_FROM_COUNT' does not exist on type 'DocumentNode'. ...

So I change the d.ts like this:

declare module '*.gql' {
  import { DocumentNode } from 'graphql';

  const value: {
    CART: DocumentNode;
    REMOVE_ALL_FROM_COUNT: DocumentNode;
    ADD_TO_CART: DocumentNode;
    REMOVE_FROM_CART: DocumentNode;
    REMOVE_COUNT_FROM_CART: DocumentNode;
  };
  export = value;
}

It works fine. tslint error gone. It's a little more complex for adding definition for each query.

So change d.ts again like this:

declare module '*.gql' {
  import { DocumentNode } from 'graphql';

  const value: {
    [key: string]: DocumentNode;
  };
  export = value;
}
annguyen0505 commented 6 years ago

@gustavoalfaro

Did you include the declare module '*.graphql'... declaration suggested above? I figure that with 20 up-votes, it's probably worth a shot!

What error do you see?

i got the imported module is just a string? just like that "/static/media/postsByUser.79323037.graphql"

sneko commented 6 years ago

@buggy thank you for the solution!

I'm still having an issue because my GraphQL client (Apollo) is expecting a object typed GraphQLSchema by using your method I just get a DocumentNode.

ts]
Argument of type '{ schema: DocumentNode; }' is not assignable to parameter of type 'Options'.
  Types of property 'schema' are incompatible.
    Type 'DocumentNode' is not assignable to type 'GraphQLSchema'.
      Property 'astNode' is missing in type 'DocumentNode'.
(property) schema: DocumentNode

Do you know how to fit to this requirement? Thank you!

Note: I tried to set GraphQLSchema instead of DocumentNode in the index.d.ts but it doesn't work

nemcek commented 6 years ago

@gustavoalfaro Did you include the declare module '*.graphql'... declaration suggested above? I figure that with 20 up-votes, it's probably worth a shot! What error do you see?

i got the imported module is just a string? just like that "/static/media/postsByUser.79323037.graphql"

@annguyen0505 I got the same problem and you probably aren't using graphql-tag/loader in your webpack configuration.

Check out react-app-rewired with react-app-rewire-graphql-tag so you don't need to eject your react app (assuming you have a project created by create-react-app).

irace commented 5 years ago

@nemcek Can you elaborate on “you probably aren't using graphql-tag/loader in your webpack configuration”?

My webpack configuration looks just like:

{
  test: /\.(graphql|gql)$/,
  exclude: /node_modules/,
  loader: 'graphql-tag/loader',
}

I’ve added the following as per this comment:

declare module '*.graphql' {
    import {DocumentNode} from 'graphql';

    const value: DocumentNode;
    export = value;
}

Which does get rid of my compile-time errors, but I still get hit with the following at runtime:

GraphQLError: Syntax Error: Unexpected Name "module"

Any thoughts would be appreciated, I really have no idea how to get past this.

switz commented 5 years ago

The solution works for me, but does anyone have a solution that works across many directories? My codebase is based on a module structure so the graphql tags are not flat in a single directory.

jbroadice commented 5 years ago

I've tried the solutions listed here and I simply cannot get this to work. I receive the following error, no matter what:

src/modules/auth/index.ts(2,27): error TS2307: Cannot find module './schema.graphql'.``

I have tried placing the graphql.d.ts file within the same directory as the .graphql file, at the src level, at the project root. None have altered a single thing. It's as if TS isn't even seeing the .d.ts file.

Here is the tsconfig:

  "compilerOptions": {
    "lib": ["es2017", "esnext.asynciterable", "dom"],
    "target": "es2017",
    "module": "commonjs",
    "moduleResolution": "node",
    "sourceMap": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "outDir": "dist",
    "typeRoots": ["node_modules/@types"],
    "paths": {
      "@lib/*": ["./lib/*"],
      "@models/*": ["./models/*"],
      "@modules/*": ["./modules/*"]
    },
    "baseUrl": "./src/",
    "allowSyntheticDefaultImports": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}
eug48 commented 5 years ago

@jchapple it would help if you post a link to your file structure (e.g. output of find .) to a pastebin, and also mention your TypeScript version. But anyway, I think the typeRoots line in your tsconfig might be the source of this problem.

defrex commented 5 years ago

I don't see it mentioned here, and this is the top google result for this problem. So FYI, graphql-code-generator has a plugin to generate these module definitions. It solved the problem for me. https://graphql-code-generator.com/docs/plugins/typescript-graphql-files-modules

yakovlevkll commented 5 years ago

Faced the problem with importing .gql across many directories (same as @switz has mentioned).

Solved it with global.d.ts. For example, let it be placed in src folder

// src/global.d.ts
declare module '*.gql' {
    import { DocumentNode } from 'graphql';

    const value: DocumentNode;
    export = value;
}

Be sure that src folder is in include section of your tsconfig.json

// tsconfig.json
{
    // ...
    "include": ["src/**/*.ts"]
}

Thanks to @buggy and others.

khaledosman commented 5 years ago

I'm facing the same issue, any ideas?

tsconfig.json

{
  "compilerOptions": {
    "lib": ["es2017"],
    "moduleResolution": "node",
    "noUnusedLocals": false,
    "noUnusedParameters": true,
    "sourceMap": true,
    "target": "es2017",
    "outDir": "lib"
  },
  "exclude": ["node_modules"]
}

webpack.config.js

const path = require('path')
const slsw = require('serverless-webpack')

module.exports = {
  mode: slsw.lib.webpack.isLocal ? 'development' : 'production',
  entry: slsw.lib.entries,
  devtool: 'source-map',
  resolve: {
    extensions: ['.js', '.jsx', '.json', '.ts', '.tsx', '.mjs', '.gql', '.graphql']
  },
  output: {
    libraryTarget: 'commonjs',
    path: path.join(__dirname, '.webpack'),
    filename: '[name].js'
  },
  target: 'node',
  module: {
    rules: [
      // all files with a `.ts` or `.tsx` extension will be handled by `ts-loader`
      { test: /\.(tsx?|mjs)$/, loader: 'ts-loader' },
      {
        test: /\.(graphql|gql)$/,
        exclude: /node_modules/,
        loader: 'graphql-tag/loader'
      }
    ]
  }
}

usage:

import typeDefs from './schema.graphql'
ecomachio commented 3 years ago

if you having trouble importing gql files with jest I solved by using @ddetkovs solution plus adding this to jest config. transform: { '^.+\\.(gql|graphql)$': 'jest-transform-graphql', ... } more info here https://github.com/remind101/jest-transform-graphql

Hope this helps.

aleksanderfret commented 2 years ago

I used code from @buggy:

declare module '*.graphql' {
  import { DocumentNode } from 'graphql';

  const value: DocumentNode;
  export = value;
}

I put this code into index.d.ts file into my src/@types folder. Then I add this folder to my typeRoots setting in tsconfing.json file:

"typeRoots": ["./src/@types"]

This solution works for me for all .graphql files inside my src folder.