Skip to content

Commit 8959fb3

Browse files
committed
try imports
1 parent 13e171c commit 8959fb3

13 files changed

+199
-127
lines changed

integrationTests/ts/package.json

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,6 @@
88
"dependencies": {
99
"graphql": "file:../graphql.tgz",
1010
"graphql-esm": "file:../graphql-esm.tgz",
11-
"typescript-4.4": "npm:[email protected]",
12-
"typescript-4.5": "npm:[email protected]",
13-
"typescript-4.6": "npm:[email protected]",
14-
"typescript-4.7": "npm:[email protected]",
15-
"typescript-4.8": "npm:[email protected]",
16-
"typescript-4.9": "npm:[email protected]",
1711
"typescript-5.0": "npm:[email protected]",
1812
"typescript-5.1": "npm:[email protected]",
1913
"typescript-5.2": "npm:[email protected]",

package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@
2828
"engines": {
2929
"node": "^16.19.0 || ^18.14.0 || >=19.7.0"
3030
},
31+
"imports": {
32+
"#instanceOf": {
33+
"development": "./src/jsutils/instanceOfForDevelopment.ts",
34+
"default": "./src/jsutils/instanceOf.ts"
35+
}
36+
},
3137
"scripts": {
3238
"preversion": "bash -c '. ./resources/checkgit.sh && npm ci --ignore-scripts'",
3339
"version": "node --loader ts-node/esm resources/gen-version.ts && npm test && git add src/version.ts",

resources/build-deno.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,14 @@ import {
1414
fs.rmSync('./denoDist', { recursive: true, force: true });
1515
fs.mkdirSync('./denoDist');
1616

17-
const tsProgram = ts.createProgram(['src/index.ts'], readTSConfig());
17+
const tsProgram = ts.createProgram(
18+
[
19+
'src/index.ts',
20+
'src/jsutils/instanceOf.ts',
21+
'src/jsutils/instanceOfForDevelopment.ts',
22+
],
23+
readTSConfig(),
24+
);
1825
for (const sourceFile of tsProgram.getSourceFiles()) {
1926
if (
2027
tsProgram.isSourceFileFromExternalLibrary(sourceFile) ||

resources/build-npm.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import ts from 'typescript';
66

77
import { changeExtensionInImportPaths } from './change-extension-in-import-paths.ts';
88
import { inlineInvariant } from './inline-invariant.ts';
9+
import type { ImportsMap, PathOrConditionalPath } from './utils.ts';
910
import {
1011
prettify,
1112
readPackageJSON,
@@ -102,12 +103,22 @@ async function buildPackage(outDir: string, isESMOnly: boolean): Promise<void> {
102103
packageJSON.exports['./*.js'] = './*.ts';
103104
packageJSON.exports['./*'] = './*.ts';
104105

106+
rewriteImports(packageJSON.imports, (value: string) =>
107+
replaceImportPath(value, '.js'),
108+
);
109+
105110
packageJSON.publishConfig.tag += '-esm';
106111
packageJSON.version += '+esm';
107112
} else {
108113
delete packageJSON.type;
109114
packageJSON.main = 'index';
110115
packageJSON.module = 'index.mjs';
116+
117+
rewriteImports(packageJSON.imports, (value: string) => ({
118+
import: replaceImportPath(value, '.mjs'),
119+
default: replaceImportPath(value, '.js'),
120+
}));
121+
111122
emitTSFiles({ outDir, module: 'commonjs', extension: '.js' });
112123
emitTSFiles({ outDir, module: 'es2020', extension: '.mjs' });
113124
}
@@ -121,6 +132,23 @@ async function buildPackage(outDir: string, isESMOnly: boolean): Promise<void> {
121132
writeGeneratedFile(packageJsonPath, prettified);
122133
}
123134

135+
function replaceImportPath(value: string, extension: string) {
136+
return value.replace(/\/src\//g, '/').replace(/\.ts$/, extension);
137+
}
138+
139+
function rewriteImports(
140+
imports: ImportsMap,
141+
replacer: (value: string) => PathOrConditionalPath,
142+
) {
143+
for (const [key, value] of Object.entries(imports)) {
144+
if (typeof value === 'string') {
145+
imports[key] = replacer(value);
146+
continue;
147+
}
148+
rewriteImports(value, replacer);
149+
}
150+
}
151+
124152
// Based on https://github.com/Microsoft/TypeScript/wiki/Using-the-Compiler-API#getting-the-dts-from-a-javascript-file
125153
function emitTSFiles(options: {
126154
outDir: string;
@@ -143,7 +171,15 @@ function emitTSFiles(options: {
143171
tsHost.writeFile = (filepath, body) =>
144172
writeGeneratedFile(filepath.replace(/.js$/, extension), body);
145173

146-
const tsProgram = ts.createProgram(['src/index.ts'], tsOptions, tsHost);
174+
const tsProgram = ts.createProgram(
175+
[
176+
'src/index.ts',
177+
'src/jsutils/instanceOf.ts',
178+
'src/jsutils/instanceOfForDevelopment.ts',
179+
],
180+
tsOptions,
181+
tsHost,
182+
);
147183
const tsResult = tsProgram.emit(undefined, undefined, undefined, undefined, {
148184
before: [changeExtensionInImportPaths({ extension }), inlineInvariant],
149185
});

resources/utils.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,12 @@ export function writeGeneratedFile(filepath: string, body: string): void {
227227
fs.writeFileSync(filepath, body);
228228
}
229229

230+
export type PathOrConditionalPath = string | { [condition: string]: string };
231+
232+
export interface ImportsMap {
233+
[path: string]: PathOrConditionalPath;
234+
}
235+
230236
interface PackageJSON {
231237
description: string;
232238
version: string;
@@ -235,6 +241,7 @@ interface PackageJSON {
235241
scripts?: { [name: string]: string };
236242
type?: string;
237243
exports: { [path: string]: string };
244+
imports: ImportsMap;
238245
types?: string;
239246
typesVersions: { [ranges: string]: { [path: string]: Array<string> } };
240247
devDependencies?: { [name: string]: string };

src/jsutils/__tests__/instanceOf-test.ts

Lines changed: 1 addition & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ describe('instanceOf', () => {
77
it('do not throw on values without prototype', () => {
88
class Foo {
99
get [Symbol.toStringTag]() {
10+
/* c8 ignore next 2 */
1011
return 'Foo';
1112
}
1213
}
@@ -15,65 +16,4 @@ describe('instanceOf', () => {
1516
expect(instanceOf(null, Foo)).to.equal(false);
1617
expect(instanceOf(Object.create(null), Foo)).to.equal(false);
1718
});
18-
19-
it('detect name clashes with older versions of this lib', () => {
20-
function oldVersion() {
21-
class Foo {}
22-
return Foo;
23-
}
24-
25-
function newVersion() {
26-
class Foo {
27-
get [Symbol.toStringTag]() {
28-
return 'Foo';
29-
}
30-
}
31-
return Foo;
32-
}
33-
34-
const NewClass = newVersion();
35-
const OldClass = oldVersion();
36-
expect(instanceOf(new NewClass(), NewClass)).to.equal(true);
37-
expect(() => instanceOf(new OldClass(), NewClass)).to.throw();
38-
});
39-
40-
it('allows instances to have share the same constructor name', () => {
41-
function getMinifiedClass(tag: string) {
42-
class SomeNameAfterMinification {
43-
get [Symbol.toStringTag]() {
44-
return tag;
45-
}
46-
}
47-
return SomeNameAfterMinification;
48-
}
49-
50-
const Foo = getMinifiedClass('Foo');
51-
const Bar = getMinifiedClass('Bar');
52-
expect(instanceOf(new Foo(), Bar)).to.equal(false);
53-
expect(instanceOf(new Bar(), Foo)).to.equal(false);
54-
55-
const DuplicateOfFoo = getMinifiedClass('Foo');
56-
expect(() => instanceOf(new DuplicateOfFoo(), Foo)).to.throw();
57-
expect(() => instanceOf(new Foo(), DuplicateOfFoo)).to.throw();
58-
});
59-
60-
it('fails with descriptive error message', () => {
61-
function getFoo() {
62-
class Foo {
63-
get [Symbol.toStringTag]() {
64-
return 'Foo';
65-
}
66-
}
67-
return Foo;
68-
}
69-
const Foo1 = getFoo();
70-
const Foo2 = getFoo();
71-
72-
expect(() => instanceOf(new Foo1(), Foo2)).to.throw(
73-
/^Cannot use Foo "{}" from another module or realm./m,
74-
);
75-
expect(() => instanceOf(new Foo2(), Foo1)).to.throw(
76-
/^Cannot use Foo "{}" from another module or realm./m,
77-
);
78-
});
7919
});
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { expect } from 'chai';
2+
import { describe, it } from 'mocha';
3+
4+
import { instanceOf as instanceOfForDevelopment } from '../instanceOfForDevelopment.ts';
5+
6+
describe('instanceOfForDevelopment', () => {
7+
it('do not throw on values without prototype', () => {
8+
class Foo {
9+
get [Symbol.toStringTag]() {
10+
return 'Foo';
11+
}
12+
}
13+
14+
expect(instanceOfForDevelopment(true, Foo)).to.equal(false);
15+
expect(instanceOfForDevelopment(null, Foo)).to.equal(false);
16+
expect(instanceOfForDevelopment(Object.create(null), Foo)).to.equal(false);
17+
});
18+
19+
it('detect name clashes with older versions of this lib', () => {
20+
function oldVersion() {
21+
class Foo {}
22+
return Foo;
23+
}
24+
25+
function newVersion() {
26+
class Foo {
27+
get [Symbol.toStringTag]() {
28+
return 'Foo';
29+
}
30+
}
31+
return Foo;
32+
}
33+
34+
const NewClass = newVersion();
35+
const OldClass = oldVersion();
36+
expect(instanceOfForDevelopment(new NewClass(), NewClass)).to.equal(true);
37+
expect(() => instanceOfForDevelopment(new OldClass(), NewClass)).to.throw();
38+
});
39+
40+
it('allows instances to have share the same constructor name', () => {
41+
function getMinifiedClass(tag: string) {
42+
class SomeNameAfterMinification {
43+
get [Symbol.toStringTag]() {
44+
return tag;
45+
}
46+
}
47+
return SomeNameAfterMinification;
48+
}
49+
50+
const Foo = getMinifiedClass('Foo');
51+
const Bar = getMinifiedClass('Bar');
52+
expect(instanceOfForDevelopment(new Foo(), Bar)).to.equal(false);
53+
expect(instanceOfForDevelopment(new Bar(), Foo)).to.equal(false);
54+
55+
const DuplicateOfFoo = getMinifiedClass('Foo');
56+
expect(() =>
57+
instanceOfForDevelopment(new DuplicateOfFoo(), Foo),
58+
).to.throw();
59+
expect(() =>
60+
instanceOfForDevelopment(new Foo(), DuplicateOfFoo),
61+
).to.throw();
62+
});
63+
64+
it('fails with descriptive error message', () => {
65+
function getFoo() {
66+
class Foo {
67+
get [Symbol.toStringTag]() {
68+
return 'Foo';
69+
}
70+
}
71+
return Foo;
72+
}
73+
const Foo1 = getFoo();
74+
const Foo2 = getFoo();
75+
76+
expect(() => instanceOfForDevelopment(new Foo1(), Foo2)).to.throw(
77+
/^Cannot use Foo "{}" from another module or realm./m,
78+
);
79+
expect(() => instanceOfForDevelopment(new Foo2(), Foo1)).to.throw(
80+
/^Cannot use Foo "{}" from another module or realm./m,
81+
);
82+
});
83+
});

src/jsutils/instanceOf.ts

Lines changed: 3 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,6 @@
1-
import { inspect } from './inspect.ts';
2-
3-
/* c8 ignore next 3 */
4-
const isProduction =
5-
globalThis.process != null &&
6-
// eslint-disable-next-line no-undef
7-
process.env.NODE_ENV === 'production';
8-
9-
/**
10-
* A replacement for instanceof which includes an error warning when multi-realm
11-
* constructors are detected.
12-
* See: https://expressjs.com/en/advanced/best-practice-performance.html#set-node_env-to-production
13-
* See: https://webpack.js.org/guides/production/
14-
*/
15-
export const instanceOf: (value: unknown, constructor: Constructor) => boolean =
16-
/* c8 ignore next 6 */
17-
// FIXME: https://github.com/graphql/graphql-js/issues/2317
18-
isProduction
19-
? function instanceOf(value: unknown, constructor: Constructor): boolean {
20-
return value instanceof constructor;
21-
}
22-
: function instanceOf(value: unknown, constructor: Constructor): boolean {
23-
if (value instanceof constructor) {
24-
return true;
25-
}
26-
if (typeof value === 'object' && value !== null) {
27-
// Prefer Symbol.toStringTag since it is immune to minification.
28-
const className = constructor.prototype[Symbol.toStringTag];
29-
const valueClassName =
30-
// We still need to support constructor's name to detect conflicts with older versions of this library.
31-
Symbol.toStringTag in value
32-
? value[Symbol.toStringTag]
33-
: value.constructor?.name;
34-
if (className === valueClassName) {
35-
const stringifiedValue = inspect(value);
36-
throw new Error(
37-
`Cannot use ${className} "${stringifiedValue}" from another module or realm.
38-
39-
Ensure that there is only one instance of "graphql" in the node_modules
40-
directory. If different versions of "graphql" are the dependencies of other
41-
relied on modules, use "resolutions" to ensure only one version is installed.
42-
43-
https://yarnpkg.com/en/docs/selective-version-resolutions
44-
45-
Duplicate "graphql" modules cannot be used at the same time since different
46-
versions may have different capabilities and behavior. The data from one
47-
version used in the function from another could produce confusing and
48-
spurious results.`,
49-
);
50-
}
51-
}
52-
return false;
53-
};
54-
1+
export function instanceOf(value: unknown, constructor: Constructor): boolean {
2+
return value instanceof constructor;
3+
}
554
interface Constructor extends Function {
565
prototype: {
576
[Symbol.toStringTag]: string;

0 commit comments

Comments
 (0)