diff --git a/docs/lib/content/using-npm/developers.md b/docs/lib/content/using-npm/developers.md index de0cb848c59ff..b1fff0e3894f3 100644 --- a/docs/lib/content/using-npm/developers.md +++ b/docs/lib/content/using-npm/developers.md @@ -171,6 +171,24 @@ to install it locally into the node_modules folder in that other place. Then go into the node-repl, and try using require("my-thing") to bring in your module's main module. +#### Catching undeclared ("phantom") dependencies + +Under the default hoisted `node_modules` layout, your package can `import` a dependency it never declared and still resolve it. +A transitive dependency hoisted alongside it, or your workspace root's `node_modules`, happens to satisfy the `import`. +That undeclared ("phantom") dependency passes your own build silently, then fails for anyone who installs your package on its own. + +We recommend developing your package under [`install-strategy=linked`](/using-npm/config#install-strategy). +The isolated layout only exposes a package's *declared* dependencies, so an `import` of an undeclared package fails for you during development instead of resolving by accident, shipping broken, and failing for your users: + +```bash +npm install --install-strategy=linked +npm test +``` + +> **Note:** This doesn't catch every case. +> A dependency that's still satisfied at your build by a `devDependency` or by your workspace root's `node_modules` can resolve fine for you and still be missing for whoever installs your package. +> So treat it as one check, not a guarantee, alongside auditing the dependencies your published package actually uses. + ### Create a User Account Create a user with the adduser command. diff --git a/tap-snapshots/test/lib/docs.js.test.cjs b/tap-snapshots/test/lib/docs.js.test.cjs index a4c1e235ed784..686a10f794521 100644 --- a/tap-snapshots/test/lib/docs.js.test.cjs +++ b/tap-snapshots/test/lib/docs.js.test.cjs @@ -1092,6 +1092,14 @@ place, no hoisting. shallow (formerly --global-style) only install direct deps at top-level. linked: install in node_modules/.store, link in place, unhoisted. +We recommend that package authors use \`--install-strategy=linked\` during +development to catch undeclared ("phantom") dependencies before publishing: +the isolated layout only exposes a package's declared dependencies, so an +\`import\` of a package that was never added to \`package.json\` can fail +instead of resolving by accident and shipping broken. See [Catching +undeclared ("phantom") +dependencies](/using-npm/developers#catching-undeclared-phantom-dependencies). + #### \`json\` diff --git a/workspaces/config/lib/definitions/definitions.js b/workspaces/config/lib/definitions/definitions.js index ed1a25bf93a34..acf2d3e93a48e 100644 --- a/workspaces/config/lib/definitions/definitions.js +++ b/workspaces/config/lib/definitions/definitions.js @@ -1209,6 +1209,14 @@ const definitions = { nested: (formerly --legacy-bundling) install in place, no hoisting. shallow (formerly --global-style) only install direct deps at top-level. linked: install in node_modules/.store, link in place, unhoisted. + + We recommend that package authors use \`--install-strategy=linked\` + during development to catch undeclared ("phantom") dependencies before + publishing: the isolated layout only exposes a package's declared + dependencies, so an \`import\` of a package that was never added to + \`package.json\` can fail instead of resolving by accident and shipping + broken. See [Catching undeclared ("phantom") + dependencies](/using-npm/developers#catching-undeclared-phantom-dependencies). `, flatten, }),