Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cspell.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ words:

# TODO: contribute upstream
- deno
- denoland
- hashbang
- Rspack
- Rollup
Expand Down
4 changes: 4 additions & 0 deletions integrationTests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ Each subdirectory represents a different environment/bundler:
- `ts` - tests for supported Typescript versions
- `webpack` - tests for Webpack

### Verifying Conditional Exports

The `conditions` subdirectory contains tests that verify the conditional exports of GraphQL.js. These tests ensure that the correct files are imported based on the environment being used.

### Verifying Development Mode Tests

Each subdirectory represents a different environment/bundler demonstrating enabling development mode by setting the environment variable `NODE_ENV` to `development`.
Expand Down
21 changes: 21 additions & 0 deletions integrationTests/conditions/check.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import assert from 'node:assert';

import { GraphQLObjectType as ESMGraphQLObjectType } from 'graphql';

import { CJSGraphQLObjectType, cjsPath } from './cjs-importer.cjs';

const moduleSync = process.env.MODULE_SYNC === 'true';
const expectedExtension = moduleSync ? '.mjs' : '.js';
assert.ok(
cjsPath.endsWith(expectedExtension),
`require('graphql') should resolve to a file with extension "${expectedExtension}", but got "${cjsPath}".`,
);

const isSameModule = ESMGraphQLObjectType === CJSGraphQLObjectType;
assert.strictEqual(
isSameModule,
true,
'ESM and CJS imports should be the same module instances.',
);

console.log('Module identity and path checks passed.');
11 changes: 11 additions & 0 deletions integrationTests/conditions/cjs-importer.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
'use strict';

const { GraphQLObjectType } = require('graphql');

const cjsPath = require.resolve('graphql');

// eslint-disable-next-line import/no-commonjs
module.exports = {
CJSGraphQLObjectType: GraphQLObjectType,
cjsPath,
};
10 changes: 10 additions & 0 deletions integrationTests/conditions/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"description": "graphql-js should be loaded correctly on different versions of Node.js, Deno and Bun",
"private": true,
"scripts": {
"test": "node test.js"
},
"dependencies": {
"graphql": "file:../graphql.tgz"
}
}
31 changes: 31 additions & 0 deletions integrationTests/conditions/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import childProcess from 'node:child_process';

const nodeTests = [
// Old node versions, require => CJS
{ version: '20.18.0', moduleSync: false },
{ version: '22.11.0', moduleSync: false },
// New node versions, module-sync => ESM
{ version: '20.19.0', moduleSync: true },
{ version: '22.12.0', moduleSync: true },
{ version: '24.0.0', moduleSync: true },
];

for (const { version, moduleSync } of nodeTests) {
console.log(`Testing on node@${version} (moduleSync: ${moduleSync}) ...`);
childProcess.execSync(
`docker run --rm --volume "$PWD":/usr/src/app -w /usr/src/app --env MODULE_SYNC=${moduleSync} node:${version}-slim node ./check.mjs`,
{ stdio: 'inherit' },
);
}

console.log('Testing on bun (moduleSync: true) ...');
childProcess.execSync(
`docker run --rm --volume "$PWD":/usr/src/app -w /usr/src/app --env MODULE_SYNC=true oven/bun:alpine bun ./check.mjs`,
{ stdio: 'inherit' },
);

console.log('Testing on deno (moduleSync: false) ...');
childProcess.execSync(
`docker run --rm --volume "$PWD":/usr/src/app -w /usr/src/app --env MODULE_SYNC=false denoland/alpine-2.4.1 deno run --allow-read --allow-env ./check.mjs`,
{ stdio: 'inherit' },
);
2 changes: 1 addition & 1 deletion integrationTests/ts/esm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const queryType: GraphQLObjectType = new GraphQLObjectType({
default: { value: 'World' },
},
},
resolve(_root, args: { who: string }) {
resolve(_root: unknown, args: { who: string }) {
return 'Hello ' + args.who;
},
},
Expand Down
5 changes: 4 additions & 1 deletion integrationTests/ts/extensions-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ interface SomeExtension {
meaningOfLife: 42;
}

