diff --git a/config/plugins/esbuild/graphQLImportPlugin.ts b/config/plugins/esbuild/graphQLImportPlugin.ts new file mode 100644 index 000000000..c09a5907b --- /dev/null +++ b/config/plugins/esbuild/graphQLImportPlugin.ts @@ -0,0 +1,34 @@ +import fs from 'fs/promises' +import type { Plugin } from 'esbuild' + +/** + * A plugin to replace `require('graphql')` statements with `await import('graphql')` + * only for ESM bundles. This makes the GraphQL module to be imported lazily + * while maintaining the CommonJS compatibility. + * @see https://github.com/mswjs/msw/issues/2254 + */ +export function graphqlImportPlugin(): Plugin { + return { + name: 'graphql-import-plugin', + setup(build) { + if (build.initialOptions.format !== 'esm') { + return + } + + build.onLoad({ filter: /\.ts$/ }, async (args) => { + const contents = await fs.readFile(args.path, 'utf-8') + const match = /require\(['"]graphql['"]\)/g.exec(contents) + + if (match) { + return { + loader: 'ts', + contents: + contents.slice(0, match.index - 1) + + `await import('graphql').catch((error) => {console.error('[MSW] Failed to parse a GraphQL query: cannot import the "graphql" module. Please make sure you install it if you wish to intercept GraphQL requests. See the original import error below.'); throw error})` + + contents.slice(match.index + match[0].length), + } + } + }) + }, + } +} diff --git a/src/core/utils/internal/parseGraphQLRequest.ts b/src/core/utils/internal/parseGraphQLRequest.ts index 81f6b1097..922375fae 100644 --- a/src/core/utils/internal/parseGraphQLRequest.ts +++ b/src/core/utils/internal/parseGraphQLRequest.ts @@ -40,12 +40,14 @@ export function parseDocumentNode(node: DocumentNode): ParsedGraphQLQuery { } async function parseQuery(query: string): Promise { - const { parse } = await import('graphql').catch((error) => { - devUtils.error( - 'Failed to parse a GraphQL query: cannot import the "graphql" module. Please make sure you install it if you wish to intercept GraphQL requests. See the original import error below.', - ) - throw error - }) + /** + * @note Use `require` to get the "graphql" module here. + * It has to be scoped to this function because this module leaks to the + * root export. It has to be `require` because tools like Jest have trouble + * handling dynamic imports. It gets replaced with a dynamic import on build time. + */ + // eslint-disable-next-line @typescript-eslint/no-require-imports + const { parse } = require('graphql') try { const ast = parse(query) diff --git a/tsup.config.ts b/tsup.config.ts index 68c889463..8c606d11e 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -7,6 +7,7 @@ import { } from './config/plugins/esbuild/copyWorkerPlugin' import { resolveCoreImportsPlugin } from './config/plugins/esbuild/resolveCoreImportsPlugin' import { forceEsmExtensionsPlugin } from './config/plugins/esbuild/forceEsmExtensionsPlugin' +import { graphqlImportPlugin } from './config/plugins/esbuild/graphQLImportPlugin' import packageJson from './package.json' // Externalize the in-house dependencies so that the user @@ -33,7 +34,7 @@ const coreConfig: Options = { sourcemap: true, dts: true, tsconfig: path.resolve(__dirname, 'src/tsconfig.core.build.json'), - esbuildPlugins: [forceEsmExtensionsPlugin()], + esbuildPlugins: [graphqlImportPlugin(), forceEsmExtensionsPlugin()], } const nodeConfig: Options = {