Skip to content

Commit

Permalink
readme - tighten
Browse files Browse the repository at this point in the history
  • Loading branch information
dustingetz committed Jan 27, 2024
1 parent 784dbe8 commit ccfc968
Showing 1 changed file with 25 additions and 77 deletions.
102 changes: 25 additions & 77 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,47 +1,32 @@
# Electric Starter App

A minimal Electric Clojure app.
Plus instructions on how to integrate it into an existing app.
A minimal Electric Clojure app, and instructions on how to integrate it into an existing app. For more demos and examples, see [Electric Fiddle](https://github.com/hyperfiddle/electric-fiddle).

For real-world examples, check out [Electric Fiddle](https://github.com/hyperfiddle/electric-fiddle).
## Instructions

## Run it
Dev build:

```shell
clj -A:dev
```

At the REPL:
```clojure
(dev/-main)
```

Navigate to [http://localhost:8080](http://localhost:8080)

## Customize it
* Shell: `clj -A:dev -X dev/-main`, or repl: `(dev/-main)`
* http://localhost:8080
* ELectric root function: [src/electric_starter_app/main.cljc](src/electric_starter_app/main.cljc)
* Hot code reloading works: edit -> save -> see app reload in browser

With the app running:
1. Go to [src/electric_starter_app/main.cljc](src/electric_starter_app/main.cljc)
2. Edit and save
3. App will automatically update in your browser

## Release it for prod
Prod build:

```shell
clj -X:build:prod build-client # build it
clj -M:prod -m prod # run it
clj -X:build:prod build-client
clj -M:prod -m prod
```

### Uberjar

```shell
Uberjar (optional):
```
clj -X:build:prod uberjar :build/jar-name "target/app.jar"
java -cp target/app.jar clojure.main -m prod
```

There is also:
- an example [Dockerfile](Dockerfile)
- `fly.io` deployment example (look at [.github/workflows/deploy.yml](.github/workflows/deploy.yml) and [fly.toml](fly.toml))
Deployment example:
- [Dockerfile](Dockerfile)
- fly.io deployment through github actions: [.github/workflows/deploy.yml](.github/workflows/deploy.yml) & [fly.toml](fly.toml)

## Integrate it in an existing clojure app

Expand All @@ -53,53 +38,16 @@ There is also:
- an example Jetty integration
- required ring middlewares

You'll also need to account for the following requirements:

### For a client to connect to the server, the two program’s version must match.

Electric Clojure compiles a single program into two artifacts:
- a javascript program - runs in the browser,
- a JVM program - runs on the server,
- these two programs work in pairs,
- they communicate via websocket.

If an mismatching client tries to connect to the server, the server will:
- reject the connection,
- inform the client it needs to refresh (so to get the newer client js file)

How it works:
- `clj -X:build:prod build-client` will:
- generate js files with the version baked in,
- write the version in `resources/electric-manifest.edn`
- On server start, the server will:
- read `resources/electric-manifest.edn`
- configure a ring middleware rejecting mismatching (supposedly outdated) clients’ connection.
- On client -> server connection, the client will:
- send it's baked-in version,
- try to refresh the page if the server rejects the version.

Here are examples you might want to adapt to your existing app:
- [src/electric_starter_app/server_jetty.clj](src/electric_starter_app/server_jetty.clj) esp. `reject-stale-client`
- [src-build/build.clj](src-build/build.clj)


Notice that [src-prod/prod.clj](src-prod/prod.clj) reads `electric-manifest.edn`.

### Ensuring cache invalidation

Only if your production system doesn't already handles it.

Complied js files are fingerprinted with their respective hash, to ensure a new release
properly invalidates asset caches. [index.html](resources/public/electric_starter_app/index.html) is templated with the generated
js file name. The generated name comes from the `manifest.edn` file
(in `resources/public/electric_starter_app/js/manifest.edn`), produced by `clj -X:build:prod build-client`.

Notice that [src/electric_starter_app/server_jetty.clj](src/electric_starter_app/server_jetty.clj) -> `wrap-index-page` reads `:manifest-path` from config.
The config comes from [src-prod/prod.clj](src-prod/prod.clj).

## Build documentation

Electric Clojure programs compile down to separate client and server target programs, which are compiled from the same Electric application source code.

---
Notes
* For an Electric client/server pair to successfully connect, they must be built from matching source code. The server will reject mismatched clients (based on a version number handshake coordinated by the Electric build) and instruct the client to refresh (to get the latest javascript artifact).
* [src-build/build.cljc](src-build/build.clj bakes the Electric app version into both client and server artifacts.
* server Electric app version is baked into `electric-manifest.edn` which is read in [src-prod/prod.clj](src-prod/prod.clj).
* client Electric app version is baked into the .js artifact as `hyperfiddle.electric-client/ELECTRIC_USER_VERSION`

- This starter app logs with Logback for simplicity. Feel free to replace it by your favorite equivalent.
Consequently, you need **robust cache invalidation** in prod!
* In this example, complied js files are fingerprinted with their respective hash, to ensure a new release properly invalidates asset caches. [index.html](resources/public/electric_starter_app/index.html) is templated with the generated js file name.
* The generated name comes from shadow-cljs's `manifest.edn` file (in `resources/public/electric_starter_app/js/manifest.edn`), produced by `clj -X:build:prod build-client`. Watch out: this shadow-cljs compilation manifest is not the same manifest as `electric-manifest.edn`!
* Notice that [src/electric_starter_app/server_jetty.clj](src/electric_starter_app/server_jetty.clj) -> `wrap-index-page` reads `:manifest-path` from config. The config comes from [src-prod/prod.clj](src-prod/prod.clj).

0 comments on commit ccfc968

Please sign in to comment.