Skip to content

Modules

An NX file is still a module, but imports now target libraries. A library is a directory whose .nx files contribute declarations recursively. For the full grammar, see nx-grammar.md.

  • Imports appear at the top of the file and pull in declarations from library directories.
  • Local declarations use let, type, action, or component.
  • A module may export a root element directly or expose named bindings.
import "./theme"
import { Button, Input } from "./ui"
import { Stack as Layout.Stack } from "./layout"
let greeting = "Hello"
private let <WelcomeMessage/> = <span>Hello World</span>
export let accentName = "hello"
<Layout.Stack>
<text>{accentName}</text>
<WelcomeMessage/>
<Button/>
<Input/>
</Layout.Stack>
  • import "<library>" brings every visible export into scope unqualified.
  • import "<library>" as Namespace keeps imported symbols under Namespace.
  • import { Name } from "<library>" imports specific declarations without qualification.
  • import { Name as Prefix.Name } from "<library>" adds only a qualification prefix; the final segment must remain Name.
  • Importing the same library path twice in one file is a compile error.
  • If two libraries export the same unqualified name, NX reports an error only when that ambiguous name is used.
  • Local directory libraries are supported today. Git directory URLs and HTTP zip URLs parse, but currently resolve with a “not yet supported” diagnostic.
  • A library is a directory, not a barrel file.
  • Every .nx file under that directory contributes declarations recursively.
  • Declarations are internal by default, so helper bindings stay inside the library unless marked export.

NX’s built-in types are declared in the NX prelude, a module every file imports automatically. Nothing names it and nothing enables it: its exported declarations are simply in scope in every module, in every build — a single file, a workspace, a library, an editor session.

  • The prelude lives under the reserved @nx/ root, as the module @nx/prelude.nx. A workspace may not supply a module under that identity.
  • Its names are shadowable. A declaration of your own, or a name from any import you wrote, takes the name silently, exactly as it would over a wildcard import. There is no diagnostic and no ambiguity: a prelude name is bound last, and only where nothing else claimed it.
  • Shadowing a built-in disables the syntax that means it. A module that declares its own Range cannot use .. or ..= there, because those operators construct the prelude’s Range; the element form still works, and every other module is unaffected.
  • A library’s exports are its own declarations. The prelude is never re-exported.

The prelude is one ordinary NX declaration:

export type Range = {
T:type
start:T
end:T
endInclusive:boolean
}

See Ranges for what Range means and for for counting over one.

  • A root element at the end of the file behaves like main. Tooling can render it immediately or expose it as the module default.
  • Alternatively, export named bindings and let consumers choose what to render.
Keyword Same file Other library files Consumers
private Yes No No
default Yes Yes No
export Yes Yes Yes
  • private keeps declarations in the current file only.
  • Omitting a visibility keyword shares declarations across files in the same library or program while hiding them from external consumers.
  • export exposes declarations to importing libraries.