Skip to content

Modules

Modules are the recommended way to organize a Jule project. A module defines the project's structure and groups related packages in a safe, modular fashion. This enables the use of subpackages, helps maintain clean organization, and is especially useful when designing third-party packages.

Modules standardize code layout and establish the main structure of a project. The module file acts as the entry point: the directory containing it is treated as the module's root, and the compiler processes your code accordingly.

Initialize a Module

To create a module with a name, run the compiler command in the target directory. This generates the required files and marks the current working directory as the root of your module.

For example:

$ julec mod init mylib

Module Files

A module is defined by a file named jule.mod. Its presence marks the directory as the root of the module and provides the necessary functionality for modular development.

Using Modules

Modules are not optional if you want to use Jule's testing features—without them, testing becomes more difficult and less functional. See writing tests for details.

For most projects, subpackages are essential to maintainable development. Modules are required to organize and import these subpackages.

Modules resolve subpackages relative to the module root. In a use declaration, every import path must begin with the module name. Any import path that does not start with the module name is considered invalid.

For example:

project/
├─ foo/
│  ├─ bar/
│  │  └─ bar.jule
│  └─ foo.jule
├─ jule.mod (name is baz)
└─ main.jule

Here a module with name baz, the file main.jule must import the bar package as "baz/foo/bar", because the module name is baz. Likewise, the foo package must also use "baz/foo/bar", not just bar.

Nested Modules

Nested modules are supported. This is common when importing third-party packages, which define their own modules independently of your project.

In such cases, each module treats its own jule.mod as the root. This ensures safe and predictable import paths.

For example:

project/
├─ foo/
│  ├─ bar/
│  │  └─ bar.jule
│  ├─ foo.jule
│  └─ jule.mod (name is foo)
├─ jule.mod (name is baz)
└─ main.jule

In this structure main.jule located in the root module, imports bar as "baz/foo/bar". The foo package, however, contains its own jule.mod, making it a separate module. Inside foo, the bar package is imported as "foo/bar".

This design makes the foo package self-contained and portable. Thanks to its own module file, it can be moved or reused in different projects without breaking imports.