From c767c02015ed3d35b2e8ee21f12fc0f836c0a9e5 Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Thu, 18 Nov 2021 15:31:49 -0500 Subject: [PATCH] Normalize 'ts-invariant/process' again in config/resolveModuleIds.ts. From @apollo/client@3.5.0 until my recent PR #9073 (v3.5.3), non-relative imported module identifiers like 'ts-invariant/process' were rewritten during the build process (during 'npm run resolve') to include a specific module with a file extension (not a directory), producing in this example 'ts-invariant/process/index.js'. That rewriting was the wrong choice in general (for example, it would rewrite 'graphql' to 'graphql/index.mjs'), so PR #9073 disabled it for all non-relative imports. However, the specific replacement of 'ts-invariant/process' with 'ts-invariant/process/index.js' is still useful to prevent the strange webpack 5-specific error The request 'ts-invariant/process' failed to resolve only because it was resolved as fully specified which apparently happens because the [resolve.fullySpecified](https://webpack.js.org/configuration/module/#resolvefullyspecified) option is true by default now, and 'ts-invariant/process/package.json' is ignored, effectively forbidding directory-style imports like 'ts-invariant/process' when the package.json of the enclosing package has `"type": "module"` (which became true in Apollo Client v3.5, thanks to #8396). App developers could configure resolve.fullySpecified explicitly false, but we (the Apollo Client team) prefer a general workaround. --- config/resolveModuleIds.ts | 37 +++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/config/resolveModuleIds.ts b/config/resolveModuleIds.ts index a63ed739f3f..f8798c654d1 100644 --- a/config/resolveModuleIds.ts +++ b/config/resolveModuleIds.ts @@ -3,6 +3,18 @@ import * as path from "path"; import resolve from "resolve"; import { distDir, eachFile, reparse, reprint } from './helpers'; +// 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 +// this goal by rewriting internal ./ and ../ (relative) imports to refer to a +// specific ESM module (not a directory), including its file extension. Because +// of this limited goal, this script only touches ESM modules that have .js file +// extensions, not .cjs CommonJS bundles. + +// A secondary goal of this script is to enforce that any module using the +// __DEV__ global constant imports the @apollo/client/utilities/globals polyfill +// module first. + eachFile(distDir, (file, relPath) => new Promise((resolve, reject) => { fs.readFile(file, "utf8", (error, source) => { if (error) return reject(error); @@ -83,12 +95,25 @@ class Transformer { } normalizeSourceString(file: string, source?: Node | null) { - if (source && n.StringLiteral.check(source) && this.isRelative(source.value)) { - try { - source.value = this.normalizeId(source.value, file); - } catch (error) { - console.error(`Failed to resolve ${source.value} in ${file} with error ${error}`); - process.exit(1); + if (source && n.StringLiteral.check(source)) { + // We mostly only worry about normalizing _relative_ module identifiers, + // which start with a ./ or ../ and refer to other modules within the + // @apollo/client package, but we also manually normalize one non-relative + // identifier, ts-invariant/process, to prevent webpack 5 errors + // containing the phrase "failed to resolve only because it was resolved + // as fully specified," referring to webpack's resolve.fullySpecified + // option, which is apparently now true by default when the enclosing + // package's package.json file has "type": "module" (which became true for + // Apollo Client in v3.5). + if (source.value.split("/", 2).join("/") === "ts-invariant/process") { + source.value = "ts-invariant/process/index.js"; + } else if (this.isRelative(source.value)) { + try { + source.value = this.normalizeId(source.value, file); + } catch (error) { + console.error(`Failed to resolve ${source.value} in ${file} with error ${error}`); + process.exit(1); + } } } }