ardatan / graphql-tools

:wrench: Utility library for GraphQL to build, stitch and mock GraphQL schemas in the SDL-first approach
https://www.graphql-tools.com
MIT License
5.35k stars 816 forks source link

Support for static *.graphql file to define schema #273

Closed rogchap closed 7 years ago

rogchap commented 7 years ago

This was breifly mentioned by @stubailo here: https://github.com/apollographql/graphql-tools/issues/124#issuecomment-245037518

What I've found is that as my project grows it's increasingly harder to read my schema as it's formatted as a single string. (especially when you're diligently adding descriptions for everything).

We want all of this: https://dev-blog.apollodata.com/5-benefits-of-static-graphql-queries-b7fa90b0b69a but on the server.

stubailo commented 7 years ago

Great! I think a require hook could be a good first step - then you can just require .graphql files.

rogchap commented 7 years ago

@stubailo Yea that would be awesome. I'll have a crack at putting this PR together.

For the "hook", there is only one way to do this but it's technically depreciated: https://nodejs.org/api/globals.html#globals_require_extensions

Since the module system is locked, this feature will probably never go away. However, it may have subtle bugs and complexities that are best left untouched.

Other modules are still using this, are we ok with this, I don't know of an alternative?

Also, is there a suitable place to have this extension initiated?

DxCx commented 7 years ago

i think i saw webpack plugin which gives you the ability to just import them.. but that was for graphql-tag client side. maybe we should add such loader to graphql-tools?

rogchap commented 7 years ago

So, this should be straightforward however, it does not play nice with TypeScript.

Problem 1: [ts] Can't find module myRootQuery.graphql - This can be resolved by using require instead of import Problem 2: TypeScript has no --copy-files, therefore won't copy any non-ts/js files to the output dir

Anybody have any smart idea's?

Don't want to have to use a build script / gulp etc.

bman654 commented 7 years ago

This is what I've been doing on the server to "import" a graphql file. Not as pretty, but easy enough:

// read schema.graphql file in same folder as this module
const schema = [fs.readFileSync(path.join(__dirname, "schema.graphql"), "utf8")];
advance512 commented 7 years ago

I've been using require-text for this.

rigobcastro commented 7 years ago

It's a good temp solution @bman654 Thanks!

abdulhaq-e commented 7 years ago

With TS > 2.0 you can use a wildcard when declaring a module type:

declare module "*.graphql" {
    const value: any;
    export default value;
}

Then you can import it just like a .ts file. (Edit: a loader is still required)

stubailo commented 7 years ago

@abdulhaq-e whoa - do you have to use that with require-text as well, or does it just work?

abdulhaq-e commented 7 years ago

Sorry I just tried it and a loader is required.

typings.d.ts:

declare module "*.graphql" {
    const value: any;
    export default value;
}

code:

import * as query from query.graphql

