gitoria
All repositories: gitoria
5.9 KB
# hl:web — the web framework`hl:web` serves an app's pages from the server and keeps them live in the browser. It is writtenin Hybriel: `WebFramework.hl` is the server half, `client.hl` the browser half, and `compile.hl`turns each component's View into that component's own code. The model behind it (components,Views, realms, the boundary) is in [COMPONENTS.md](../../COMPONENTS.md). This page lists the rulesan app is held to. Each rule is a located refusal at boot unless it says otherwise.## Starting an app```hybrielimport WebFramework from 'hl:web'import Home from './components/home.hl'import Styles from './styles.hl'routes = [ { pattern = "/" component = Home } ]server = new WebFramework(routes = routes, styles = Styles, port = 8080)```Settings may be passed as named arguments. Most of them may instead be members of the file thatruns the `new`:| setting | default | meaning ||---|---|---|| `routes` | `[]` | the route table (below); pass it as an argument || `styles` | none | the app's styles file: the imported class, or a path string. It must be in the project graph || `port` | 8080 | the port to listen on || `host` | `HL_HOST`/`HOST`, else 0.0.0.0 | the interface to bind || `audience` | `{}` | per outward event, who receives it (see Events) || `minify` | false | one-line HTML and compact modules || `watchMode` | true | a saved `.hl` file re-analyses the app and reloads every open tab || `sessionDir` | `<app>/.sessions` | where sessions are stored; `false` keeps them in memory || `sessionMaxAge`, `sessionIdle`, `sweepInterval` | 14 days, 15 min, 5 min | the session clocks, in seconds || `sessionCookie` | `hlsid` | the cookie's name: letters, digits, `-` and `_` || `sessionSecure` | false | set the cookie's `Secure` flag (for an app behind https) || `appTitle`, `appDescription`, `appImage`, `appFavicon`, `meta` | none | the head every page gets unless it declares its own |An app runs on one web framework. If its files reach two packages that hold a `WebFramework.hl`(for example an old vendored copy beside this one), it is refused at boot, naming the import rowof each.## RoutesEach entry has a `pattern` (`/posts/:id`, `/assets/*`) and one kind:| kind | answers with ||---|---|| `component = Home` or `"./components/home.hl"` | the page, rendered on the server; the component must be imported by the app, and must declare a View || `function = (route, req) => { … }` | what the function returns (a Hybrid becomes JSON); `req.session` is the cookie's session, or null || `direct = "text"` | that text || `file = "./x"` | one file || `directory = "./assets"` | the files under a directory |A named group (`:id`) reaches the component as a construction argument of the same name: declare`id = null` at its root. A named wildcard does the same with the rest of the path:`/code/*path` gives `path = 'src/lib/util.hl'` for `/code/src/lib/util.hl`, on the first load andon a navigation. A bare `*` has no name a member could take; it is what a `directory` routeserves from.## ComponentsA component is a `.hl` file with a `View`. Its root members are its state, and are constructed onthe server for each request.**A View attribute is a literal, a member or a field path.** `href = "/"`, `href = url` and`href = p.url` are allowed. `href = '/posts/' + id` is refused at its line: declare`postUrl = '/posts/' + id` at the file's root and write `href = postUrl`. The same holds for avalue bound on a component reference (`Card { title = t }`) and for the condition of an `if` in aView, which may also be a `for` row variable.**The tree carries the elements the HTML parser implies.** `table { tr { td { … } } }` is renderedas `table > tbody > tr > td`, because that is the tree the browser builds from the HTML, and thepage is continued from the tree the browser built. The pairs are listed in `view.hl`(`impliedElements`): `tr`, `td` and `th` under `table` get a `tbody`, `td` and `th` under `tbody`,`thead` or `tfoot` get a `tr`, and `col` gets a `colgroup`. Consecutive children that need the samecontainer share one.The other rules:- A View reads only names the file declares. A name it declares nowhere is refused.- `parent './main.hl'` wraps a page in a shell. The shell declares `slot = null` and places`slot` in its View exactly once. A component that is filled (`Card { p { … } }`) must declare aslot too.- `body { … }` is allowed only as the root of a View, and a page wrapped by a parent cannot haveone: the shell owns the body.- A View cannot render itself, directly or through other components.- A Style rule cannot be written on a component reference. Write it on an element inside thatcomponent.- A page's head comes from its members `__title`, `__description`, `__image`, `__favicon` and`__meta`, and falls back to the app's `appTitle`, `appDescription`, `appImage`, `appFavicon` and`meta`. A handler that writes one of them updates the document.## Events`on name(…)` handlers and `emit` statements cross between the browser and the server over aWebSocket on the app's own port. `POST /__hl/emit` is the fallback for a peer without a socket.A handler that takes `session` as its last parameter gets the connection's session from theserver, never from the peer.An outward event reaches the tab that caused it. To send it to other connections, give it an`audience` entry: a function of the event's arguments and the receiving connection's session thatanswers true or false.A value-form emit (`answer = emit server ask()`) waits for one answer, so it has one answerer.If two files in the target realm answer the event, it is refused.## What it serves| url | what ||---|---|| `/<file key>` (e.g. `/components/home.hl`) | a component's browser module || `/__hl/hl-runtime.js` | the runtime every module imports || `/__hl/web/client.js` | this package's browser half (other packages' halves are at `/__hl/<name>/client.js`) || `/__hl/app.css` | the one stylesheet, built from the styles file and every component's `Style` || `/__hl/emit` | the POST fallback for emits |
Branches
- mainmain branch
Latest commits
- 4a2d7125initial commitmre