Skip to content

Commit

Permalink
.
Browse files Browse the repository at this point in the history
  • Loading branch information
Quafadas committed Jul 17, 2024
1 parent 807fe9b commit d71308b
Show file tree
Hide file tree
Showing 6 changed files with 248 additions and 3 deletions.
4 changes: 3 additions & 1 deletion site/docs/_blog/_posts/2024-05-22-Viteless.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,9 @@ Originall, I wanted to use module preloads. However, it's not possible to use mo

What we do intead, is to provide each module with a hash of it's content. When the module is loaded, we check the hash. If the hash is different, we reload the module. This is a very simple approach, but it works. If we configure middleware correctly, then wqhen the browser comes to reload, it can send the ETag and Validity of the existing resource. If we match, then we simply send back a 304 and the browser uses the cached resource.

So reloading? Fast. Very, fast. And the difficult module resolution problems? All dealt with by your friendly neighbourhood browser.
So reloading? Fast. Very, fast. And the difficult module resolution problems? All dealt with by your friendly neighbourhood browser. Even better, we can take advantage of a little knowledge of scala-js to preload the fat `internal` dependancies!

This is a big win, because the fat dependancies are the slowest to load and appear at the end of the module graph. I believe this change makes us faster than vite for non-trivial projects.

To generate our `index.html`, our dev server monitors file changes, and updates a `MapRef[File, Hash]`. We use that `MapRef` to generate the `index.html` on demand. It appears natural, to request a page refresh (and a new `index.html`) when we detect linker success.

Expand Down
29 changes: 29 additions & 0 deletions site/docs/_blog/_posts/2024-07-17-On Cdns.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
title: On CDNs
---

One of the objects which seems to crop up rather often when discussing this idea, is that

> My users are now dependant on a CDN!
This is true, although my invesigtaion makes this appear way less scary then it first looks. In fact on balance I've developed a strong preference for them...

