Home About Documentation Templates Examples Showcase GitHub
Theme

Scripting · Reusable code

Imports & exports.

import(path) executes an external Nift script in an isolated scope and imports only the bindings that script explicitly exports.

Use bare import(path) in script land: standalone .f files, package source, the REPL and @script blocks. At template top level, use the @import(path) directive. Script land continues to accept @import(path) for compatibility, but bare import(path) is the canonical spelling.

// scripts/math.f
factor := 2
double := x => x * factor

export(double)
@import("scripts/math.f")

<p>$[double(21)]</p>

The imported script cannot implicitly read bindings from its caller. export(name) exports the actual binding, not a value snapshot, so exported closures retain private state correctly. Closures capture script locals (use a lambda such as double := x => x * factor); named fn declarations resolve free names in the calling scope, matching ordinary template @fn semantics.

count := 0
increment := () => ++count

export(count)
export(increment)

Exports are validated and installed atomically. Exporting a missing name, exporting the same name twice, or colliding with an existing caller binding makes the import fail without partially changing the caller.

Completion

CompletionMeaning during an import
Fall throughSuccessful import; commit valid exports.
returnEarly successful completion; commit valid exports.
return valueError. Imports communicate programmatically through export().

Imported scripts participate in Nift's path-security and tracked dependency model, including applicable transitive imports.

Recoverable import failures

A missing or unreadable import source is a recoverable operational failure that try/catch can handle. The codes are io.import_source_unreadable for an ordinary non-package import, and package.import_source_unreadable / package.not_installed for package imports. Rollback happens before the caller's catch runs, so no partial exports or types are visible.

try {
    import("./optional-module.f")
} catch(err) {
    print(err.code)   // io.import_source_unreadable
}

Malformed import syntax, invalid argument types, import cycles, malformed package metadata, authority/path violations, and failed imports that created a worker remain fatal and bypass catch.

Git-native packages

Nift packages are ordinary Git repositories containing a manifest.json and Nift .f modules. Install an official registry package with nift add sqlite, an arbitrary GitHub package with nift add github:user/repo, or a local development package with nift add ../package.

nift add sqlite
nift add github:user/package --ref=latest-tag
nift install
nift update sqlite
nift remove sqlite

Package references may track latest, latest-tag, an explicit tag/branch/commit, or a local path. Remote dependencies are recorded in .nift/packages.lock.json at an exact Git commit so normal installs are reproducible; nift update deliberately advances floating references.

Dependencies are transitive: installing a package installs its declared dependencies too, and packages.lock.json records the complete resolved graph (every package node with its exact commit plus the requirement edges). nift packages [name] [--json] explains why a package is installed, listing every dependency path and what each parent requested.

An installed package is imported by its package name:

import("sqlite")

db := sqlite.open("content.db")
rows := sqlite.query(db, "SELECT * FROM posts")

Module-style exports

A package can export a struct instance or plain object whose callable fields are invoked as methods, giving packages a natural vips.resize(...)-style interface while keeping package-private helpers out of the importer. Callable fields retain access to the module's private helpers; those helpers and any other private bindings never leak into the importing project. In package .f source the helpers use ordinary script-land fn syntax.

fn(scale_helper(x)) { return x * 2 }
@struct(vips_lib) { resize := (w, h) => { return {"w": scale_helper(w), "h": scale_helper(h)} } }
vips := vips_lib()
export(vips)
import("vips")
r := vips.resize(100, 50)
print(r.w)  // 200

Source ownership

Relative imports resolve from their defining source, not the process working directory: a file that imports ./helper.f finds helper.f next to that file, even when the file is later invoked through a function, callback, lambda or worker. Missing siblings are errors and never fall through to a same-named file in the consuming project.

module_path() returns the absolute directory of the currently executing file-backed module; package_path() returns the absolute root of the package that owns the current module. Both accept one optional relative path argument and are confined to their owner (package-owned results never escape the package root). They only produce path strings and never fall back to the working directory.

Installed packages carry frozen lock provenance (canonical source + exact commit or local). A package whose remote generation changed since it was imported is a controlled stale-provenance error; an unchanged local package continues to follow its live local contents.