Builder 🔨

Builder is a code generation framework for Gleam that enables easy and universal code generation.

⚠️ This project is still in alpha. Some API features may change and become unreliable without notice. It is recommended to wait for the full version 1.0 for a richer and more stable experience.

🦑 Sara now has its own dedicated repository!
Curious to explore Sara, a serialization code generator for Gleam?
Check it out here : https://github.com/gungun974/Sara

What is a Builder?

Builder provides the infrastructure to create custom code generators (called “builders”) for Gleam projects. It handles file watching, project analysis, and the build lifecycle, letting you focus on the generation logic.

Creating a Builder

1. Basic Builder Structure

A builder is created using one of these constructor functions:

Single File Builder - Processes each matching file individually:

builder.new_gleam_builder(fn(ctx, gleam_asset) {
  Nil
})

builder.new_generic_builder(["txt", "md"], fn(ctx, asset) {
  Nil
})

Multiple Files Builder - Processes all files together in one batch:

builder.new_gleam_multiple_builder(fn(ctx, gleam_assets) {
  Nil
})

builder.new_generic_multiple_builder(["css"], fn(ctx, assets) {
  Nil
})

2. Working with Gleam Modules

For Gleam builders, the input parameter provides access to the parsed module:

import builder/inspect

pub fn my_builder() {
  builder.new_gleam_builder(fn(ctx, input) {
    let annotated_types =
      input.module.custom_types
      |> inspect.filter_attributes("my_annotation")

    Nil
  })
}

3. Generating Code

The BuildContext help you read and write new files. It’s abstract the file system which make more simple after to write tests to check behavior.

Please note you can’t write file your builder have read access

import builder/context
import builder/asset
import builder/format

pub fn my_builder() {
  builder.new_gleam_builder(fn(ctx, input) {
    let generated_code = "pub fn hello() { \"Hello!\" }"

    let assert Ok(formatted) = format.format_gleam_code(generated_code)

    let _ = context.write(
      ctx,
      input.file |> asset.change_extension("_generated.gleam"),
      formatted,
    )

    Nil
  })
}

4. Running Your Builder

Create a new gleam project inside your existing project gleam new build_runner

Why having an extra gleam project ?

The build_runner is a separate Gleam project that must remain compilable even when your main gleam project’s src directory contains broken or malformed code. (This can happen if you write a builder that generates invalid code) If other builders were inside your main project, you wouldn’t be able to run the build_runner if src contain errors, preventing you to rerun your build_runner. By keeping the build_runner as an independent project and linking it as a dev dependency to your main one, you can always rerun it to regenerate and fix problematic code beside having a broken src.

Note: Also having the build_runner into a separate Gleam project make it easier to share it configuration in a monorepo situation

Install both builder as regular dependencies (not dev dependencies) in your build_runner project:

gleam add builder

Edit src/build_runner.gleam:

import builder
import my_package/my_builder

pub fn main() {
  builder.execute_builders([
    my_builder.my_builder(),
  ])
}

Make your main project depend on the build runner as a dev dependency:

[dev-dependencies]
build_runner = { path = "./build_runner" }

Run the builder:

gleam run -m build_runner

Or use watch mode for continuous regeneration:

gleam run -m build_runner watch
Search Document