howdy/dev

Hot reload for development.

Put this in dev/<app>_dev.gleam and run it with gleam dev:

import howdy/dev
import my_app

pub fn main() {
  dev.start(my_app.app)
}

where my_app.app is the function that builds your howdy.App. Pass it by name rather than calling it: the server rebuilds the app from it for every request, so a reloaded module is used at once.

While it runs, every change under src triggers gleam build. If the build succeeds the changed modules are loaded into the running VM and every open page reloads. If it fails the compiler output appears in the terminal and over the page, and the old code keeps serving.

Generating code before a build

before_build runs a task of your own before each build, such as regenerating messages when a catalogue changes:

import howdy/i18n/cli

pub fn main() {
  let assert Ok(_) =
    dev.new(my_app.app)
    |> dev.watch("locales")
    |> dev.before_build(fn(_changed) { cli.execute(["gen"]) })
    |> dev.run
  process.sleep_forever()
}

A task that fails stops the build, and its output is shown like a compiler error.

Staying out of production

howdy_dev is a dev dependency and the entry point lives in dev/. gleam export erlang-shipment includes neither, so a deployed app has no reload code in it. gleam run starts src/<app>.gleam as usual.

Limits

Processes started once in main, such as a shared live runtime, keep running the code they were started with. They are ended when that code is purged, two reloads later. Restart gleam dev after changing them. Changes to dev/, test/, gleam.toml or dependencies also need a restart. Files are polled four times a second, so a change is noticed within a quarter of a second.

Types

A development server under construction.

pub opaque type Dev

Values

pub fn allow_hosts(dev: Dev, hosts: List(String)) -> Dev

Exact request hostnames (without port) allowed to read development pages and connect to reload. This prevents hostile DNS-rebinding hosts from obtaining the reload token. Include brackets for IPv6, e.g. [::1].

pub fn before_build(
  dev: Dev,
  task: fn(List(String)) -> Result(String, String),
) -> Dev

Run task before each build, with the files that changed. Its output is printed; an Error stops the build and is shown over the page like a compiler error. Files a task writes do not trigger another build. Tasks run in the order they are added.

pub fn bind(dev: Dev, to address: String) -> Dev

Override the development listener’s loopback default. Network exposure is explicit: also list the hostname/IP browsers use with allow_hosts.

pub fn interval(dev: Dev, milliseconds: Int) -> Dev

How often to look for changes, in milliseconds. Defaults to 250.

pub fn new(build: fn() -> howdy.App) -> Dev

Describe a development server for the app build makes. Watches src.

pub fn run(
  dev: Dev,
) -> Result(actor.Started(howdy.Address), actor.StartError)

Start the server and the file watcher. Returns like howdy.start; keep the calling process alive with process.sleep_forever().

pub fn start(
  build: fn() -> howdy.App,
) -> Result(actor.Started(howdy.Address), actor.StartError)

new(build) |> start.

pub fn watch(dev: Dev, directory: String) -> Dev

Watch another directory as well, such as priv/static for assets that pages should reload for even though nothing compiles.

✨ Search Document