The reliability concern is dealt with in two ways. Firstly according to jsdelivr [status](https://status.jsdelivr.com), it's uptime was 100% in 2023. So, that's not too bad.

And if you specifiy the explicit dependance of the module you are using... actually, it gets waaaay better ... because when the CDN responds to an explicitly versioned request, it includes a bunch of headers which say "This artifact is immutable. Don't bother me about this again. Ever".

And the browser wqill respect that - next time you go ask for your dependancy, it simply loads it out of it's cache. There _is no network request_. Dependancy load time : 0ms, according to the browser network tools.

It's tought to get faster than 0. Also, no request means no netork risk.

So, under "ideal" conditions:

- You are using a modern browser
- You are making the request for a second+ time
- You have explicitly specified the version of the dependancies you're using

Dependancy resolution is both very reliable and very fast. What's cool - that cache survives redeployment. So your app is slow for the user the first time... but the cache survives a redeployment. It's fast afterwards. We can reword the statment as follows;

> My users are now dependant on a CDN being available the first time they visit my site.
Which is less scary given the historical uptime!
84 changes: 84 additions & 0 deletions site/docs/_docs/bundler.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
---
title: Ummm... no Bundler?
---

Yup, no bundler.

Instead, consider using [ESModules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules), and resolving NPM dependancies out of a [CDN](https://www.jsdelivr.com).

It's less scary than it appears, and it feels _very_ good once it's up and going. An example that uses shoelace. [Sans Bundler](https://github.com/Quafadas/ShoelaceSansBundler).

# Scala-CLI

You'll need an `importmap.json` file.

```json
{
"imports": {
"@shoelace-style/shoelace/dist/": "https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.13.1/cdn/",
}
}
```

and directives that tells scala-cli to use scala js, esmodules and where to find it.

```
//> using platform js
//> using jsModuleKind es
//> using jsEsModuleImportMap importmap.json
//> using jsModuleSplitStyleStr smallmodulesfor
//> using jsSmallModuleForPackage frontend
//> using dep com.raquo::laminar-shoelace::0.1.0
```

# Mill (0.11.8+)

In your frontend module

```scala sc:nocompile

override def scalaJSImportMap = T {
Seq(
ESModuleImportMapping.Prefix("@shoelace-style/shoelace/dist/", "https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.13.1/cdn/")
)
}
```

Don't forget to depend on the facade too :-)...

```scala sc:nocompile
def ivyDeps = Agg( ivy"com.raquo::laminar-shoelace::0.1.0" )
```

# SBT

I haven't personally used, but it would be possible, with this plugin;

https://github.com/armanbilge/scalajs-importmap

# Misc

If you're walking on the bleeding edge with modules that aren't designed to be loaded out of a CDN (looking at you, SAP UI5 webcomponents), then things are not easy. You may need to give the browser some hints, on where it can resolve other parts of the module grap in your index.html;


```json
<script type="importmap">
{
"imports": {
"@ui5/webcomponents-theming/": "https://cdn.jsdelivr.net/npm/@ui5/webcomponents-theming/",
"@ui5/webcomponents-localization/": "https://cdn.jsdelivr.net/npm/@ui5/webcomponents-localization@1.24.7/",
"@ui5/webcomponents/": "https://cdn.jsdelivr.net/npm/@ui5/webcomponents@1.24.7/",
"@ui5/webcomponents-theming/": "https://cdn.jsdelivr.net/npm/@ui5/webcomponents-theming@1.24.7/",
"@ui5/webcomponents-icons/": "https://cdn.jsdelivr.net/npm/@ui5/webcomponents-icons@1.24.7/",
"@ui5/webcomponents-base/": "https://cdn.jsdelivr.net/npm/@ui5/webcomponents-base@1.24.7/",
"@sap-theming/": "https://cdn.jsdelivr.net/npm/@sap-theming@1.24.7/",
"@types/openui5/": "https://cdn.jsdelivr.net/npm/@types/openui5@1.24.7/",
"@types/jquery/": "https://cdn.jsdelivr.net/npm/@types/jquery/",
"lit-html": "https://cdn.jsdelivr.net/npm/lit-html",
"lit-html/": "https://cdn.jsdelivr.net/npm/lit-html/"
}
}
```
This actually got 95% of the way there... but localisation doesn't work. That means (for example) the date picker doesn't work. I have no good answer for this. Given that project explicitly recommends not using a CDN, it's not an obvious move to attempt this.

The browser network tools are your friend.
109 changes: 108 additions & 1 deletion site/docs/_docs/config.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# Config

The server is a CLI. It has a number of flags that can be used to configure it. Here is the current list of flags and what they do.
The server is a CLI. It has a number of flags that can be used to configure it. Here is the current list of flags and what they do. You can see these flags by running ` --help` in your terminal.

```
cs launch io.github.quafadas:live-server-scala-cli-js_3:{{projectVersion}} -- --help
```


```
Usage: LiveServer [--project-dir <string>] [--out-dir <string>] [--port <integer>] [--proxy-target-port <integer>] [--proxy-prefix-path <string>] [--log-level <string>] [--build-tool <string>] [--browse-on-open-at <string>] [--extra-build-args <string>]... [--mill-module-name <string>] [--path-to-index-html <string>] [--styles-dir <string>]
Expand Down Expand Up @@ -37,3 +43,104 @@ Options and flags:
--styles-dir <string>
A fully qualified path to your styles directory with LESS files in - e.g. c:/temp/helloScalaJS/styles
```

# Examples;

## Scala-cli single module

Fire up a terminal in projectDir

```
| projectDir
├── hello.scala
```

```sh
cs launch io.github.quafadas::sjsls:{{projectVersion}}
```
This is the classic [viteless](https://github.com/Quafadas/viteless/tree/main) example

## Styles baby, make it look good!

With styles.

```
| projectDir
├── hello.scala
├── styles
├── ├── index.less
```
Run

```sh
cs launch io.github.quafadas::sjsls:{{projectVersion}} -- --styles-dir --fully/qualified/dir/to/styles
```

## Did I mention I want a full blown SPA?

With client side routing under `/app`?

```
| projectDir
├── hello.scala
├── styles
├── ├── index.less
```
Run

```sh
cs launch io.github.quafadas::sjsls:{{projectVersion}} -- --client-routes-prefix app
```

## Stop generating my HTML. I want to bring my own.

Okay.

```
| projectDir
├── hello.scala
├── assets
├── ├── index.less
├── ├── index.html
```
With
```sh
cs launch io.github.quafadas::sjsls:{{projectVersion}} -- --path-to-index-html fully/qualified/path/to/assets
```

Note: if you're brining your own html, drop the `--styles` flag - reference `index.less` from your html and read [docs](https://lesscss.org) to get it working in browser.

***
You need to include this javascript script tag in the body html - otherwise no page refresh.

```
<script>
const sse = new EventSource("/refresh/v1/sse");
sse.addEventListener("message", (e) => {
const msg = JSON.parse(e.data);
if ("KeepAlive" in msg) console.log("KeepAlive");
if ("PageRefresh" in msg) location.reload();
});
</script>
```
***

## Full stack - need proxy to backend

With a backend running on `8080` and a frontend on `3000`, it is configured that requests beginning with `api` are proxied to localhost:8080.

Also, we're now using mill. We need to tell the cli the frontend module name and the directory the compiles JS ends up in.

```sh
cs launch io.github.quafadas::sjsls:{{projectVersion}} -- \
--path-to-index-html /Users/simon/Code/mill-full-stack/frontend/ui \
--build-tool mill \
--mill-module-name frontend \
--port 3000 \
--out-dir /Users/simon/Code/mill-full-stack/out/frontend/fastLinkJS.dest \
--proxy-prefix-path /api \
--proxy-target-port 8080 \

```
23 changes: 23 additions & 0 deletions site/docs/_docs/deployment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
title: Deployment
---

This project targets the dev loop. When comes to deploy however, I always hit the same problem. From discord;


> Ah, yeah, the classic SPA problem. While you develop, everything works because vite/react-scripts/... takes care of it. And when you deploy, everything seems to work fine, because navigation works via the history object and doesn't really send new requests. Only if you reload the page are you getting the 404. At least for modern SPAs and frameworks. There also was that brief period where most SPAs used the fragment for navigation, which didn't have that problem.
>
> The main thing complicating things is that - in most cases - you also need to serve resources, so neither "proxy /api, everything else to index.html" nor "everything /app to index html, rest proxied" work directly.
>
> What I've seen a few times is:
> - Everything at /api gets proxied
> - Everything else is resolved as is
> - index.html is the fallback document that you get instead of a 404.
This project provides a tiny helper library and an opinionated strategy to get over this hurdle, following exacltly the strategy laid out above.





2 changes: 1 addition & 1 deletion site/docs/_docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ scala-cli --version && \
cs version && \
git clone https://github.com/Quafadas/viteless.git && \
cd viteless && \
cs launch io.github.quafadas:live-server-scala-cli-js_3:0.1.1
cs launch io.github.quafadas:live-server-scala-cli-js_3:{{projectVersion}}
```

## It worked... okay... I have 20 more seconds
Expand Down

0 comments on commit d71308b

Please sign in to comment.