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
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>)
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>
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.
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 likeI know of this type of thing:
https://github.com/karaxnim/karax/blob/master/tests/nativehtmlgen.nim
I covered it in one of my videos; IIRC. The catch is that you are not compiling to html/js; but compiling to an executable that, in turn, generates the html.
Using the above file as an example. Can you think of a way that line 24 could be rewritten from
a(href = "#/", onclick = "javascript:myFunc()"):
text"haha"
To something like:
a(href = "#/", onclick = myFunc(1, 2, 3)):
text"haha"
And then in the somehow let the system know about the JS nim file; something like
includelib "myfunctions.nim"
And then in myfunctions.nim:
export myFunc
proc myFunc(int a, int b, int c) =
var answer = a + b + c
if answer > 20:
document.getElementById(otherPlace).style.color = "red"
And the scripting / compiler / macros would verify that the onclick is correctly calling the function with three integers. And the function is using a legit element id (otherPlace)?
A pipe dream? Or is it perhaps possible with lots of work and a very meta set of libraries and utilities?
@alexeypetrushin
A good set of goals is a good thing!
A website development tool that creates the content for two servers:
A front-end with:
(Actually 1 and 2 could be on separate servers; perhaps that is wiser.)
An API-only server (serving the frontend-server) with:
All of this from one source code repo with exhaustive compile-time checking. If the front-end javascript is written to call the backend api with the wrong object format, a compile-time error is thrown. If the front-end HTML form is written to send a POST to the front-end CGI with the wrong object format, a compile-time error is thrown.
When the CI/CD pipeline for the repo generates a new version for production release, both websites are updated (or could be updated if desired). Kubernetes or AWS or etc handles details; but perhaps the opinionated generated provides hints/details for the CI/CD pipeline as well.
Taken to the extreme, the goal is compile-time type-checking and verification".
Later perhaps CSS could be pulled in. if the CSS "source code" has a ".class" that does not match the compiled repo, a compiler error is thrown.
fictionally as seen in the repos for the XYZ website*
source repo:
/web/index.nim
/web/foo.nim
/web/bar.nim
/web/commonmodels.nim
/web/bobsfancylib.nim
/web/addthatthing.nim
/web/deletethatotherthing.nim
/api/view.nim
/api/controllers.nim
/api/database.nim
/api/dbmodels/zing.nim
/api/dbmodels/bang.nim
repo output for front-end:
/site/index.html
/site/index.js
/site/foo.html
/site/foo.js
/site/bar.html
/site/bar.js
/site/static/js/commonmodels.nim
/site/static/js/bobsfancylib.nim
/cgi/addthatthing.js
/cgi/deletethatotherthing.js
repo output for api
/apisite/app/xyz
/apisite/conf/details.conf
A pipe dream?
Doesn't look that hard. Call the compiler twice, one time for the native code generation that produces the HTML so you need to run the binary, one time to produce JS code.
maybe take something from js world
the mainstream hydration or etc like this https://fresh.deno.dev/
that means you can generate not only static js code for running SPA, but also a version with prefilled values and raw html, that after sending to web comes alive.
If the front-end HTML form is written to send a POST to the front-end CGI with the wrong object format, a compile-time error is thrown.
you can’t rely on the compiler, anyone who visits the site using curl will knock down the server
I'm not sure of the possible drawbacks, but something like polyrpc seems promising for small projects where one intends to use Nim for both front end and back end.
You then still run the compiler twice, right? No additonal 'magic' on that front I presume?
I think html generating dsl's are a design mistake.
Since you cannot copy paste from old code.
You must learn a new language just to create html.
Most DSL's lack some corner cases etc pp.
Imho such frameworks must work with html, or they're just toys.
You kind of need to learn a new language for a templating engine as well, right? ({% if %}, {% endif %}, etc)
Anyway, taking that mentality to the extreme you'd conclude that HTML + vanilla JS is the one true way for any dynamic content on the web.
Sure you need to learn the template syntax in addition. But you still know most of the other syntax which is html.
When you create a dsl that generates html you loose this, you learn a completely new syntax (with their quirks and workarounds kdiv tdiv) .
You also loose the benefit of copy and pasting stuff from the internet, since you just invented your own syntax. Every example must be first converted to your own syntax.
I think, its no surprise all of the big "spa" frameworks out there use html, since the rest is not worth it.
kdiv tdiv
Ha ha, yeas that's ugly
You also loose the benefit of copy and pasting stuff from the internet, since you just invented > your own syntax. Every example must be first converted to your own syntax.
The https://github.com/nim-lang-cn/html2karax exists and works well so that's not really an issue. For best results just make sure your input HTML is valid with html tidy.