For this to work, a loader such as raw-loader for those using webpack will work.

    module: {
      rules: [
        { test: /\.json$/, loader: 'json-loader' },
        { test: /\.html$/, loader: 'raw-loader' },
        { test: /\.graphql$/, loader: 'raw-loader' },
      ]
advance512 commented 7 years ago

A loader is required if you build your server with Webpack. If you're just using Node.js directly (as you probably should in case of a GraphQL/API endpoint), loaders don't help.

helfer commented 7 years ago

We should suggest a solution in the docs that doesn't require a loader. Most people don't want to use webpack or similar on the server for good reasons.

DxCx commented 7 years ago

@helfer im not familiar with that most people prefere not to use webpack for server side as well, can you give example of the good reasons?

advance512 commented 7 years ago

A much more complex build process, more difficult debugging and deploying, and little if any added benefits for GraphQL/API type servers.

What are you bundling, after all?

DxCx commented 7 years ago

well, bundling gives you alot of power when you go from typescript to javascript... for example, how much output(s) do you want? where? also, being able to hook the output gives more values (for example, i've written a plugin which instruments the whole output, so you know exactly how much code is covered out of the whole product you are going to deploy) maybe it's just me who's appreciate those things..

advance512 commented 7 years ago

These are great things, but I think they're rather better left for the CI and not hidden inside webpack.config..js files..

As for transpiling from TypeScript, I admit I have no experience with that as of yet, so I don't know. I do know that using webpack should be optional.

jamiter commented 7 years ago

Meteor now has a loader for this that allows you to simply import the schema file:

import schema from './schema.graphql'

Underwater this uses the webpack loader from graphql-tag which doesn't return a string, but a parsed schema. It works for the server and client.

Sadly, makeExecutableSchema only accepts strings, so now I need to parse it back again before passing it on:

import schema from './schema.graphql'
import { print } from 'graphql/language/printer';

const schema = makeExecutableSchema({
  typeDefs: print(schema),
  resolvers,
});

What do you all expect from a loader or a require hook to return when importing .graphql files? A string, or the result of parsed schema like the webpack loader in graphql-tag returns? If the latter, then makeExecutableSchema shouldn't just allow strings.

Perhaps this should be discussed in a separate issue, but support for a schema object, and not only a string, would be great.

stubailo commented 7 years ago

@jamiter actually it might be nice if makeExecutableSchema could accept Schema ASTs just as well as strings, it would just be a matter of running print.

jamiter commented 7 years ago

Cool, I'll make a PR for that!

jamiter commented 7 years ago

@stubailo, see https://github.com/apollographql/graphql-tools/pull/300

helfer commented 7 years ago

Just merged #300 which should make this possible.

heddendorp commented 7 years ago

I am still unclear on the best way to use static schema files on a typescript + node server without webpack. Is there currently a solution to this Problem?

stubailo commented 7 years ago

@Isigiel right now there isn't really anything in graphql-tools preventing you from doing that, sounds like you are looking for some kind of static file loader for node. Basically you need something that will let you import or read .graphql files, and then you should just pass the resulting strings into makeExecutableSchema?

heddendorp commented 7 years ago

Yeah right, but I'm unclear on how to import them, webpack seems overkill and just fs.rading them does not work because tsc does not copy the files. Right now to me, the best option seems to be, have a second gulp task running that will watch the files and copy the graphql files. I was mainly wondering if someone had come up with a more elegant solution.

jelder commented 7 years ago

@Isigiel https://github.com/quadric/babel-plugin-inline-import seems like a good option, if you're already using Babel.

vincenzo commented 6 years ago

One issue you have using babel-plugin-inline-import is when you use nodemon. Even if you tell it to monitor .graphql files, the changes to the schema will not be reloaded from file when nodemon is triggered. One must make a change to the file importing the schema in order for that to happen. Developers suggest disabling babel cache, but that doesn't seem like a wise idea.

stubailo commented 6 years ago

Maybe this tool can also work? https://github.com/graphcool/graphql-import

ericnograles commented 6 years ago

Here's a quick way to assemble .graphql strings as one to load into makeExecutableSchema that I've been using: https://gist.github.com/ericnograles/d59e32f244c9ea04e69f66a7caa96940

It'll sweep through .graphql files in a folder and assemble all of them, leaving Query.graphql for the last concat, and finally export out the finished string for usage.

vincenzo commented 6 years ago

@stubailo graphql-import is definitely the way to go -- it's also included in https://github.com/graphcool/graphql-yoga, which I would suggest anyone check out.

ericnograles commented 6 years ago

graphql-import def looks slick, will need to check that out!

iamlothian commented 6 years ago

I am trying to make use of graphql-import with typescript, It loads files fine but I still have to find a way to copy the .qraphql files to a dist folder... which mean involving some more complex build steps. And the idea of maintain both a AST version and AST.ts string version is not ideal.

I'm considering trying out something like the following: Folder structure for a singe entity

- Book
-- index.ts
-- query.qraphql
-- query.qraphql.ts
-- resolver.ts
-- schema.qraphql
-- schema.qraphql.ts

*.qraphql.ts

import { importSchema } from "graphql-import"
export const Query:string = importSchema(__dirname + '/' + __filename.replace('.ts',''));

when i build i want to take the *.qraphql content and replace the importSchema with the actual qraphql string. so that the dist folder will contain a baked version of the qraphql at build time.

Thoughts?

vincenzo commented 6 years ago

@iamlothian I think it's fair to assume that if you take your query to https://github.com/graphcool/graphql-import (open an issue there), the maintainer could help you with that.

iamlothian commented 6 years ago

@vincenzo thanks, here is a link for reference

eliperelman commented 6 years ago

In a similar vein to @ericnograles's solution, if you are using webpack for Node.js, you can use raw-loader for .graphql files, then use webpack's require.context to load them all as a string:

// ./graphql/index.js

const importer = require.context('./', true, /\.graphql$/);
const keys = importer.keys();
const root = './Query.graphql';

module.exports = [
  ...keys
    .filter(key => key !== root)
    .reduce(
      (typeDefs, key) => typeDefs.add(importer(key)),
      new Set([importer(root)])
    ),
].join('\n');

You can then import this and pass it to makeExecutableSchema:

import { makeExecutableSchema } from 'graphql-tools';
import typeDefs from './graphql';

makeExecutableSchema({ typeDefs });
mxmzb commented 6 years ago

You could also take a very dumb approach and copy your .graphql files to your /dist folder with webpack plugin, like: https://github.com/webpack-contrib/copy-webpack-plugin

FredericLatour commented 6 years ago

Hi, In case you are still looking for an option I use "copyfiles" node module for this.

package.json
"scripts": {

     "build": "yarn run build-ts && copyfiles -u 1 src/*.json src/*.yml src/config/*.* dist"

}

You could easily add your ".graphql" files.

webberwang commented 6 years ago

GraphQL Import works great with importing .graphql

const typeDefs = importSchema('./schema.graphql')

but it requires a Node.js environment & won't work for the front end. The only solution for the browser side is using a Webpack loader for .graphql files, but I hadn't been able to get the loader working with a CRA Typescript version yet.

nutboltu commented 5 years ago

I didn't want to use webpack just to copy .graphql files to the dist folder. I used cpx package to copy my .graphql files keeping same file tree structure. It works fine.

Here's my package.json

"scripts": {
  "copy-schemas": "$(npm bin)/cpx src/**/*.graphql dist",
  "start": "tsc && npm run copy-schemas && node ./dist/index.js"
}
jeongsd commented 5 years ago

Hi I'm use merge-graphql-schemas I think it cover many folder structure

import { fileLoader, mergeTypes, } from 'merge-graphql-schemas'

const typesArray = fileLoader(path.join(__dirname, 'graphql/**/*.graphql'))
const typeDefs = mergeTypes(typesArray, { all: true })
ankur20us commented 5 years ago

Hi I'm use merge-graphql-schemas I think it cover many folder structure

import { fileLoader, mergeTypes, } from 'merge-graphql-schemas'

const typesArray = fileLoader(path.join(__dirname, 'graphql/**/*.graphql'))
const typeDefs = mergeTypes(typesArray, { all: true })

This package is now not maintainable.

webdevike commented 4 years ago

I didn't want to use webpack just to copy .graphql files to the dist folder. I used cpx package to copy my .graphql files keeping same file tree structure. It works fine.

Here's my package.json

"scripts": {
  "copy-schemas": "$(npm bin)/cpx src/**/*.graphql dist",
  "start": "tsc && npm run copy-schemas && node ./dist/index.js"
}

Thank you so much!!! I tweaked it a bit for my project(not using typescript) and it works brilliantly.

"scripts": {
    "copy-schemas": "cpx src/**/*.graphql dist",
    "start": "npm run copy-schemas && node ./dist/app.js",
  },
CyxouD commented 4 years ago

thank you @nutboltu. In my case I needed to copy .gql files and it looks like files contained deeper in project than in your case, so I changed also output directory:

"copy-schemas": "$(npm bin)/cpx src/**/*.gql dist/src"