diff --git a/website/pages/docs/_meta.ts b/website/pages/docs/_meta.ts index 8a32a1f04c..6e9b9870da 100644 --- a/website/pages/docs/_meta.ts +++ b/website/pages/docs/_meta.ts @@ -74,6 +74,7 @@ const meta = { }, 'development-mode': '', 'going-to-production': '', + 'production-build-optimization': '', 'scaling-graphql': '', }; diff --git a/website/pages/docs/production-build-optimization.mdx b/website/pages/docs/production-build-optimization.mdx new file mode 100644 index 0000000000..f868e5218f --- /dev/null +++ b/website/pages/docs/production-build-optimization.mdx @@ -0,0 +1,340 @@ +--- +title: Optimize your GraphQL build for production +description: Bundle, tree-shake, and configure GraphQL.js applications for production, and skip validation safely for trusted schemas and operations. +--- + +import { Callout } from 'nextra/components'; + +When you deploy your GraphQL application to production, you +want to ship the smallest, fastest bundle you can. This guide shows you +the general build techniques that apply to any JavaScript application, plus +the few things that are specific to GraphQL.js. + +## What production builds change for GraphQL.js + +Optimizing a GraphQL.js application for production is mostly the same work as +optimizing any JavaScript application: you bundle your code, eliminate what you +don't use, and minify the result. On top of that general work, there are two +GraphQL.js-specific things to understand—one that your environment controls, and +a few validation steps you can choose to skip for trusted schemas and operations. + +### The one behavior tied to NODE_ENV + +Internally, GraphQL.js reads `process.env.NODE_ENV` in a single place: its +`instanceOf` check, which backs every type predicate such as `isObjectType` +and `isSchema`. In development, that check does extra work to detect when two +copies of the `graphql` package are loaded at once—a common and hard-to-debug +problem—and throws a descriptive error. In production, it falls back to a plain +`instanceof`. + +Setting `NODE_ENV=production` removes this small per-call overhead and lets your +bundler drop the development-only code path. It does **not** disable schema +validation, change how errors are formatted, or turn off introspection—those are +separate, explicit choices. For a full explanation of development mode, the +duplicate-module check, and how this changes in v17, see +[Development Mode](./development-mode). + + +In GraphQL.js v17 this behavior is no longer driven by `NODE_ENV`. It is +selected by the `production` and `development` conditional exports instead, with +production behavior enabled by default. See +[graphql-js#4551](https://github.com/graphql/graphql-js/pull/4551). + + +### Validation you can skip for trusted schemas and operations + +By default, `validate()` and `execute()` call `assertValidSchema()`, and servers +typically call `validate()` on every incoming operation. These checks are +valuable while you develop, but redundant once a schema and its operations are +known to be correct. GraphQL.js lets you opt out: + +- **Trust a schema.** Build it with `assumeValid` so the automatic schema + validation short-circuits: + + ```js + const schema = new GraphQLSchema({ query, assumeValid: true }); + ``` + + When building from SDL, pass `assumeValid` or `assumeValidSDL` to + `buildSchema`, `buildASTSchema`, or `extendSchema` to skip SDL validation. +- **Trust an operation.** For persisted or otherwise pre-validated operations, + you can skip the per-request `validate()` call and execute the parsed document + directly. + +Only skip validation for schemas and operations you control. Validating +untrusted, client-supplied operations is a security boundary, not just a +development convenience. + +## Build tools and bundling + +Build tools are programs that transform your source code into files ready +for production deployment. Build tools perform several transformations +on your code: + +- **Combine files**: Take your source code spread across many files and +combine them into one or more bundles. +- **Transform code**: Convert modern JavaScript syntax to versions that work +in your target environments. +- **Remove unused code**: Analyze your code and eliminate functions, variables, +and entire modules that your application never uses. +- **Replace variables**: Substitute configuration values +(like environment variables) with their actual values. +- **Compress files**: Minimize file sizes by removing whitespace, shortening +variable names, and applying other size reductions. + +Some common build tools include [Webpack](https://webpack.js.org/), +[Vite](https://vite.dev/), [Rollup](https://rollupjs.org/), +[esbuild](https://esbuild.github.io/), and [Parcel](https://parceljs.org/). +Each tool has different configuration syntax, but supports the same +core concepts needed for GraphQL production preparation. + +## Configure environment variables + +Setting `NODE_ENV` to `'production'` at build time is what lets your bundler +drop the development-only `instanceOf` path described above. Many other +libraries (including Express) follow the same convention, so a single setting +optimizes your whole dependency tree. + +### Set NODE_ENV to production + +Ensure your build tool replaces `process.env.NODE_ENV` with the string +`'production'` during the build process, not at runtime. + +```js +// Before build-time replacement +if (process.env.NODE_ENV !== 'production') { + // GraphQL.js runs its development-only instanceof safety check here +} +``` + +Once your build tool substitutes `'production'` for `process.env.NODE_ENV`, the +condition becomes statically false, and dead code elimination removes the branch +entirely from your production bundle. + +## Enable dead code elimination + +Dead code elimination (also called tree shaking) is essential for removing +GraphQL.js development code from your production bundle. + +### Configure your build tool + +Most build tools require specific configuration to enable aggressive +dead code elimination: + +- Mark your project as side-effect free. This tells your build tool that it's +safe to remove any code that isn't explicitly used. +- Use ES modules. Modern syntax (import/export) enables better code analysis +than older CommonJS syntax (require/exports). +- Enable unused export removal. Configure your build tool to remove functions +and variables that are exported but never imported. +- Configure minification. Set up your minifier to remove unreachable code after +environment variable replacement. + +### Configuration pattern + +While syntax varies by tool, most build tools support this pattern: + +```js +{ + "optimization": { + "usedExports": true, + "sideEffects": false + } +} +``` + +This configuration enables the build tool to safely remove any GraphQL.js code that +won't execute in production. + +## Handle browser compatibility + +If your GraphQL application runs in web browsers, you need to address Node.js +compatibility issues. + +### Provide process polyfills + +GraphQL.js assumes Node.js globals like `process` are available. Browsers don't +have these globals, so your build tool needs to provide them or replace references +to them. + +Most build tools let you define global variables that get replaced throughout +your code: + +```js +{ + "define": { + "globalThis.process": "true" + } +} +``` + +This replaces any reference to `process` with a minimal object that satisfies +GraphQL.js's needs. + +### Avoid Node.js-specific APIs + +Ensure your GraphQL client code doesn't use Node.js-specific APIs like `fs` +(file system) or path. These APIs don't exist in browsers and will cause runtime +errors. + +## Configure code splitting + +Code splitting separates your GraphQL code into its own bundle file, which can +improve loading performance and caching. Benefits of code splitting include +better caching, parallel loading, and selective loading. + +### Basic code splitting configuration + +Most build tools support splitting specific packages into separate bundles: + +```js +{ + "splitChunks": { + "cacheGroups": { + "graphql": { + "test": "/graphql/", + "name": "graphql", + "chunks": "all" + } + } + } +} +``` + +This configuration creates a separate bundle file containing all +GraphQL-related code. + +## Apply build tool configurations + +These examples show how to apply the universal principles using +different build tools. Adapt these patterns to your specific tooling. + +Note: These are illustrative examples showing common patterns. Consult +your specific build tool's documentation for exact syntax and available features. + +### Webpack configuration + +Webpack uses plugins and configuration objects to control the build process: + +```js +import webpack from 'webpack'; + +export default { + mode: 'production', + + plugins: [ + new webpack.DefinePlugin({ + 'process.env.NODE_ENV': JSON.stringify('production'), + 'globalThis.process': JSON.stringify(true), + }), + ], + + optimization: { + usedExports: true, + sideEffects: false, + }, +}; +``` + +The DefinePlugin replaces environment variables at build time. The +optimization section enables dead code elimination. + +### Rollup configuration + +Rollup uses plugins to transform code during the build process: + +```js +import replace from '@rollup/plugin-replace'; + +export default { + plugins: [ + replace({ + 'process.env.NODE_ENV': JSON.stringify('production'), + preventAssignment: true, + }), + ], +}; +``` + +The replace plugin substitutes environment variables with their +values throughout your code. + +### esbuild configuration + +esbuild uses a configuration object to control build behavior: + +```js +{ + "define": { + "process.env.NODE_ENV": "\"production\"", + "globalThis.process": "true" + }, + "treeShaking": true +} +``` + +The define section replaces variables, and treeShaking enables +dead code elimination. + +## Measure your results + +You should measure your bundle size before and after these +changes to verify they're working correctly. + +### Install analysis tools + +Most build tools provide bundle analysis capabilities through plugins +or built-in commands. These include bundle visualizers, size reporters, +and dependency analyzers. + +### Expected improvements + +Bundling, tree shaking, and minification typically shrink a GraphQL.js +application's bundle noticeably. The exact reduction depends on how much of +GraphQL.js you use and how effectively your build tool eliminates unused code, +so measure your own before-and-after numbers rather than relying on a fixed +figure. In a well-configured build you should see: + +- A smaller compressed bundle after tree shaking and minification. +- The development-only `instanceOf` code path removed from the output. + +Disabling introspection is a separate, runtime concern rather than a build-time +optimization. See [Going to Production](./going-to-production) for how to turn it +off with a validation rule. + +## Test production preparation + +Follow these steps to confirm your production preparation is working correctly. + +### Check environment variable replacement + +Search your built files to ensure environment variables were replaced: + +```bash +grep -r "process.env.NODE_ENV" your-build-directory/ +``` + +This command should return no results. If you find unreplaced variables, +your build tool isn't performing build-time replacement correctly. + +### Confirm development code removal + +Add this temporary test code to your application: + +```js +if (process.env.NODE_ENV !== 'production') { + console.log('This message should never appear in production'); +} +``` + +Build your application and load it in a browser. If this console message +appears, your dead code elimination isn't working. + +### Test GraphQL functionality + +Deploy your prepared build to a test environment and verify: + +- GraphQL queries execute successfully +- Error handling still works as expected +- Application performance improved +- No new runtime errors occur