The website generators written in nim tend to fall into two types:
1. An all-in-one website dynamic generator that is meant to build an actual
web server. Think Karax and the like.
2. Static website-generators written in Nim but use generic text templating.
The benefit of type #1 is that the DSL support of the compiler helps find flaws
at compile-time. The downside is that you are now left with something that is
not a true website. It's a custom webserver that may or may not scale well in
hostile distributed security-locked cloud environments.
The benefit of type #2 is that you have a true website. You can use NGINX,
Lighttpd, Apache, etc and it is pretty universal. But you just lost the benefit
of catching bugs and organizing things during compilation. Welcome to run-time
bug hell on a big enough project.
An alternative that is the best of both worlds, is Angular. Sort of. But, that
isn't a nim thing anyway. Is it possible to do something in Nim? Can we get
that meta?
Just throwing out an idea.
A tool that runs the nim compiler many times against a collection of source
code files to ultimately generate a full website. aka `plusplus [sourcedir]
[destdir] [versionTag]` ?
Floating out some bogus code to show what I mean:
**model_todolist.nim**
#@ PLUSPLUS:
#@ role = model
export ToDoListModel
export AddToDoModel
type
ToDoListModel = object
todos: seq[string]
AddToDoModel = object
content: string
Run
**index.nim**
import plusplus
import plusplus.html
import std/json
import add_todo
import model_todolist
#@ PLUSPLUS:
#@ role = html
#@ start = page
#@ route = "/"
HTMLGEN(todo_form):
button(onclick = showFormJs("this_form")):
html("click me -> to show form")
p
form(id = this_form, `method` = POST, action=addTheTodo,
format=AddToDoModel style=html("display: none;")):
input_text(name = todo_content)
input_submit(value = html("Add"))
HTMLGEN(showTodoItems):
h1:
html("Todo items")
ul(id = current_items)
HTMLGEN(page):
head:
title("ToDo App")
body:
showTodoItems()
todoForm()
JSGEN(showFormJs, something: string):
Element[something].style.display = block
JSGEN(getTodoItemsJs):
ajax fetch("https://foo.bar/gettodos" `method` = post) format
ToDoListModel:
for item in ajaxResponse.todos:
li = li():
html(item)
Element[current_items].add li
# this file creates 'index.html'; which is '/' in Lighttpd
# alternatively, it creates 'index.html' and 'index.js' (which is loaded in
<head>)
Run
which outputs index.html (or index.html and index.js):
<html>
<head>
<title>ToDo App</title>
</head>
<h1>Todo items</h1>
<ul id='current_items'>
</ul>
<button onclick = 'showFormJs("this_form")'>
click me -> to show form
</button>
<p />
<form id='this_form' method=POST action='/add_todo' style='display:
none;'>
<input type='text' name='todo_content'>
<input type='submit' value='Add'>
</form>
<script>
let ToDoListModel = {
todos: []
}
let AddToDoModel = {
content: ''
}
</script>
<script>
fetch("https://foo.bar/gettodos", {
method: 'post',
body: post,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(todos => {
const todoList = todos.map(todo => todo.content);
todoList.forEach((item)=>{
let li = document.createElement("li");
li.innerText = item;
document.getElementById('current_items').appendChild(li);
})
})
.catch((error) => {
console.log(error)
})
</script>
<script>
function showFormJs(something) {
document.getElementById(something).style.display = 'block';
}
</script>
</html>
Run
While the resulting html and JS is using generic strings for IDs and generic
JSON objects for models, the nim source code is using real identifiers and type
structs for compile-time verification of everything.
The resulting [destdir] contains just HTML5/JS/CSS etc. Choose your favorite
cloud webserver to host it.
A possiblity for pages that do not show content, but only take
action-then-redirect:
**add_todo.nim**
import plusplus
import plusplus.action
import std/json
import model_todolist
#@ PLUSPLUS:
#@ role = action
#@ start = addTheTodo
#@ route "/add_todo"
export addTheToDo
proc addTheToDo(request: Request, body: AddToDoModel): redirection =
let newContent = body.content
var msg = ""
if newContent.len > 0:
apiCall(post, "https://foo.bar/newtodo", %{"content" = newContent}))
msg = "something added"
else:
msg = "message is empty"
result = redirectionUrl("/", msg=msg)
# this file could create an actual linux app named add_todo.cgi which is
called by Lighttpd
# (or Apache) using mod_cgi
# or things could be kept pure with a html/js redirect function.
Run
Thoughts? Has someone already done something like this? I'm sure there are lots
of flaws in my above example psuedo-code, but has something like