Better dev tooling with lustre_dev_tools v2

As many of Lustre’s users are new to frontend or would prefer to avoid modern JavaScript toolchains, Lustre’s own development tooling has become a core part of the developer experience.

No more native dependencies

One of the standout improvements in this release is the move away from the Erlang fs package and the need for the Erlang build tool to compile a native C program for file system watching. This was most-commonly a sticking point for Windows users, where rebar3 is often packaged separately to Erlang/OTP and can be difficult or confusing to set up correctly.

With Lustre Dev Tools v2, we have moved to using Bun for bundling and as a side effect can piggy back off of Bun’s built-in file watching capabilities. This means dev tools no longer needs to compile native code and onboarding should be much smoother for folks new to Lustre, Gleam, and the BEAM.

TOML-based configuration

The number of Gleam-based development tools is steadily growing and with it has come some conversations on how to handle configuration more consistently. After some discussion, the community mostly settled on a tools.* table in gleam.toml inspired by Python’s pyproject.toml.

In an effort to push this standard forward, Lustre’s new dev tools support a wide variety of configuration options under the tools.lustre.* namespace. A typical configuration for a Lustre project going forward might look something like this:

[tools.lustre.bin]
# Use the system-installed `bun` binary instead of downloading one automatically.
bun = "system"

[tools.lustre.build]
minify = true
# Build the application into our server's `priv/static` directory so it can be
# deployed together with the backend.
outdir = "../server/priv/static"

[tools.lustre.dev]
host = "0.0.0.0"
# Configure an API proxy to forward requests to our backend during development.
# This lets us avoid CORS issues while the frontend and backend are running on
# different ports.
proxy = { from = "/api", to = "http://localhost:3000/api" }

[tools.lustre.html]
# Include Bootstrap in our project for simple styling.
stylesheets = [{ href = "https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" }]
scripts = [{ src = "https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js" }]

This should make it easier for teams to share configuration for Lustre projects without needing external scripts or documentation on what flags to pass. More information can be found on the TOML reference page.

Clearer handling of HTML, static assets, and build artifacts

One big wart in previous versions of dev tools was the handling of index.html, static assets, and where build artifacts were placed. Previously when starting the dev server, an index.html file would be generated in the root of your project and used as the entry point during development. At the same time development builds of your application would be placed in the same priv/static directory as your production builds, leading to confusion over which files were safe to commit or deploy.

This has been massively cleaned up in v2, with the development server now no longer generating any build artifacts in your project. Instead, dev tools is now fully in control of the HTML entry file it serves. At build time, an index.html file will now be generated in the output directory alongside your JavaScript and CSS bundles.

In the previous section we already saw how this HTML file can be customised under the tools.lustre.html table in your gleam.toml. Additional scripts, stylesheets and meta tags can be injected through this configuration.

Additionally assets placed in the assets/ directory in the root of your project will now be served during development and copied to the output directory at build time. This makes it easy and clear to include static assets like images, fonts, or CSS in your projects. In development, changes to assets will be reflected in real-time without a full page reload so feel free to nudge that div pixel-by-pixel until it’s just right.

Finally, the default output directory for builds has been changed from priv/static to dist/. The previous directory was chosen to follow OTP application conventions, but Lustre applications are JavaScript and following this convention was more confusing than helpful. For full stack Gleam projects you should continue to build your client applications into your server’s priv/static directory, but this new default will make it clearer for client-only projects that build artifacts placed in dist/ are meant to be deployed.

For more information on how assets are handled, check out the assets page in the docs.

And the rest

Nix users can finally rejoice with the new ability to specify local paths to the Bun and Tailwind binaries dev tools uses to build your projects. Users that want to dip into the JavaScript ecosystem without changing their build tooling can now properly import dependencies from node_modules and have them work both during development and in production builds. Dev tools will update your project’s .gitignore when it is first run to ensure build artifacts don’t make their way into your Git history.


Lustre is still largely maintained by me – Hayleigh – with the support of a small number of contributors. To my existing sponsors on GitHub, thank you! Your support has fueled me with both motivation and caffeine to keep the project growing 💕.

If you’re interested in supporting Lustre, one of the best things you can do is build something with it and tell everyone about it!

If you or your company are using Lustre in production, please consider supporting the project financially over on GitHub sponsors.

Search Document