Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
srid committed May 9, 2021
0 parents commit 1642c77
Show file tree
Hide file tree
Showing 38 changed files with 1,682 additions and 0 deletions.
1 change: 1 addition & 0 deletions .ghcid
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--warnings -T ":main -C ./content"
27 changes: 27 additions & 0 deletions .github/workflows/publish.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: "Publish site"
on:
# Run only when pushing to master branch
push:
branches:
- master
jobs:
docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: cachix/install-nix-action@v13
with:
install_url: https://nixos-nix-install-tests.cachix.org/serve/lb41az54kzk6j12p81br4bczary7m145/install
install_options: "--tarball-url-prefix https://nixos-nix-install-tests.cachix.org/serve"
extra_nix_config: |
experimental-features = nix-command flakes
- name: Build and generate docs HTML 🔧
run: |
mkdir ./output
nix run . -- -C ./content gen $(pwd)/output
- name: Deploy to gh-pages 🚀
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: output
cname: ema.srid.ca
25 changes: 25 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
dist
dist-*
cabal-dev
*.o
*.hi
*.hie
*.chi
*.chs.h
*.dyn_o
*.dyn_hi
.hpc
.hsenv
.cabal-sandbox/
cabal.sandbox.config
*.prof
*.aux
*.hp
*.eventlog
.stack-work/
cabal.project.local
cabal.project.local~
.HTF/
.ghc.environment.*
result
result-*
10 changes: 10 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"haskell.haskell",
"arrterian.nix-env-selector",
"bbenoist.nix",
"jnoortheen.nix-ide"
]
}
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"editor.formatOnType": true,
"editor.formatOnSave": true,
"nixEnvSelector.nixFile": "${workspaceRoot}/shell.nix"
}
23 changes: 23 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "Dev Server",
"type": "shell",
// You may also use bin/run-via-tmux if you have tmux
// This is useful if you often see ghost ghcid left behind by VSCode reloads.
"command": "bin/run",
"args": [],
"problemMatcher": [],
"group": {
"kind": "build",
"isDefault": true
},
"runOptions": {
// "runOn": "folderOpen"
}
}
]
}
29 changes: 29 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
BSD 3-Clause License

Copyright (c) 2021, Sridhar Ratnakumar
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Ema Template

