From a72d49e5ab24c95a0d70260b41302b50b3d27659 Mon Sep 17 00:00:00 2001 From: Yaacov Rydzinski Date: Fri, 20 Jun 2025 06:57:00 +0300 Subject: [PATCH 1/6] update node versions to test --- .github/workflows/ci.yml | 2 +- .node-version | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 20cb72df95..1ef3bcec2e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -145,7 +145,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node_version_to_setup: [16, 18, 19] + node_version_to_setup: [20, 22, 23] permissions: contents: read # for actions/checkout steps: diff --git a/.node-version b/.node-version index 9a2a0e219c..53d1c14db3 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -v20 +v22 From 41702bca7ca0cb19465f77acfd5fe53495f80b4c Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Wed, 11 Jun 2025 22:10:14 +0300 Subject: [PATCH 2/6] try esm only Co-Authored-By: Yaacov Rydzinski --- eslint.config.mjs | 2 +- integrationTests/node/index.mjs | 17 +---- integrationTests/node/package.json | 3 +- integrationTests/ts/esm.ts | 10 +-- integrationTests/ts/package.json | 1 - integrationTests/webpack/entry-esm.mjs | 10 --- integrationTests/webpack/package.json | 1 - integrationTests/webpack/test.js | 12 +--- integrationTests/webpack/webpack.config.json | 7 +-- package-lock.json | 2 +- package.json | 2 +- resources/build-npm.ts | 66 +++++--------------- resources/integration-test.ts | 6 -- 13 files changed, 31 insertions(+), 108 deletions(-) delete mode 100644 integrationTests/webpack/entry-esm.mjs diff --git a/eslint.config.mjs b/eslint.config.mjs index 65bf540c5f..e143d5cd11 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -764,7 +764,7 @@ export default tsConfig( 'n/no-missing-import': [ 'error', { - allowModules: ['graphql', 'graphql-esm'], + allowModules: ['graphql'], }, ], 'n/no-missing-require': [ diff --git a/integrationTests/node/index.mjs b/integrationTests/node/index.mjs index e06bd19de6..1e7a71c6cd 100644 --- a/integrationTests/node/index.mjs +++ b/integrationTests/node/index.mjs @@ -1,18 +1,7 @@ import assert from 'node:assert'; -import { readFileSync } from 'node:fs'; - -import { - experimentalExecuteIncrementally, - graphqlSync, - parse, -} from 'graphql-esm'; -import { buildSchema } from 'graphql-esm/utilities'; -import { version } from 'graphql-esm/version'; - -assert.deepStrictEqual( - version + '+esm', - JSON.parse(readFileSync('./node_modules/graphql-esm/package.json')).version, -); + +import { experimentalExecuteIncrementally, graphqlSync, parse } from 'graphql'; +import { buildSchema } from 'graphql/utilities'; const schema = buildSchema('type Query { hello: String }'); diff --git a/integrationTests/node/package.json b/integrationTests/node/package.json index 8271998588..2d2665ed9a 100644 --- a/integrationTests/node/package.json +++ b/integrationTests/node/package.json @@ -6,7 +6,6 @@ "test": "node test.js" }, "dependencies": { - "graphql": "file:../graphql.tgz", - "graphql-esm": "file:../graphql-esm.tgz" + "graphql": "file:../graphql.tgz" } } diff --git a/integrationTests/ts/esm.ts b/integrationTests/ts/esm.ts index 77d011e847..368b799cf1 100644 --- a/integrationTests/ts/esm.ts +++ b/integrationTests/ts/esm.ts @@ -1,11 +1,7 @@ -import type { ExecutionResult } from 'graphql-esm/execution'; +import type { ExecutionResult } from 'graphql/execution'; -import { graphqlSync } from 'graphql-esm'; -import { - GraphQLString, - GraphQLSchema, - GraphQLObjectType, -} from 'graphql-esm/type'; +import { graphqlSync } from 'graphql'; +import { GraphQLString, GraphQLSchema, GraphQLObjectType } from 'graphql/type'; const queryType: GraphQLObjectType = new GraphQLObjectType({ name: 'Query', diff --git a/integrationTests/ts/package.json b/integrationTests/ts/package.json index 6e5bfb367d..b3a3586b8a 100644 --- a/integrationTests/ts/package.json +++ b/integrationTests/ts/package.json @@ -7,7 +7,6 @@ }, "dependencies": { "graphql": "file:../graphql.tgz", - "graphql-esm": "file:../graphql-esm.tgz", "typescript-4.9": "npm:typescript@4.9.x", "typescript-5.0": "npm:typescript@5.0.x", "typescript-5.1": "npm:typescript@5.1.x", diff --git a/integrationTests/webpack/entry-esm.mjs b/integrationTests/webpack/entry-esm.mjs deleted file mode 100644 index 5a04dc1ada..0000000000 --- a/integrationTests/webpack/entry-esm.mjs +++ /dev/null @@ -1,10 +0,0 @@ -import { graphqlSync } from 'graphql-esm'; -import { buildSchema } from 'graphql-esm/utilities/buildASTSchema'; - -const schema = buildSchema('type Query { hello: String }'); - -export const result = graphqlSync({ - schema, - source: '{ hello }', - rootValue: { hello: 'world' }, -}); diff --git a/integrationTests/webpack/package.json b/integrationTests/webpack/package.json index 66dd836f1e..74a2502ff7 100644 --- a/integrationTests/webpack/package.json +++ b/integrationTests/webpack/package.json @@ -7,7 +7,6 @@ }, "dependencies": { "graphql": "file:../graphql.tgz", - "graphql-esm": "file:../graphql-esm.tgz", "webpack": "5.x.x", "webpack-cli": "4.x.x" } diff --git a/integrationTests/webpack/test.js b/integrationTests/webpack/test.js index db6ba9db84..60ab7b4153 100644 --- a/integrationTests/webpack/test.js +++ b/integrationTests/webpack/test.js @@ -1,18 +1,10 @@ import assert from 'node:assert'; /* eslint-disable n/no-missing-import */ -import cjs from './dist/main-cjs.cjs'; -import mjs from './dist/main-mjs.cjs'; +import main from './dist/main.cjs'; /* eslint-enable n/no-missing-import */ -assert.deepStrictEqual(cjs.result, { - data: { - __proto__: null, - hello: 'world', - }, -}); - -assert.deepStrictEqual(mjs.result, { +assert.deepStrictEqual(main.result, { data: { __proto__: null, hello: 'world', diff --git a/integrationTests/webpack/webpack.config.json b/integrationTests/webpack/webpack.config.json index 8381df4cee..4d3276186c 100644 --- a/integrationTests/webpack/webpack.config.json +++ b/integrationTests/webpack/webpack.config.json @@ -1,11 +1,8 @@ { "mode": "production", - "entry": { - "cjs": "./entry.js", - "mjs": "./entry-esm.mjs" - }, + "entry": "./entry.js", "output": { - "filename": "main-[name].cjs", + "filename": "main.cjs", "library": { "type": "commonjs2" } diff --git a/package-lock.json b/package-lock.json index 53feb3a2cd..bead91b71e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,7 +30,7 @@ "typescript-eslint": "^8.17.0" }, "engines": { - "node": "^16.19.0 || ^18.14.0 || >=19.7.0" + "node": "^20.19.0 || ^22.12.0 || >=23.0.0" } }, "node_modules/@bcoe/v8-coverage": { diff --git a/package.json b/package.json index a49bbe09ca..dace8eb888 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "graphql-js" ], "engines": { - "node": "^16.19.0 || ^18.14.0 || >=19.7.0" + "node": "^20.19.0 || ^22.12.0 || >=23.0.0" }, "scripts": { "preversion": "bash -c '. ./resources/checkgit.sh && npm ci --ignore-scripts'", diff --git a/resources/build-npm.ts b/resources/build-npm.ts index c29747efb8..81d2872e55 100644 --- a/resources/build-npm.ts +++ b/resources/build-npm.ts @@ -15,14 +15,10 @@ import { } from './utils.js'; console.log('\n./npmDist'); -await buildPackage('./npmDist', false); +await buildPackage('./npmDist'); showDirStats('./npmDist'); -console.log('\n./npmEsmDist'); -await buildPackage('./npmEsmDist', true); -showDirStats('./npmEsmDist'); - -async function buildPackage(outDir: string, isESMOnly: boolean): Promise { +async function buildPackage(outDir: string): Promise { fs.rmSync(outDir, { recursive: true, force: true }); fs.mkdirSync(outDir); @@ -81,37 +77,25 @@ async function buildPackage(outDir: string, isESMOnly: boolean): Promise { 'Publish tag and version tag should match!', ); } + packageJSON.exports = {}; - if (isESMOnly) { - packageJSON.exports = {}; - - const { emittedTSFiles } = emitTSFiles({ - outDir, - module: 'es2020', - extension: '.js', - }); + const { emittedTSFiles } = emitTSFiles({ + outDir, + module: 'es2020', + extension: '.js', + }); - for (const filepath of emittedTSFiles) { - if (path.basename(filepath) === 'index.js') { - const relativePath = './' + path.relative('./npmEsmDist', filepath); - packageJSON.exports[path.dirname(relativePath)] = relativePath; - } + for (const filepath of emittedTSFiles) { + if (path.basename(filepath) === 'index.js') { + const relativePath = './' + path.relative('./npmDist', filepath); + packageJSON.exports[path.dirname(relativePath)] = relativePath; } - - // Temporary workaround to allow "internal" imports, no grantees provided - packageJSON.exports['./*.js'] = './*.js'; - packageJSON.exports['./*'] = './*.js'; - - packageJSON.publishConfig.tag += '-esm'; - packageJSON.version += '+esm'; - } else { - delete packageJSON.type; - packageJSON.main = 'index'; - packageJSON.module = 'index.mjs'; - emitTSFiles({ outDir, module: 'commonjs', extension: '.js' }); - emitTSFiles({ outDir, module: 'es2020', extension: '.mjs' }); } + // Temporary workaround to allow "internal" imports, no grantees provided + packageJSON.exports['./*.js'] = './*.js'; + packageJSON.exports['./*'] = './*.js'; + const packageJsonPath = `./${outDir}/package.json`; const prettified = await prettify( packageJsonPath, @@ -140,23 +124,7 @@ function emitTSFiles(options: { }); const tsHost = ts.createCompilerHost(tsOptions); - tsHost.writeFile = (filepath, body) => { - if (filepath.match(/.js$/) && extension === '.mjs') { - let bodyToWrite = body; - bodyToWrite = bodyToWrite.replace( - '//# sourceMappingURL=graphql.js.map', - '//# sourceMappingURL=graphql.mjs.map', - ); - writeGeneratedFile(filepath.replace(/.js$/, extension), bodyToWrite); - } else if (filepath.match(/.js.map$/) && extension === '.mjs') { - writeGeneratedFile( - filepath.replace(/.js.map$/, extension + '.map'), - body, - ); - } else { - writeGeneratedFile(filepath, body); - } - }; + tsHost.writeFile = (filepath, body) => writeGeneratedFile(filepath, body); const tsProgram = ts.createProgram(['src/index.ts'], tsOptions, tsHost); const tsResult = tsProgram.emit(undefined, undefined, undefined, undefined, { diff --git a/resources/integration-test.ts b/resources/integration-test.ts index 47afc844d6..471af0952c 100644 --- a/resources/integration-test.ts +++ b/resources/integration-test.ts @@ -16,12 +16,6 @@ describe('Integration Tests', () => { const archiveName = npm({ cwd: tmpDirPath(), quiet: true }).pack(distDir); fs.renameSync(tmpDirPath(archiveName), tmpDirPath('graphql.tgz')); - const esmDistDir = localRepoPath('npmEsmDist'); - const archiveEsmName = npm({ cwd: tmpDirPath(), quiet: true }).pack( - esmDistDir, - ); - fs.renameSync(tmpDirPath(archiveEsmName), tmpDirPath('graphql-esm.tgz')); - npm().run('build:deno'); function testOnNodeProject(projectName: string) { From 00a1db0856ac8ceeb76091057968e5b58edd1521 Mon Sep 17 00:00:00 2001 From: Yaacov Rydzinski Date: Thu, 12 Jun 2025 15:41:47 +0300 Subject: [PATCH 3/6] generate npm export paths correctly from windows --- resources/build-npm.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/resources/build-npm.ts b/resources/build-npm.ts index 81d2872e55..b492dcb86b 100644 --- a/resources/build-npm.ts +++ b/resources/build-npm.ts @@ -87,7 +87,8 @@ async function buildPackage(outDir: string): Promise { for (const filepath of emittedTSFiles) { if (path.basename(filepath) === 'index.js') { - const relativePath = './' + path.relative('./npmDist', filepath); + const relativePath = + './' + crossPlatformRelativePath('./npmDist', filepath); packageJSON.exports[path.dirname(relativePath)] = relativePath; } } @@ -140,3 +141,11 @@ function emitTSFiles(options: { emittedTSFiles: tsResult.emittedFiles.sort((a, b) => a.localeCompare(b)), }; } + +function crossPlatformRelativePath(from: string, to: string): string { + const relativePath = path.relative(from, to); + if (process.platform !== 'win32') { + return relativePath; + } + return path.posix.format({ ...path.parse(relativePath), root: '' }); +} From ef851e058a26b68058af7a71879de25f5af508c9 Mon Sep 17 00:00:00 2001 From: Yaacov Rydzinski Date: Thu, 19 Jun 2025 06:01:23 +0300 Subject: [PATCH 4/6] use dev/prod condition to trigger instanceOf check --- package.json | 4 +- resources/build-npm.ts | 71 ++++++++++++++++++++---- resources/utils.ts | 10 +++- src/dev/README.md | 10 ++++ src/dev/index.ts | 46 +++++++++++++++ src/jsutils/__tests__/instanceOf-test.ts | 7 ++- src/jsutils/instanceOf.ts | 71 +++++++----------------- 7 files changed, 155 insertions(+), 64 deletions(-) create mode 100644 src/dev/README.md create mode 100644 src/dev/index.ts diff --git a/package.json b/package.json index dace8eb888..187e797f8c 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,9 @@ ] } }, - "sideEffects": false, + "sideEffects": [ + "./dev/index.js" + ], "homepage": "https://github.com/graphql/graphql-js", "bugs": { "url": "https://github.com/graphql/graphql-js/issues" diff --git a/resources/build-npm.ts b/resources/build-npm.ts index b492dcb86b..899e07e3d1 100644 --- a/resources/build-npm.ts +++ b/resources/build-npm.ts @@ -19,11 +19,14 @@ await buildPackage('./npmDist'); showDirStats('./npmDist'); async function buildPackage(outDir: string): Promise { + const devDir = path.join(outDir, '__dev__'); + fs.rmSync(outDir, { recursive: true, force: true }); fs.mkdirSync(outDir); + fs.mkdirSync(devDir); - fs.copyFileSync('./LICENSE', `./${outDir}/LICENSE`); - fs.copyFileSync('./README.md', `./${outDir}/README.md`); + fs.copyFileSync('./LICENSE', `${outDir}/LICENSE`); + fs.copyFileSync('./README.md', `${outDir}/README.md`); const packageJSON = readPackageJSON(); @@ -85,17 +88,61 @@ async function buildPackage(outDir: string): Promise { extension: '.js', }); - for (const filepath of emittedTSFiles) { - if (path.basename(filepath) === 'index.js') { - const relativePath = - './' + crossPlatformRelativePath('./npmDist', filepath); - packageJSON.exports[path.dirname(relativePath)] = relativePath; + for (const prodFile of emittedTSFiles) { + const { dir, base } = path.parse(prodFile); + + const match = base.match(/^([^.]*)\.?(.*)$/); + assert(match); + const [, name, ext] = match; + + if (ext === 'js.map') { + continue; + } else if (path.basename(dir) === 'dev') { + packageJSON.exports['./dev'] = './dev/index.js'; + continue; + } + + const relativePathToProd = crossPlatformRelativePath(prodFile, outDir); + const relativePathAndName = crossPlatformRelativePath( + outDir, + `${dir}/${name}`, + ); + + const lines = + ext === 'd.ts' ? [] : [`import '${relativePathToProd}/dev/index.js';`]; + lines.push( + `export * from '${relativePathToProd}/${relativePathAndName}.js';`, + ); + const body = lines.join('\n'); + + writeGeneratedFile( + path.join(devDir, path.relative(outDir, prodFile)), + body, + ); + + if (base === 'index.js') { + const dirname = path.dirname(relativePathAndName); + packageJSON.exports[dirname === '.' ? dirname : `./${dirname}`] = { + development: `./__dev__/${relativePathAndName}.js`, + default: `./${relativePathAndName}.js`, + }; } } // Temporary workaround to allow "internal" imports, no grantees provided - packageJSON.exports['./*.js'] = './*.js'; - packageJSON.exports['./*'] = './*.js'; + packageJSON.exports['./*.js'] = { + development: './__dev__/*.js', + default: './*.js', + }; + packageJSON.exports['./*'] = { + development: './__dev__/*.js', + default: './*.js', + }; + + packageJSON.sideEffects = [ + ...(packageJSON.sideEffects as Array), + '__dev__/*', + ]; const packageJsonPath = `./${outDir}/package.json`; const prettified = await prettify( @@ -127,7 +174,11 @@ function emitTSFiles(options: { const tsHost = ts.createCompilerHost(tsOptions); tsHost.writeFile = (filepath, body) => writeGeneratedFile(filepath, body); - const tsProgram = ts.createProgram(['src/index.ts'], tsOptions, tsHost); + const tsProgram = ts.createProgram( + ['src/index.ts', 'src/dev/index.ts'], + tsOptions, + tsHost, + ); const tsResult = tsProgram.emit(undefined, undefined, undefined, undefined, { after: [changeExtensionInImportPaths({ extension }), inlineInvariant], }); diff --git a/resources/utils.ts b/resources/utils.ts index 4291ddc20a..0b3b821568 100644 --- a/resources/utils.ts +++ b/resources/utils.ts @@ -234,7 +234,15 @@ interface PackageJSON { repository?: { url?: string }; scripts?: { [name: string]: string }; type?: string; - exports: { [path: string]: string }; + sideEffects?: boolean | Array; + exports: { + [path: string]: + | string + | { + development: string; + default: string; + }; + }; types?: string; typesVersions: { [ranges: string]: { [path: string]: Array } }; devDependencies?: { [name: string]: string }; diff --git a/src/dev/README.md b/src/dev/README.md new file mode 100644 index 0000000000..78d0588666 --- /dev/null +++ b/src/dev/README.md @@ -0,0 +1,10 @@ +## GraphQL Dev + +The `graphql/dev` module is responsible for enabling development mode. + +In development mode, GraphQL.js provides additional checks to help avoid developer errors. + +```js +import 'graphql/dev'; // ES6 +require('graphql/dev'); // CommonJS +``` diff --git a/src/dev/index.ts b/src/dev/index.ts new file mode 100644 index 0000000000..9cf01de4a7 --- /dev/null +++ b/src/dev/index.ts @@ -0,0 +1,46 @@ +import { inspect } from '../jsutils/inspect.js'; +import type { Constructor } from '../jsutils/instanceOf.js'; + +/** + * An additional check to be included within instanceOf warning when + * multi-realm constructors are detected. + * + * This additional check will be included: + * 1. if 'graphql/development.js' is imported explicitly prior to all + * other imports from this library, or + * 2. if the "development" condition is set. + */ +export function developmentInstanceOfCheck( + value: unknown, + constructor: Constructor, +): void { + if (typeof value === 'object' && value !== null) { + // Prefer Symbol.toStringTag since it is immune to minification. + const className = constructor.prototype[Symbol.toStringTag]; + const valueClassName = + // We still need to support constructor's name to detect conflicts with older versions of this library. + Symbol.toStringTag in value + ? value[Symbol.toStringTag] + : value.constructor?.name; + if (className === valueClassName) { + const stringifiedValue = inspect(value); + throw new Error( + `Cannot use ${className} "${stringifiedValue}" from another module or realm. + +Ensure that there is only one instance of "graphql" in the node_modules +directory. If different versions of "graphql" are the dependencies of other +relied on modules, use "resolutions" to ensure only one version is installed. + +https://yarnpkg.com/en/docs/selective-version-resolutions + +Duplicate "graphql" modules cannot be used at the same time since different +versions may have different capabilities and behavior. The data from one +version used in the function from another could produce confusing and +spurious results.`, + ); + } + } +} + +(globalThis as any)[Symbol.for('graphql.instanceOfCheck')] = + developmentInstanceOfCheck; diff --git a/src/jsutils/__tests__/instanceOf-test.ts b/src/jsutils/__tests__/instanceOf-test.ts index 5a54a641e5..198a2524f3 100644 --- a/src/jsutils/__tests__/instanceOf-test.ts +++ b/src/jsutils/__tests__/instanceOf-test.ts @@ -1,7 +1,12 @@ import { expect } from 'chai'; import { describe, it } from 'mocha'; -import { instanceOf } from '../instanceOf.js'; +import { developmentInstanceOfCheck } from '../../dev/index.js'; + +(globalThis as any)[Symbol.for('graphql.instanceOfCheck')] = + developmentInstanceOfCheck; + +const { instanceOf } = await import(`../instanceOf.js?ts${Date.now()}`); describe('instanceOf', () => { it('do not throw on values without prototype', () => { diff --git a/src/jsutils/instanceOf.ts b/src/jsutils/instanceOf.ts index 66811433ae..dea65be9c9 100644 --- a/src/jsutils/instanceOf.ts +++ b/src/jsutils/instanceOf.ts @@ -1,58 +1,27 @@ -import { inspect } from './inspect.js'; - -/* c8 ignore next 3 */ -const isProduction = - globalThis.process != null && - // eslint-disable-next-line no-undef - process.env.NODE_ENV === 'production'; - /** - * A replacement for instanceof which includes an error warning when multi-realm - * constructors are detected. - * See: https://expressjs.com/en/advanced/best-practice-performance.html#set-node_env-to-production - * See: https://webpack.js.org/guides/production/ + * "src/development.ts" includes an additional check for development mode + * which throws on multiple versions of graphql-js. + * + * This additional check will be included: + * 1. if 'graphql/dev' is imported explicitly prior to all + * other imports from this library, or + * 2. if the "development" condition is set. */ -export const instanceOf: (value: unknown, constructor: Constructor) => boolean = - /* c8 ignore next 6 */ - // FIXME: https://github.com/graphql/graphql-js/issues/2317 - isProduction - ? function instanceOf(value: unknown, constructor: Constructor): boolean { - return value instanceof constructor; - } - : function instanceOf(value: unknown, constructor: Constructor): boolean { - if (value instanceof constructor) { - return true; - } - if (typeof value === 'object' && value !== null) { - // Prefer Symbol.toStringTag since it is immune to minification. - const className = constructor.prototype[Symbol.toStringTag]; - const valueClassName = - // We still need to support constructor's name to detect conflicts with older versions of this library. - Symbol.toStringTag in value - ? value[Symbol.toStringTag] - : value.constructor?.name; - if (className === valueClassName) { - const stringifiedValue = inspect(value); - throw new Error( - `Cannot use ${className} "${stringifiedValue}" from another module or realm. - -Ensure that there is only one instance of "graphql" in the node_modules -directory. If different versions of "graphql" are the dependencies of other -relied on modules, use "resolutions" to ensure only one version is installed. +const check: (_value: unknown, _constructor: Constructor) => void = + (globalThis as any)[Symbol.for('graphql.instanceOfCheck')] ?? + ((_value: unknown, _constructor: Constructor) => { + /* no-op */ + }); -https://yarnpkg.com/en/docs/selective-version-resolutions - -Duplicate "graphql" modules cannot be used at the same time since different -versions may have different capabilities and behavior. The data from one -version used in the function from another could produce confusing and -spurious results.`, - ); - } - } - return false; - }; +export function instanceOf(value: unknown, constructor: Constructor): boolean { + if (value instanceof constructor) { + return true; + } + check(value, constructor); + return false; +} -interface Constructor { +export interface Constructor { prototype: { [Symbol.toStringTag]: string; }; From 7445e8e446c9240b9ecc71e79f517335646e19a1 Mon Sep 17 00:00:00 2001 From: Yaacov Rydzinski Date: Thu, 19 Jun 2025 06:02:27 +0300 Subject: [PATCH 5/6] update docs --- cspell.yml | 3 +- website/pages/docs/_meta.ts | 1 + website/pages/docs/development-mode.mdx | 191 +++++++++++++++++++++ website/pages/docs/going-to-production.mdx | 137 +-------------- website/pages/upgrade-guides/v16-v17.mdx | 21 +++ 5 files changed, 220 insertions(+), 133 deletions(-) create mode 100644 website/pages/docs/development-mode.mdx diff --git a/cspell.yml b/cspell.yml index 3870f6f531..20725dc801 100644 --- a/cspell.yml +++ b/cspell.yml @@ -40,7 +40,8 @@ overrides: - tada - Graphile - precompiled - - debuggable + - Rollup + - Turbopack validateDirectives: true ignoreRegExpList: diff --git a/website/pages/docs/_meta.ts b/website/pages/docs/_meta.ts index 2e6156976b..5006571650 100644 --- a/website/pages/docs/_meta.ts +++ b/website/pages/docs/_meta.ts @@ -41,6 +41,7 @@ const meta = { type: 'separator', title: 'Production & Scaling', }, + 'development-mode': '', 'going-to-production': '', 'scaling-graphql': '', }; diff --git a/website/pages/docs/development-mode.mdx b/website/pages/docs/development-mode.mdx new file mode 100644 index 0000000000..7ee7a76959 --- /dev/null +++ b/website/pages/docs/development-mode.mdx @@ -0,0 +1,191 @@ +--- +title: Development Mode +--- + +# Development Mode + +In development mode, GraphQL.JS can provides additional runtime check appropriate for +development-time errors, including primarily the erroneous inclusion of multiple +GraphQL.JS modules. + +Unlike earlier versions of GraphQL.JS, by default, development mode is disabled. +This is to best ensure that production builds do not incur the performance and bundle +size penalties associated with the additional checks. + +Also, unlike earlier versions, development mode is not configured by use of +environment variables, which are accessed in disparate ways in varying environments. +In particular, the `NODE_ENV` environment variable now has no effect on triggering +development mode. Rather, development mode is either enabled: +1. explicitly, by importing `graphql/dev` prior to other Graphql.JS imports or +2. implicitly, by setting the 'development' condition, which is possible only in + environments that support `package.json` conditional exports and custom conditions. + +Conditional exports are supported by: Node.js, Deno (canary), Bun, Webpack 5, Rollup +(via the `node-resolve` plugin), esbuild, and Vite. create-react-app and Next.js +support conditional exports when using Webpack 5 as their bundler. + +Conditional exports are not supported by Deno (current), Webpack 4, Rollup (without +the `node-resolve` plugin), or swc. create-react-app and Next.js do not support +conditional exports when using Webpack 4 as their bundler, nor does Next.js yet +support conditional exports when using Turbopack (see +https://github.com/vercel/next.js/discussions/78912). + +We encourage enabling development mode within the development environments, either +explicitly or implicitly. Additional development-time checks may also be added in the future. +For now, the primary check is to ensure that only a single GraphQL.JS module is used. +First, we will discuss the implications of using multiple GraphQL.JS modules, and then +we will share additional details for how to enable development mode in various environments. + +## Multiple Graphql.JS Modules + +Only a single GraphQL.JS can be used within a project. Different GraphQL.JS versions cannot be +used at the same time since different versions may have different capabilities and behavior. +The data from one version used in the function from another could produce confusing and spurious +results. + +Duplicate modules of GraphQL.JS of the same version may also fail at runtime, sometimes in +unexpected ways. This is because GraphQL.JS relies on the identity of the module for +key features. Most significantly, `instanceof` checks are used throughout GraphQL.JS to +identify and distinguish between GraphQL schema elements, such as the particular GraphQL type. +Also, special exported constants like `BREAK` allow library users to manipulate visitor +behavior, also relying on the module identity. + +To ensure that only a single GraphQL.JS module is used, all libraries depending on GraphQL.JS +should use the appropriate peer dependency mechanism, as provided by their package manager, +bundler, build tool, or runtime environment. + +In development, GraphQL.JS provides a validation check triggered by any use of `instanceof` +that should catch most cases of multiple GraphQL.js modules being used in the same project. + +This additional validation is unnecessary in production, where the GraphQL.js library is +expected to be have been setup correctly as a single module. So as to avoid the performance +and bundle size overhead this check entails, it is only included when the `development` +exports condition is explicitly enabled or when the `graphql/dev` module is +explicitly imported before any other GraphQL.JS import. + +## Enabling Development Mode + +### A Catch-All Option: Explicit Enabling of Development Mode + +Development mode is activated by importing the `graphql/dev` module before +any other GraphQL.js import. Introducing a new bootstrapping entrypoint simplifies this +workflow: + +```js +// bootstrap.js +import 'graphql/dev'; +import './path/to/my/original-entry-point.js'; +``` + +The bootstrapping file can be used to enable development mode conditionally: + +```js +import process from 'node:process'; +// bootstrap.js +if (process.env.NODE_ENV === 'development') { + await import('graphql/dev'); +} +import './path/to/my/entry.js'; +``` + +The above boiler plate is compatible with Node.js; the exact environment variable and method +of accessing it depends on the individual environment and desired variable. + + +### Conditional Exports and Implicit Enabling of Development Mode + +Depending on your environment, you may be able to use the 'development' condition +to enable development mode without the need for an explicit import. + +### Node.js + +In Node.js, the development condition can be enabled by passing the `--conditions=development` +flag to the Node.js runtime. This can be done within `package.json` scripts: + +```json +{ + "scripts": { + "start": "node --conditions=development index.js" + } +} +``` + +Alternatively, this can be included within the `NODE_OPTIONS` environment variable: + +```bash +export NODE_OPTIONS="--conditions=development" +``` + +#### Deno + +In Deno, conditional exports are not yet released, but are available within the canary build +(see https://github.com/denoland/deno/issues/23757) as follows: + +```bash +deno run --unstable-node-conditions=development main.js +``` + +#### Bun + +In Bun, you can enable the development condition by passing the `--conditions=development` flag +when running your script: + +```bash +bun --conditions=development main.js +``` + +#### Webpack + +Webpack 5 supports the 'development' condition natively and requires no additional configuration. + +### Rollup + +Rollup supports the 'development' condition only when using the `@rollup/plugin-node-resolve` plugin. + +```ts +//rollup.config.js +import resolve from '@rollup/plugin-node-resolve'; + +export default { + plugins: [ + resolve({ + exportConditions: ['development'], + }) + ] +}; +``` + +### esbuild + +When using esbuild, you can enable the 'development' condition by setting the `--conditions=development` +flag in your build command: + +```bash +esbuild --conditions=development entrypoint.js +``` + +Note that setting any custom conditions will drop the default 'module' condition (used to avoid the dual +package hazard), so you may need to use: + +```bash +esbuild --conditions=development,module entrypoint.js +``` + +See further discussion within the [esbuild documentation](https://esbuild.github.io/api/#conditions) for +more details. + +### Vite + +Vite supports the 'development' condition natively and requires no additional configuration. + +### Next.js + +When using Webpack 5 as its bundler, Next.js supports the 'development' condition natively +and require no additional configuration. When using Webpack 4 or Turbopack, development mode +must be enabled explicitly. + +### create-react-app + +When using Webpack 5 as its bundler, create-react-app support the 'development' condition +natively and requires no additional configuration. When using Webpack 4, development mode +must be enabled explicitly. \ No newline at end of file diff --git a/website/pages/docs/going-to-production.mdx b/website/pages/docs/going-to-production.mdx index da69a36942..3054b4855c 100644 --- a/website/pages/docs/going-to-production.mdx +++ b/website/pages/docs/going-to-production.mdx @@ -4,142 +4,15 @@ title: Going to Production # Going to Production -GraphQL.JS contains a few development checks which in production will cause slower performance and -an increase in bundle-size. Every bundler goes about these changes different, in here we'll list -out the most popular ones. +GraphQL.JS contains a few development-mode checks which cause slower performance and an increase +in bundle size. As discussing within the [Development Mode](./development-mode) section, +development mode is disabled by default, but if you have enabled it in your development +environment, verify that you have not also enabled it in production. -GraphQL.js includes development-time checks that are useful during local testing but should -be disabled in production to reduce overhead. Additional concerns include caching, error handling, -schema management, and operational monitoring. +Additional concerns include caching, error handling, schema management, and operational monitoring. This guide covers key practices to prepare a server built with GraphQL.js for production use. -## Optimize your build for production - -In development, GraphQL.js includes validation checks to catch common mistakes like invalid schemas -or resolver returns. These checks are not needed in production and can increase runtime overhead. - -You can disable them by setting `process.env.NODE_ENV` to `'production'` during your build process. -GraphQL.js will automatically skip over development-only code paths. - -Bundlers are tools that compile and optimize JavaScript for deployment. Most can be configured to -replace environment variables such as `process.env.NODE_ENV` at build time, -allowing for unused code (such as development only code paths) to be elided by -minification tools. - -### Bundler configuration examples - -The following examples show how to configure common bundlers to set `process.env.NODE_ENV` -and remove development-only code: - -#### Vite - -```js -// vite.config.js -import { defineConfig } from 'vite'; - -export default defineConfig({ - define: { - 'process.env.NODE_ENV': '"production"', - }, -}); -``` - -#### Next.js - -When you build your application with `next build` and run it using `next start`, Next.js sets -`process.env.NODE_ENV` to `'production'` automatically. No additional configuration is required. - -```bash -next build -next start -``` - -If you run a custom server, make sure `NODE_ENV` is set manually. - -#### Create React App (CRA) - -To customize Webpack behavior in CRA, you can use a tool like [`craco`](https://craco.js.org/). -This example uses CommonJS syntax instead of ESM syntax, which is required by `craco.config.js`: - -```js -// craco.config.js -const webpack = require('webpack'); - -module.exports = { - webpack: { - plugins: [ - new webpack.DefinePlugin({ - 'globalThis.process': JSON.stringify(true), - 'process.env.NODE_ENV': JSON.stringify('production'), - }), - ], - }, -}; -``` - -#### esbuild - -```json -{ - "define": { - "globalThis.process": true, - "process.env.NODE_ENV": "production" - } -} -``` - -#### Webpack - -```js -// webpack.config.js -import { fileURLToPath } from 'url'; -import { dirname } from 'path'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -export default { - mode: 'production', // Automatically sets NODE_ENV - context: __dirname, -}; -``` - -#### Rollup - -```js -// rollup.config.js -import replace from '@rollup/plugin-replace'; - -export default { - plugins: [ - replace({ - preventAssignment: true, - 'process.env.NODE_ENV': JSON.stringify('production'), - }), - ], -}; -``` - -#### SWC - -```json filename=".swcrc" -{ - "jsc": { - "transform": { - "optimizer": { - "globals": { - "vars": { - "globalThis.process": true, - "process.env.NODE_ENV": "production" - } - } - } - } - } -} -``` - ## Secure your schema GraphQL gives clients a lot of flexibility, which can be a strength or a liability depending on diff --git a/website/pages/upgrade-guides/v16-v17.mdx b/website/pages/upgrade-guides/v16-v17.mdx index df97e69606..95b663862a 100644 --- a/website/pages/upgrade-guides/v16-v17.mdx +++ b/website/pages/upgrade-guides/v16-v17.mdx @@ -12,6 +12,27 @@ import { Callout } from 'nextra/components' # Breaking changes +## `graphql-js` === ESM-only secondary to improved `require(esm)` support + +`graphql-js` has been converted to an ESM only library. Previously, the library included both CJS and ESM versions. This is primarily +motivated by the release of unflagged support for synchronously `require()`-ing ESM-only modules (that --like our library-- do not use +top-level `await`). + +In conjunction with this change, we have bumped the minimum required Node.JS versions to `20.19.0`, `22.12.0`, or `23.0.0` and above. + +For more information, see: +- https://joyeecheung.github.io/blog/2024/03/18/require-esm-in-node-js/ +- https://nodejs.org/en/blog/release/v23.0.0 +- https://nodejs.org/en/blog/release/v22.12.0 +- https://nodejs.org/en/blog/release/v20.19.0 + +This exact behavior is also supported in other runtimes such as [Bun](https://bun.sh/docs/runtime/modules#top-level-await) and +[Deno](https://docs.deno.com/runtime/fundamentals/node/#require(esm)). + +## Development checks no longer depend on the `NODE_ENV` environment variable. + +... + ## Default values GraphQL schemas allow default values for input fields and arguments. Historically, GraphQL.js did not rigorously validate or coerce these From c28e741da4c2320412ea7a17314b7888d41b286c Mon Sep 17 00:00:00 2001 From: Yaacov Rydzinski Date: Thu, 19 Jun 2025 06:03:04 +0300 Subject: [PATCH 6/6] update integration tests --- cspell.yml | 4 +- integrationTests/README.md | 45 ++++++++++++++++++- integrationTests/dev-bun/cjs-module.cjs | 8 ++++ integrationTests/dev-bun/esm-module.js | 1 + integrationTests/dev-bun/package.json | 10 +++++ integrationTests/dev-bun/test.js | 27 +++++++++++ .../dev-deno-explicit/cjs-module.cjs | 8 ++++ .../dev-deno-explicit/esm-module.js | 1 + .../dev-deno-explicit/package.json | 10 +++++ integrationTests/dev-deno-explicit/test.js | 29 ++++++++++++ .../dev-deno-implicit/cjs-module.cjs | 8 ++++ .../dev-deno-implicit/esm-module.js | 1 + .../dev-deno-implicit/package.json | 10 +++++ integrationTests/dev-deno-implicit/test.js | 27 +++++++++++ integrationTests/dev-esbuild/package.json | 13 ++++++ .../dev-esbuild/src/cjs-module.cjs | 8 ++++ .../dev-esbuild/src/esm-module.js | 1 + integrationTests/dev-esbuild/src/index.js | 27 +++++++++++ .../dev-node-explicit/bootstrap.js | 3 ++ .../dev-node-explicit/cjs-module.cjs | 8 ++++ .../dev-node-explicit/esm-module.js | 1 + .../dev-node-explicit/package.json | 11 +++++ integrationTests/dev-node-explicit/test.js | 27 +++++++++++ .../dev-node-implicit/cjs-module.cjs | 8 ++++ .../dev-node-implicit/esm-module.js | 1 + .../dev-node-implicit/package.json | 11 +++++ integrationTests/dev-node-implicit/test.js | 27 +++++++++++ integrationTests/dev-rollup/package.json | 14 ++++++ integrationTests/dev-rollup/rollup.config.js | 18 ++++++++ .../dev-rollup/src/cjs-module.cjs | 8 ++++ integrationTests/dev-rollup/src/esm-module.js | 1 + integrationTests/dev-rollup/src/index.js | 27 +++++++++++ integrationTests/dev-rspack/package.json | 12 +++++ integrationTests/dev-rspack/rspack.config.js | 20 +++++++++ .../dev-rspack/src/cjs-module.cjs | 8 ++++ integrationTests/dev-rspack/src/esm-module.js | 1 + integrationTests/dev-rspack/src/index.js | 27 +++++++++++ integrationTests/dev-swc/.swcrc | 12 +++++ integrationTests/dev-swc/bootstrap.js | 4 ++ integrationTests/dev-swc/package.json | 14 ++++++ integrationTests/dev-swc/src/cjs-module.cjs | 8 ++++ integrationTests/dev-swc/src/esm-module.js | 1 + integrationTests/dev-swc/src/index.js | 27 +++++++++++ integrationTests/dev-vitest/cjs-module.cjs | 8 ++++ integrationTests/dev-vitest/esm-module.js | 1 + integrationTests/dev-vitest/index.test.js | 24 ++++++++++ integrationTests/dev-vitest/package.json | 12 +++++ integrationTests/dev-vitest/vitest.config.js | 14 ++++++ integrationTests/dev-webpack/package.json | 12 +++++ .../dev-webpack/src/cjs-module.cjs | 8 ++++ .../dev-webpack/src/esm-module.js | 1 + integrationTests/dev-webpack/src/index.js | 27 +++++++++++ .../dev-webpack/webpack.config.js | 20 +++++++++ integrationTests/prod-bun/cjs-module.cjs | 8 ++++ integrationTests/prod-bun/esm-module.js | 1 + integrationTests/prod-bun/package.json | 10 +++++ integrationTests/prod-bun/test.js | 20 +++++++++ integrationTests/prod-deno/cjs-module.cjs | 8 ++++ integrationTests/prod-deno/esm-module.js | 1 + integrationTests/prod-deno/package.json | 10 +++++ integrationTests/prod-deno/test.js | 21 +++++++++ integrationTests/prod-esbuild/package.json | 13 ++++++ .../prod-esbuild/src/cjs-module.cjs | 8 ++++ .../prod-esbuild/src/esm-module.js | 1 + integrationTests/prod-esbuild/src/index.js | 23 ++++++++++ integrationTests/prod-node/cjs-module.cjs | 8 ++++ integrationTests/prod-node/esm-module.js | 1 + integrationTests/prod-node/package.json | 11 +++++ integrationTests/prod-node/test.js | 23 ++++++++++ integrationTests/prod-rollup/package.json | 14 ++++++ integrationTests/prod-rollup/rollup.config.js | 10 +++++ .../prod-rollup/src/cjs-module.cjs | 8 ++++ .../prod-rollup/src/esm-module.js | 1 + integrationTests/prod-rollup/src/index.js | 23 ++++++++++ integrationTests/prod-rspack/package.json | 12 +++++ integrationTests/prod-rspack/rspack.config.js | 16 +++++++ .../prod-rspack/src/cjs-module.cjs | 8 ++++ .../prod-rspack/src/esm-module.js | 1 + integrationTests/prod-rspack/src/index.js | 23 ++++++++++ integrationTests/prod-swc/.swcrc | 12 +++++ integrationTests/prod-swc/package.json | 14 ++++++ integrationTests/prod-swc/src/cjs-module.cjs | 8 ++++ integrationTests/prod-swc/src/esm-module.js | 1 + integrationTests/prod-swc/src/index.js | 21 +++++++++ integrationTests/prod-webpack/package.json | 12 +++++ .../prod-webpack/src/cjs-module.cjs | 8 ++++ .../prod-webpack/src/esm-module.js | 1 + integrationTests/prod-webpack/src/index.js | 23 ++++++++++ .../prod-webpack/webpack.config.js | 16 +++++++ resources/integration-test.ts | 23 ++++++++++ website/pages/docs/development-mode.mdx | 4 +- 91 files changed, 1086 insertions(+), 4 deletions(-) create mode 100644 integrationTests/dev-bun/cjs-module.cjs create mode 100644 integrationTests/dev-bun/esm-module.js create mode 100644 integrationTests/dev-bun/package.json create mode 100644 integrationTests/dev-bun/test.js create mode 100644 integrationTests/dev-deno-explicit/cjs-module.cjs create mode 100644 integrationTests/dev-deno-explicit/esm-module.js create mode 100644 integrationTests/dev-deno-explicit/package.json create mode 100644 integrationTests/dev-deno-explicit/test.js create mode 100644 integrationTests/dev-deno-implicit/cjs-module.cjs create mode 100644 integrationTests/dev-deno-implicit/esm-module.js create mode 100644 integrationTests/dev-deno-implicit/package.json create mode 100644 integrationTests/dev-deno-implicit/test.js create mode 100644 integrationTests/dev-esbuild/package.json create mode 100644 integrationTests/dev-esbuild/src/cjs-module.cjs create mode 100644 integrationTests/dev-esbuild/src/esm-module.js create mode 100644 integrationTests/dev-esbuild/src/index.js create mode 100644 integrationTests/dev-node-explicit/bootstrap.js create mode 100644 integrationTests/dev-node-explicit/cjs-module.cjs create mode 100644 integrationTests/dev-node-explicit/esm-module.js create mode 100644 integrationTests/dev-node-explicit/package.json create mode 100644 integrationTests/dev-node-explicit/test.js create mode 100644 integrationTests/dev-node-implicit/cjs-module.cjs create mode 100644 integrationTests/dev-node-implicit/esm-module.js create mode 100644 integrationTests/dev-node-implicit/package.json create mode 100644 integrationTests/dev-node-implicit/test.js create mode 100644 integrationTests/dev-rollup/package.json create mode 100644 integrationTests/dev-rollup/rollup.config.js create mode 100644 integrationTests/dev-rollup/src/cjs-module.cjs create mode 100644 integrationTests/dev-rollup/src/esm-module.js create mode 100644 integrationTests/dev-rollup/src/index.js create mode 100644 integrationTests/dev-rspack/package.json create mode 100644 integrationTests/dev-rspack/rspack.config.js create mode 100644 integrationTests/dev-rspack/src/cjs-module.cjs create mode 100644 integrationTests/dev-rspack/src/esm-module.js create mode 100644 integrationTests/dev-rspack/src/index.js create mode 100644 integrationTests/dev-swc/.swcrc create mode 100644 integrationTests/dev-swc/bootstrap.js create mode 100644 integrationTests/dev-swc/package.json create mode 100644 integrationTests/dev-swc/src/cjs-module.cjs create mode 100644 integrationTests/dev-swc/src/esm-module.js create mode 100644 integrationTests/dev-swc/src/index.js create mode 100644 integrationTests/dev-vitest/cjs-module.cjs create mode 100644 integrationTests/dev-vitest/esm-module.js create mode 100644 integrationTests/dev-vitest/index.test.js create mode 100644 integrationTests/dev-vitest/package.json create mode 100644 integrationTests/dev-vitest/vitest.config.js create mode 100644 integrationTests/dev-webpack/package.json create mode 100644 integrationTests/dev-webpack/src/cjs-module.cjs create mode 100644 integrationTests/dev-webpack/src/esm-module.js create mode 100644 integrationTests/dev-webpack/src/index.js create mode 100644 integrationTests/dev-webpack/webpack.config.js create mode 100644 integrationTests/prod-bun/cjs-module.cjs create mode 100644 integrationTests/prod-bun/esm-module.js create mode 100644 integrationTests/prod-bun/package.json create mode 100644 integrationTests/prod-bun/test.js create mode 100644 integrationTests/prod-deno/cjs-module.cjs create mode 100644 integrationTests/prod-deno/esm-module.js create mode 100644 integrationTests/prod-deno/package.json create mode 100644 integrationTests/prod-deno/test.js create mode 100644 integrationTests/prod-esbuild/package.json create mode 100644 integrationTests/prod-esbuild/src/cjs-module.cjs create mode 100644 integrationTests/prod-esbuild/src/esm-module.js create mode 100644 integrationTests/prod-esbuild/src/index.js create mode 100644 integrationTests/prod-node/cjs-module.cjs create mode 100644 integrationTests/prod-node/esm-module.js create mode 100644 integrationTests/prod-node/package.json create mode 100644 integrationTests/prod-node/test.js create mode 100644 integrationTests/prod-rollup/package.json create mode 100644 integrationTests/prod-rollup/rollup.config.js create mode 100644 integrationTests/prod-rollup/src/cjs-module.cjs create mode 100644 integrationTests/prod-rollup/src/esm-module.js create mode 100644 integrationTests/prod-rollup/src/index.js create mode 100644 integrationTests/prod-rspack/package.json create mode 100644 integrationTests/prod-rspack/rspack.config.js create mode 100644 integrationTests/prod-rspack/src/cjs-module.cjs create mode 100644 integrationTests/prod-rspack/src/esm-module.js create mode 100644 integrationTests/prod-rspack/src/index.js create mode 100644 integrationTests/prod-swc/.swcrc create mode 100644 integrationTests/prod-swc/package.json create mode 100644 integrationTests/prod-swc/src/cjs-module.cjs create mode 100644 integrationTests/prod-swc/src/esm-module.js create mode 100644 integrationTests/prod-swc/src/index.js create mode 100644 integrationTests/prod-webpack/package.json create mode 100644 integrationTests/prod-webpack/src/cjs-module.cjs create mode 100644 integrationTests/prod-webpack/src/esm-module.js create mode 100644 integrationTests/prod-webpack/src/index.js create mode 100644 integrationTests/prod-webpack/webpack.config.js diff --git a/cspell.yml b/cspell.yml index 20725dc801..2bb5b85011 100644 --- a/cspell.yml +++ b/cspell.yml @@ -39,7 +39,7 @@ overrides: - URQL - tada - Graphile - - precompiled + - precompileds - Rollup - Turbopack @@ -68,6 +68,8 @@ words: # TODO: contribute upstream - deno - hashbang + - Rspack + - Rsbuild # Website tech - Nextra diff --git a/integrationTests/README.md b/integrationTests/README.md index 706449103a..8b17a1f24d 100644 --- a/integrationTests/README.md +++ b/integrationTests/README.md @@ -1 +1,44 @@ -# TBD +# Integration Tests + +This directory contains integration tests for GraphQL.js across different environments and bundlers, testing basic GraphQL.JS functionality, as well as development mode and production mode behavior. + +Tests are run via the main integration test suite in `resources/integration-test.ts`. + +## Test Structure + +### Basic GraphQL.JS Functionality Tests + +Each subdirectory represents a different environment/bundler: + +- `node` - tests for supported Node.js versions +- `ts` - tests for supported Typescript versions +- `webpack` - tests for Webpack + +### Verifying Development Mode Tests + +Each subdirectory represents a different environment/bundler demonstrating enabling development mode via conditional exports or by explicitly importing `graphql/dev`: + +- `dev-bun/`: via `bun --conditions=development test.js` +- `dev-deno-implicit`: via `deno run --unstable-node-conditions=development test.js` +- `dev-deno-explicit`: via `import 'graphql/dev'` +- `dev-node-implicit`: via `node --conditions=development test.js` +- `dev-node-explicit`: via `import 'graphql/dev'` +- `dev-webpack`: via `{resolve: { conditionNames: ['development'] } }` +- `dev-rspack`: via `{resolve: { conditionNames: ['development'] } }` +- `dev-esbuild`: via `esbuild --conditions=development test.js` +- `dev-rollup`: via `@rollup/plugin-node-resolve` with `conditions: ['development']` +- `dev-swc`: via `import 'graphql/dev'` +- `dev-vitest`: via `resolve.conditions: ['development']` + +### Verifying Production Mode Tests + +Each subdirectory represents a different environment/bundler demonstrating production mode when development mode is not enabled: + +- `prod-bun/`: via `bun test.js` +- `prod-deno`: via `deno run test.js` +- `prod-node`: via `node test.js` +- `prod-webpack`: via default Webpack configuration +- `prod-rspack`: via default Rspack configuration +- `prod-esbuild`: via `esbuild test.js` +- `prod-rollup`: via default Rollup configuration +- `prod-swc`: via default SWC configuration diff --git a/integrationTests/dev-bun/cjs-module.cjs b/integrationTests/dev-bun/cjs-module.cjs new file mode 100644 index 0000000000..8b0100a638 --- /dev/null +++ b/integrationTests/dev-bun/cjs-module.cjs @@ -0,0 +1,8 @@ +'use strict'; + +const { GraphQLSchema } = require('graphql'); + +// eslint-disable-next-line import/no-commonjs +module.exports = { + GraphQLSchema, +}; diff --git a/integrationTests/dev-bun/esm-module.js b/integrationTests/dev-bun/esm-module.js new file mode 100644 index 0000000000..8daec696ac --- /dev/null +++ b/integrationTests/dev-bun/esm-module.js @@ -0,0 +1 @@ +export { GraphQLSchema } from 'graphql'; diff --git a/integrationTests/dev-bun/package.json b/integrationTests/dev-bun/package.json new file mode 100644 index 0000000000..c11cb5263d --- /dev/null +++ b/integrationTests/dev-bun/package.json @@ -0,0 +1,10 @@ +{ + "description": "graphql-js development condition should work with Bun", + "private": true, + "scripts": { + "test": "docker run --rm --volume \"$PWD:/usr/src/app\" -w /usr/src/app oven/bun:latest bun --conditions=development test.js" + }, + "dependencies": { + "graphql": "file:../graphql.tgz" + } +} diff --git a/integrationTests/dev-bun/test.js b/integrationTests/dev-bun/test.js new file mode 100644 index 0000000000..85a0b6b80c --- /dev/null +++ b/integrationTests/dev-bun/test.js @@ -0,0 +1,27 @@ +import { isObjectType } from 'graphql'; + +import { GraphQLSchema as GraphQLSchemaFromCJS } from './cjs-module.cjs'; +import { GraphQLSchema as GraphQLSchemaFromESM } from './esm-module.js'; + +class GraphQLObjectType { + get [Symbol.toStringTag]() { + return 'GraphQLObjectType'; + } +} + +try { + isObjectType(new GraphQLObjectType()); + throw new Error( + 'Expected isObjectType to throw an error in Bun development mode.', + ); +} catch (error) { + if (!error.message.includes('from another module or realm')) { + throw error; + } +} + +if (GraphQLSchemaFromCJS !== GraphQLSchemaFromESM) { + throw new Error( + 'Dual package hazard detected: GraphQLSchema instances from CJS and ESM imports are not identical.', + ); +} diff --git a/integrationTests/dev-deno-explicit/cjs-module.cjs b/integrationTests/dev-deno-explicit/cjs-module.cjs new file mode 100644 index 0000000000..8b0100a638 --- /dev/null +++ b/integrationTests/dev-deno-explicit/cjs-module.cjs @@ -0,0 +1,8 @@ +'use strict'; + +const { GraphQLSchema } = require('graphql'); + +// eslint-disable-next-line import/no-commonjs +module.exports = { + GraphQLSchema, +}; diff --git a/integrationTests/dev-deno-explicit/esm-module.js b/integrationTests/dev-deno-explicit/esm-module.js new file mode 100644 index 0000000000..8daec696ac --- /dev/null +++ b/integrationTests/dev-deno-explicit/esm-module.js @@ -0,0 +1 @@ +export { GraphQLSchema } from 'graphql'; diff --git a/integrationTests/dev-deno-explicit/package.json b/integrationTests/dev-deno-explicit/package.json new file mode 100644 index 0000000000..5b316da3ad --- /dev/null +++ b/integrationTests/dev-deno-explicit/package.json @@ -0,0 +1,10 @@ +{ + "description": "graphql-js explicit development import should work with Deno", + "private": true, + "scripts": { + "test": "docker run --rm --volume \"$PWD:/usr/src/app\" -w /usr/src/app denoland/deno:latest deno run test.js" + }, + "dependencies": { + "graphql": "file:../graphql.tgz" + } +} diff --git a/integrationTests/dev-deno-explicit/test.js b/integrationTests/dev-deno-explicit/test.js new file mode 100644 index 0000000000..5d053902dd --- /dev/null +++ b/integrationTests/dev-deno-explicit/test.js @@ -0,0 +1,29 @@ +// eslint-disable-next-line import/no-unassigned-import, simple-import-sort/imports +import 'graphql/dev'; +import { isObjectType } from 'graphql'; + +import { GraphQLSchema as GraphQLSchemaFromCJS } from './cjs-module.cjs'; +import { GraphQLSchema as GraphQLSchemaFromESM } from './esm-module.js'; + +class GraphQLObjectType { + get [Symbol.toStringTag]() { + return 'GraphQLObjectType'; + } +} + +try { + isObjectType(new GraphQLObjectType()); + throw new Error( + 'Expected isObjectType to throw an error in Deno explicit dev import mode.', + ); +} catch (error) { + if (!error.message.includes('from another module or realm')) { + throw error; + } +} + +if (GraphQLSchemaFromCJS !== GraphQLSchemaFromESM) { + throw new Error( + 'Dual package hazard detected: GraphQLSchema instances from CJS and ESM imports are not identical.', + ); +} diff --git a/integrationTests/dev-deno-implicit/cjs-module.cjs b/integrationTests/dev-deno-implicit/cjs-module.cjs new file mode 100644 index 0000000000..8b0100a638 --- /dev/null +++ b/integrationTests/dev-deno-implicit/cjs-module.cjs @@ -0,0 +1,8 @@ +'use strict'; + +const { GraphQLSchema } = require('graphql'); + +// eslint-disable-next-line import/no-commonjs +module.exports = { + GraphQLSchema, +}; diff --git a/integrationTests/dev-deno-implicit/esm-module.js b/integrationTests/dev-deno-implicit/esm-module.js new file mode 100644 index 0000000000..8daec696ac --- /dev/null +++ b/integrationTests/dev-deno-implicit/esm-module.js @@ -0,0 +1 @@ +export { GraphQLSchema } from 'graphql'; diff --git a/integrationTests/dev-deno-implicit/package.json b/integrationTests/dev-deno-implicit/package.json new file mode 100644 index 0000000000..61da7bfc93 --- /dev/null +++ b/integrationTests/dev-deno-implicit/package.json @@ -0,0 +1,10 @@ +{ + "description": "graphql-js development condition should work with Deno", + "private": true, + "scripts": { + "test": "docker run --rm --volume \"$PWD:/usr/src/app\" -w /usr/src/app denoland/deno:latest deno run --unstable-node-conditions=development test.js" + }, + "dependencies": { + "graphql": "file:../graphql.tgz" + } +} diff --git a/integrationTests/dev-deno-implicit/test.js b/integrationTests/dev-deno-implicit/test.js new file mode 100644 index 0000000000..2dfa1b4780 --- /dev/null +++ b/integrationTests/dev-deno-implicit/test.js @@ -0,0 +1,27 @@ +import { isObjectType } from 'graphql'; + +import { GraphQLSchema as GraphQLSchemaFromCJS } from './cjs-module.cjs'; +import { GraphQLSchema as GraphQLSchemaFromESM } from './esm-module.js'; + +class GraphQLObjectType { + get [Symbol.toStringTag]() { + return 'GraphQLObjectType'; + } +} + +try { + isObjectType(new GraphQLObjectType()); + throw new Error( + 'Expected isObjectType to throw an error in Deno implicit dev mode.', + ); +} catch (error) { + if (!error.message.includes('from another module or realm')) { + throw error; + } +} + +if (GraphQLSchemaFromCJS !== GraphQLSchemaFromESM) { + throw new Error( + 'Dual package hazard detected: GraphQLSchema instances from CJS and ESM imports are not identical.', + ); +} diff --git a/integrationTests/dev-esbuild/package.json b/integrationTests/dev-esbuild/package.json new file mode 100644 index 0000000000..9149ebe4cf --- /dev/null +++ b/integrationTests/dev-esbuild/package.json @@ -0,0 +1,13 @@ +{ + "description": "graphql-js development condition should work with esbuild", + "private": true, + "type": "module", + "scripts": { + "build": "esbuild src/index.js --bundle --outfile=dist/bundle.js --format=esm --conditions=development,module", + "test": "npm run build && node dist/bundle.js" + }, + "dependencies": { + "graphql": "file:../graphql.tgz", + "esbuild": "^0.25.0" + } +} diff --git a/integrationTests/dev-esbuild/src/cjs-module.cjs b/integrationTests/dev-esbuild/src/cjs-module.cjs new file mode 100644 index 0000000000..8b0100a638 --- /dev/null +++ b/integrationTests/dev-esbuild/src/cjs-module.cjs @@ -0,0 +1,8 @@ +'use strict'; + +const { GraphQLSchema } = require('graphql'); + +// eslint-disable-next-line import/no-commonjs +module.exports = { + GraphQLSchema, +}; diff --git a/integrationTests/dev-esbuild/src/esm-module.js b/integrationTests/dev-esbuild/src/esm-module.js new file mode 100644 index 0000000000..8daec696ac --- /dev/null +++ b/integrationTests/dev-esbuild/src/esm-module.js @@ -0,0 +1 @@ +export { GraphQLSchema } from 'graphql'; diff --git a/integrationTests/dev-esbuild/src/index.js b/integrationTests/dev-esbuild/src/index.js new file mode 100644 index 0000000000..d212b55338 --- /dev/null +++ b/integrationTests/dev-esbuild/src/index.js @@ -0,0 +1,27 @@ +import { isObjectType } from 'graphql'; + +import { GraphQLSchema as GraphQLSchemaFromCJS } from './cjs-module.cjs'; +import { GraphQLSchema as GraphQLSchemaFromESM } from './esm-module.js'; + +class GraphQLObjectType { + get [Symbol.toStringTag]() { + return 'GraphQLObjectType'; + } +} + +try { + isObjectType(new GraphQLObjectType()); + throw new Error( + 'Expected isObjectType to throw an error in esbuild development mode.', + ); +} catch (error) { + if (!error.message.includes('from another module or realm')) { + throw error; + } +} + +if (GraphQLSchemaFromCJS !== GraphQLSchemaFromESM) { + throw new Error( + 'Dual package hazard detected: GraphQLSchema instances from CJS and ESM imports are not identical.', + ); +} diff --git a/integrationTests/dev-node-explicit/bootstrap.js b/integrationTests/dev-node-explicit/bootstrap.js new file mode 100644 index 0000000000..995fbbfb79 --- /dev/null +++ b/integrationTests/dev-node-explicit/bootstrap.js @@ -0,0 +1,3 @@ +/* eslint-disable import/no-unassigned-import */ +import 'graphql/dev'; +import './test.js'; diff --git a/integrationTests/dev-node-explicit/cjs-module.cjs b/integrationTests/dev-node-explicit/cjs-module.cjs new file mode 100644 index 0000000000..8b0100a638 --- /dev/null +++ b/integrationTests/dev-node-explicit/cjs-module.cjs @@ -0,0 +1,8 @@ +'use strict'; + +const { GraphQLSchema } = require('graphql'); + +// eslint-disable-next-line import/no-commonjs +module.exports = { + GraphQLSchema, +}; diff --git a/integrationTests/dev-node-explicit/esm-module.js b/integrationTests/dev-node-explicit/esm-module.js new file mode 100644 index 0000000000..8daec696ac --- /dev/null +++ b/integrationTests/dev-node-explicit/esm-module.js @@ -0,0 +1 @@ +export { GraphQLSchema } from 'graphql'; diff --git a/integrationTests/dev-node-explicit/package.json b/integrationTests/dev-node-explicit/package.json new file mode 100644 index 0000000000..d8d36e03ef --- /dev/null +++ b/integrationTests/dev-node-explicit/package.json @@ -0,0 +1,11 @@ +{ + "description": "explicit graphql-js development mode should work with node", + "private": true, + "type": "module", + "scripts": { + "test": "node bootstrap.js" + }, + "dependencies": { + "graphql": "file:../graphql.tgz" + } +} diff --git a/integrationTests/dev-node-explicit/test.js b/integrationTests/dev-node-explicit/test.js new file mode 100644 index 0000000000..654e717581 --- /dev/null +++ b/integrationTests/dev-node-explicit/test.js @@ -0,0 +1,27 @@ +import { isObjectType } from 'graphql'; + +import { GraphQLSchema as GraphQLSchemaFromCJS } from './cjs-module.cjs'; +import { GraphQLSchema as GraphQLSchemaFromESM } from './esm-module.js'; + +class GraphQLObjectType { + get [Symbol.toStringTag]() { + return 'GraphQLObjectType'; + } +} + +try { + isObjectType(new GraphQLObjectType()); + throw new Error( + 'Expected isObjectType to throw an error in Node.js explicit dev import mode.', + ); +} catch (error) { + if (!error.message.includes('from another module or realm')) { + throw error; + } +} + +if (GraphQLSchemaFromCJS !== GraphQLSchemaFromESM) { + throw new Error( + 'Dual package hazard detected: GraphQLSchema instances from CJS and ESM imports are not identical.', + ); +} diff --git a/integrationTests/dev-node-implicit/cjs-module.cjs b/integrationTests/dev-node-implicit/cjs-module.cjs new file mode 100644 index 0000000000..8b0100a638 --- /dev/null +++ b/integrationTests/dev-node-implicit/cjs-module.cjs @@ -0,0 +1,8 @@ +'use strict'; + +const { GraphQLSchema } = require('graphql'); + +// eslint-disable-next-line import/no-commonjs +module.exports = { + GraphQLSchema, +}; diff --git a/integrationTests/dev-node-implicit/esm-module.js b/integrationTests/dev-node-implicit/esm-module.js new file mode 100644 index 0000000000..8daec696ac --- /dev/null +++ b/integrationTests/dev-node-implicit/esm-module.js @@ -0,0 +1 @@ +export { GraphQLSchema } from 'graphql'; diff --git a/integrationTests/dev-node-implicit/package.json b/integrationTests/dev-node-implicit/package.json new file mode 100644 index 0000000000..be8c0511ec --- /dev/null +++ b/integrationTests/dev-node-implicit/package.json @@ -0,0 +1,11 @@ +{ + "description": "graphql-js development condition should work with node", + "private": true, + "type": "module", + "scripts": { + "test": "node --conditions=development test.js" + }, + "dependencies": { + "graphql": "file:../graphql.tgz" + } +} diff --git a/integrationTests/dev-node-implicit/test.js b/integrationTests/dev-node-implicit/test.js new file mode 100644 index 0000000000..59e2ed5e20 --- /dev/null +++ b/integrationTests/dev-node-implicit/test.js @@ -0,0 +1,27 @@ +import { isObjectType } from 'graphql'; + +import { GraphQLSchema as GraphQLSchemaFromCJS } from './cjs-module.cjs'; +import { GraphQLSchema as GraphQLSchemaFromESM } from './esm-module.js'; + +class GraphQLObjectType { + get [Symbol.toStringTag]() { + return 'GraphQLObjectType'; + } +} + +try { + isObjectType(new GraphQLObjectType()); + throw new Error( + 'Expected isObjectType to throw an error in Node.js implicit dev mode.', + ); +} catch (error) { + if (!error.message.includes('from another module or realm')) { + throw error; + } +} + +if (GraphQLSchemaFromCJS !== GraphQLSchemaFromESM) { + throw new Error( + 'Dual package hazard detected: GraphQLSchema instances from CJS and ESM imports are not identical.', + ); +} diff --git a/integrationTests/dev-rollup/package.json b/integrationTests/dev-rollup/package.json new file mode 100644 index 0000000000..30b68d8c5e --- /dev/null +++ b/integrationTests/dev-rollup/package.json @@ -0,0 +1,14 @@ +{ + "description": "graphql-js development condition should work with Rollup", + "private": true, + "type": "module", + "scripts": { + "build": "rollup -c", + "test": "npm run build && node dist/bundle.js" + }, + "dependencies": { + "graphql": "file:../graphql.tgz", + "rollup": "^4.0.0", + "@rollup/plugin-node-resolve": "^15.0.0" + } +} diff --git a/integrationTests/dev-rollup/rollup.config.js b/integrationTests/dev-rollup/rollup.config.js new file mode 100644 index 0000000000..ff555d7e68 --- /dev/null +++ b/integrationTests/dev-rollup/rollup.config.js @@ -0,0 +1,18 @@ +// eslint-disable-next-line n/no-missing-import +import resolve from '@rollup/plugin-node-resolve'; + +const rollupConfig = { + input: 'src/index.js', + output: { + file: 'dist/bundle.js', + format: 'es', + }, + plugins: [ + resolve({ + exportConditions: ['development'], + }), + ], +}; + +// eslint-disable-next-line no-restricted-exports, import/no-default-export +export default rollupConfig; diff --git a/integrationTests/dev-rollup/src/cjs-module.cjs b/integrationTests/dev-rollup/src/cjs-module.cjs new file mode 100644 index 0000000000..8b0100a638 --- /dev/null +++ b/integrationTests/dev-rollup/src/cjs-module.cjs @@ -0,0 +1,8 @@ +'use strict'; + +const { GraphQLSchema } = require('graphql'); + +// eslint-disable-next-line import/no-commonjs +module.exports = { + GraphQLSchema, +}; diff --git a/integrationTests/dev-rollup/src/esm-module.js b/integrationTests/dev-rollup/src/esm-module.js new file mode 100644 index 0000000000..8daec696ac --- /dev/null +++ b/integrationTests/dev-rollup/src/esm-module.js @@ -0,0 +1 @@ +export { GraphQLSchema } from 'graphql'; diff --git a/integrationTests/dev-rollup/src/index.js b/integrationTests/dev-rollup/src/index.js new file mode 100644 index 0000000000..56a1365f24 --- /dev/null +++ b/integrationTests/dev-rollup/src/index.js @@ -0,0 +1,27 @@ +import { isObjectType } from 'graphql'; + +import { GraphQLSchema as GraphQLSchemaFromCJS } from './cjs-module.cjs'; +import { GraphQLSchema as GraphQLSchemaFromESM } from './esm-module.js'; + +class GraphQLObjectType { + get [Symbol.toStringTag]() { + return 'GraphQLObjectType'; + } +} + +try { + isObjectType(new GraphQLObjectType()); + throw new Error( + 'Expected isObjectType to throw an error in Rollup development mode.', + ); +} catch (error) { + if (!error.message.includes('from another module or realm')) { + throw error; + } +} + +if (GraphQLSchemaFromCJS !== GraphQLSchemaFromESM) { + throw new Error( + 'Dual package hazard detected: GraphQLSchema instances from CJS and ESM imports are not identical.', + ); +} diff --git a/integrationTests/dev-rspack/package.json b/integrationTests/dev-rspack/package.json new file mode 100644 index 0000000000..d6f058a97a --- /dev/null +++ b/integrationTests/dev-rspack/package.json @@ -0,0 +1,12 @@ +{ + "description": "graphql-js development condition should work with Rspack", + "private": true, + "scripts": { + "test": "rspack --mode=development && node dist/main.js" + }, + "dependencies": { + "graphql": "file:../graphql.tgz", + "@rspack/core": "^1.0.0", + "@rspack/cli": "^1.0.0" + } +} diff --git a/integrationTests/dev-rspack/rspack.config.js b/integrationTests/dev-rspack/rspack.config.js new file mode 100644 index 0000000000..58770a9cbf --- /dev/null +++ b/integrationTests/dev-rspack/rspack.config.js @@ -0,0 +1,20 @@ +import { fileURLToPath } from 'node:url'; + +const rspack = { + entry: './src/index.js', + output: { + filename: 'main.js', + path: fileURLToPath(new URL('dist', import.meta.url)), + library: { + type: 'commonjs2', + }, + }, + mode: 'development', + target: 'node', + resolve: { + conditionNames: ['development', 'import', 'node'], + }, +}; + +// eslint-disable-next-line no-restricted-exports, import/no-default-export +export default rspack; diff --git a/integrationTests/dev-rspack/src/cjs-module.cjs b/integrationTests/dev-rspack/src/cjs-module.cjs new file mode 100644 index 0000000000..8b0100a638 --- /dev/null +++ b/integrationTests/dev-rspack/src/cjs-module.cjs @@ -0,0 +1,8 @@ +'use strict'; + +const { GraphQLSchema } = require('graphql'); + +// eslint-disable-next-line import/no-commonjs +module.exports = { + GraphQLSchema, +}; diff --git a/integrationTests/dev-rspack/src/esm-module.js b/integrationTests/dev-rspack/src/esm-module.js new file mode 100644 index 0000000000..8daec696ac --- /dev/null +++ b/integrationTests/dev-rspack/src/esm-module.js @@ -0,0 +1 @@ +export { GraphQLSchema } from 'graphql'; diff --git a/integrationTests/dev-rspack/src/index.js b/integrationTests/dev-rspack/src/index.js new file mode 100644 index 0000000000..88c2c701a7 --- /dev/null +++ b/integrationTests/dev-rspack/src/index.js @@ -0,0 +1,27 @@ +import { isObjectType } from 'graphql'; + +import { GraphQLSchema as GraphQLSchemaFromCJS } from './cjs-module.cjs'; +import { GraphQLSchema as GraphQLSchemaFromESM } from './esm-module.js'; + +class GraphQLObjectType { + get [Symbol.toStringTag]() { + return 'GraphQLObjectType'; + } +} + +try { + isObjectType(new GraphQLObjectType()); + throw new Error( + 'Expected isObjectType to throw an error in Rspack development mode.', + ); +} catch (error) { + if (!error.message.includes('from another module or realm')) { + throw error; + } +} + +if (GraphQLSchemaFromCJS !== GraphQLSchemaFromESM) { + throw new Error( + 'Dual package hazard detected: GraphQLSchema instances from CJS and ESM imports are not identical.', + ); +} diff --git a/integrationTests/dev-swc/.swcrc b/integrationTests/dev-swc/.swcrc new file mode 100644 index 0000000000..b080b3cd3f --- /dev/null +++ b/integrationTests/dev-swc/.swcrc @@ -0,0 +1,12 @@ +{ + "jsc": { + "parser": { + "syntax": "ecmascript", + "jsx": false + }, + "target": "es2020" + }, + "module": { + "type": "es6" + } +} diff --git a/integrationTests/dev-swc/bootstrap.js b/integrationTests/dev-swc/bootstrap.js new file mode 100644 index 0000000000..87f751e1fe --- /dev/null +++ b/integrationTests/dev-swc/bootstrap.js @@ -0,0 +1,4 @@ +// eslint-disable-next-line import/no-unassigned-import +import 'graphql/dev'; +// eslint-disable-next-line n/no-missing-import, import/no-unassigned-import +import './dist/index.js'; diff --git a/integrationTests/dev-swc/package.json b/integrationTests/dev-swc/package.json new file mode 100644 index 0000000000..5dd656801b --- /dev/null +++ b/integrationTests/dev-swc/package.json @@ -0,0 +1,14 @@ +{ + "description": "graphql-js development condition should work with SWC", + "private": true, + "type": "module", + "scripts": { + "build": "swc src -d dist", + "test": "npm run build && node bootstrap.js" + }, + "dependencies": { + "graphql": "file:../graphql.tgz", + "@swc/cli": "^0.1.0", + "@swc/core": "^1.3.0" + } +} diff --git a/integrationTests/dev-swc/src/cjs-module.cjs b/integrationTests/dev-swc/src/cjs-module.cjs new file mode 100644 index 0000000000..8b0100a638 --- /dev/null +++ b/integrationTests/dev-swc/src/cjs-module.cjs @@ -0,0 +1,8 @@ +'use strict'; + +const { GraphQLSchema } = require('graphql'); + +// eslint-disable-next-line import/no-commonjs +module.exports = { + GraphQLSchema, +}; diff --git a/integrationTests/dev-swc/src/esm-module.js b/integrationTests/dev-swc/src/esm-module.js new file mode 100644 index 0000000000..8daec696ac --- /dev/null +++ b/integrationTests/dev-swc/src/esm-module.js @@ -0,0 +1 @@ +export { GraphQLSchema } from 'graphql'; diff --git a/integrationTests/dev-swc/src/index.js b/integrationTests/dev-swc/src/index.js new file mode 100644 index 0000000000..fb6f714b05 --- /dev/null +++ b/integrationTests/dev-swc/src/index.js @@ -0,0 +1,27 @@ +import { isObjectType } from 'graphql'; + +import { GraphQLSchema as GraphQLSchemaFromCJS } from './cjs-module.cjs'; +import { GraphQLSchema as GraphQLSchemaFromESM } from './esm-module.js'; + +class GraphQLObjectType { + get [Symbol.toStringTag]() { + return 'GraphQLObjectType'; + } +} + +try { + isObjectType(new GraphQLObjectType()); + throw new Error( + 'Expected isObjectType to throw an error in SWC development mode.', + ); +} catch (error) { + if (!error.message.includes('from another module or realm')) { + throw error; + } +} + +if (GraphQLSchemaFromCJS !== GraphQLSchemaFromESM) { + throw new Error( + 'Dual package hazard detected: GraphQLSchema instances from CJS and ESM imports are not identical.', + ); +} diff --git a/integrationTests/dev-vitest/cjs-module.cjs b/integrationTests/dev-vitest/cjs-module.cjs new file mode 100644 index 0000000000..8b0100a638 --- /dev/null +++ b/integrationTests/dev-vitest/cjs-module.cjs @@ -0,0 +1,8 @@ +'use strict'; + +const { GraphQLSchema } = require('graphql'); + +// eslint-disable-next-line import/no-commonjs +module.exports = { + GraphQLSchema, +}; diff --git a/integrationTests/dev-vitest/esm-module.js b/integrationTests/dev-vitest/esm-module.js new file mode 100644 index 0000000000..8daec696ac --- /dev/null +++ b/integrationTests/dev-vitest/esm-module.js @@ -0,0 +1 @@ +export { GraphQLSchema } from 'graphql'; diff --git a/integrationTests/dev-vitest/index.test.js b/integrationTests/dev-vitest/index.test.js new file mode 100644 index 0000000000..f7337b052e --- /dev/null +++ b/integrationTests/dev-vitest/index.test.js @@ -0,0 +1,24 @@ +import { isObjectType } from 'graphql'; +// eslint-disable-next-line n/no-missing-import +import { expect, test } from 'vitest'; + +import cjsModule from './cjs-module.cjs'; +import { GraphQLSchema as GraphQLSchemaFromESM } from './esm-module.js'; + +const { GraphQLSchema: GraphQLSchemaFromCJS } = cjsModule; + +class GraphQLObjectTypeMimic { + get [Symbol.toStringTag]() { + return 'GraphQLObjectType'; + } +} + +test('isObjectType should throw in development mode for instances from another realm/module', () => { + expect(() => isObjectType(new GraphQLObjectTypeMimic())).toThrowError( + /from another module or realm/, + ); +}); + +test('GraphQLSchema constructors from CJS and ESM imports should be identical for dual package hazard', () => { + expect(GraphQLSchemaFromCJS === GraphQLSchemaFromESM).toBe(true); +}); diff --git a/integrationTests/dev-vitest/package.json b/integrationTests/dev-vitest/package.json new file mode 100644 index 0000000000..2b7dba1ca4 --- /dev/null +++ b/integrationTests/dev-vitest/package.json @@ -0,0 +1,12 @@ +{ + "description": "graphql-js development condition should work with Vitest", + "private": true, + "type": "module", + "scripts": { + "test": "vitest run" + }, + "dependencies": { + "graphql": "file:../graphql.tgz", + "vitest": "^1.0.0" + } +} diff --git a/integrationTests/dev-vitest/vitest.config.js b/integrationTests/dev-vitest/vitest.config.js new file mode 100644 index 0000000000..4351ba7182 --- /dev/null +++ b/integrationTests/dev-vitest/vitest.config.js @@ -0,0 +1,14 @@ +// eslint-disable-next-line n/no-missing-import +import { defineConfig } from 'vitest/config'; + +const vitestConfig = defineConfig({ + resolve: { + conditions: ['development'], + }, + test: { + include: ['**/*.test.js'], + }, +}); + +// eslint-disable-next-line no-restricted-exports, import/no-default-export +export default vitestConfig; diff --git a/integrationTests/dev-webpack/package.json b/integrationTests/dev-webpack/package.json new file mode 100644 index 0000000000..3b1b9a785c --- /dev/null +++ b/integrationTests/dev-webpack/package.json @@ -0,0 +1,12 @@ +{ + "description": "graphql-js development condition should work with Webpack", + "private": true, + "scripts": { + "test": "webpack --mode=development && node dist/main.js" + }, + "dependencies": { + "graphql": "file:../graphql.tgz", + "webpack": "^5.0.0", + "webpack-cli": "^5.0.0" + } +} diff --git a/integrationTests/dev-webpack/src/cjs-module.cjs b/integrationTests/dev-webpack/src/cjs-module.cjs new file mode 100644 index 0000000000..8b0100a638 --- /dev/null +++ b/integrationTests/dev-webpack/src/cjs-module.cjs @@ -0,0 +1,8 @@ +'use strict'; + +const { GraphQLSchema } = require('graphql'); + +// eslint-disable-next-line import/no-commonjs +module.exports = { + GraphQLSchema, +}; diff --git a/integrationTests/dev-webpack/src/esm-module.js b/integrationTests/dev-webpack/src/esm-module.js new file mode 100644 index 0000000000..8daec696ac --- /dev/null +++ b/integrationTests/dev-webpack/src/esm-module.js @@ -0,0 +1 @@ +export { GraphQLSchema } from 'graphql'; diff --git a/integrationTests/dev-webpack/src/index.js b/integrationTests/dev-webpack/src/index.js new file mode 100644 index 0000000000..a1dffab0f1 --- /dev/null +++ b/integrationTests/dev-webpack/src/index.js @@ -0,0 +1,27 @@ +import { isObjectType } from 'graphql'; + +import { GraphQLSchema as GraphQLSchemaFromCJS } from './cjs-module.cjs'; +import { GraphQLSchema as GraphQLSchemaFromESM } from './esm-module.js'; + +class GraphQLObjectType { + get [Symbol.toStringTag]() { + return 'GraphQLObjectType'; + } +} + +try { + isObjectType(new GraphQLObjectType()); + throw new Error( + 'Expected isObjectType to throw an error in Webpack development mode.', + ); +} catch (error) { + if (!error.message.includes('from another module or realm')) { + throw error; + } +} + +if (GraphQLSchemaFromCJS !== GraphQLSchemaFromESM) { + throw new Error( + 'Dual package hazard detected: GraphQLSchema instances from CJS and ESM imports are not identical.', + ); +} diff --git a/integrationTests/dev-webpack/webpack.config.js b/integrationTests/dev-webpack/webpack.config.js new file mode 100644 index 0000000000..9afa8ca9e3 --- /dev/null +++ b/integrationTests/dev-webpack/webpack.config.js @@ -0,0 +1,20 @@ +import { fileURLToPath } from 'node:url'; + +const webpackConfig = { + entry: './src/index.js', + output: { + filename: 'main.js', + path: fileURLToPath(new URL('dist', import.meta.url)), + library: { + type: 'commonjs2', + }, + }, + mode: 'development', + target: 'node', + resolve: { + conditionNames: ['development', 'import', 'node'], + }, +}; + +// eslint-disable-next-line no-restricted-exports, import/no-default-export +export default webpackConfig; diff --git a/integrationTests/prod-bun/cjs-module.cjs b/integrationTests/prod-bun/cjs-module.cjs new file mode 100644 index 0000000000..8b0100a638 --- /dev/null +++ b/integrationTests/prod-bun/cjs-module.cjs @@ -0,0 +1,8 @@ +'use strict'; + +const { GraphQLSchema } = require('graphql'); + +// eslint-disable-next-line import/no-commonjs +module.exports = { + GraphQLSchema, +}; diff --git a/integrationTests/prod-bun/esm-module.js b/integrationTests/prod-bun/esm-module.js new file mode 100644 index 0000000000..8daec696ac --- /dev/null +++ b/integrationTests/prod-bun/esm-module.js @@ -0,0 +1 @@ +export { GraphQLSchema } from 'graphql'; diff --git a/integrationTests/prod-bun/package.json b/integrationTests/prod-bun/package.json new file mode 100644 index 0000000000..e05325eff1 --- /dev/null +++ b/integrationTests/prod-bun/package.json @@ -0,0 +1,10 @@ +{ + "description": "graphql-js production mode should work with Bun", + "private": true, + "scripts": { + "test": "docker run --rm --volume \"$PWD:/usr/src/app\" -w /usr/src/app oven/bun:latest bun test.js" + }, + "dependencies": { + "graphql": "file:../graphql.tgz" + } +} diff --git a/integrationTests/prod-bun/test.js b/integrationTests/prod-bun/test.js new file mode 100644 index 0000000000..7fded3da25 --- /dev/null +++ b/integrationTests/prod-bun/test.js @@ -0,0 +1,20 @@ +import { GraphQLSchema as GraphQLSchemaFromESM, isObjectType } from 'graphql'; + +import { GraphQLSchema as GraphQLSchemaFromCJS } from './cjs-module.cjs'; + +class GraphQLObjectType { + get [Symbol.toStringTag]() { + return 'GraphQLObjectType'; + } +} + +const result = isObjectType(new GraphQLObjectType()); +if (result !== false) { + throw new Error('isObjectType should return false in Bun production mode.'); +} + +if (GraphQLSchemaFromCJS !== GraphQLSchemaFromESM) { + throw new Error( + 'Dual package hazard detected: GraphQLSchema instances from CJS and ESM imports are not identical.', + ); +} diff --git a/integrationTests/prod-deno/cjs-module.cjs b/integrationTests/prod-deno/cjs-module.cjs new file mode 100644 index 0000000000..8b0100a638 --- /dev/null +++ b/integrationTests/prod-deno/cjs-module.cjs @@ -0,0 +1,8 @@ +'use strict'; + +const { GraphQLSchema } = require('graphql'); + +// eslint-disable-next-line import/no-commonjs +module.exports = { + GraphQLSchema, +}; diff --git a/integrationTests/prod-deno/esm-module.js b/integrationTests/prod-deno/esm-module.js new file mode 100644 index 0000000000..8daec696ac --- /dev/null +++ b/integrationTests/prod-deno/esm-module.js @@ -0,0 +1 @@ +export { GraphQLSchema } from 'graphql'; diff --git a/integrationTests/prod-deno/package.json b/integrationTests/prod-deno/package.json new file mode 100644 index 0000000000..335f59c69c --- /dev/null +++ b/integrationTests/prod-deno/package.json @@ -0,0 +1,10 @@ +{ + "description": "graphql-js production mode should work with Deno", + "private": true, + "scripts": { + "test": "docker run --rm --volume \"$PWD:/usr/src/app\" -w /usr/src/app denoland/deno:latest deno run test.js" + }, + "dependencies": { + "graphql": "file:../graphql.tgz" + } +} diff --git a/integrationTests/prod-deno/test.js b/integrationTests/prod-deno/test.js new file mode 100644 index 0000000000..b1894108e9 --- /dev/null +++ b/integrationTests/prod-deno/test.js @@ -0,0 +1,21 @@ +import { isObjectType } from 'graphql'; + +import { GraphQLSchema as GraphQLSchemaFromCJS } from './cjs-module.cjs'; +import { GraphQLSchema as GraphQLSchemaFromESM } from './esm-module.js'; + +class GraphQLObjectType { + get [Symbol.toStringTag]() { + return 'GraphQLObjectType'; + } +} + +const result = isObjectType(new GraphQLObjectType()); +if (result !== false) { + throw new Error('isObjectType should return false in Deno production mode.'); +} + +if (GraphQLSchemaFromCJS !== GraphQLSchemaFromESM) { + throw new Error( + 'Dual package hazard detected: GraphQLSchema instances from CJS and ESM imports are not identical.', + ); +} diff --git a/integrationTests/prod-esbuild/package.json b/integrationTests/prod-esbuild/package.json new file mode 100644 index 0000000000..fcba738b0b --- /dev/null +++ b/integrationTests/prod-esbuild/package.json @@ -0,0 +1,13 @@ +{ + "description": "graphql-js production mode should work with esbuild", + "private": true, + "type": "module", + "scripts": { + "build": "esbuild src/index.js --bundle --outfile=dist/bundle.js --format=esm", + "test": "npm run build && node dist/bundle.js" + }, + "dependencies": { + "graphql": "file:../graphql.tgz", + "esbuild": "^0.25.0" + } +} diff --git a/integrationTests/prod-esbuild/src/cjs-module.cjs b/integrationTests/prod-esbuild/src/cjs-module.cjs new file mode 100644 index 0000000000..8b0100a638 --- /dev/null +++ b/integrationTests/prod-esbuild/src/cjs-module.cjs @@ -0,0 +1,8 @@ +'use strict'; + +const { GraphQLSchema } = require('graphql'); + +// eslint-disable-next-line import/no-commonjs +module.exports = { + GraphQLSchema, +}; diff --git a/integrationTests/prod-esbuild/src/esm-module.js b/integrationTests/prod-esbuild/src/esm-module.js new file mode 100644 index 0000000000..8daec696ac --- /dev/null +++ b/integrationTests/prod-esbuild/src/esm-module.js @@ -0,0 +1 @@ +export { GraphQLSchema } from 'graphql'; diff --git a/integrationTests/prod-esbuild/src/index.js b/integrationTests/prod-esbuild/src/index.js new file mode 100644 index 0000000000..d17a4c28dd --- /dev/null +++ b/integrationTests/prod-esbuild/src/index.js @@ -0,0 +1,23 @@ +import { isObjectType } from 'graphql'; + +import { GraphQLSchema as GraphQLSchemaFromCJS } from './cjs-module.cjs'; +import { GraphQLSchema as GraphQLSchemaFromESM } from './esm-module.js'; + +class GraphQLObjectType { + get [Symbol.toStringTag]() { + return 'GraphQLObjectType'; + } +} + +const result = isObjectType(new GraphQLObjectType()); +if (result !== false) { + throw new Error( + 'isObjectType should return false in esbuild production mode.', + ); +} + +if (GraphQLSchemaFromCJS !== GraphQLSchemaFromESM) { + throw new Error( + 'Dual package hazard detected: GraphQLSchema instances from CJS and ESM imports are not identical.', + ); +} diff --git a/integrationTests/prod-node/cjs-module.cjs b/integrationTests/prod-node/cjs-module.cjs new file mode 100644 index 0000000000..8b0100a638 --- /dev/null +++ b/integrationTests/prod-node/cjs-module.cjs @@ -0,0 +1,8 @@ +'use strict'; + +const { GraphQLSchema } = require('graphql'); + +// eslint-disable-next-line import/no-commonjs +module.exports = { + GraphQLSchema, +}; diff --git a/integrationTests/prod-node/esm-module.js b/integrationTests/prod-node/esm-module.js new file mode 100644 index 0000000000..8daec696ac --- /dev/null +++ b/integrationTests/prod-node/esm-module.js @@ -0,0 +1 @@ +export { GraphQLSchema } from 'graphql'; diff --git a/integrationTests/prod-node/package.json b/integrationTests/prod-node/package.json new file mode 100644 index 0000000000..280e414e96 --- /dev/null +++ b/integrationTests/prod-node/package.json @@ -0,0 +1,11 @@ +{ + "description": "graphql-js production mode should work with Node.js", + "private": true, + "type": "module", + "scripts": { + "test": "node test.js" + }, + "dependencies": { + "graphql": "file:../graphql.tgz" + } +} diff --git a/integrationTests/prod-node/test.js b/integrationTests/prod-node/test.js new file mode 100644 index 0000000000..4f1f47e3cf --- /dev/null +++ b/integrationTests/prod-node/test.js @@ -0,0 +1,23 @@ +import { isObjectType } from 'graphql'; + +import { GraphQLSchema as GraphQLSchemaFromCJS } from './cjs-module.cjs'; +import { GraphQLSchema as GraphQLSchemaFromESM } from './esm-module.js'; + +class GraphQLObjectType { + get [Symbol.toStringTag]() { + return 'GraphQLObjectType'; + } +} + +const result = isObjectType(new GraphQLObjectType()); +if (result !== false) { + throw new Error( + 'isObjectType should return false in Node.js production mode.', + ); +} + +if (GraphQLSchemaFromCJS !== GraphQLSchemaFromESM) { + throw new Error( + 'Dual package hazard detected: GraphQLSchema instances from CJS and ESM imports are not identical.', + ); +} diff --git a/integrationTests/prod-rollup/package.json b/integrationTests/prod-rollup/package.json new file mode 100644 index 0000000000..582828543b --- /dev/null +++ b/integrationTests/prod-rollup/package.json @@ -0,0 +1,14 @@ +{ + "description": "graphql-js production mode should work with Rollup", + "private": true, + "type": "module", + "scripts": { + "build": "rollup -c", + "test": "npm run build && node dist/bundle.js" + }, + "dependencies": { + "graphql": "file:../graphql.tgz", + "rollup": "^4.0.0", + "@rollup/plugin-node-resolve": "^15.0.0" + } +} diff --git a/integrationTests/prod-rollup/rollup.config.js b/integrationTests/prod-rollup/rollup.config.js new file mode 100644 index 0000000000..41df09d09e --- /dev/null +++ b/integrationTests/prod-rollup/rollup.config.js @@ -0,0 +1,10 @@ +const rollupConfig = { + input: 'src/index.js', + output: { + file: 'dist/bundle.js', + format: 'es', + }, +}; + +// eslint-disable-next-line no-restricted-exports, import/no-default-export +export default rollupConfig; diff --git a/integrationTests/prod-rollup/src/cjs-module.cjs b/integrationTests/prod-rollup/src/cjs-module.cjs new file mode 100644 index 0000000000..8b0100a638 --- /dev/null +++ b/integrationTests/prod-rollup/src/cjs-module.cjs @@ -0,0 +1,8 @@ +'use strict'; + +const { GraphQLSchema } = require('graphql'); + +// eslint-disable-next-line import/no-commonjs +module.exports = { + GraphQLSchema, +}; diff --git a/integrationTests/prod-rollup/src/esm-module.js b/integrationTests/prod-rollup/src/esm-module.js new file mode 100644 index 0000000000..8daec696ac --- /dev/null +++ b/integrationTests/prod-rollup/src/esm-module.js @@ -0,0 +1 @@ +export { GraphQLSchema } from 'graphql'; diff --git a/integrationTests/prod-rollup/src/index.js b/integrationTests/prod-rollup/src/index.js new file mode 100644 index 0000000000..effba09ae3 --- /dev/null +++ b/integrationTests/prod-rollup/src/index.js @@ -0,0 +1,23 @@ +import { isObjectType } from 'graphql'; + +import { GraphQLSchema as GraphQLSchemaFromCJS } from './cjs-module.cjs'; +import { GraphQLSchema as GraphQLSchemaFromESM } from './esm-module.js'; + +class GraphQLObjectType { + get [Symbol.toStringTag]() { + return 'GraphQLObjectType'; + } +} + +const result = isObjectType(new GraphQLObjectType()); +if (result !== false) { + throw new Error( + 'isObjectType should return false in Rollup production mode.', + ); +} + +if (GraphQLSchemaFromCJS !== GraphQLSchemaFromESM) { + throw new Error( + 'Dual package hazard detected: GraphQLSchema instances from CJS and ESM imports are not identical.', + ); +} diff --git a/integrationTests/prod-rspack/package.json b/integrationTests/prod-rspack/package.json new file mode 100644 index 0000000000..1122a8e588 --- /dev/null +++ b/integrationTests/prod-rspack/package.json @@ -0,0 +1,12 @@ +{ + "description": "graphql-js production mode should work with Rspack", + "private": true, + "scripts": { + "test": "rspack --mode=production && node dist/main.js" + }, + "dependencies": { + "graphql": "file:../graphql.tgz", + "@rspack/core": "^1.0.0", + "@rspack/cli": "^1.0.0" + } +} diff --git a/integrationTests/prod-rspack/rspack.config.js b/integrationTests/prod-rspack/rspack.config.js new file mode 100644 index 0000000000..e569199b0f --- /dev/null +++ b/integrationTests/prod-rspack/rspack.config.js @@ -0,0 +1,16 @@ +import { fileURLToPath } from 'node:url'; + +const rspackConfig = { + entry: './src/index.js', + output: { + filename: 'main.js', + path: fileURLToPath(new URL('dist', import.meta.url)), + library: { + type: 'commonjs2', + }, + }, + target: 'node', +}; + +// eslint-disable-next-line no-restricted-exports, import/no-default-export +export default rspackConfig; diff --git a/integrationTests/prod-rspack/src/cjs-module.cjs b/integrationTests/prod-rspack/src/cjs-module.cjs new file mode 100644 index 0000000000..8b0100a638 --- /dev/null +++ b/integrationTests/prod-rspack/src/cjs-module.cjs @@ -0,0 +1,8 @@ +'use strict'; + +const { GraphQLSchema } = require('graphql'); + +// eslint-disable-next-line import/no-commonjs +module.exports = { + GraphQLSchema, +}; diff --git a/integrationTests/prod-rspack/src/esm-module.js b/integrationTests/prod-rspack/src/esm-module.js new file mode 100644 index 0000000000..8daec696ac --- /dev/null +++ b/integrationTests/prod-rspack/src/esm-module.js @@ -0,0 +1 @@ +export { GraphQLSchema } from 'graphql'; diff --git a/integrationTests/prod-rspack/src/index.js b/integrationTests/prod-rspack/src/index.js new file mode 100644 index 0000000000..754b1d1761 --- /dev/null +++ b/integrationTests/prod-rspack/src/index.js @@ -0,0 +1,23 @@ +import { isObjectType } from 'graphql'; + +import { GraphQLSchema as GraphQLSchemaFromCJS } from './cjs-module.cjs'; +import { GraphQLSchema as GraphQLSchemaFromESM } from './esm-module.js'; + +class GraphQLObjectType { + get [Symbol.toStringTag]() { + return 'GraphQLObjectType'; + } +} + +const result = isObjectType(new GraphQLObjectType()); +if (result !== false) { + throw new Error( + 'isObjectType should return false in Rspack production mode.', + ); +} + +if (GraphQLSchemaFromCJS !== GraphQLSchemaFromESM) { + throw new Error( + 'Dual package hazard detected: GraphQLSchema instances from CJS and ESM imports are not identical.', + ); +} diff --git a/integrationTests/prod-swc/.swcrc b/integrationTests/prod-swc/.swcrc new file mode 100644 index 0000000000..b080b3cd3f --- /dev/null +++ b/integrationTests/prod-swc/.swcrc @@ -0,0 +1,12 @@ +{ + "jsc": { + "parser": { + "syntax": "ecmascript", + "jsx": false + }, + "target": "es2020" + }, + "module": { + "type": "es6" + } +} diff --git a/integrationTests/prod-swc/package.json b/integrationTests/prod-swc/package.json new file mode 100644 index 0000000000..1df512d6b0 --- /dev/null +++ b/integrationTests/prod-swc/package.json @@ -0,0 +1,14 @@ +{ + "description": "graphql-js production mode should work with SWC", + "private": true, + "type": "module", + "scripts": { + "build": "swc src -d dist", + "test": "npm run build && node dist/index.js" + }, + "dependencies": { + "graphql": "file:../graphql.tgz", + "@swc/cli": "^0.1.0", + "@swc/core": "^1.3.0" + } +} diff --git a/integrationTests/prod-swc/src/cjs-module.cjs b/integrationTests/prod-swc/src/cjs-module.cjs new file mode 100644 index 0000000000..8b0100a638 --- /dev/null +++ b/integrationTests/prod-swc/src/cjs-module.cjs @@ -0,0 +1,8 @@ +'use strict'; + +const { GraphQLSchema } = require('graphql'); + +// eslint-disable-next-line import/no-commonjs +module.exports = { + GraphQLSchema, +}; diff --git a/integrationTests/prod-swc/src/esm-module.js b/integrationTests/prod-swc/src/esm-module.js new file mode 100644 index 0000000000..8daec696ac --- /dev/null +++ b/integrationTests/prod-swc/src/esm-module.js @@ -0,0 +1 @@ +export { GraphQLSchema } from 'graphql'; diff --git a/integrationTests/prod-swc/src/index.js b/integrationTests/prod-swc/src/index.js new file mode 100644 index 0000000000..66185cd969 --- /dev/null +++ b/integrationTests/prod-swc/src/index.js @@ -0,0 +1,21 @@ +import { isObjectType } from 'graphql'; + +import { GraphQLSchema as GraphQLSchemaFromCJS } from './cjs-module.cjs'; +import { GraphQLSchema as GraphQLSchemaFromESM } from './esm-module.js'; + +class GraphQLObjectType { + get [Symbol.toStringTag]() { + return 'GraphQLObjectType'; + } +} + +const result = isObjectType(new GraphQLObjectType()); +if (result !== false) { + throw new Error('isObjectType should return false in SWC production mode.'); +} + +if (GraphQLSchemaFromCJS !== GraphQLSchemaFromESM) { + throw new Error( + 'Dual package hazard detected: GraphQLSchema instances from CJS and ESM imports are not identical.', + ); +} diff --git a/integrationTests/prod-webpack/package.json b/integrationTests/prod-webpack/package.json new file mode 100644 index 0000000000..02521f48d8 --- /dev/null +++ b/integrationTests/prod-webpack/package.json @@ -0,0 +1,12 @@ +{ + "description": "graphql-js production mode should work with Webpack", + "private": true, + "scripts": { + "test": "webpack --mode=production && node dist/main.js" + }, + "dependencies": { + "graphql": "file:../graphql.tgz", + "webpack": "^5.0.0", + "webpack-cli": "^5.0.0" + } +} diff --git a/integrationTests/prod-webpack/src/cjs-module.cjs b/integrationTests/prod-webpack/src/cjs-module.cjs new file mode 100644 index 0000000000..8b0100a638 --- /dev/null +++ b/integrationTests/prod-webpack/src/cjs-module.cjs @@ -0,0 +1,8 @@ +'use strict'; + +const { GraphQLSchema } = require('graphql'); + +// eslint-disable-next-line import/no-commonjs +module.exports = { + GraphQLSchema, +}; diff --git a/integrationTests/prod-webpack/src/esm-module.js b/integrationTests/prod-webpack/src/esm-module.js new file mode 100644 index 0000000000..8daec696ac --- /dev/null +++ b/integrationTests/prod-webpack/src/esm-module.js @@ -0,0 +1 @@ +export { GraphQLSchema } from 'graphql'; diff --git a/integrationTests/prod-webpack/src/index.js b/integrationTests/prod-webpack/src/index.js new file mode 100644 index 0000000000..0d237248d9 --- /dev/null +++ b/integrationTests/prod-webpack/src/index.js @@ -0,0 +1,23 @@ +import { isObjectType } from 'graphql'; + +import { GraphQLSchema as GraphQLSchemaFromCJS } from './cjs-module.cjs'; +import { GraphQLSchema as GraphQLSchemaFromESM } from './esm-module.js'; + +class GraphQLObjectType { + get [Symbol.toStringTag]() { + return 'GraphQLObjectType'; + } +} + +const result = isObjectType(new GraphQLObjectType()); +if (result !== false) { + throw new Error( + 'isObjectType should return false in Webpack production mode.', + ); +} + +if (GraphQLSchemaFromCJS !== GraphQLSchemaFromESM) { + throw new Error( + 'Dual package hazard detected: GraphQLSchema instances from CJS and ESM imports are not identical.', + ); +} diff --git a/integrationTests/prod-webpack/webpack.config.js b/integrationTests/prod-webpack/webpack.config.js new file mode 100644 index 0000000000..b792f6442d --- /dev/null +++ b/integrationTests/prod-webpack/webpack.config.js @@ -0,0 +1,16 @@ +import { fileURLToPath } from 'node:url'; + +const webpackConfig = { + entry: './src/index.js', + output: { + filename: 'main.js', + path: fileURLToPath(new URL('dist', import.meta.url)), + library: { + type: 'commonjs2', + }, + }, + target: 'node', +}; + +// eslint-disable-next-line no-restricted-exports, import/no-default-export +export default webpackConfig; diff --git a/resources/integration-test.ts b/resources/integration-test.ts index 471af0952c..575730d530 100644 --- a/resources/integration-test.ts +++ b/resources/integration-test.ts @@ -32,4 +32,27 @@ describe('Integration Tests', () => { testOnNodeProject('ts'); testOnNodeProject('node'); testOnNodeProject('webpack'); + + // Development mode tests + testOnNodeProject('dev-node-implicit'); + testOnNodeProject('dev-node-explicit'); + testOnNodeProject('dev-deno-implicit'); + testOnNodeProject('dev-deno-explicit'); + testOnNodeProject('dev-bun'); + testOnNodeProject('dev-webpack'); + testOnNodeProject('dev-rspack'); + testOnNodeProject('dev-rollup'); + testOnNodeProject('dev-esbuild'); + testOnNodeProject('dev-swc'); + testOnNodeProject('dev-vitest'); + + // Production mode tests + testOnNodeProject('prod-node'); + testOnNodeProject('prod-deno'); + testOnNodeProject('prod-bun'); + testOnNodeProject('prod-webpack'); + testOnNodeProject('prod-rspack'); + testOnNodeProject('prod-rollup'); + testOnNodeProject('prod-esbuild'); + testOnNodeProject('prod-swc'); }); diff --git a/website/pages/docs/development-mode.mdx b/website/pages/docs/development-mode.mdx index 7ee7a76959..7d8aafa4d6 100644 --- a/website/pages/docs/development-mode.mdx +++ b/website/pages/docs/development-mode.mdx @@ -20,8 +20,8 @@ development mode. Rather, development mode is either enabled: 2. implicitly, by setting the 'development' condition, which is possible only in environments that support `package.json` conditional exports and custom conditions. -Conditional exports are supported by: Node.js, Deno (canary), Bun, Webpack 5, Rollup -(via the `node-resolve` plugin), esbuild, and Vite. create-react-app and Next.js +Conditional exports are supported by: Node.js, Deno (canary), Bun, Webpack 5, Rspack, +Rollup (via the `node-resolve` plugin), esbuild, Vite, and Rsbuild. create-react-app and Next.js support conditional exports when using Webpack 5 as their bundler. Conditional exports are not supported by Deno (current), Webpack 4, Rollup (without