Avoid importing entire crypto dependency tree if not in Node.js. (#2304)

The apollo-server-core package uses Node's built-in crypto module only to
create SHA-256 and -512 hashes.

When we're actually running in Node, the native crypto library is clearly
the best way to create these hashes, not least because we can assume it
will be available without having to bundle it first.

Outside of Node (such as in React Native apps), bundlers tend to fall back
on the crypto-browserify polyfill, which comprises more than a hundred
separate modules. Importing this polyfill at runtime (likely during
application startup) takes precious time and memory, even though almost
all of it is unused.

Since we only need to create SHA hashes, we can import the much smaller
sha.js library in non-Node environments, which happens to be what
crypto-browserify uses for SHA hashing, and is a widely used npm package
in its own right: https://www.npmjs.com/package/sha.js.
This commit is contained in:
Ben Newman 2019-02-12 14:07:42 -05:00 committed by GitHub
parent bf120018bf
commit 3f7a7f3d67
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 67 additions and 53 deletions

View file

@ -41,6 +41,7 @@
"graphql-tag": "^2.9.2",
"graphql-tools": "^4.0.0",
"graphql-upload": "^8.0.2",
"sha.js": "^2.4.11",
"subscriptions-transport-ws": "^0.9.11",
"ws": "^6.0.0"
},

View file

@ -29,7 +29,6 @@ import {
PersistedQueryNotSupportedError,
PersistedQueryNotFoundError,
} from 'apollo-server-errors';
import { createHash } from 'crypto';
import {
GraphQLRequest,
GraphQLResponse,
@ -53,8 +52,10 @@ export {
InvalidGraphQLRequestError,
};
import createSHA from './utils/createSHA';
function computeQueryHash(query: string) {
return createHash('sha256')
return createSHA('sha256')
.update(query)
.digest('hex');
}

View file

@ -0,0 +1,10 @@
import isNode from './isNode';
export default function(kind: string): import('crypto').Hash {
if (isNode) {
// Use module.require instead of just require to avoid bundling whatever
// crypto polyfills a non-Node bundler might fall back to.
return module.require('crypto').createHash(kind);
}
return require('sha.js')(kind);
}

View file

@ -0,0 +1,6 @@
export default typeof process === 'object' &&
process &&
process.release &&
process.release.name === 'node' &&
process.versions &&
typeof process.versions.node === 'string';

View file

@ -1,11 +1,7 @@
import isNode from './isNode';
const runtimeSupportsUploads = (() => {
if (
process &&
process.release &&
process.release.name === 'node' &&
process.versions &&
typeof process.versions.node === 'string'
) {
if (isNode) {
const [nodeMajor, nodeMinor] = process.versions.node
.split('.', 2)
.map(segment => parseInt(segment, 10));

View file

@ -3,7 +3,7 @@ import { execute, ExecutionResult } from 'graphql/execution';
import { getIntrospectionQuery, IntrospectionSchema } from 'graphql/utilities';
import stableStringify from 'fast-json-stable-stringify';
import { GraphQLSchema } from 'graphql/type';
import { createHash } from 'crypto';
import createSHA from './createSHA';
export function generateSchemaHash(schema: GraphQLSchema): string {
const introspectionQuery = getIntrospectionQuery();
@ -38,7 +38,7 @@ export function generateSchemaHash(schema: GraphQLSchema): string {
// layer, varying orders of the properties in the introspection
const stringifiedSchema = stableStringify(introspectionSchema);
return createHash('sha512')
return createSHA('sha512')
.update(stringifiedSchema)
.digest('hex');
}