This repository represents a **real-world example** of [Ema](https://ema.srid.ca/) — it is the source for ema.srid.ca website — and as such acts as a **template repository** to use for bootstrapping your next static site using Ema.

## Getting Started

To develop with full IDE support in Visual Studio Code, follow these steps:

- [Install Nix](https://nixos.org/download.html) & [enable Flakes](https://nixos.wiki/wiki/Flakes)
- Run `nix-shell --run haskell-language-server` to sanity check your environment
- [Open as single-folder workspace](https://code.visualstudio.com/docs/editor/workspaces#_singlefolder-workspaces) in Visual Studio Code
- Install the recommended extensions
- <kbd>Ctrl+Shift+P</kbd> to run command "Nix-Env: Select Environment" and select `shell.nix`. The extension will ask you to reload VSCode at the end.
- Press <kbd>Ctrl+Shift+B</kbd> in VSCode, or run `bin/run` (`bin/run-via-tmux` if you have tmux installed) in terminal, to launch the Ema dev server, and navigate to http://localhost:9001/

All but the final step need to be done only once. Check [the Ema tutorial](https://ema.srid.ca/start/tutorial) next.

## Note

- This project uses [relude](https://github.com/kowainik/relude) as its prelude, as well as [Tailwind+Blaze](https://ema.srid.ca/guide/helpers/tailwind) as CSS utility and HTML DSL. Even though the author highly recommends them, you are of course free to swap them out for the library of your choice.
- As a first step to using this template,
- change the project name in .cabal, flake.nix and hie.yaml files.
- Change the `cname` in .github/workflows/publish.yaml, or remove it to publish to yourname.github.io
- Configuration:
- To change the port, see file bin/run
- To change the CLI arguments used by bin/run, see file .ghcid
- To update Ema to latest Git revision, run `nix flake lock --update-input ema`
- To add/remove Haskell dependencies, see the .cabal file. If a dependency is unavailable in nixpkgs, you can override it (to point to say a Git repo) in the `overrides` attribute of flake.nix. You can imitate the manner in which the `ema` (or `lvar`) package itself is overriden.
- To generate static site, run: `nix run . -- -C ./content gen ./output`
6 changes: 6 additions & 0 deletions bin/format
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env nix-shell
#! nix-shell ../shell.nix -i bash
set -xe
find src -name \*.hs | xargs ormolu -m inplace
nixpkgs-fmt *.nix
cabal-fmt -i *.cabal
9 changes: 9 additions & 0 deletions bin/run
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/usr/bin/env bash
set -xe
export PORT=9001

# This will run ghcid, which uses `./.ghcid` to invoke your program main entry
# point, with the specified args.
#
# If you change ./.ghcid, ghcid will automatically reload.
exec nix develop -c ghcid
4 changes: 4 additions & 0 deletions bin/run-via-tmux
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env bash
set -xe
PROJECT=$(basename `pwd`)
tmux new-session -A -s $PROJECT bin/run
11 changes: 11 additions & 0 deletions content/concepts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
order: 9
---

# Concepts

* [Hot Reload](concepts/hot-reload.md)
* [LVar](concepts/lvar.md)
* [Slug](concepts/slug.md)
* [CLI](concepts/cli.md)
* [Logging](concepts/logging.md)
11 changes: 11 additions & 0 deletions content/concepts/cli.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
order: 4
---
# CLI

Ema apps have a basic CLI argument structure that takes two kinds of input:

1. `-C <dir>`: specifies the "input directory" (current working directory by default)
2. `gen` subcommand: generates the static site, instead of starting up the dev server

Ema (`runEma`) will change the [current working directory](https://hackage.haskell.org/package/directory-1.3.6.1/docs/System-Directory.html#v:getCurrentDirectory) to the "input directory" before running your application code. It, along with the "gen" subcommand (if used), is passed as the `Ema.CLI.Action` type to your `render` function. You can also use `runEmaWith` if you are manually handling the CLI arguments yourself.
30 changes: 30 additions & 0 deletions content/concepts/hot-reload.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
order: 1
---
# Hot Reload

**Hot Reloading** is a feature of Ema's dev server wherein any changes to your Haskell source or data files (such as Markdown files or HTML templates) _propagate instantly_ to the web browser without requiring any manual intervention like a full browser refresh. In practice, this is a such a delightful feature to work with. Imagine changing CSS style of an element, and see it reflect on your site in a split second.

## How Ema implements hot reload

### Websocket

The Ema dev server uses websockets to keep a bi-directional connection open between the web browser and the backend server. When you click on a link or when something changes in the backend, they are communicated via this connection. In a statically generated site, however, no such activity happens - and a link click behaves like a normal link, in that the browser makes a full HTTP request to the linked page.

### DOM patching

When switching to a new route or when receiving the new HTML, Ema uses [morphdom](https://github.com/patrick-steele-idem/morphdom) to _patch_ the existing DOM tree rather than replace it in its entirety. This, in addition to use of websockets, makes it possible to support **instant** hot reload with nary a delay.

### Haskell reload

Finally, hot reload on _code_ changes are supported via [ghcid](https://github.com/ndmitchell/ghcid). The [template repo](https://github.com/srid/ema-docs)'s `bin/run` script uses ghcid underneath. Any HTML DSL (like blaze-html -- as used by the [Tailwind helper](guide/helpers/tailwind.md)) or CSS DSL automatically gets supported for hot-reload. If you choose to use a file-based HTML template language, you can enable hot-reload on template change using the [FileSystem helper](guide/helpers/filesystem.md).

Note that if your application makes use of threads, it is important to setup cleanup handlers so that `ghcid` doesn't leave [ghost](https://stackoverflow.com/q/24999636/55246) processes behind. Helpers like [`race_`](https://hackage.haskell.org/package/async-2.2.3/docs/Control-Concurrent-Async.html#v:race_) will do this automatically (incidentally it is used by `runEma` for running the user IO action).

### Data reload

For anything outside of the Haskell code, your code becomes responsible for monitoring and updating the model [LVar](concepts/lvar.md). The [filesystem helper](guide/helpers/filesystem.md) already provides utilities to facilitate this for monitoring changes to files and directories.

## Handling errors

If your code throws a Haskell exception, they will be gracefully handled and displayed in the browser, allowing you to recover without breaking hot-reload flow.
10 changes: 10 additions & 0 deletions content/concepts/logging.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
order: 5
---
# Logging

`runEma`'s action monad supports the `MonadLoggerIO` constraint, as defined by [monad-logger](https://hackage.haskell.org/package/monad-logger). This means that you can use any of the logging functions from `monad-logger` to add logging to your application. [monad-logger-extras](https://hackage.haskell.org/package/monad-logger-extras) is used to colorize the logs.

```haskell
logInfoNS "myapp" "This is an info message"
logDebugNS "myapp" "This is a debug message info"
8 changes: 8 additions & 0 deletions content/concepts/lvar.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
order: 2
---
# LVar

If you are familiar with Haskell's `stm` package, a `LVar` is essentially a [`TMVar`](https://hackage.haskell.org/package/stm-2.5.0.0/docs/Control-Concurrent-STM-TMVar.html) but with an extra ability for other threads to observe changes. Ema uses it for [hot reload](concepts/hot-reload.md), and your application code is expected to set and update its [model](guide/model.md) through the LVar.

Documentation on `LVar` is available [on Hackage](https://hackage.haskell.org/package/lvar).
16 changes: 16 additions & 0 deletions content/concepts/slug.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
order: 3
---
# Slug

A slug is a component of a URL or file path. In an _URL_ like `/foo/bar`, there are two _slugs_: "foo" and "bar". URLs (as well as filepaths) can therefore be represented as lists of slugs (`[Slug]`).

```haskell
import Ema (Slug)

type URL = [Slug]
```

Slugs are integral to Ema's routing system. When defining [route](guide/routes.md) encoders and decoders (via [Ema class instance](guide/class.md)) you are effectively writing functions that convert back and forth between your route type and `[Slug]`. These functions are ultimately used to determine the *filename* of the statically generated HTML (i.e., `./foo/bar.html`) as well as the linking URL in the rendered HTML (i.e., `/foo/bar`).

Slugs are also automatically [unicode normalized](https://www.unicode.org/faq/normalization.html) to NFC to ensure that route links work reliably regardless of the underlying representation of any non-ascii link text.
Binary file added content/ema-demo.mp4
Binary file not shown.
114 changes: 114 additions & 0 deletions content/ema.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 1642c77

Please sign in to comment.