Skip to content

Import options

Markdown for agents

Add one of these options to an import condition (mayNotImport, mayOnlyImport, mayOnlyBeImportedBy) or to a table. The condition then applies only to some imports.

Option Values The condition applies only to
names list of names imports that bring in one of these names
specifier 'relative', 'parent', 'alias', 'package' imports whose specifier has this form
instance 'same', 'different' imports inside one instance, or between two instances
dependency 'dev', 'undeclared' packages with this entry in the nearest package.json
within set of files imports into an area, or importers in an area
imports 'all', 'runtime', 'static', 'types' imports of this kind

An import that one of these options leaves out does not count as judged by the rule. archangel rules lists it under the imports that no rule judges.

rule('no-effects-in-views', { files: 'view', mayNotImport: { packages: ['react'] }, names: ['useEffect'], examples: { rejected: [{ 'src/view/a.ts': "import { useEffect } from 'react';" }] } })
  • A default import brings in the name default.
  • A namespace import (import * as x), an export *, a dynamic import() and a require() bring in each name of the module, because archangel cannot see which names the code uses.
  • For a module in a package, archangel reads the names that the module exports, through its relative export * from statements too. import * as fr from 'date-fns/locale/fr' brings in only the names of that locale. When archangel cannot see every name (a CommonJS module, an export =, an export * from another package), the import brings in each name there is.
rule('no-parent-imports', { files: 'feature', mayNotImport: 'feature', specifier: 'parent', examples: { rejected: [{ 'src/features/login/ui/a.ts': "import '../b';", 'src/features/login/b.ts': '' }] } })
Value The specifier
'relative' starts with ./ or ../
'parent' starts with ../
'alias' resolves through a tsconfig path or a package imports field
'package' is a package name
rule('features-apart', { files: 'feature', mayNotImport: 'feature', instance: 'different', examples: { rejected: ['src/features/login/a.ts imports src/features/cart/b.ts'] } })
  • 'same': imports whose two ends are in one instance of the part in files.
  • 'different': imports between two instances of that part.

files must be one part with a capture. Other files give rule-needs-capture.

rule('no-dev-packages-in-src', { files: 'src/**', mayNotImport: { packages: ['*'] }, dependency: 'dev', examples: { rejected: [{ 'package.json': '{ "devDependencies": { "react": "1.0.0" } }', 'src/a.ts': "import 'react';" }] } })
Value The package
'dev' is only in devDependencies of the nearest package.json of the importing file
'undeclared' is not in the nearest package.json of the importing file

The nearest package.json is the first one at or above the importing file. In a monorepo, this is the package.json of the workspace package. Read Monorepos.

within gives a rule an area. The rule then permits each import outside the area.

rule('entity-imports', { files: 'entity', mayOnlyImport: ['value-object', 'error'], within: 'src/**', examples: { rejected: ['src/entity/a.ts imports src/use-case/a.ts'] } })
On within limits
mayOnlyImport the targets: the rule judges only imports into the area
mayOnlyBeImportedBy the importers: the rule judges only imports from files in the area
a table the targets: each row judges only imports into the area
  • Without within, mayOnlyImport judges each import, packages too.
  • within can name an externals set. Then mayOnlyImport judges the imports of those packages.
  • With within, the list of mayOnlyImport can be empty. The rule then refuses each import into the area, except into its own files.
  • Archangel refuses within on mayNotImport, and on a condition that judges no import. To limit mayNotImport to an area, write the area on the other side: mayNotImport: { all: ['use-case', 'src/**'] }.
  • An area that finds no file is an error.
Value The rule counts
'all' each import. This is the default.
'runtime' the imports that stay in the compiled code. This is the default for noCycles and for a transitive rule.
'static' the runtime imports other than import(): the imports that load with the file, in its bundle chunk. A require() is static.
'types' only type imports, such as import type

An import type, and an import that names only types with import { type A }, is a type import. With verbatimModuleSyntax in your tsconfig, import { type A } from './a' stays in the compiled code, so it is a runtime import.

Add transitively: true to mayNotImport. The rule then finds chains of imports of each length, not only direct imports. The report shows the shortest chain.

rule('client-stays-client', {
files: { directive: 'use client' },
mayNotImport: { packages: ['node:*'] },
transitively: true,
stopAt: { directive: 'use server' },
examples: {
rejected: [{ 'src/view/a.tsx': "'use client';\nimport '../shared/a';", 'src/shared/a.ts': "import 'node:fs';" }],
},
})
  • A chain stops at a package, and at each file in stopAt.
  • A transitive rule’s side cannot name a package subpath, such as lodash-es/*, because a chain ends at a package.
  • With imports: 'static', a chain also stops at each import(). A bundler starts a new chunk there, so the rule then means “the initial bundle may not reach this”: rule('initial-bundle-no-carousel', { files: 'feature', mayNotImport: { packages: ['carousel'] }, transitively: true, imports: 'static' }).
  • A transitive rule counts only runtime imports by default. imports: 'types' is refused, because a chain of type imports never runs.
  • A transitive rule cannot also have names, specifier, instance, dependency or within.
  • For each file that reaches the other side, archangel reports one problem with the shortest chain. Among chains of one length, it shows the first in path order.

archangel why <file> <target> shows the shortest chain between two files, or from a file to a package. It does not know stopAt, so it can show a chain that the rule stops.