aboutsummaryrefslogtreecommitdiff
path: root/docs/public/page/docs/quickstart.md
diff options
context:
space:
mode:
Diffstat (limited to 'docs/public/page/docs/quickstart.md')
-rw-r--r--docs/public/page/docs/quickstart.md272
1 files changed, 272 insertions, 0 deletions
diff --git a/docs/public/page/docs/quickstart.md b/docs/public/page/docs/quickstart.md
new file mode 100644
index 0000000..f706478
--- /dev/null
+++ b/docs/public/page/docs/quickstart.md
@@ -0,0 +1,272 @@
+# Quickstart
+
+Lustre is a frontend web framework for Gleam. It is primarily focused on helping
+you build robust single-page applications (SPAs), but it can also be used on the
+server to render static HTML. To get an idea of what it's all about, here's a
+quick overview of Lustre's key features:
+
+- Elm-inspired runtime with state management and controlled side effects out of
+ the box.
+- A simple, declarative API for building type-safe user interfaces.
+- Stateful components built as custom elements and useable just like any other
+ HTML element.
+- Static HTML rendering anywhere Gleam can run: the BEAM, Node.js, Deno, or the
+ browser.
+
+In this quickstart guide we'll take a look at how to get up and running with
+Lustre in both the browser and on the server.
+
+## In the browser | javascript
+
+To get started, we'll scaffold a new Gleam project using `gleam new`. If you've
+found your way to this guide but don't already know what Gleam is you can read
+about it over at [gleam.run](https://gleam.run).
+
+```shell
+$ gleam new lustre_quickstart && gleam add lustre
+```
+
+In a real project you probably want to use a build tool like [vite](https://vitejs.dev)
+along with the [vite-gleam](https://github.com/Enderchief/vite-gleam) plugin, but
+to keep this guide simple we'll just show you what code you need to write and leave
+the details on serving the app up to you. MDN have a handy guide covering some
+different options to [set up a local web server for development](https://developer.mozilla.org/en-US/docs/Learn/Common_questions/Tools_and_setup/set_up_a_local_testing_server)
+if you need some ideas.
+
+### Basic HTML setup
+
+With our Gleam project scaffolded, go ahead and create an `index.html` in the root
+of the project. This is the minimal code you'll typically want to get started:
+
+```html
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="UTF-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <title>Lustre Quickstart</title>
+
+ <script type="module">
+ import { main } from "./build/dev/javascript/lustre_quickstart/app.mjs";
+
+ document.addEventListener("DOMContentLoaded", () => {
+ main();
+ });
+ </script>
+ </head>
+
+ <body>
+ <div data-lustre-app></div>
+ </body>
+</html>
+```
+
+We wait until the DOM has loaded before calling the our app's `main` function.
+This will mount the Lustre app and start rendering. We also add the `data-lustre-app`
+attribute to the element we want to mount the app to. You could use a class or an
+id instead, or none of that: [`lustre.start`](/api/lustre#start) takes a CSS
+selector so go wild!
+
+### Hello, world!
+
+Go ahead and rename the generated `lustre_quickstart.gleam` file to `app.gleam`
+and replace the contents with the following:
+
+```gleam
+import lustre
+import lustre/element.{text}
+
+pub fn main() {
+ let app = lustre.element(text("Hello, world!"))
+ let assert Ok(_) = lustre.start(app, "[data-lustre-app]", Nil)
+
+ Nil
+}
+```
+
+This will create a static Lustre app and mount it onto the element that matches
+the CSS selector. While we're asserting everything is OK here, it is possible
+for `lustre.start` to fail in a couple of ways. Check out the docs for the
+[`lustre.Error`](/api/lustre#error-type) type if you want to know more.
+
+Run `gleam build` and serve the HTML with your preferred static file server (this
+step is necessary: JavaScript modules can't be imported when just opening a HTML
+file) and admire your handiwork.
+
+### Adding interactivity
+
+Now that we know how to get things up and running, let's try something a little
+more exciting and add some interactivity. Replace the contents of your `app.gleam`
+file with the code below and rebuild the project.
+
+```gleam
+import gleam/int
+import lustre
+import lustre/element.{text}
+import lustre/element/html.{div, button, p}
+import lustre/event.{on_click}
+
+pub fn main() {
+ let app = lustre.simple(init, update, view)
+ let assert Ok(_) = lustre.start("[data-lustre-app]", Nil)
+
+ Nil
+}
+
+fn init(_) {
+ 0
+}
+
+type Msg {
+ Incr
+ Decr
+}
+
+fn update(model, msg) {
+ case msg {
+ Incr -> model + 1
+ Decr -> model - 1
+ }
+}
+
+fn view(model) {
+ let count = int.to_string(model)
+
+ div([], [
+ button([on_click(Decr)], [text(" + ")]),
+ p([], [text(count)]),
+ button([on_click(Incr)], [text(" - ")])
+ ])
+}
+```
+
+You should know have a very exciting counter app! Almost every Lustre app will
+boil down to the same three parts:
+
+- A `Model` type that represents your application's state and a function to
+ `init` it.
+- A `Msg` type and an `update` function to update that state based on incoming
+ messages.
+- A `view` function that takes the current state and renders some HTML.
+
+This architecture is not unique to Lustre. It was introduced by the Elm community
+and known as the [Elm Architecture](https://guide.elm-lang.org/architecture/)
+before making its way to React as [Redux](https://redux.js.org) and beyond, known
+more generally as the Model-View-Update architecture. If you work through the
+rest of our guides you'll see how this architecture helps keep side effects out
+of our view code and how to create components that can encapsulate their own state
+and update logic.
+
+For now though, we'll leave things here. If you're interested in seeing how Lustre
+can be used to render static HTML on the server, read on! Otherwise, you can take
+this counter application as a base and start building something of your own.
+"
+
+## On the server | erlang javascript
+
+As we've seen, Lustre is primarily meant to be used in the browser to build
+interactive SPAs. It is possible to render Lustre elements to static HTML and
+simply use Lustre as a templating DSL. As before, we'll start by scaffolding a
+new Gleam project and adding Lustre as a dependency:
+
+```shell
+$ gleam new lustre_quickstart && gleam add lustre
+```
+
+The [`lustre/element`](/api/lustre/element) module contains functions to render
+an element as either a `String` or `StringBuilder`. Copy the following code into
+`lustre_quickstart.gleam`:
+
+```gleam
+import gleam/io
+import lustre/attribute.{attribute}
+import lustre/element.{text}
+import lustre/element/html.{html, head, title, body, div, h1}
+
+pub fn main() {
+ html([attribute("lang", "en")], [
+ head([], [
+ title([], [text("Lustre Quickstart")])
+ ]),
+ body([], [
+ h1([], [text("Hello, world!")])
+ ])
+ ])
+ |> element.to_string
+ |> io.println
+}
+```
+
+We can test this out by running `gleam run` and seeing the HTML printed to the
+console. From here we could set up a web server using [Mist](/guides/mist) or
+[Wisp](/guides/wisp) to serve the HTML to the browser or write it to a file using
+[simplifile](https://hexdocs.pm/simplifile/). Because the API is the same for
+both client and server rendering, it is easy to create reusable components that
+can be rendered anywhere Gleam can run!
+
+### An example with Wisp
+
+Before we go, let's just take a quick look at what it would look like to use
+Lustre in a [Wisp](https://hexdocs.pm/wisp) application. We won't scaffold out a
+real app in this example, but we'll adapt one of the examples from Wisp's own
+documentation.
+
+Specifically, we'll take a look at the `show_form` function from the
+["working with form data"](https://github.com/lpil/wisp/blob/ea8a40bc20745f172695c8cc2dc0a63769f890a7/examples/2-working-with-form-data/src/app/router.gleam#L20)
+example:
+
+```gleam
+...
+
+pub fn show_form() -> Response {
+ // In a larger application a template library or HTML form library might
+ // be used here instead of a string literal.
+ let html =
+ string_builder.from_string(
+ "<form method='post'>
+ <label>Title:
+ <input type='text' name='title'>
+ </label>
+ <label>Name:
+ <input type='text' name='name'>
+ </label>
+ <input type='submit' value='Submit'>
+ </form>",
+ )
+ wisp.ok()
+ |> wisp.html_body(html)
+}
+```
+
+They've helpfully left a comment telling us that in a larger application we might
+want to use a template library, and Lustre is up to the task! Let's refactor this
+using Lustre:
+
+```gleam
+import gleam/string
+import lustre/attribute.{attribute}
+import lustre/element
+import lustre/element/html
+...
+
+pub fn show_form() -> Response {
+ html.form([attribute("method", "post")], [
+ labelled_input("Title"),
+ labelled_input("Name"),
+ html.input([attribute("type", "submit"), attribute("value", "Submit")])
+ ])
+ |> element.to_string_builder
+ |> wisp.html_body
+ |> wisp.ok
+}
+
+fn labelled_input(name: String) -> Element(Nil) {
+ html.label([], [
+ element.text(name <> ": "),
+ html.input([
+ attribute("type", "text"),
+ attribute("name", string.lowercase(name))
+ ])
+ ])
+}
+```