Skip to content

Commit

Permalink
Simplify __DEV__ management.
Browse files Browse the repository at this point in the history
  • Loading branch information
benjamn committed Feb 6, 2023
1 parent 52cf576 commit 05bf63f
Show file tree
Hide file tree
Showing 8 changed files with 70 additions and 89 deletions.
25 changes: 0 additions & 25 deletions config/distSpotFixes.ts

This file was deleted.

3 changes: 0 additions & 3 deletions config/postprocessDist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@ import * as path from "path";
import resolve from "resolve";
import { distDir, eachFile, reparse, reprint } from './helpers';

import { applyDistSpotFixes } from "./distSpotFixes";
applyDistSpotFixes();

// The primary goal of the 'npm run resolve' script is to make ECMAScript
// modules exposed by Apollo Client easier to consume natively in web browsers,
// without bundling, and without help from package.json files. It accomplishes
Expand Down
65 changes: 58 additions & 7 deletions config/processInvariants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as fs from "fs";
import * as path from "path";
import { posix, join as osPathJoin } from "path";
import { distDir, eachFile, reparse, reprint } from './helpers';

eachFile(distDir, (file, relPath) => {
Expand All @@ -10,7 +10,7 @@ eachFile(distDir, (file, relPath) => {
}
}).then(() => {
fs.writeFileSync(
path.join(distDir, "invariantErrorCodes.js"),
osPathJoin(distDir, "invariantErrorCodes.js"),
recast.print(errorCodeManifest, {
tabWidth: 2,
}).code + "\n",
Expand Down Expand Up @@ -56,14 +56,15 @@ function getErrorCode(
return numLit;
}

function transform(code: string, file: string) {
function transform(code: string, relativeFilePath: string) {
// If the code doesn't seem to contain anything invariant-related, we
// can skip parsing and transforming it.
if (!/invariant/i.test(code)) {
return code;
}

const ast = reparse(code);
let addedDEV = false;

recast.visit(ast, {
visitCallExpression(path) {
Expand All @@ -76,8 +77,9 @@ function transform(code: string, file: string) {
}

const newArgs = node.arguments.slice(0, 1);
newArgs.push(getErrorCode(file, node));
newArgs.push(getErrorCode(relativeFilePath, node));

addedDEV = true;
return b.conditionalExpression(
makeDEVExpr(),
node,
Expand All @@ -94,6 +96,7 @@ function transform(code: string, file: string) {
if (isDEVLogicalAnd(path.parent.node)) {
return;
}
addedDEV = true;
return b.logicalExpression("&&", makeDEVExpr(), node);
}
},
Expand All @@ -106,8 +109,9 @@ function transform(code: string, file: string) {
return;
}

const newArgs = [getErrorCode(file, node)];
const newArgs = [getErrorCode(relativeFilePath, node)];

addedDEV = true;
return b.conditionalExpression(
makeDEVExpr(),
node,
Expand All @@ -120,11 +124,58 @@ function transform(code: string, file: string) {
}
});

if (addedDEV) {
// Make sure there's an import { __DEV__ } from "../utilities/globals" or
// similar declaration in any module where we injected __DEV__.
let foundExistingImportDecl = false;

recast.visit(ast, {
visitImportDeclaration(path) {
this.traverse(path);
const node = path.node;
const importedModuleId = node.source.value;

// Normalize node.source.value relative to the current file.
if (
typeof importedModuleId === "string" &&
importedModuleId.startsWith(".")
) {
const normalized = posix.normalize(posix.join(
posix.dirname(relativeFilePath),
importedModuleId,
));
if (normalized === "utilities/globals") {
foundExistingImportDecl = true;
if (node.specifiers?.some(s => isIdWithName(s.local || s.id, "__DEV__"))) {
return false;
}
if (!node.specifiers) node.specifiers = [];
node.specifiers.push(b.importSpecifier(b.identifier("__DEV__")));
return false;
}
}
}
});

if (!foundExistingImportDecl) {
// We could modify the AST to include a new import declaration, but since
// this code is running at build time, we can simplify things by throwing
// here, because we expect invariant and InvariantError to be imported
// from the utilities/globals subpackage.
throw new Error(`Missing import from "${
posix.relative(
posix.dirname(relativeFilePath),
"utilities/globals",
)
} in ${relativeFilePath}`);
}
}

return reprint(ast);
}

function isIdWithName(node: Node, ...names: string[]) {
return n.Identifier.check(node) &&
function isIdWithName(node: Node | null | undefined, ...names: string[]) {
return node && n.Identifier.check(node) &&
names.some(name => name === node.name);
}

Expand Down
4 changes: 2 additions & 2 deletions src/core/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* Core */

import { DEV } from '../utilities/globals';
import { __DEV__ } from '../utilities/globals';

export {
ApolloClient,
Expand Down Expand Up @@ -90,7 +90,7 @@ export {
// Note that all invariant.* logging is hidden in production.
import { setVerbosity } from "ts-invariant";
export { setVerbosity as setLogVerbosity }
setVerbosity(DEV ? "log" : "silent");
setVerbosity(__DEV__ ? "log" : "silent");

// Note that importing `gql` by itself, then destructuring
// additional properties separately before exporting, is intentional.
Expand Down
2 changes: 1 addition & 1 deletion src/utilities/common/maybeDeepFreeze.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import '../globals'; // For __DEV__
import { __DEV__ } from '../globals';
import { isNonNullObject } from './objects';

function deepFreeze(value: any) {
Expand Down
28 changes: 6 additions & 22 deletions src/utilities/globals/DEV.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,13 @@
import global from "./global";
import { maybe } from "./maybe";

// To keep string-based find/replace minifiers from messing with __DEV__ inside
// string literals or properties like global.__DEV__, we construct the "__DEV__"
// string in a roundabout way that won't be altered by find/replace strategies.
const __ = "__";
const GLOBAL_KEY = [__, __].join("DEV");

function getDEV() {
try {
return Boolean(__DEV__);
} catch {
Object.defineProperty(global, GLOBAL_KEY, {
// In a buildless browser environment, maybe(() => process.env.NODE_ENV)
// evaluates as undefined, so __DEV__ becomes true by default, but can be
// initialized to false instead by a script/module that runs earlier.
value: maybe(() => process.env.NODE_ENV) !== "production",
enumerable: false,
configurable: true,
writable: true,
});
// Using computed property access rather than global.__DEV__ here prevents
// string-based find/replace strategies from munging this to global.false:
return (global as any)[GLOBAL_KEY];
}
return "__DEV__" in global
? Boolean(global.__DEV__)
// In a buildless browser environment, maybe(() => process.env.NODE_ENV)
// evaluates as undefined, so __DEV__ becomes true by default, but can be
// initialized to false instead by a script/module that runs earlier.
: maybe(() => process.env.NODE_ENV) !== "production";
}

export default getDEV();
22 changes: 1 addition & 21 deletions src/utilities/globals/global.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,5 @@
import { maybe } from "./maybe";

declare global {
// Despite our attempts to reuse the React Native __DEV__ constant instead of
// inventing something new and Apollo-specific, declaring a useful type for
// __DEV__ unfortunately conflicts (TS2451) with the global declaration in
// @types/react-native/index.d.ts.
//
// To hide that harmless conflict, we @ts-ignore this line, which should
// continue to provide a type for __DEV__ elsewhere in the Apollo Client
// codebase, even when @types/react-native is not in use.
//
// However, because TypeScript drops @ts-ignore comments when generating .d.ts
// files (https://github.com/microsoft/TypeScript/issues/38628), we also
// sanitize the dist/utilities/globals/global.d.ts file to avoid declaring
// __DEV__ globally altogether when @apollo/client is installed in the
// node_modules directory of an application.
//
// @ts-ignore
const __DEV__: boolean | undefined;
}

export default (
maybe(() => globalThis) ||
maybe(() => window) ||
Expand All @@ -33,5 +13,5 @@ export default (
// is an arms race you cannot win, at least not in JavaScript.
maybe(function() { return maybe.constructor("return this")() })
) as typeof globalThis & {
__DEV__: typeof __DEV__;
__DEV__?: boolean;
};
10 changes: 2 additions & 8 deletions src/utilities/globals/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@ import { invariant, InvariantError } from "ts-invariant";
// Just in case the graphql package switches from process.env.NODE_ENV to
// __DEV__, make sure __DEV__ is polyfilled before importing graphql.
import DEV from "./DEV";
export { DEV }
export function checkDEV() {
invariant("boolean" === typeof DEV, DEV);
}
export { DEV };
export const __DEV__ = DEV;

// Import graphql/jsutils/instanceOf safely, working around its unchecked usage
// of process.env.NODE_ENV and https://github.com/graphql/graphql-js/pull/2894.
Expand All @@ -19,7 +17,3 @@ removeTemporaryGlobals();
export { maybe } from "./maybe";
export { default as global } from "./global";
export { invariant, InvariantError }

// Ensure __DEV__ was properly initialized, and prevent tree-shaking bundlers
// from mistakenly pruning the ./DEV module (see issue #8674).
checkDEV();

0 comments on commit 05bf63f

Please sign in to comment.