graphql-boilerplates / typescript-graphql-server

Boilerplate code for scalable, production-ready GraphQL servers written in TypeScript

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

What happens to "typeDefs: './src/schema.graphql'" in production builds?

zadigus opened this issue · comments

Hi,
This might not be an issue as I didn't actually test it but GraphQLServer instance is initialized with

typeDefs: './src/schema.graphql'.

While this is no problem on dev builds, because we start the server with

ts-node src/index.ts

and thus the './src/schema.graphql' is accessible, this might be a problem on production builds where the typescript code is compiled into javascript code in a 'dist' folder. In that case, only the files present in that folder are deployed to the server which has no reference any more to the './src/schema.graphql' file! As I haven't seen any particular rule in the tsconf file to handle this, I suppose this will not work on production builds.
Cheers

I posted a question related to this here: prisma-labs/graphql-import-loader#11. I still have no idea how to solve this.

Ok. Here's how I solved it:

First, I integrated graphql-import-loader and webpack into my framework:

package.json

[...]
"devDependencies": {
    [...]
    "graphql-import-loader": "^0.2.1",
    [...]
    "webpack": "^4.6.0",
    "webpack-cli": "^2.0.15",
    "webpack-node-externals": "^1.7.2"
  }
[...]
"scripts": {
    "start": "node server/main.js",
    "start:dev": "webpack --config ./webpack.dev.js --watch",
    "dev": "npm-run-all --parallel start playground",
    "debug": "dotenv -- nodemon -e ts,graphql -x ts-node --inspect src/index.ts",
    "clean": "rimraf server",
    "playground": "graphql playground",
    "build": "npm run clean && webpack --config ./webpack.prod.js",
    "deploy:prisma": "cross-var prisma login --key $PRISMA_SERVICE_TOKEN && prisma deploy",
    "postinstall": "npm run deploy:prisma && npm run build"
  },
[...]

With the above config, I am able to launch a dev server that restarts everytime I change something to the code and I can build the production server too. The graphql-import-loader package allows to load graphql files in the code, so that I can do this:

index.ts

import { GraphQLServer } from 'graphql-yoga';
import { Prisma } from './generated/prisma';

import resolvers from './resolvers';
import * as typeDefs from './schema.graphql';

const server = new GraphQLServer({
  typeDefs,
  resolvers,
  resolverValidationOptions: {
    requireResolversForResolveType: false
  },
  context: req => ({
    ...req,
    db: new Prisma({
      endpoint: process.env.PRISMA_ENDPOINT, 
      secret: process.env.PRISMA_SECRET, 
      debug: true 
    })
  })
});
[...]

The important change here above is

import * as typeDefs from './schema.graphql';

It could also be a

import typeDefs = require('./schema.graphql');

When the code has been generated for production, then I don't care about the 'src/schema.graphql' any more as well as any of its dependencies in the 'src/generated/' folder.

In order for this to work, though, I need the following typings declaration:

typings.d.ts

declare module '*.graphql' {
  const content: any
  export = content
}

Here the important part is the syntax to export the content. Indeed, I did not do a

export default content

as this doesn't work. Of course, the tsconfig.json is updated in such a way that this typings declaration is taken into account.

Then, I wrote the following webpack configs:

webpack.dev.js

const webpack = require('webpack');
const path = require('path');
const nodeExternals = require('webpack-node-externals');
const NodemonPlugin = require( 'nodemon-webpack-plugin' ) 

module.exports = {
	target: 'node',
	entry: './src/index.ts',
	externals: [nodeExternals()],
	output: {
		filename: '[name].js',
		chunkFilename: '[name].js',
		path: path.resolve(__dirname, 'server'),
		libraryTarget: 'commonjs'
	},
	resolve: {
		extensions: ['.graphql', '.ts', '.js', '.json'] 
        },
       node: {
          fs: 'empty'
        },
	module: {
		rules: [
		{
                  exclude: /node_modules/,
                  test: /\.ts$/,
                  use: [
			{ loader: 'babel-loader' },
			{ loader: 'ts-loader' },
                  ],
		},
		{
		  test: /\.json$/,
		  exclude: /node_modules/,
		  loader: 'json-loader'
		}, 
		{
		 test: /\.graphql$/,
		 exclude: /node_modules/,
		 loader: 'graphql-import-loader'
		}
		]
	},
	plugins: [
		new NodemonPlugin()
	],
	mode: 'development'
};

as well as

webpack.prod.js

const webpack = require('webpack');
const path = require('path');
const nodeExternals = require('webpack-node-externals');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');

module.exports = {
	target: 'node',
	entry: './src/index.ts',
	externals: [nodeExternals()],
	output: {
		filename: '[name].js',
		chunkFilename: '[name].js',
		path: path.resolve(__dirname, 'server'),
		libraryTarget: 'commonjs'
	},
	resolve: {
		extensions: ['.graphql', '.ts', '.js', '.json']
  },
  node: {
    fs: 'empty'
  },
	module: {
		rules: [
			{
        exclude: /node_modules/,
        test: /\.ts$/,
        use: [
					{ loader: 'babel-loader' },
					{ loader: 'ts-loader' },
        ],
			},
			{
			  test: /\.json$/,
			  exclude: /node_modules/,
			  loader: 'json-loader'
			}, 
			{
				test: /\.graphql$/,
				exclude: /node_modules/,
				loader: 'graphql-import-loader',
			}
		]
	},

	plugins: [new UglifyJSPlugin()],
	mode: 'production',

	optimization: {
		splitChunks: {
			chunks: 'async',
			minSize: 30000,
			minChunks: 1,
			name: false,

			cacheGroups: {
				vendors: {
					test: /[\\/]node_modules[\\/]/,
					priority: -10
				}
			}
		}
	}
};