--- applyTo: "**/*.marko" --- # Marko Overview Marko uses an **HTML-like Tags API** (not JSX) with embedded JS/TS expressions. It is a **superset of well-formed HTML**. **Examples:** ```marko // tags/Layout.marko import styles from "./styles.module.css"; <div class=styles.layout> <header> <${data.header}/> </header> <main> <${data.body}/> </main> <footer> <${data.footer}/> </footer> </div> ``` ```marko import styles from "./styles.module.css"; <let/name="World"> <Layout> <@header> <h1 class=styles.heading>Welcome ${name}!</h1> </@header> <@body> <!-- Also use `id/` with `aria-` attributes for accessibility --> <id/nameId> <label for=nameId>Name:</label> <input type="text" id=nameId value:=name> </@body> <@footer> <p class=styles.footer>© 2023 My Site</p> </@footer> </Layout> ``` ```marko // Run once on the server server import { initDb, queryData } from "~/utils"; server initDb("users", "comments"); <try> <@placeholder>Loading...</@placeholder> <@catch|err|> <p>Error: ${err.message}</p> </@catch> <!-- ALWAYS use the `<await>` tag to handle async data --> <await|users|=queryData("users", $signal)> <for|{ id, name, age }, index| of=users by="id"> User ${index}: <Profile id=id name=name age=age/> // <Profile> is an autodiscovered custom component and does not need to be imported </for> </await> <await|posts|=queryData("posts", $signal)> <for|post| of=posts.filter(p => p.date > Date.now() - 1000*60*60*24) by(p) { return `${p.user}-${p.date}` }> <Post ...post/> </for> </await> </try> ``` ```marko // tags/online.marko // Encapsulate logic in "renderless components" (similar to hooks) <let/online=true> <!-- A RARE case where you might use `<script>` --> <script> window.addEventListener("online", () => online = navigator.onLine), { signal: $signal }); window.addEventListener("offline", () => online = navigator.onLine), { signal: $signal }); </script> <return online/> ``` ```marko // Then use the renderless component in your templates <online/isOnline/> <h1>Network Status: ${isOnline ? "Online" : "Offline"}</h1> ``` ```marko <let/count:=input.count> <const/doubleCount=count * 2> <log=doubleCount> // console.log(doubleCount) on initial render and each time `doubleCount` changes <debug=count> // debugger; when the component renders and each time `count` changes // Define an inline component with scoped CSS <define/CustomButton|attrs|> <style/styles> .fancy { color: blue; } </style> <button ...attrs class=[styles.fancy, attrs.class]> <${attrs.content}/> </button> </define> <CustomButton onClick() { count++ }> Increment Count </CustomButton> <CustomButton onClick() { count = 0 }> Reset </CustomButton> <if=count % 2 === 0> <p>Even count</p> </if> <else> <debug/> // debugger; when this branch renders <p>Odd count</p> </else> ``` ```marko // Integrate with third-party libraries import Map from "map"; <let/x=0> <let/y=0> <div/mapTarget/> <!-- A RARE case where you might use `<lifecycle>` for imperative code --> <lifecycle onMount() { this.map = new Map(mapTarget()); this.map.setCoords(x, y); } onUpdate() { this.map.setCoords(x, y); } onDestroy() { this.map.destroy(); }> ``` ```marko <!-- ALWAYS use the `<await>` tag to fetch data --> <await|{ ingredients }|=fetch(`/recipies/${input.id}`), { signal:$signal })> <IngredientsList ingredients=ingredients/> </await> ``` ```marko <!-- NEVER wrap attributes in `{}` --> <my-tag str="Hello"/> <my-tag str=`Hello ${name}`/> <my-tag num=1 + 1/> <my-tag date=new Date()/> <my-tag fn=function myFn(param1) { console.log("hi"); }/> <my-tag method(param1) { console.log("hi"); }/> ``` ```marko <!-- use static to define values that don't change or close over reactive variables --> static const handleClick1 = () => { console.log("Button clicked"); }; <!-- use <const> tag to define functions that close over reactive variables --> <const/handleClick2() { console.log(input.message); }> <button onClick=handleClick1>Click Me</button> <button onClick=handleClick2>Click Me for a message</button> ``` ## Key Concepts - **Attributes**: JS/TS expressions without `{}`. - **Default value attribute**: `<tag=123>` is shorthand for `<tag value=123>` - **Reactivity**: Use `<let>` for state, `<const>` for derived values. All template variables (`input`, etc.) are reactive. - **Dynamic Tags**: `<${TagName}(args)>` syntax for dynamic components. - **Scoped CSS**: `<style/var>` enables CSS Modules. Supports preprocessors via extension (e.g. `<style.scss>`). - **Element Refs**: Native tags support `ref()` via `<div/ref/>`. - **Events**: `onClick`, etc. attached directly as attributes. - **Controllable Components**: Use `value:=state` (short for `value=state valueChange(val){ state=val }`) to control `<let>`, `<input>`, `<select>`, etc. - **Static JS/TS Keywords**: `import`, `export`, `static`, `server`, `client` can be used to write top-level JS that executes outside the component instance (`server`/`client` control what env it runs in) - **Custom tags**: auto-discovered from `tags/` directories and do not need to be imported ## Reference ### Core Tags - `<let>`, `<const>`, `<return>`, `<id>`, `<log>`, `<debug>`, `<lifecycle>` (void) - `<if>`, `<else>`, `<for|item| of=list>` `<await|val|=promise>`, `<try>` - `<define>` - `<script>`, `<style>` ### Controllable tags - `<let>` supports `valueChange(value)` - useful to binding to `input` to make your own controllable components - `<input>`, `<select>`, and `<textarea>` support `valueChange(value)` - `<input type="checkbox">` and `<input type="radio">` support `checkedValueChange(value)` - `<details>` and `<dialog>` support `openChange(open)` ### Enhanced `value` attributes - `<input type="radio">` and `<input type="checkbox">` support a `checkedValue=` attribute - `<select>` supports a `value=` attribute that can be set to a string or an array of strings (for `<select multiple>`) - `<textarea>` supports a `value=` attribute that can be set to a string instead of providing the content as child text nodes ### Enhanced `class` & `style` ```marko <div class="a c"/> <div class={a: true, b: false, c: true}/> <div class=["a", null, { c: true } ]/> <div style="display:block;margin-right:16px"/> <div style={ display: "block", color: false, "margin-right": 16 }/> <div style=["display:block", null, { "margin-right": 16 }]/> ``` ## Anti-patterns ### Avoid the old Marko Class API - NEVER write class components. The OLD top-level `class { ... }` block is NOT available → use the NEW core tags and template variables to manage state and other functionality - NEVER use the OLD `data`, `state`, `component`, and `out` → ONLY `input` (data passed to the template), `$signal` (AbortSignal for the current reactive expression), and `$global` (global render data) are available - NEVER use the OLD tags `<while>`, `<macro>`, `<include>` - NEVER use the OLD `key` attribute → use the NEW `by` attribute on `<for>` loops to ensure stable identity ### Marko is not JSX/React/Vue/etc. - NEVER wrap attributes in `{}` - NEVER prefix names with `use` - NEVER use `<template>` to wrap top-level content - NEVER use `<script>` to define functions or variables for use in the template → use `<const>` instead ### Other anti-patterns - NEVER use `<let>` for constants → use `<const>` - NEVER use `<const>` for mutable state → use `<let>` - NEVER use `<script>` for reactive computation → use `<const>` - NEVER use `<script>` or lifecycle methods to await data → use `<await>` - NEVER mutate reactive values (eg. `items.push(4)`) → assign a new immutable value (eg. `items = items.concat(4)`) - NEVER access `input` or other reactive variables in top-level `static`/`server`/`client` blocks as this is a ReferenceError - DO NOT use `<script>` or `<lifecycle>` unless there is no alternative - Not every form needs to use onSubmit handlers → use `<form>` with `action=` and `method=` attributes for simple forms