GOMODEST is a Multi Page App(MPA) starter kit using Go's html/template
, SvelteJS
and StimulusJS
. It is inspired from modest approaches to building webapps as enlisted in https://modestjs.works/.
I am a devops engineer who dabbles in UI for side projects, internal tools and such. The SPA/ReactJS ecosystem is too costly for me. This is an alternative approach.
The main idea is to use server rendered html with spots of client-side dynamism using SvelteJS & StimulusJS
The webapp is mostly plain old javascript, html, css but with sprinkles of StimulusJS & spots of SvelteJS used for interactivity sans page reloads. StimulusJS is used for sprinkling interactivity in server rendered html & mounting Svelte components into divs.
A few things which were used:
- Go, html/template, goview,
- Authentication: github.com/adnaan/authn
- SvelteJS
- Hotwire
- Bulma CSS
Many more things in go.mod
& web/package.json
To run, clone this repo and:
$ make install
# another terminal
$ make run-go
The ideas in this starter kit follow the JS gradient as noted here. I have taken the liberty to organise them into the following big blocks: server-rendered html, sprinkles and spots.
Use html/template
and goview
to render html pages. It's quite powerful when do you don't need client-side interactions.
example:
func accountPage(w http.ResponseWriter, r *http.Request) (goview.M, error) {
session, err := store.Get(r, "auth-session")
if err != nil {
return nil, fmt.Errorf("%v, %w", err, InternalErr)
}
profileData, ok := session.Values["profile"]
if !ok {
return nil, fmt.Errorf("%v, %w", err, InternalErr)
}
profile, ok := profileData.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("%v, %w", err, InternalErr)
}
return goview.M{
"name": profile["name"],
}, nil
}
Use stimulusjs
to level up server-rendered html to handle simple interactions like: navigations, form validations etc.
example:
<button class="button is-primary is-outlined is-fullwidth"
data-action="click->navigate#goto"
data-goto="/ "
type="button">
Home
</button>
goto(e){
if (e.currentTarget.dataset.goto){
window.location = e.currentTarget.dataset.goto;
}
}
Use sveltejs
to take over spots
of a server-rendered html page to provide more complex interactivity without page reloads.
This snippet is the most interesting part of this project:
{{define "content"}}
<div class="columns is-mobile is-centered">
<div class="column is-half-desktop">
<div
data-target="svelte.component"
data-component-name="app"
data-component-props="{{.Data}}">
</div>
</div>
</div>
</div>
{{end}}
In the above snippet, we use StimulusJS to mount a Svelte component by using the following code:
import { Controller } from "stimulus";
import components from "./components";
export default class extends Controller {
static targets = ["component"]
connect() {
if (this.componentTargets.length > 0){
this.componentTargets.forEach(el => {
const componentName = el.dataset.componentName;
const componentProps = el.dataset.componentProps ? JSON.parse(el.dataset.componentProps): {};
if (!(componentName in components)){
console.log(`svelte component: ${componentName}, not found!`)
return;
}
const app = new components[componentName]({
target: el,
props: componentProps
});
})
}
}
}
This strategy allows us to mix server rendered HTML pages with client side dynamism.
Other possibly interesting aspects could be the layout of web/html and the usage of the super nice goview library to render html in these files:
That is all.