Skip to content
NewBun 1.4.5 is out. Release notes
AphrodyBun

Linter · Rules

import/no-extraneous-dependencies

Disallow importing a package that package.json does not list

A package can end up in node_modules only because another package depends on it. Importing it works on your machine until that other package drops or upgrades it. It also fails for anyone who installs your package, because nothing tells the package manager to install it.

The rule reads the closest package.json above the file. A package is fine if it's in dependencies, devDependencies, optionalDependencies, peerDependencies or bundledDependencies. The rule checks import, export ... from, import() and require(). It skips relative paths, Node.js builtins, import type and packages that aren't installed.

Configure

Off by default. To turn it on:

bunfig.toml
[lint.rules]
"import/no-extraneous-dependencies" = "error" # or "warn"

Every option, set to its default. The ones that are commented out are unset by default.

bunfig.toml
[lint.rules]
"import/no-extraneous-dependencies" = ["error", {
  devDependencies = true,
  optionalDependencies = true,
  peerDependencies = true,
  bundledDependencies = true,
  # packageDir = <string or list>,
  includeInternal = false,
  includeTypes = false,
}]

Examples

Here ms is installed, but only express is listed. Reported:

package.json
{
  "name": "shop",
  "dependencies": { "express": "^5.0.0" }
}
node_modules/ms/package.json
{ "name": "ms", "main": "index.js" }
node_modules/ms/index.js
module.exports = text => Number.parseInt(text) * 1000;
import express from "express";
import ms from "ms";

Not reported, with the same package.json:

package.json
{
  "name": "shop",
  "dependencies": { "express": "^5.0.0" }
}
import express from "express";
import { join } from "node:path";
import { cart } from "./cart";

Options

OptionDefaultWhat it does
devDependenciestruefalse reports imports of packages that are only in devDependencies. An array of globs lists the files that can import them.
optionalDependenciestrueThe same for optionalDependencies.
peerDependenciestrueThe same for peerDependencies.
bundledDependenciestrueThe same for bundledDependencies.
packageDirnoneA directory, or an array of directories, to read package.json from instead of the closest one. In a monorepo, list the package and the root.
includeInternalfalseAlso check names that resolve to a file in your own project.
includeTypesfalseAlso check import type.

Globs and packageDir are relative to the directory where bun lint runs.

Most projects allow devDependencies in tests and scripts only:

bunfig.toml
[lint.rules]
"import/no-extraneous-dependencies" = ["error", {
  devDependencies = ["test/**", "scripts/**"],
}]

With that config, code that ships can't import a package that's missing in production:

package.json
{
  "name": "shop",
  "devDependencies": { "ms": "^2.0.0" }
}
node_modules/ms/package.json
{ "name": "ms", "main": "index.js" }
node_modules/ms/index.js
module.exports = text => Number.parseInt(text) * 1000;
src/cart.js
import ms from "ms";