I am building a gql server application using apollo server.
When I try to load my .graphql
files using import { loadFilesSync } from '@graphql-tools/load-files'
, this works very well, but when I load my resolver files, i get an error
node:internal/errors:464
ErrorCaptureStackTrace(err);
^
Error [ERR_REQUIRE_ESM]: require() of ES Module /userpath/index.js from /userpath/server-gql/noop.js not supported.
Instead change the require of index.js in /userpath/server-gql/noop.js to a dynamic import() which is available in all CommonJS modules.
at Object.newLoader [as .js] (/userpath/server-gql/node_modules/pirates/lib/index.js:141:7)
at file:///userpath/server-gql/node_modules/@graphql-tools/load-files/esm/index.js:104:33
at Array.map (<anonymous>)
at loadFilesSync (file:///userpath/server-gql/node_modules/@graphql-tools/load-files/esm/index.js:95:10)
at file:///userpath/server-gql/schema.js:20:24
at async Promise.all (index 0) {
code: 'ERR_REQUIRE_ESM'
}
I am using "type": "module"
in my package.json
.
Here’s my code snippet for where i get the error
import path from 'path'
import { fileURLToPath } from 'url'
import { loadFilesSync } from '@graphql-tools/load-files'
import { mergeTypeDefs, mergeResolvers } from '@graphql-tools/merge'
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const typesArray = loadFilesSync(path.join(__dirname, '.'), {
recursive: true,
extensions: ['graphql'],
})
const resolversArray = loadFilesSync(path.join(__dirname, './graphql/**/*.resolvers.js'), {
recursive: true,
extensions: ['js'],
})
const newResolversArray = resolversArray.slice(1)
export const typeDefs = mergeTypeDefs(typesArray)
export const resolvers = mergeResolvers(newResolversArray)
I think the error occurs in the resolvers array.
2 Answers
I was running into a very similar issue and found this workaround/solution worked for me: https://github.com/ardatan/graphql-tools/issues/1750#issuecomment-655828240
I had to modify that answer a bit to get it working in my codebase…below is what my own resolverFiles
snippet now looks like (I’m not 100% sure what your import/naming conventions look like, or I’d try to apply this to your code snippet).
I have a mixed, nested directory schema/
that contains resolver files of the format SomethingResolvers.ts
. Each of these look like the following (I’m using codegen):
import { Resolvers } from "../../generated/graphql";
export const resolvers: Resolvers = { /* resolver implementations */ };
const resolverFiles = await loadFiles(
path.join(__dirname, "**/*Resolvers.js"),
{
requireMethod: async (path) => {
const module = await import(pathToFileURL(path).toString());
return module["resolvers"];
},
recursive: true,
}
);
ESM(ECMAScript modules – import/export) loading logic is asynchronous, so it seems like you can’t use loadFilesSync when using ESM.
The following code will technically work but won’t be right because it will use require
to load the modules.
const resolversArray = await loadFiles(resolversPath);
So I think a more correct version will be:
import { pathToFileURL } from 'url';
const resolversArray = await loadFiles(resolversPath, {
requireMethod: async (path) => {
console.debug('Using resolver at:', path);
return await import(pathToFileURL(path));
}
});
Now, apparently if you’re using the mjs
file extension for your modules, you will need to also specify a useRequire
option to load the file using the requireMethod
no matter the file’s extension:
import { pathToFileURL } from 'url';
const resolversArray = await loadFiles(resolversPath, {
useRequire: true,
requireMethod: async (path) => {
console.debug('Using resolver at:', path);
return await import(pathToFileURL(path));
}
});
+You’ll need to specify the extension in either the path or the extensions
option
You can see the default extensions here
I just used the extension in the path, like so:
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const resolversPath = path.join(
__dirname, 'resolvers', '**', '*.resolvers.mjs'
);
So now every file(recursively) inside the resolvers folder that’s inside the folder of the current file with the file extension of resolvers.mjs
will be loaded.
Oh, on an unrelated note, you can use mergeResolvers
from the @graphql-tools/merge
package to merge needed resolvers gracefully(resolvers under Query for example).
Full example:
import { loadFiles } from '@graphql-tools/load-files';
import path from 'path';
import { fileURLToPath, pathToFileURL } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
async function getResolvers() {
const resolversPath = path.join(
__dirname, 'resolvers', '**', '*.resolvers.mjs'
);
const resolversArray = await loadFiles(resolversPath, {
useRequire: true,
requireMethod: async (path) => {
console.debug('Using resolver at:', path);
return await import(pathToFileURL(path));
}
});
return mergeResolvers(resolversArray);
}
const resolvers = await getResolvers();
console.debug('GraphQL used resolvers:');
console.debug(resolvers);