// Import added to support deno, which does not scan node_modules automatically.
import 'graphql';
declare module 'graphql' {
interface GraphQLObjectTypeExtensions<_TSource, _TContext> {
someObjectExtension?: SomeExtension;
Expand Down Expand Up @@ -32,7 +34,8 @@ const queryType: GraphQLObjectType = new GraphQLObjectType({
},
},
},
resolve: (_root, args) => 'Hello ' + (args.who || 'World'),
resolve: (_root: unknown, args: { who: string }) =>
'Hello ' + (args.who || 'World'),
extensions: {
someFieldExtension: { meaningOfLife: 42 },
},
Expand Down
1 change: 1 addition & 0 deletions integrationTests/ts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"dependencies": {
"graphql": "file:../graphql.tgz",
"graphql-esm": "file:../graphql-esm.tgz",
"@types/node": "~24.0.10",
"typescript-4.9": "npm:[email protected]",
"typescript-5.0": "npm:[email protected]",
"typescript-5.1": "npm:[email protected]",
Expand Down
8 changes: 7 additions & 1 deletion integrationTests/ts/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,16 @@
.sort((a, b) => b.localeCompare(a));

for (const version of tsVersions) {
console.log(`Testing on ${version} ...`);
console.log(`Testing on node ${version} ...`);
childProcess.execSync(tscPath(version), { stdio: 'inherit' });
}

console.log('Testing on deno ...');
childProcess.execSync(
`docker run --rm --volume "${process.cwd()}:/usr/src/app" -w /usr/src/app denoland/deno:alpine-2.4.1 deno check`,
{ stdio: 'inherit' },
);

function tscPath(version) {
return path.join('node_modules', version, 'bin', 'tsc');
}
75 changes: 58 additions & 17 deletions resources/build-npm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import ts from 'typescript';

import { changeExtensionInImportPaths } from './change-extension-in-import-paths.js';
import { inlineInvariant } from './inline-invariant.js';
import type { ConditionalExports } from './utils.js';
import {
prettify,
readPackageJSON,
Expand Down Expand Up @@ -98,18 +99,35 @@ async function buildPackage(outDir: string, isESMOnly: boolean): Promise<void> {
}
}

// 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.main = 'index.js';
packageJSON.module = 'index.mjs';
emitTSFiles({ outDir, module: 'commonjs', extension: '.js' });
packageJSON.types = 'index.d.ts';

const { emittedTSFiles } = emitTSFiles({
outDir,
module: 'commonjs',
extension: '.js',
});
emitTSFiles({ outDir, module: 'es2020', extension: '.mjs' });

packageJSON.exports = {};
for (const filepath of emittedTSFiles) {
if (path.basename(filepath) === 'index.js') {
const relativePath = './' + path.relative('./npmDist', filepath);
packageJSON.exports[path.dirname(relativePath)] =
buildExports(relativePath);
}
}

packageJSON.exports['./*.js'] = buildExports('./*.js');
packageJSON.exports['./*'] = buildExports('./*.js');
}

const packageJsonPath = `./${outDir}/package.json`;
Expand Down Expand Up @@ -141,21 +159,31 @@ 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);
if (extension === '.mjs') {
if (filepath.match(/.js$/)) {
let bodyToWrite = body;
bodyToWrite = bodyToWrite.replace(
'//# sourceMappingURL=graphql.js.map',
'//# sourceMappingURL=graphql.mjs.map',
);
writeGeneratedFile(filepath.replace(/.js$/, extension), bodyToWrite);
return;
}

if (filepath.match(/.js.map$/)) {
writeGeneratedFile(
filepath.replace(/.js.map$/, extension + '.map'),
body,
);
return;
}

if (filepath.match(/.d.ts$/)) {
writeGeneratedFile(filepath.replace(/.d.ts$/, '.d.mts'), body);
return;
}
}
writeGeneratedFile(filepath, body);
};

const tsProgram = ts.createProgram(['src/index.ts'], tsOptions, tsHost);
Expand All @@ -172,3 +200,16 @@ function emitTSFiles(options: {
emittedTSFiles: tsResult.emittedFiles.sort((a, b) => a.localeCompare(b)),
};
}

function buildExports(filepath: string): ConditionalExports {
const { dir, name } = path.parse(filepath);
const base = `./${path.join(dir, name)}`;
return {
module: `${base}.mjs`,
bun: `${base}.mjs`,
'module-sync': `${base}.mjs`,
node: `${base}.js`,
require: `${base}.js`,
default: `${base}.mjs`,
};
}
3 changes: 3 additions & 0 deletions resources/integration-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ describe('Integration Tests', () => {
testOnNodeProject('node');
testOnNodeProject('webpack');

// Conditional export tests
testOnNodeProject('conditions');

// Development mode tests
testOnNodeProject('dev-node');
testOnNodeProject('dev-deno');
Expand Down
11 changes: 10 additions & 1 deletion resources/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ interface PackageJSON {
repository?: { url?: string };
scripts?: { [name: string]: string };
type?: string;
exports: { [path: string]: string };
exports: { [path: string]: string | ConditionalExports };
types?: string;
typesVersions: { [ranges: string]: { [path: string]: Array<string> } };
devDependencies?: { [name: string]: string };
Expand All @@ -245,6 +245,15 @@ interface PackageJSON {
module?: string;
}

export interface ConditionalExports {
module: string;
bun: string;
'module-sync': string;
node: string;
require: string;
default: string;
}

export function readPackageJSON(
dirPath: string = localRepoPath(),
): PackageJSON {
Expand Down
Loading